Skip to content

ci(py-315): prune cp315 wheels from all public publication paths (PROF-15857) - #19880

Open
vlad-scherbich wants to merge 5 commits into
mainfrom
vlad/no-cp315-to-pypi
Open

ci(py-315): prune cp315 wheels from all public publication paths (PROF-15857)#19880
vlad-scherbich wants to merge 5 commits into
mainfrom
vlad/no-cp315-to-pypi

Conversation

@vlad-scherbich

@vlad-scherbich vlad-scherbich commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Description

build linux and build linux serverless build cp315 wheels under an allow_failure
rule for CI signal only — 3.15 is not a supported target
(requires-python = ">=3.9,<3.15") — but nothing downstream removed them, so they reached
PyPI, s3://dd-trace-py-builds, and the private prerelease index. Requires-Python is not
a sufficient guard: pip enforces the upper bound, uv
"ignores upper bounds entirely".

New .gitlab/scripts/prune-unsupported-wheels.sh deletes *<tag>*.whl for each tag in
UNSUPPORTED_TAGS (cp315 today), and runs before all three publish commands — twine upload, aws s3 cp, adms first-party python upload — and in both ddtrace package
jobs. It exits 1 on a missing directory, so a typo in a call site fails the job.

Testing

tests/internal/test_unsupported_wheel_pruning.py runs the real script against fixtures
and reads the wiring from the shipped YAML, so a new publisher that doesn't prune fails
CI.

Risks

Low. The glob matches no supported wheel filename, and validate-ddtrace-package.py still
runs after the prune and fails on a missing wheel.

Additional Notes

Known gap: cp315 entries in debugwheelhouse/*.zip still reach S3 — not installable,
out of scope. Follow-up: make validate-ddtrace-package.py fail on unrecognised tags.

No release note: CI chore, changelog/no-changelog applied.

Base: main. Next: #19911.

@vlad-scherbich vlad-scherbich added the changelog/no-changelog A changelog entry is not required for this PR. label Aug 27, 2026
@cit-pr-commenter-54b7da

cit-pr-commenter-54b7da Bot commented Aug 27, 2026

Copy link
Copy Markdown

Circular import analysis

⚠️ Existing circular imports

There are 3 circular imports that already exist on the base branch and have not been changed by this PR.

ddtrace.errortracking._handled_exceptions.bytecode_injector -> ddtrace.errortracking._handled_exceptions.callbacks -> ddtrace.errortracking._handled_exceptions.collector -> ddtrace.errortracking._handled_exceptions.bytecode_reporting -> ddtrace.errortracking._handled_exceptions.bytecode_injector
ddtrace.llmobs -> ddtrace.llmobs._evaluators -> ddtrace.llmobs._evaluators.format -> ddtrace.llmobs._experiment -> ddtrace.llmobs
ddtrace.appsec._asm_request_context -> ddtrace.appsec._iast._iast_request_context_base -> ddtrace.appsec._iast._iast_env -> ddtrace.appsec._iast.reporter -> ddtrace.appsec._exploit_prevention.stack_traces -> ddtrace.appsec._asm_request_context

@datadog-prod-us1-4

datadog-prod-us1-4 Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Pipelines  Tests

⚠️ Warnings

Your PR has failed checks. Please review the issues below and take necessary action before merging.

🚦 5 Pipeline jobs failed

DataDog/apm-reliability/dd-trace-py | build linux serverless: [amd64, cp315-cp315, v113741238-d2b8243-manylinux2014_x86_64, 1] — 🔧 Needs a code fix, caused by this PR

View more details · View in GitLab

DataDog/apm-reliability/dd-trace-py | build linux serverless: [arm64, cp315-cp315, v113741357-d2b8243-manylinux2014_aarch64, 1]

View more details · View in GitLab

DataDog/apm-reliability/dd-trace-py | build linux: [amd64, cp315-cp315, v113741238-d2b8243-manylinux2014_x86_64]

View more details · View in GitLab

View all 5 failed jobs.

ℹ️ Info

No other issues found (see more)

🧪 All tests passed
❄️ No new flaky tests detected

🔄 Datadog retried 13 tests - 11 passed on retry View in Datadog

🚧 5 tests that failed were ignored due to quarantine View in Datadog

Useful? React with 👍 / 👎

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: 1a43844 | Docs | View more details | Give us feedback!

@pr-commenter

pr-commenter Bot commented Aug 27, 2026

Copy link
Copy Markdown

Benchmarks

Benchmark execution time: 2026-08-28 18:55:35

Comparing candidate commit 1a43844 in PR branch vlad/no-cp315-to-pypi with baseline commit 23f857d in branch main.

📊 Benchmarking dashboard

Found 0 performance improvements and 7 performance regressions! Performance is the same for 556 metrics, 9 unstable metrics, 3 known flaky benchmarks, 13 flaky benchmarks without significant changes.

Explanation

This is an A/B test comparing a candidate commit's performance against that of a baseline commit. Performance changes are noted in the tables below as:

  • 🟩 = significantly better candidate vs. baseline
  • 🟥 = significantly worse candidate vs. baseline

We compute a confidence interval (CI) over the relative difference of means between metrics from the candidate and baseline commits, considering the baseline as the reference.

If the CI is entirely outside the configured SIGNIFICANT_IMPACT_THRESHOLD (or the deprecated UNCONFIDENCE_THRESHOLD), the change is considered significant.

Feel free to reach out to #apm-benchmarking-platform on Slack if you have any questions.

More details about the CI and significant changes

You can imagine this CI as a range of values that is likely to contain the true difference of means between the candidate and baseline commits.

CIs of the difference of means are often centered around 0%, because often changes are not that big:

---------------------------------(------|---^--------)-------------------------------->
                              -0.6%    0%  0.3%     +1.2%
                                 |          |        |
         lower bound of the CI --'          |        |
sample mean (center of the CI) -------------'        |
         upper bound of the CI ----------------------'

As described above, a change is considered significant if the CI is entirely outside the configured SIGNIFICANT_IMPACT_THRESHOLD (or the deprecated UNCONFIDENCE_THRESHOLD).

For instance, for an execution time metric, this confidence interval indicates a significantly worse performance:

----------------------------------------|---------|---(---------^---------)---------->
                                       0%        1%  1.3%      2.2%      3.1%
                                                  |   |         |         |
       significant impact threshold --------------'   |         |         |
                      lower bound of CI --------------'         |         |
       sample mean (center of the CI) --------------------------'         |
                      upper bound of CI ----------------------------------'

scenario:httppropagationextract-tracecontext_headers

  • 🟥 execution_time [+2.308µs; +2.474µs] or [+7.263%; +7.785%]

scenario:httppropagationinject-ids_only

  • 🟥 execution_time [+2.515µs; +2.655µs] or [+15.255%; +16.108%]

scenario:iastaspects-add_aspect

  • 🟥 execution_time [+8.874µs; +11.135µs] or [+8.790%; +11.029%]

scenario:iastaspectsospath-ospathbasename_aspect

  • 🟥 execution_time [+129.878µs; +135.956µs] or [+31.034%; +32.487%]

scenario:iastaspectssplit-rsplit_aspect

  • 🟥 execution_time [+32.441µs; +40.595µs] or [+22.623%; +28.309%]

scenario:telemetryaddmetric-1-count-metric-1-times

  • 🟥 execution_time [+628.985ns; +670.476ns] or [+23.126%; +24.651%]

scenario:tracer-small

  • 🟥 execution_time [+36.682µs; +39.355µs] or [+11.498%; +12.336%]

Unstable benchmarks

These benchmarks have a confidence interval too wide to call a change; treat them as noise rather than signal.

scenario:coreapiscenario-context_with_data_listeners

  • unstable execution_time [-753.579ns; +709.095ns] or [-6.908%; +6.500%]

scenario:coreapiscenario-core_dispatch_1_listener

  • unstable execution_time [-38.971ns; +27.454ns] or [-6.314%; +4.448%]

scenario:coreapiscenario-core_dispatch_50_listeners

  • unstable execution_time [-1747.123ns; +1551.997ns] or [-10.208%; +9.068%]

scenario:coreapiscenario-core_dispatch_exception_listeners

  • unstable execution_time [-1246.029ns; +1308.837ns] or [-9.435%; +9.910%]

scenario:coreapiscenario-core_dispatch_listeners

  • unstable execution_time [-334.174ns; +317.229ns] or [-9.066%; +8.606%]

scenario:coreapiscenario-core_dispatch_no_args_listeners

  • unstable execution_time [-256.232ns; +258.273ns] or [-8.723%; +8.793%]

scenario:coreapiscenario-core_dispatch_with_results_1_listener

  • unstable execution_time [-59.931ns; +85.082ns] or [-5.147%; +7.307%]

scenario:coreapiscenario-core_dispatch_with_results_50_listeners

  • unstable execution_time [-3815.395ns; +4092.452ns] or [-9.402%; +10.085%]

scenario:coreapiscenario-core_dispatch_with_results_listeners

  • unstable execution_time [-828.021ns; +717.646ns] or [-10.190%; +8.832%]

Known flaky benchmarks

These benchmarks are marked as flaky and will not trigger a failure. Modify FLAKY_BENCHMARKS_REGEX to control which benchmarks are marked as flaky.

scenario:iastaspects-casefold_noaspect

  • 🟥 execution_time [+68.165µs; +74.607µs] or [+27.440%; +30.034%]

scenario:iastaspects-translate_noaspect

  • 🟥 execution_time [+25.521µs; +29.740µs] or [+7.212%; +8.404%]

scenario:span-start

  • 🟥 execution_time [+1.655ms; +1.916ms] or [+12.256%; +14.183%]

Known flaky benchmarks without significant changes:

  • scenario:errortrackingflasksqli-baseline
  • scenario:flasksimple-iast-get
  • scenario:iastaspects-casefold_aspect
  • scenario:iastaspects-index_aspect
  • scenario:iastaspects-ljust_noaspect
  • scenario:iastaspects-lower_aspect
  • scenario:iastaspects-replace_aspect
  • scenario:iastaspects-swapcase_aspect
  • scenario:iastaspects-title_noaspect
  • scenario:iastaspects-translate_aspect
  • scenario:iastaspects-upper_noaspect
  • scenario:sethttpmeta-all-enabled
  • scenario:telemetryaddmetric-record-100-metrics

@cit-pr-commenter-54b7da

cit-pr-commenter-54b7da Bot commented Aug 27, 2026

Copy link
Copy Markdown

Codeowners resolved as

Resolved from the full PR diff against main using the target branch CODEOWNERS file.
CODEOWNERS team requests not listed below are not required by the current file set.

.gitlab/package.yml                                                     @DataDog/python-guild @DataDog/apm-core-python
.gitlab/release.yml                                                     @DataDog/python-guild @DataDog/apm-core-python
.gitlab/scripts/prune-unsupported-wheels.sh                             @DataDog/python-guild @DataDog/apm-core-python
.gitlab/scripts/upload-wheels-to-s3.sh                                  @DataDog/python-guild @DataDog/apm-core-python
tests/internal/test_unsupported_wheel_pruning.py                        @DataDog/apm-core-python

@cit-pr-commenter-54b7da

cit-pr-commenter-54b7da Bot commented Aug 27, 2026

Copy link
Copy Markdown

Dependency direction analysis

⚠️ Existing dependency direction violations

There are 240 dependency direction violations that already exist on the base branch and have not been changed by this PR.

Show existing violations (showing 5 of 240 highest severity)
ddtrace.internal.tracemethods -×-> ddtrace.trace  (internal-core -> product:tracing, score=135)
ddtrace.internal.opentelemetry.span -×-> ddtrace.trace  (product:opentelemetry -> product:tracing, score=133)
ddtrace.llmobs._integrations.openai -×-> ddtrace.trace  (product:llmobs -> product:tracing, score=133)
ddtrace.llmobs._integrations.crewai -×-> ddtrace.trace  (product:llmobs -> product:tracing, score=133)
ddtrace.llmobs._utils -×-> ddtrace.trace  (product:llmobs -> product:tracing, score=133)

To see all violations, download the layers-base.json and layers-pr.json artifacts from this CI job and run:

uv run --script scripts/import-analysis/layers.py compare layers-base.json layers-pr.json

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adjusts the GitLab release pipeline so release_pypi_prod no longer uploads cp315 wheels to PyPI, preventing publication of wheels for an unsupported (and potentially ABI-incompatible) Python 3.15 target while leaving internal distribution paths unchanged.

Changes:

  • Builds an explicit upload list from pywheels/ddtrace-* and filters out any *cp315* artifacts before both twine check --strict and twine upload.
  • Fails the release job early with a clear error if filtering results in an empty upload set, and logs the exact distributions that will be published.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@vlad-scherbich

Copy link
Copy Markdown
Contributor Author

@codex review

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 👍

Reviewed commit: 42ff65d63c

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@vlad-scherbich
vlad-scherbich marked this pull request as ready for review August 27, 2026 17:18
@vlad-scherbich
vlad-scherbich requested review from a team as code owners August 27, 2026 17:18

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bea839769e

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread tests/internal/test_release_pypi_filter.py Outdated
Comment thread tests/internal/test_release_pypi_filter.py Outdated

@brettlangdon brettlangdon left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any reason to not update ddtrace package to delete *cp315* wheels before saving the artifacts?

would be much simpler implementation, no?

@vlad-scherbich

vlad-scherbich commented Aug 27, 2026

Copy link
Copy Markdown
Contributor Author

any reason to not update ddtrace package to delete *cp315* wheels before saving the artifacts?

would be much simpler implementation, no?

@brettlangdon Agreed, implemented.

Went a bit wider than rm in ddtrace package, because upload serverless and theupload manylinux2014* jobs pull from build linux directly rather than from ddtrace package. `

Upload serverless` produced all 40,814 cp315 serverless wheels on dd-trace-py-builds — which cloud-inventory registers as a public, customer-facing bucket, so dropping cp315 from the default index is a nice improve, imo.

So: one prune script called from all three places (twine upload, aws s3 cp,
adms upload). Thoughts?

@vlad-scherbich
vlad-scherbich marked this pull request as ready for review August 27, 2026 19:11
vlad-scherbich added a commit that referenced this pull request Aug 27, 2026
PR #19880 filtered cp315 out of the PyPI upload only, on the stated premise that
the other destination was "internal S3 and the private prerelease index". That is
wrong: s3://dd-trace-py-builds is anonymously readable and listable and is
registered in DataDog/cloud-inventory as a "Public bucket to host dev builds of
dd-trace-py from GitLab to share with customers". The pipeline republished cp315
wheels to main/ on 2026-08-27, so the leak was live, not historical.

Three mechanisms publish a ddtrace wheel outside the pipeline: twine to PyPI,
aws s3 cp to that bucket, and adms to pypi-private-prereleases. All three now call
.gitlab/scripts/prune-unsupported-wheels.sh first, which holds the withheld ABI
tags in one place. Putting it inside upload-wheels-to-s3.sh covers every S3 caller
and every index suffix, including "upload serverless", which no filter reached
before.

The PyPI-side shell in release.yml reverts to the pre-filter form plus one prune
call, per review feedback: with cp315 gone from the "ddtrace package" artifact,
twine check no longer needs a hand-built file list.

tests/internal/test_unsupported_wheel_pruning.py replaces the PyPI-specific test.
It pins the prune behaviour and asserts that every file under .gitlab/ running a
publish command also calls the prune script, so a new upload path cannot silently
reintroduce cp315.
@vlad-scherbich vlad-scherbich changed the title ci(py-315): withhold cp315 wheels from the PyPI upload ci(py-315): prune cp315 wheels from all public publication path Aug 27, 2026
@vlad-scherbich vlad-scherbich changed the title ci(py-315): prune cp315 wheels from all public publication path ci(py-315): prune cp315 wheels from all public publication paths Aug 27, 2026
@vlad-scherbich
vlad-scherbich requested review from brettlangdon and a lite review from Copilot August 27, 2026 19:58

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Suppressed comments (1)

tests/internal/test_unsupported_wheel_pruning.py:223

  • Like the missing-directory test above, this test assumes bash exists but doesn’t guard against shutil.which("bash") returning None, which would crash the test suite with a TypeError rather than producing a useful skip.
    shell = shutil.which("bash")


def test_a_missing_directory_is_a_hard_error(prune, tmp_path):
"""A typo in a call site must fail the job, not skip the prune."""
shell = shutil.which("bash")
vlad-scherbich added a commit that referenced this pull request Aug 28, 2026
PR #19880 filtered cp315 out of the PyPI upload only, on the stated premise that
the other destination was "internal S3 and the private prerelease index". That is
wrong: s3://dd-trace-py-builds is anonymously readable and listable and is
registered in DataDog/cloud-inventory as a "Public bucket to host dev builds of
dd-trace-py from GitLab to share with customers". The pipeline republished cp315
wheels to main/ on 2026-08-27, so the leak was live, not historical.

Three mechanisms publish a ddtrace wheel outside the pipeline: twine to PyPI,
aws s3 cp to that bucket, and adms to pypi-private-prereleases. All three now call
.gitlab/scripts/prune-unsupported-wheels.sh first, which holds the withheld ABI
tags in one place. Putting it inside upload-wheels-to-s3.sh covers every S3 caller
and every index suffix, including "upload serverless", which no filter reached
before.

The PyPI-side shell in release.yml reverts to the pre-filter form plus one prune
call, per review feedback: with cp315 gone from the "ddtrace package" artifact,
twine check no longer needs a hand-built file list.

tests/internal/test_unsupported_wheel_pruning.py replaces the PyPI-specific test.
It pins the prune behaviour and asserts that every file under .gitlab/ running a
publish command also calls the prune script, so a new upload path cannot silently
reintroduce cp315.
The release_pypi job uploaded every artifact matching pywheels/ddtrace-*,
which includes the cp315 wheels that "build linux" produces for internal S3
and the private prerelease index. Nothing prevented a Python 3.15 artifact
from reaching PyPI other than the Requires-Python bound baked into the wheel
metadata, which is a property of the artifact rather than a release guard.

Filter cp315 out of the upload list and refuse to upload at all if the
resulting list is empty, so a broken filter fails the job instead of
silently uploading everything or nothing. twine check --strict still runs
over the whole of pywheels/, so cp315 artifacts remain validated. The S3
and adms prerelease paths are unchanged: those are the intended consumers.
…is uploaded

The comment used DEV(py-315), a marker form this repo has never used; main has
three TODO(py-315) anchors and only a bare "# DEV:" convention.

Build the upload list before "twine check" so both steps see the same
distributions. --strict promotes warnings to errors, so checking the unfiltered
directory let a malformed cp315 wheel from an allow_failure build fail a release
whose supported wheels were all fine. release_pypi_prod depends on "ddtrace
package", which carries no serverless artifacts, so the narrowed check loses no
coverage beyond cp315.
release_pypi_prod is gated on .is_release, so a PR pipeline never runs its
shell and the filter added in the previous commit had no coverage. Pull the
script block out of .gitlab/release.yml and run it against fixture directories
with aws and uvx stubbed on PATH, so the assertions cannot drift from the
shipped YAML.

Pins three invariants: cp315 wheels are withheld while 3.15 is unsupported
and everything else including the sdist still uploads; twine check and twine
upload receive the same list, so a malformed wheel from the allow_failure
cp315 build cannot fail a release it is not part of; and an empty list is a
hard error rather than a silent no-op.
Test docstrings are never Sphinx-rendered, and AGENTS.md (L43-50) treats rST
inline markup in them as editor noise. Replace the double-backtick literals in
tests/internal/test_release_pypi_filter.py with plain text.
PR #19880 filtered cp315 out of the PyPI upload only, on the stated premise that
the other destination was "internal S3 and the private prerelease index". That is
wrong: s3://dd-trace-py-builds is anonymously readable and listable and is
registered in DataDog/cloud-inventory as a "Public bucket to host dev builds of
dd-trace-py from GitLab to share with customers". The pipeline republished cp315
wheels to main/ on 2026-08-27, so the leak was live, not historical.

Three mechanisms publish a ddtrace wheel outside the pipeline: twine to PyPI,
aws s3 cp to that bucket, and adms to pypi-private-prereleases. All three now call
.gitlab/scripts/prune-unsupported-wheels.sh first, which holds the withheld ABI
tags in one place. Putting it inside upload-wheels-to-s3.sh covers every S3 caller
and every index suffix, including "upload serverless", which no filter reached
before.

The PyPI-side shell in release.yml reverts to the pre-filter form plus one prune
call, per review feedback: with cp315 gone from the "ddtrace package" artifact,
twine check no longer needs a hand-built file list.

tests/internal/test_unsupported_wheel_pruning.py replaces the PyPI-specific test.
It pins the prune behaviour and asserts that every file under .gitlab/ running a
publish command also calls the prune script, so a new upload path cannot silently
reintroduce cp315.
@vlad-scherbich
vlad-scherbich requested a review from a team August 28, 2026 19:21
@vlad-scherbich vlad-scherbich changed the title ci(py-315): prune cp315 wheels from all public publication paths ci(py-315): prune cp315 wheels from all public publication paths (PROF-15857) Aug 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

changelog/no-changelog A changelog entry is not required for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants