From 99964c62c27a238f52a18751cb9a815b9e77c20b Mon Sep 17 00:00:00 2001 From: brettlangdon Date: Thu, 13 Nov 2025 10:18:01 -0500 Subject: [PATCH 01/29] chore: use static versions instead of setuptools-scm --- .github/workflows/build_deploy.yml | 50 ++-------------------------- .github/workflows/build_python_3.yml | 39 ++-------------------- .gitlab/package.yml | 34 +------------------ ddtrace/_version.py | 15 ++------- docs/build_system.rst | 4 +-- pyproject.toml | 10 +----- scripts/ddtest | 16 --------- setup.py | 2 +- 8 files changed, 13 insertions(+), 157 deletions(-) diff --git a/.github/workflows/build_deploy.yml b/.github/workflows/build_deploy.yml index cc97a3c8220..9ff25ae50f2 100644 --- a/.github/workflows/build_deploy.yml +++ b/.github/workflows/build_deploy.yml @@ -24,61 +24,17 @@ on: - cron: 0 2 * * 2-6 jobs: - compute_version: - name: Compute Library Version - runs-on: ubuntu-latest - outputs: - library_version: ${{ steps.compute-version.outputs.library_version }} - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - # Include all history and tags - with: - persist-credentials: false - fetch-depth: 0 - - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 - name: Install Python - with: - python-version: '3.12' - - name: Compute Version - id: compute-version - run: | - pip install "setuptools_scm[toml]>=4" - - # If we are on the main or release branch, strip away the dev version - if [[ "$GITHUB_REF_NAME" == "main" || \ - "$GITHUB_REF_NAME" =~ ^[0-9]+\.[0-9]+$ || \ - "$GITHUB_REF_NAME" =~ ^[0-9]+\.x$ ]]; then - LIBRARY_VERSION=$(setuptools-scm --strip-dev) - else - # use version string explicitly set in the project metadata, if exists - LIBRARY_VERSION=$(grep '^version = ' pyproject.toml | tr -d '"' | cut -d' ' -f3) - if [[ -z $LIBRARY_VERSION ]]; then - # All else, maintain the dev version - LIBRARY_VERSION=$(setuptools-scm) - fi - fi - - echo "${LIBRARY_VERSION}" | tee version.txt - echo "library_version=${LIBRARY_VERSION}" >> $GITHUB_OUTPUT - - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 - with: - name: library-version - path: version.txt - build_wheels: - needs: [ "compute_version" ] + needs: [ ] uses: ./.github/workflows/build_python_3.yml with: cibw_build: 'cp39* cp310* cp311* cp312* cp313* cp314*' cibw_skip: 'cp39-win_arm64 cp310-win_arm64 cp314t*' - library_version: ${{ needs.compute_version.outputs.library_version }} build_sdist: - needs: [ "compute_version" ] + needs: [ ] name: Build source distribution runs-on: ubuntu-latest - env: - SETUPTOOLS_SCM_PRETEND_VERSION_FOR_DDTRACE: ${{ needs.compute_version.outputs.library_version }} steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 # Include all history and tags @@ -92,7 +48,7 @@ jobs: python-version: '3.12' - name: Build sdist run: | - pip install "setuptools_scm[toml]>=4" "cython" "cmake>=3.24.2,<3.28" "setuptools-rust" + pip install "cython" "cmake>=3.24.2,<3.28" "setuptools-rust" python setup.py sdist - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: diff --git a/.github/workflows/build_python_3.yml b/.github/workflows/build_python_3.yml index 6b718bb4502..d840aa52223 100644 --- a/.github/workflows/build_python_3.yml +++ b/.github/workflows/build_python_3.yml @@ -12,39 +12,8 @@ on: cibw_prerelease_pythons: required: false type: string - library_version: - required: false - type: string jobs: - compute_version: - name: Compute Library Version - runs-on: ubuntu-latest - outputs: - library_version: ${{ steps.compute-version.outputs.library_version }} - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - # Include all history and tags - with: - persist-credentials: false - fetch-depth: 0 - - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 - name: Install Python - with: - python-version: '3.12' - - name: Compute Version - id: compute-version - run: | - if [ -n "${{ inputs.library_version}}" ]; then - LIBRARY_VERSION="${{ inputs.library_version}}" - else - pip install "setuptools_scm[toml]>=4" - LIBRARY_VERSION=$(setuptools-scm) - fi - - echo "${LIBRARY_VERSION}" - echo "library_version=${LIBRARY_VERSION}" >> $GITHUB_OUTPUT - build-wheels-matrix: runs-on: ubuntu-latest outputs: @@ -76,7 +45,7 @@ jobs: echo "include=${MATRIX_INCLUDE}" >> $GITHUB_OUTPUT build: - needs: ["compute_version", "build-wheels-matrix" ] + needs: [ "build-wheels-matrix" ] runs-on: ${{ matrix.os }} name: Build ${{ matrix.only }} strategy: @@ -84,7 +53,6 @@ jobs: matrix: include: ${{ fromJson(needs.build-wheels-matrix.outputs.include) }} env: - SETUPTOOLS_SCM_PRETEND_VERSION_FOR_DDTRACE: ${{ needs.compute_version.outputs.library_version }} CIBW_SKIP: ${{ inputs.cibw_skip }} CIBW_PRERELEASE_PYTHONS: ${{ inputs.cibw_prerelease_pythons }} CIBW_MANYLINUX_X86_64_IMAGE: manylinux2014 @@ -96,12 +64,11 @@ jobs: if [[ "$(uname -m)-$(uname -i)-$(uname -o | tr '[:upper:]' '[:lower:]')-$(ldd --version 2>&1 | head -n 1 | awk '{print $1}')" != "i686-unknown-linux-musl" ]]; then curl -sSf https://sh.rustup.rs | sh -s -- -y; fi - CIBW_ENVIRONMENT_LINUX: PATH=$HOME/.cargo/bin:$PATH CMAKE_BUILD_PARALLEL_LEVEL=24 CMAKE_ARGS="-DNATIVE_TESTING=OFF" SETUPTOOLS_SCM_PRETEND_VERSION_FOR_DDTRACE=${{ needs.compute_version.outputs.library_version }} + CIBW_ENVIRONMENT_LINUX: PATH=$HOME/.cargo/bin:$PATH CMAKE_BUILD_PARALLEL_LEVEL=24 CMAKE_ARGS="-DNATIVE_TESTING=OFF" # SYSTEM_VERSION_COMPAT is a workaround for versioning issue, a.k.a. # `platform.mac_ver()` reports incorrect MacOS version at 11.0 # See: https://stackoverflow.com/a/65402241 - CIBW_ENVIRONMENT_MACOS: CMAKE_BUILD_PARALLEL_LEVEL=24 SYSTEM_VERSION_COMPAT=0 CMAKE_ARGS="-DNATIVE_TESTING=OFF" SETUPTOOLS_SCM_PRETEND_VERSION_FOR_DDTRACE=${{ needs.compute_version.outputs.library_version }} - CIBW_ENVIRONMENT_WINDOWS: SETUPTOOLS_SCM_PRETEND_VERSION_FOR_DDTRACE=${{ needs.compute_version.outputs.library_version }} + CIBW_ENVIRONMENT_MACOS: CMAKE_BUILD_PARALLEL_LEVEL=24 SYSTEM_VERSION_COMPAT=0 CMAKE_ARGS="-DNATIVE_TESTING=OFF" # cibuildwheel repair will copy anything's under /output directory from the # build container to the host machine. This is a bit hacky way, but seems # to be the only way getting debug symbols out from the container while diff --git a/.gitlab/package.yml b/.gitlab/package.yml index 151664c18c1..2c3aa1dd140 100644 --- a/.gitlab/package.yml +++ b/.gitlab/package.yml @@ -1,31 +1,3 @@ -compute_library_version: - image: registry.ddbuild.io/images/dd-octo-sts-ci-base:2025.06-1 - tags: [ "arch:amd64" ] - stage: package - id_tokens: - DDOCTOSTS_ID_TOKEN: - aud: dd-octo-sts - script: | - if [ -z ${GH_TOKEN} ] - then - # Use dd-octo-sts to get GitHub token - dd-octo-sts token --scope DataDog/dd-trace-py --policy gitlab.github-access.read > token - gh auth login --with-token < token - rm token - fi - # Prevent git operation errors: - # failed to determine base repo: failed to run git: fatal: detected dubious ownership in repository at ... - git config --global --add safe.directory "${CI_PROJECT_DIR}" - .gitlab/download-library-version-from-gh-actions.sh - - echo "SETUPTOOLS_SCM_PRETEND_VERSION_FOR_DDTRACE=$(cat library-version/version.txt)" | tee library_version.env - echo "DDTRACE_VERSION=$(cat library-version/version.txt)" | tee -a library_version.env - artifacts: - reports: - dotenv: library_version.env - paths: - - "library-version/version.txt" - download_ddtrace_artifacts: image: registry.ddbuild.io/images/dd-octo-sts-ci-base:2025.06-1 tags: [ "arch:amd64" ] @@ -88,8 +60,6 @@ publish-wheels-to-s3: needs: - job: download_ddtrace_artifacts artifacts: true - - job: compute_library_version - artifacts: true variables: BUCKET: dd-trace-py-builds script: @@ -105,9 +75,7 @@ publish-wheels-to-s3: - printf ' - %s\n' "${WHEELS[@]}" - | - if [ -f library-version/version.txt ]; then - VERSION="$(tr -d '\r\n' < library-version/version.txt)" - fi + VERSION=$(unzip -p $(ls ./pywheels/*.whl | head -n 1) '*.dist-info/METADATA' | awk -F': ' '/^Version:/ { print $2; exit }') if [ -z "${VERSION:-}" ]; then echo "ERROR: VERSION is not defined or library-version/version.txt missing!" diff --git a/ddtrace/_version.py b/ddtrace/_version.py index 9fc278ef97a..e7e5fe018cd 100644 --- a/ddtrace/_version.py +++ b/ddtrace/_version.py @@ -1,13 +1,8 @@ -# file generated by setuptools-scm -# don't change, don't track in version control - __all__ = [ "__version__", "__version_tuple__", "version", "version_tuple", - "__commit_id__", - "commit_id", ] TYPE_CHECKING = False @@ -16,19 +11,13 @@ from typing import Union VERSION_TUPLE = Tuple[Union[int, str], ...] - COMMIT_ID = Union[str, None] else: VERSION_TUPLE = object - COMMIT_ID = object version: str __version__: str __version_tuple__: VERSION_TUPLE version_tuple: VERSION_TUPLE -commit_id: COMMIT_ID -__commit_id__: COMMIT_ID - -__version__ = version = "4.0.0.dev0" -__version_tuple__ = version_tuple = (4, 0, 0, "dev0", "") -# __commit_id__ = commit_id = 'g5db831a3e' +__version__ = version = "4.0.0rc1" +__version_tuple__ = version_tuple = (4, 0, 0, "rc1") diff --git a/docs/build_system.rst b/docs/build_system.rst index 6b181ca1e59..03ff17466d6 100644 --- a/docs/build_system.rst +++ b/docs/build_system.rst @@ -41,13 +41,13 @@ To see the current build dependencies, check the `[build-system]` section in the .. code-block:: toml [build-system] - requires = ["setuptools_scm[toml]>=4", "cython", "cmake>=3.24.2,<3.28; python_version>='3.8'", "setuptools-rust<2"] + requires = ["cython", "cmake>=3.24.2,<3.28; python_version>='3.8'", "setuptools-rust<2"] To install all dependencies in one step, use: .. code-block:: bash - pip install 'setuptools_scm[toml]>=4' 'cython' 'cmake>=3.24.2,<3.28' 'setuptools-rust<2' + pip install 'cython' 'cmake>=3.24.2,<3.28' 'setuptools-rust<2' Note that `pip install -e` (described below) also installs these build dependencies automatically. diff --git a/pyproject.toml b/pyproject.toml index 94f61c12f1f..9301c6d7ef6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,5 @@ [build-system] requires = [ - "setuptools_scm[toml]>=4", "cython", "cmake>=3.24.2,<3.28; python_version>='3.8'", "setuptools-rust<2", @@ -10,10 +9,7 @@ build-backend = "setuptools.build_meta" [project] name = "ddtrace" -# DEV: to directly override the version specifier, comment this... -dynamic = ["version"] -# ...and uncomment this -#version = "4.0.0.dev0" +version = "4.0.0rc1" description = "Datadog APM client library" readme = "README.md" license = { text = "LICENSE.BSD3" } @@ -83,10 +79,6 @@ Documentation = "https://ddtrace.readthedocs.io/en/stable/" Homepage = "https://github.com/DataDog/dd-trace-py" "Source Code" = "https://github.com/DataDog/dd-trace-py/" -[tool.setuptools_scm] -version_scheme = "release-branch-semver" # Must be "release-branch-semver" for now in main, see https://github.com/DataDog/dd-trace-py/issues/8801 -write_to = "ddtrace/_version.py" - [tool.cython-lint] max-line-length = 120 exclude = ''' diff --git a/scripts/ddtest b/scripts/ddtest index d3b8f7eb821..9bbd5148fe0 100755 --- a/scripts/ddtest +++ b/scripts/ddtest @@ -9,24 +9,8 @@ then CMD=bash fi -# If we are in a worktree inside of the container then git/setuptools_scm doesn't work right -# we need to tell setuptools_scm what the version is manually -if git rev-parse --is-inside-work-tree > /dev/null 2>&1 -then - if command -v uv > /dev/null 2>&1 - then - version=$(uvx --from=setuptools_scm --quiet setuptools-scm) - else - version=$(git describe --tags --abbrev=0 --match "v[0-9]*" | sed 's/^v//') - fi - - export SETUPTOOLS_SCM_PRETEND_VERSION_FOR_DDTRACE="${version}" -fi - docker compose run \ -e DD_TRACE_AGENT_URL \ - -e SETUPTOOLS_SCM_PRETEND_VERSION \ - -e SETUPTOOLS_SCM_PRETEND_VERSION_FOR_DDTRACE \ --rm \ -i \ testrunner \ diff --git a/setup.py b/setup.py index 88b679aaa03..7049e4f369e 100644 --- a/setup.py +++ b/setup.py @@ -1181,7 +1181,7 @@ def get_exts_for(name): "clean": CleanLibraries, "ext_hashes": ExtensionHashes, }, - setup_requires=["setuptools_scm[toml]>=4", "cython", "cmake>=3.24.2,<3.28", "setuptools-rust"], + setup_requires=["cython", "cmake>=3.24.2,<3.28", "setuptools-rust"], ext_modules=ext_modules + cythonize( [ From d82bdce047452a80d1696b2b8f94f81e702a19b2 Mon Sep 17 00:00:00 2001 From: brettlangdon Date: Thu, 13 Nov 2025 10:25:44 -0500 Subject: [PATCH 02/29] add missing ddtrace/internal/dist_computing/__init__.py file --- ddtrace/internal/dist_computing/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 ddtrace/internal/dist_computing/__init__.py diff --git a/ddtrace/internal/dist_computing/__init__.py b/ddtrace/internal/dist_computing/__init__.py new file mode 100644 index 00000000000..e69de29bb2d From d84a543b47f581b83809f81842896b75284c0c58 Mon Sep 17 00:00:00 2001 From: brettlangdon Date: Thu, 13 Nov 2025 10:37:38 -0500 Subject: [PATCH 03/29] add ddtrace/appsec/_exploit_prevention/__init__.py --- ddtrace/appsec/_exploit_prevention/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 ddtrace/appsec/_exploit_prevention/__init__.py diff --git a/ddtrace/appsec/_exploit_prevention/__init__.py b/ddtrace/appsec/_exploit_prevention/__init__.py new file mode 100644 index 00000000000..e69de29bb2d From 7086fe3308ed8f6f6e92bdd932b5ab725aa06f8a Mon Sep 17 00:00:00 2001 From: brettlangdon Date: Thu, 13 Nov 2025 11:20:15 -0500 Subject: [PATCH 04/29] fix missing pyx and namespace packages --- .github/workflows/build_deploy.yml | 3 +- MANIFEST.in | 1 + ddtrace/contrib/internal/aiohttp/__init__.py | 0 ddtrace/contrib/internal/asgi/__init__.py | 0 .../internal/botocore/services/__init__.py | 0 ddtrace/contrib/internal/bottle/__init__.py | 0 ddtrace/contrib/internal/celery/__init__.py | 0 ddtrace/contrib/internal/cherrypy/__init__.py | 0 ddtrace/contrib/internal/falcon/__init__.py | 0 .../contrib/internal/flask_cache/__init__.py | 0 ddtrace/contrib/internal/pylibmc/__init__.py | 0 ddtrace/contrib/internal/pyramid/__init__.py | 0 ddtrace/contrib/internal/requests/__init__.py | 0 .../contrib/internal/sqlalchemy/__init__.py | 0 ddtrace/contrib/internal/tornado/__init__.py | 0 ddtrace/contrib/internal/valkey/__init__.py | 0 ddtrace/contrib/internal/wsgi/__init__.py | 0 ddtrace/internal/appsec/__init__.py | 0 .../internal/ci_visibility/api/__init__.py | 0 .../datadog/profiling/ddup/test/__init__.py | 0 ddtrace/internal/evp_proxy/__init__.py | 0 ddtrace/internal/iast/__init__.py | 0 ddtrace/internal/opentelemetry/__init__.py | 0 ddtrace/internal/test_visibility/__init__.py | 0 ddtrace/llmobs/_evaluators/__init__.py | 0 ddtrace/llmobs/_evaluators/ragas/__init__.py | 0 setup.py | 59 ++++++++++--------- 27 files changed, 34 insertions(+), 29 deletions(-) create mode 100644 ddtrace/contrib/internal/aiohttp/__init__.py create mode 100644 ddtrace/contrib/internal/asgi/__init__.py create mode 100644 ddtrace/contrib/internal/botocore/services/__init__.py create mode 100644 ddtrace/contrib/internal/bottle/__init__.py create mode 100644 ddtrace/contrib/internal/celery/__init__.py create mode 100644 ddtrace/contrib/internal/cherrypy/__init__.py create mode 100644 ddtrace/contrib/internal/falcon/__init__.py create mode 100644 ddtrace/contrib/internal/flask_cache/__init__.py create mode 100644 ddtrace/contrib/internal/pylibmc/__init__.py create mode 100644 ddtrace/contrib/internal/pyramid/__init__.py create mode 100644 ddtrace/contrib/internal/requests/__init__.py create mode 100644 ddtrace/contrib/internal/sqlalchemy/__init__.py create mode 100644 ddtrace/contrib/internal/tornado/__init__.py create mode 100644 ddtrace/contrib/internal/valkey/__init__.py create mode 100644 ddtrace/contrib/internal/wsgi/__init__.py create mode 100644 ddtrace/internal/appsec/__init__.py create mode 100644 ddtrace/internal/ci_visibility/api/__init__.py create mode 100644 ddtrace/internal/datadog/profiling/ddup/test/__init__.py create mode 100644 ddtrace/internal/evp_proxy/__init__.py create mode 100644 ddtrace/internal/iast/__init__.py create mode 100644 ddtrace/internal/opentelemetry/__init__.py create mode 100644 ddtrace/internal/test_visibility/__init__.py create mode 100644 ddtrace/llmobs/_evaluators/__init__.py create mode 100644 ddtrace/llmobs/_evaluators/ragas/__init__.py diff --git a/.github/workflows/build_deploy.yml b/.github/workflows/build_deploy.yml index 9ff25ae50f2..bdb9b42bcba 100644 --- a/.github/workflows/build_deploy.yml +++ b/.github/workflows/build_deploy.yml @@ -49,7 +49,8 @@ jobs: - name: Build sdist run: | pip install "cython" "cmake>=3.24.2,<3.28" "setuptools-rust" - python setup.py sdist + # Do not Cythonize during sdist to avoid generating .c files + DD_CYTHONIZE=0 python setup.py sdist - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: source-dist diff --git a/MANIFEST.in b/MANIFEST.in index 1baa89a6711..2ec6a869775 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,4 @@ +recursive-include ddtrace *.pyx prune .riot/ prune benchmarks/ prune releasenotes/ diff --git a/ddtrace/contrib/internal/aiohttp/__init__.py b/ddtrace/contrib/internal/aiohttp/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/ddtrace/contrib/internal/asgi/__init__.py b/ddtrace/contrib/internal/asgi/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/ddtrace/contrib/internal/botocore/services/__init__.py b/ddtrace/contrib/internal/botocore/services/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/ddtrace/contrib/internal/bottle/__init__.py b/ddtrace/contrib/internal/bottle/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/ddtrace/contrib/internal/celery/__init__.py b/ddtrace/contrib/internal/celery/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/ddtrace/contrib/internal/cherrypy/__init__.py b/ddtrace/contrib/internal/cherrypy/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/ddtrace/contrib/internal/falcon/__init__.py b/ddtrace/contrib/internal/falcon/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/ddtrace/contrib/internal/flask_cache/__init__.py b/ddtrace/contrib/internal/flask_cache/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/ddtrace/contrib/internal/pylibmc/__init__.py b/ddtrace/contrib/internal/pylibmc/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/ddtrace/contrib/internal/pyramid/__init__.py b/ddtrace/contrib/internal/pyramid/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/ddtrace/contrib/internal/requests/__init__.py b/ddtrace/contrib/internal/requests/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/ddtrace/contrib/internal/sqlalchemy/__init__.py b/ddtrace/contrib/internal/sqlalchemy/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/ddtrace/contrib/internal/tornado/__init__.py b/ddtrace/contrib/internal/tornado/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/ddtrace/contrib/internal/valkey/__init__.py b/ddtrace/contrib/internal/valkey/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/ddtrace/contrib/internal/wsgi/__init__.py b/ddtrace/contrib/internal/wsgi/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/ddtrace/internal/appsec/__init__.py b/ddtrace/internal/appsec/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/ddtrace/internal/ci_visibility/api/__init__.py b/ddtrace/internal/ci_visibility/api/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/ddtrace/internal/datadog/profiling/ddup/test/__init__.py b/ddtrace/internal/datadog/profiling/ddup/test/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/ddtrace/internal/evp_proxy/__init__.py b/ddtrace/internal/evp_proxy/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/ddtrace/internal/iast/__init__.py b/ddtrace/internal/iast/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/ddtrace/internal/opentelemetry/__init__.py b/ddtrace/internal/opentelemetry/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/ddtrace/internal/test_visibility/__init__.py b/ddtrace/internal/test_visibility/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/ddtrace/llmobs/_evaluators/__init__.py b/ddtrace/llmobs/_evaluators/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/ddtrace/llmobs/_evaluators/ragas/__init__.py b/ddtrace/llmobs/_evaluators/ragas/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/setup.py b/setup.py index 7049e4f369e..8dab5c2ea3d 100644 --- a/setup.py +++ b/setup.py @@ -1157,33 +1157,10 @@ def get_exts_for(name): else: ext_modules = [] -interpose_sccache() -setup( - name="ddtrace", - packages=find_packages(exclude=["tests*", "benchmarks*", "scripts*"]), - package_data={ - "ddtrace": ["py.typed"], - "ddtrace.appsec": ["rules.json"], - "ddtrace.appsec._ddwaf": ["libddwaf/*/lib/libddwaf.*"], - "ddtrace.appsec._iast._taint_tracking": ["CMakeLists.txt"], - "ddtrace.internal.datadog.profiling": ( - ["libdd_wrapper*.*"] - + (["ddtrace/internal/datadog/profiling/test/*"] if BUILD_PROFILING_NATIVE_TESTS else []) - ), - }, - zip_safe=False, - # enum34 is an enum backport for earlier versions of python - # funcsigs backport required for vendored debtcollector - cmdclass={ - "build_ext": CustomBuildExt, - "build_py": LibraryDownloader, - "build_rust": CustomBuildRust, - "clean": CleanLibraries, - "ext_hashes": ExtensionHashes, - }, - setup_requires=["cython", "cmake>=3.24.2,<3.28", "setuptools-rust"], - ext_modules=ext_modules - + cythonize( + +cython_exts = [] +if os.getenv("DD_CYTHONIZE", "1") == "1": + cython_exts = cythonize( [ Cython.Distutils.Extension( "ddtrace.internal._rand", @@ -1244,6 +1221,32 @@ def get_exts_for(name): compiler_directives={"language_level": "3"}, cache=True, ) - + get_exts_for("psutil"), + +interpose_sccache() +setup( + name="ddtrace", + packages=find_packages(exclude=["tests*", "benchmarks*", "scripts*"]), + package_data={ + "ddtrace": ["py.typed"], + "ddtrace.appsec": ["rules.json"], + "ddtrace.appsec._ddwaf": ["libddwaf/*/lib/libddwaf.*"], + "ddtrace.appsec._iast._taint_tracking": ["CMakeLists.txt"], + "ddtrace.internal.datadog.profiling": ( + ["libdd_wrapper*.*"] + + (["ddtrace/internal/datadog/profiling/test/*"] if BUILD_PROFILING_NATIVE_TESTS else []) + ), + }, + zip_safe=False, + # enum34 is an enum backport for earlier versions of python + # funcsigs backport required for vendored debtcollector + cmdclass={ + "build_ext": CustomBuildExt, + "build_py": LibraryDownloader, + "build_rust": CustomBuildRust, + "clean": CleanLibraries, + "ext_hashes": ExtensionHashes, + }, + setup_requires=["cython", "cmake>=3.24.2,<3.28", "setuptools-rust"], + ext_modules=ext_modules + cython_exts + get_exts_for("psutil"), distclass=PatchedDistribution, ) From 8b796a04759af551bb2fd307e48e7b8758b9121a Mon Sep 17 00:00:00 2001 From: brettlangdon Date: Thu, 13 Nov 2025 11:24:57 -0500 Subject: [PATCH 05/29] fix ddtrace.__version__ --- .gitignore | 3 --- ddtrace/__init__.py | 6 ++---- ddtrace/_version.py | 23 ----------------------- ddtrace/version.py | 36 +++++++++++++++++++++++------------- 4 files changed, 25 insertions(+), 43 deletions(-) delete mode 100644 ddtrace/_version.py diff --git a/.gitignore b/.gitignore index 1cf2c168a14..69974cb3929 100644 --- a/.gitignore +++ b/.gitignore @@ -148,9 +148,6 @@ CLAUDE.local.md .riot/venv* .riot/requirements/*.in -# Auto-generated version file -ddtrace/_version.py - # Benchmarks artifacts/ diff --git a/ddtrace/__init__.py b/ddtrace/__init__.py index dac0448fb68..1d02cf93c0f 100644 --- a/ddtrace/__init__.py +++ b/ddtrace/__init__.py @@ -21,16 +21,14 @@ from .internal.compat import PYTHON_VERSION_INFO # noqa: E402 from .internal.settings._config import config from .internal.utils.deprecations import DDTraceDeprecationWarning # noqa: E402 -from .version import get_version # noqa: E402 - - -__version__ = get_version() +from .version import __version__ # TODO: Deprecate accessing tracer from ddtrace.__init__ module in v4.0 if os.environ.get("_DD_GLOBAL_TRACER_INIT", "true").lower() in ("1", "true"): from ddtrace.trace import tracer # noqa: F401 __all__ = [ + "__version__", "patch", "patch_all", "config", diff --git a/ddtrace/_version.py b/ddtrace/_version.py deleted file mode 100644 index e7e5fe018cd..00000000000 --- a/ddtrace/_version.py +++ /dev/null @@ -1,23 +0,0 @@ -__all__ = [ - "__version__", - "__version_tuple__", - "version", - "version_tuple", -] - -TYPE_CHECKING = False -if TYPE_CHECKING: - from typing import Tuple - from typing import Union - - VERSION_TUPLE = Tuple[Union[int, str], ...] -else: - VERSION_TUPLE = object - -version: str -__version__: str -__version_tuple__: VERSION_TUPLE -version_tuple: VERSION_TUPLE - -__version__ = version = "4.0.0rc1" -__version_tuple__ = version_tuple = (4, 0, 0, "rc1") diff --git a/ddtrace/version.py b/ddtrace/version.py index 016f71773e4..e7e5fe018cd 100644 --- a/ddtrace/version.py +++ b/ddtrace/version.py @@ -1,13 +1,23 @@ -def get_version() -> str: - try: - from ._version import version - - return version - except ImportError: - from importlib.metadata import version as ilm_version - - try: - return ilm_version("ddtrace") - except ModuleNotFoundError: - # package is not installed - return "dev" +__all__ = [ + "__version__", + "__version_tuple__", + "version", + "version_tuple", +] + +TYPE_CHECKING = False +if TYPE_CHECKING: + from typing import Tuple + from typing import Union + + VERSION_TUPLE = Tuple[Union[int, str], ...] +else: + VERSION_TUPLE = object + +version: str +__version__: str +__version_tuple__: VERSION_TUPLE +version_tuple: VERSION_TUPLE + +__version__ = version = "4.0.0rc1" +__version_tuple__ = version_tuple = (4, 0, 0, "rc1") From 432a2ca5b0e2f83ab50fbe1f5a62afb4f2fe45cf Mon Sep 17 00:00:00 2001 From: brettlangdon Date: Thu, 13 Nov 2025 11:40:45 -0500 Subject: [PATCH 06/29] use static versions internally --- ddtrace/_trace/tracer.py | 4 +- ddtrace/internal/core/crashtracking.py | 4 +- ddtrace/internal/datastreams/processor.py | 8 ++-- ddtrace/internal/processor/stats.py | 4 +- .../settings/dynamic_instrumentation.py | 4 +- ddtrace/internal/telemetry/data.py | 4 +- ddtrace/internal/utils/version.py | 4 +- tests/appsec/architectures/mini.py | 4 +- tests/appsec/contrib_appsec/conftest.py | 2 +- tests/contrib/patch.py | 4 +- tests/debugging/test_config.py | 4 +- tests/lib_injection/conftest.py | 4 +- tests/tracer/test_version.py | 41 ------------------- 13 files changed, 24 insertions(+), 67 deletions(-) delete mode 100644 tests/tracer/test_version.py diff --git a/ddtrace/_trace/tracer.py b/ddtrace/_trace/tracer.py index 091286187ed..7ea9be3b4df 100644 --- a/ddtrace/_trace/tracer.py +++ b/ddtrace/_trace/tracer.py @@ -59,7 +59,7 @@ from ddtrace.internal.utils.formats import format_trace_id from ddtrace.internal.writer import AgentWriterInterface from ddtrace.internal.writer import HTTPWriter -from ddtrace.version import get_version +from ddtrace.version import __version__ log = get_logger(__name__) @@ -165,7 +165,7 @@ def __init__(self) -> None: metadata = PyTracerMetadata( runtime_id=get_runtime_id(), - tracer_version=get_version(), + tracer_version=__version__, hostname=get_hostname(), service_name=config.service or None, service_env=config.env or None, diff --git a/ddtrace/internal/core/crashtracking.py b/ddtrace/internal/core/crashtracking.py index 180b97274f5..9d86eb30118 100644 --- a/ddtrace/internal/core/crashtracking.py +++ b/ddtrace/internal/core/crashtracking.py @@ -53,7 +53,7 @@ def _get_tags(additional_tags: Optional[Dict[str, str]]) -> Dict[str, str]: runtime_version = platform.python_version() if runtime_version: tags["runtime_version"] = runtime_version - library_version = version.get_version() + library_version = version.__version__ if library_version: tags["library_version"] = library_version @@ -127,7 +127,7 @@ def _get_args(additional_tags: Optional[Dict[str, str]]): tags = _get_tags(additional_tags) - metadata = CrashtrackerMetadata("dd-trace-py", version.get_version(), "python", tags) + metadata = CrashtrackerMetadata("dd-trace-py", version.__version__, "python", tags) return config, receiver_config, metadata diff --git a/ddtrace/internal/datastreams/processor.py b/ddtrace/internal/datastreams/processor.py index e9b8a874259..c08e94d63e3 100644 --- a/ddtrace/internal/datastreams/processor.py +++ b/ddtrace/internal/datastreams/processor.py @@ -22,7 +22,7 @@ from ddtrace.internal.settings._agent import config as agent_config from ddtrace.internal.settings._config import config from ddtrace.internal.utils.retry import fibonacci_backoff_with_jitter -from ddtrace.version import get_version +from ddtrace.version import __version__ from .._encoding import packb from ..agent import get_connection @@ -112,10 +112,8 @@ def __init__( self._timeout = timeout # Have the bucket size match the interval in which flushes occur. self._bucket_size_ns = int(interval * 1e9) # type: int - self._buckets = defaultdict( - lambda: Bucket(defaultdict(PathwayStats), defaultdict(int), defaultdict(int)) - ) # type: DefaultDict[int, Bucket] - self._version = get_version() + self._buckets = defaultdict(lambda: Bucket(defaultdict(PathwayStats), defaultdict(int), defaultdict(int))) # type: DefaultDict[int, Bucket] + self._version = __version__ self._headers = { "Datadog-Meta-Lang": "python", "Datadog-Meta-Tracer-Version": self._version, diff --git a/ddtrace/internal/processor/stats.py b/ddtrace/internal/processor/stats.py index ea2227aee1b..0ad1397c1e6 100644 --- a/ddtrace/internal/processor/stats.py +++ b/ddtrace/internal/processor/stats.py @@ -14,7 +14,7 @@ from ddtrace.internal.native import DDSketch from ddtrace.internal.settings._config import config from ddtrace.internal.utils.retry import fibonacci_backoff_with_jitter -from ddtrace.version import get_version +from ddtrace.version import __version__ from ...constants import _SPAN_MEASURED_KEY from .. import agent @@ -105,7 +105,7 @@ def __init__( ) self._headers: Dict[str, str] = { "Datadog-Meta-Lang": "python", - "Datadog-Meta-Tracer-Version": get_version(), + "Datadog-Meta-Tracer-Version": __version__, "Content-Type": "application/msgpack", } self._hostname = "" diff --git a/ddtrace/internal/settings/dynamic_instrumentation.py b/ddtrace/internal/settings/dynamic_instrumentation.py index 99431165d5a..707f0758c95 100644 --- a/ddtrace/internal/settings/dynamic_instrumentation.py +++ b/ddtrace/internal/settings/dynamic_instrumentation.py @@ -8,7 +8,7 @@ from ddtrace.internal.settings._agent import config as agent_config from ddtrace.internal.settings._core import DDConfig from ddtrace.internal.utils.config import get_application_name -from ddtrace.version import get_version +from ddtrace.version import __version__ DEFAULT_MAX_PROBES = 100 @@ -17,7 +17,7 @@ def _derive_tags(c): # type: (DDConfig) -> str - _tags = dict(env=ddconfig.env, version=ddconfig.version, debugger_version=get_version()) + _tags = dict(env=ddconfig.env, version=ddconfig.version, debugger_version=__version__) _tags.update(ddconfig.tags) # Add git metadata tags, if available diff --git a/ddtrace/internal/telemetry/data.py b/ddtrace/internal/telemetry/data.py index 7d3fa4ede91..6ac4689f29d 100644 --- a/ddtrace/internal/telemetry/data.py +++ b/ddtrace/internal/telemetry/data.py @@ -11,7 +11,7 @@ from ddtrace.internal.packages import get_module_distribution_versions from ddtrace.internal.runtime.container import get_container_info from ddtrace.internal.utils.cache import cached -from ddtrace.version import get_version +from ddtrace.version import __version__ from ..hostname import get_hostname @@ -64,7 +64,7 @@ def _get_application(key): "env": env or "", "language_name": "python", "language_version": _format_version_info(sys.version_info), - "tracer_version": get_version(), + "tracer_version": __version__, "runtime_name": platform.python_implementation(), "runtime_version": _format_version_info(sys.implementation.version), } diff --git a/ddtrace/internal/utils/version.py b/ddtrace/internal/utils/version.py index b3ab75d5ef4..be10973b63f 100644 --- a/ddtrace/internal/utils/version.py +++ b/ddtrace/internal/utils/version.py @@ -2,7 +2,7 @@ from typing import Optional # noqa:F401 import ddtrace.vendor.packaging.version as packaging_version -from ddtrace.version import get_version +from ddtrace.version import __version__ def parse_version(version): @@ -71,7 +71,7 @@ def _pep440_to_semver(version=None): # # e.g. 1.7.1-rc2.dev3+gf258c7d9 is valid - tracer_version = version or get_version() + tracer_version = version or __version__ if "rc" in tracer_version and "-rc" not in tracer_version: tracer_version = tracer_version.replace("rc", "-rc", 1) elif ".dev" in tracer_version: diff --git a/tests/appsec/architectures/mini.py b/tests/appsec/architectures/mini.py index 179c85bfb80..199cc1826e2 100644 --- a/tests/appsec/architectures/mini.py +++ b/tests/appsec/architectures/mini.py @@ -13,7 +13,7 @@ from ddtrace.internal.settings.asm import config as asm_config # noqa: E402 import ddtrace.internal.telemetry.writer # noqa: E402 -from ddtrace.version import get_version # noqa: E402 +from ddtrace.version import __version__ app = Flask(__name__) @@ -46,7 +46,7 @@ def hello_world(): k: getattr(asm_config, k) for k in dir(asm_config) if isinstance(getattr(asm_config, k), (int, bool, float)) }, "aws": "AWS_LAMBDA_FUNCTION_NAME" in os.environ, - "version": get_version(), + "version": __version__, "env": dict(os.environ), "file_length": file_length, } diff --git a/tests/appsec/contrib_appsec/conftest.py b/tests/appsec/contrib_appsec/conftest.py index 951465bedcf..53a44674a7f 100644 --- a/tests/appsec/contrib_appsec/conftest.py +++ b/tests/appsec/contrib_appsec/conftest.py @@ -3,7 +3,7 @@ # ensure the tracer is loaded and started first for possible iast patching -print(f"ddtrace version {ddtrace.version.get_version()}") +print(f"ddtrace version {ddtrace.version.__version__}") import pytest # noqa: E402 diff --git a/tests/contrib/patch.py b/tests/contrib/patch.py index 1e15cc9a828..1dad7472cd2 100644 --- a/tests/contrib/patch.py +++ b/tests/contrib/patch.py @@ -9,13 +9,13 @@ import unittest from ddtrace.internal.compat import is_wrapted -from ddtrace.version import get_version +from ddtrace.version import __version__ from tests.subprocesstest import SubprocessTestCase from tests.subprocesstest import run_in_subprocess from tests.utils import call_program -TRACER_VERSION = get_version() +TRACER_VERSION = __version__ class PatchMixin(unittest.TestCase): diff --git a/tests/debugging/test_config.py b/tests/debugging/test_config.py index 07c8ef7d739..03a6679b29e 100644 --- a/tests/debugging/test_config.py +++ b/tests/debugging/test_config.py @@ -5,7 +5,7 @@ from ddtrace.internal.settings._agent import config as agent_config from ddtrace.internal.settings.dynamic_instrumentation import DynamicInstrumentationConfig from ddtrace.internal.utils.formats import parse_tags_str -from ddtrace.version import get_version +from ddtrace.version import __version__ from tests.utils import override_env @@ -39,7 +39,7 @@ def test_tags(): c="d", env="test-env", version="test-version", - debugger_version=get_version(), + debugger_version=__version__, ) diff --git a/tests/lib_injection/conftest.py b/tests/lib_injection/conftest.py index fa7ed195bbe..77e5ace1580 100644 --- a/tests/lib_injection/conftest.py +++ b/tests/lib_injection/conftest.py @@ -13,10 +13,10 @@ import pytest -from ddtrace.version import get_version +from ddtrace.version import __version__ -HOST_DDTRACE_VERSION = get_version() +HOST_DDTRACE_VERSION = __version__ LIBS_INJECTION_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../lib-injection")) LIBS_INJECTION_SRC_DIR = os.path.join(LIBS_INJECTION_DIR, "sources") PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "../..")) diff --git a/tests/tracer/test_version.py b/tests/tracer/test_version.py deleted file mode 100644 index 5ab2033b459..00000000000 --- a/tests/tracer/test_version.py +++ /dev/null @@ -1,41 +0,0 @@ -import sys - -import mock - -from ddtrace.version import get_version -from tests.tracer import _version # noqa: F401 -> we need to import it so that it can be swapped with the test module - - -def test_get_version_from_version_file(): - with mock.patch.dict(sys.modules, {"ddtrace._version": sys.modules["tests.tracer._version"]}): - assert get_version() == "my_test_version_from_generated_file" - - -def test_get_version_from_importlib_metadata(): - with mock.patch.dict(sys.modules, {"ddtrace._version": None}): - version_str = "importlib.metadata.version" - with mock.patch(version_str, return_value="my_test_version_from_import_lib") as mock_get_version: - assert get_version() == "my_test_version_from_import_lib" - mock_get_version.assert_called_with("ddtrace") - - -def test_get_version_dev_fallback(): - with mock.patch.dict(sys.modules, {"ddtrace._version": None}): - version_str = "importlib.metadata.version" - with mock.patch(version_str, side_effect=ModuleNotFoundError): - assert get_version() == "dev" - - -class FakeDistributionIterator: - def __init__(self, distribution): - pass - - def __next__(self): - raise StopIteration - - -class FakeDistribution: - version = "my_test_version_from_pkg_resources" - - def __iter__(self): - return FakeDistributionIterator(self) From dfed56d2205ba9ce8ae225bd88a5f308199e4f22 Mon Sep 17 00:00:00 2001 From: brettlangdon Date: Thu, 13 Nov 2025 11:51:17 -0500 Subject: [PATCH 07/29] fix formatting --- ddtrace/internal/datastreams/processor.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ddtrace/internal/datastreams/processor.py b/ddtrace/internal/datastreams/processor.py index c08e94d63e3..9f05ecfcc7c 100644 --- a/ddtrace/internal/datastreams/processor.py +++ b/ddtrace/internal/datastreams/processor.py @@ -112,7 +112,9 @@ def __init__( self._timeout = timeout # Have the bucket size match the interval in which flushes occur. self._bucket_size_ns = int(interval * 1e9) # type: int - self._buckets = defaultdict(lambda: Bucket(defaultdict(PathwayStats), defaultdict(int), defaultdict(int))) # type: DefaultDict[int, Bucket] + self._buckets = defaultdict( + lambda: Bucket(defaultdict(PathwayStats), defaultdict(int), defaultdict(int)) + ) # type: DefaultDict[int, Bucket] self._version = __version__ self._headers = { "Datadog-Meta-Lang": "python", From b709231fe5b21f9ca09808f042d0f981ef050faf Mon Sep 17 00:00:00 2001 From: brettlangdon Date: Thu, 13 Nov 2025 11:58:40 -0500 Subject: [PATCH 08/29] fix linting --- tests/appsec/architectures/mini.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/appsec/architectures/mini.py b/tests/appsec/architectures/mini.py index 199cc1826e2..be8990b5423 100644 --- a/tests/appsec/architectures/mini.py +++ b/tests/appsec/architectures/mini.py @@ -13,7 +13,7 @@ from ddtrace.internal.settings.asm import config as asm_config # noqa: E402 import ddtrace.internal.telemetry.writer # noqa: E402 -from ddtrace.version import __version__ +from ddtrace.version import __version__ # noqa: E402 app = Flask(__name__) From 6a88eddbbc186988f117e2229ae2c8256559acee Mon Sep 17 00:00:00 2001 From: brettlangdon Date: Thu, 13 Nov 2025 12:50:56 -0500 Subject: [PATCH 09/29] include .pxd --- MANIFEST.in | 1 + 1 file changed, 1 insertion(+) diff --git a/MANIFEST.in b/MANIFEST.in index 2ec6a869775..6b7be8bdc35 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,4 @@ +recursive-include ddtrace *.pxd recursive-include ddtrace *.pyx prune .riot/ prune benchmarks/ From 9661bc08a024c3f28ee2750d1432e989ec77e95f Mon Sep 17 00:00:00 2001 From: brettlangdon Date: Thu, 13 Nov 2025 12:59:29 -0500 Subject: [PATCH 10/29] also include src dir --- MANIFEST.in | 2 ++ 1 file changed, 2 insertions(+) diff --git a/MANIFEST.in b/MANIFEST.in index 6b7be8bdc35..d083ad69f9b 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,7 @@ recursive-include ddtrace *.pxd recursive-include ddtrace *.pyx +graft src prune .riot/ prune benchmarks/ prune releasenotes/ +prune src/native/target* From e46fee7f3d0568367e4e47653a234470724512e4 Mon Sep 17 00:00:00 2001 From: brettlangdon Date: Thu, 13 Nov 2025 13:27:08 -0500 Subject: [PATCH 11/29] fix MANIFEST.in? --- .github/workflows/build_deploy.yml | 1 + MANIFEST.in | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build_deploy.yml b/.github/workflows/build_deploy.yml index bdb9b42bcba..8c073a89b0c 100644 --- a/.github/workflows/build_deploy.yml +++ b/.github/workflows/build_deploy.yml @@ -84,6 +84,7 @@ jobs: - name: Install source package env: CMAKE_BUILD_PARALLEL_LEVEL: 12 + CARGO_BUILD_JOBS: 12 run: pip install dist/*.tar.gz - name: Test the source package diff --git a/MANIFEST.in b/MANIFEST.in index d083ad69f9b..7cfe3f38db1 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,6 +1,7 @@ -recursive-include ddtrace *.pxd -recursive-include ddtrace *.pyx +graft ddtrace graft src + +recursive-exclude * **/__pycache__/* prune .riot/ prune benchmarks/ prune releasenotes/ From bfb96dd3ad8bf1c937e59671fd5eacd222ae7c6d Mon Sep 17 00:00:00 2001 From: brettlangdon Date: Thu, 13 Nov 2025 14:36:01 -0500 Subject: [PATCH 12/29] remnants of _version.py going away --- benchmarks/appsec_iast_aspects_split/functions.py | 9 +++++++-- pyproject.toml | 1 - tests/suitespec.yml | 1 - 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/benchmarks/appsec_iast_aspects_split/functions.py b/benchmarks/appsec_iast_aspects_split/functions.py index 7d9f72bf7b1..3706ccc24da 100644 --- a/benchmarks/appsec_iast_aspects_split/functions.py +++ b/benchmarks/appsec_iast_aspects_split/functions.py @@ -2,7 +2,12 @@ import os import re -import ddtrace._version as version +try: + from ddtrace import __version__ +except ImportError: + from ddtrace.version import get_version + + __version__ = get_version() # Some old versions could not have or export some symbols, so we import them dynamically and assign None if not found @@ -29,7 +34,7 @@ # print(f"Warning: {symbol} not found in the current version") if notfound_symbols: - print("Warning: symbols not found in the tested version [%s]: %s" % (version.version, str(notfound_symbols))) + print("Warning: symbols not found in the tested version [%s]: %s" % (__version__, str(notfound_symbols))) def iast_add_aspect(): diff --git a/pyproject.toml b/pyproject.toml index 9301c6d7ef6..69e6ccfc09e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -116,7 +116,6 @@ exclude = ''' | ddtrace/vendor/ | ddtrace/appsec/_iast/_taint_tracking/_vendor/ | ddtrace/appsec/_iast/_taint_tracking/cmake-build-debug/ - | ddtrace/_version.py | \.eggs | \.git | \.hg diff --git a/tests/suitespec.yml b/tests/suitespec.yml index 245c21d1a95..f6fac918afc 100644 --- a/tests/suitespec.yml +++ b/tests/suitespec.yml @@ -76,7 +76,6 @@ components: - ddtrace/__init__.py - ddtrace/py.typed - ddtrace/version.py - - ddtrace/_version.py - ddtrace/internal/settings/_config.py - src/native/* datastreams: From e01460f7f712a94067dae4d25cb58c8bca94f3ae Mon Sep 17 00:00:00 2001 From: brettlangdon Date: Thu, 13 Nov 2025 14:49:54 -0500 Subject: [PATCH 13/29] replace slotscheck with ruff --- hatch.toml | 2 +- pyproject.toml | 49 +++++---------------------------------------- riotfile.py | 6 ------ tests/suitespec.yml | 6 ------ 4 files changed, 6 insertions(+), 57 deletions(-) diff --git a/hatch.toml b/hatch.toml index 21d918c7a30..5d45d126eab 100644 --- a/hatch.toml +++ b/hatch.toml @@ -18,7 +18,7 @@ dependencies = [ "packaging==23.1", "pygments==2.16.1", "riot==0.20.1", - "ruff==0.11.11", + "ruff==0.14.4", "clang-format==18.1.5", "cmake-format==0.6.13", "ruamel.yaml==0.18.6", diff --git a/pyproject.toml b/pyproject.toml index 69e6ccfc09e..1a0ca0f72f8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -145,50 +145,6 @@ exclude = [ ".worktrees", ] -[tool.slotscheck] -exclude-modules = ''' -( - ^ddtrace.(contrib|vendor) - | ^tests.(contrib|vendor) - # avoid sitecustomize modules as they start services - | ddtrace.bootstrap.sitecustomize - | ddtrace.openfeature - | ddtrace.internal.openfeature - | ddtrace.profiling.bootstrap.sitecustomize - | ddtrace.profiling.auto - # also ignore preload module to avoid exception after moving ddtrace.tracing module - | ddtrace.bootstrap.preload - # protobuf file fails to import - | tests.profiling.collector.pprof_3_pb2 - | tests.profiling.collector.pprof_312_pb2 - | tests.profiling.collector.pprof_319_pb2 - | tests.profiling.collector.pprof_421_pb2 - # TODO: resolve slot inheritance issues with profiling - | ddtrace.profiling.collector - | ddtrace.profiling._gevent - | ddtrace,appsec,iast,_taint_tracking.vendor - | ddtrace.appsec._ddwaf.ddwaf_types - | ddtrace.appsec._iast._taint_tracking - | ddtrace.appsec._iast._ast.aspects - | ddtrace.appsec._iast._taint_utils - | ddtrace.appsec._iast.taint_sinks.sql_injection - # DSM specific contribs - | ddtrace.internal.datastreams.kafka - # libdd_wrapper is a common native dependency, not a module - | ddtrace.internal.datadog.profiling.libdd_wrapper - # _ddup and _stack_v2 miss a runtime dependency in slotscheck, but ddup and stack_v2 are fine - | ddtrace.internal.datadog.profiling.ddup._ddup - | ddtrace.internal.datadog.profiling.stack_v2._stack_v2 - # coverage has version-specific checks that prevent import - | ddtrace.internal.coverage.instrumentation_py3_8 - | ddtrace.internal.coverage.instrumentation_py3_10 - | ddtrace.internal.coverage.instrumentation_py3_11 - | ddtrace.internal.coverage.instrumentation_py3_12 - | ddtrace.internal.coverage.instrumentation_py3_13 - | ddtrace.internal.coverage.instrumentation_py3_14 -) -''' - [tool.bandit] targets = ["ddtrace/"] @@ -255,10 +211,14 @@ lint.ignore = [ "D413", "E203", "E231", + "E262", + "E266", "E721", + "F822", "G201", ] line-length = 120 +preview = true lint.select = [ "A", "D", @@ -266,6 +226,7 @@ lint.select = [ "F", "G", "I", + "PLW0244", # redefined-slots-in-subclass "W", ] lint.unfixable =[ diff --git a/riotfile.py b/riotfile.py index 1ba8eb7492e..c7b34e41293 100644 --- a/riotfile.py +++ b/riotfile.py @@ -128,12 +128,6 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT "DD_CIVISIBILITY_FLAKY_RETRY_ENABLED": "0", }, ), - Venv( - name="slotscheck", - command="python -m slotscheck -v ddtrace/", - pys=["3.10"], - pkgs={"slotscheck": "==0.17.0"}, - ), Venv( name="build_docs", command="scripts/docs/build.sh", diff --git a/tests/suitespec.yml b/tests/suitespec.yml index f6fac918afc..1fc2c319d87 100644 --- a/tests/suitespec.yml +++ b/tests/suitespec.yml @@ -217,12 +217,6 @@ suites: parallelism: 2 runner: riot pattern: ^lib_injection$ - slotscheck: - parallelism: 1 - paths: - - 'ddtrace/**/*.py' - runner: riot - snapshot: false runtime: paths: - '@bootstrap' From 099d2b382d69197ad95368b677eaa6cfff60e50e Mon Sep 17 00:00:00 2001 From: brettlangdon Date: Thu, 13 Nov 2025 15:05:32 -0500 Subject: [PATCH 14/29] fix new ruff issues --- benchmarks/appsec_iast_aspects_split/functions.py | 1 + ddtrace/appsec/_processor.py | 1 - ddtrace/contrib/internal/botocore/services/kinesis.py | 2 +- ddtrace/contrib/internal/botocore/services/sqs.py | 2 +- ddtrace/contrib/internal/pyramid/patch.py | 2 +- ddtrace/internal/writer/writer.py | 1 - scripts/freshvenvs.py | 3 ++- tests/contrib/boto/test.py | 4 ++-- tests/contrib/langgraph/conftest.py | 2 +- tests/profiling/simple_program_fork.py | 4 ++-- tests/profiling_v2/simple_program_fork.py | 4 ++-- tests/profiling_v2/simple_program_gevent.py | 2 +- tests/profiling_v2/simple_program_pytorch_gpu.py | 2 +- tests/tracer/test_tracer.py | 2 +- 14 files changed, 16 insertions(+), 16 deletions(-) diff --git a/benchmarks/appsec_iast_aspects_split/functions.py b/benchmarks/appsec_iast_aspects_split/functions.py index 3706ccc24da..42820b29fb3 100644 --- a/benchmarks/appsec_iast_aspects_split/functions.py +++ b/benchmarks/appsec_iast_aspects_split/functions.py @@ -2,6 +2,7 @@ import os import re + try: from ddtrace import __version__ except ImportError: diff --git a/ddtrace/appsec/_processor.py b/ddtrace/appsec/_processor.py index f09944d351d..cb88ca06bfe 100644 --- a/ddtrace/appsec/_processor.py +++ b/ddtrace/appsec/_processor.py @@ -2,7 +2,6 @@ import errno from json.decoder import JSONDecodeError import os -import os.path from typing import TYPE_CHECKING from typing import Any from typing import ClassVar diff --git a/ddtrace/contrib/internal/botocore/services/kinesis.py b/ddtrace/contrib/internal/botocore/services/kinesis.py index f1f71d0b819..ce7d79ccc9d 100644 --- a/ddtrace/contrib/internal/botocore/services/kinesis.py +++ b/ddtrace/contrib/internal/botocore/services/kinesis.py @@ -6,7 +6,7 @@ from typing import List from typing import Tuple -import botocore.client +import botocore.client # noqa: F401 import botocore.exceptions from ddtrace import config diff --git a/ddtrace/contrib/internal/botocore/services/sqs.py b/ddtrace/contrib/internal/botocore/services/sqs.py index 5bf238c8cfd..19062ae8b63 100644 --- a/ddtrace/contrib/internal/botocore/services/sqs.py +++ b/ddtrace/contrib/internal/botocore/services/sqs.py @@ -3,7 +3,7 @@ from typing import Dict # noqa:F401 from typing import Optional # noqa:F401 -import botocore.client +import botocore.client # noqa: F401 import botocore.exceptions from ddtrace import config diff --git a/ddtrace/contrib/internal/pyramid/patch.py b/ddtrace/contrib/internal/pyramid/patch.py index 6f5ee9c6b5f..aef10a5e96e 100644 --- a/ddtrace/contrib/internal/pyramid/patch.py +++ b/ddtrace/contrib/internal/pyramid/patch.py @@ -1,7 +1,7 @@ from typing import Dict import pyramid -import pyramid.config +import pyramid.config # noqa: F401 import wrapt from ddtrace import config diff --git a/ddtrace/internal/writer/writer.py b/ddtrace/internal/writer/writer.py index 15e60e7ba62..9aad9ca77fa 100644 --- a/ddtrace/internal/writer/writer.py +++ b/ddtrace/internal/writer/writer.py @@ -22,7 +22,6 @@ from ddtrace.internal.settings._agent import config as agent_config from ddtrace.internal.settings.asm import ai_guard_config from ddtrace.internal.settings.asm import config as asm_config -import ddtrace.internal.utils.http from ddtrace.internal.utils.retry import fibonacci_backoff_with_jitter from ...constants import _KEEP_SPANS_RATE_KEY diff --git a/scripts/freshvenvs.py b/scripts/freshvenvs.py index 9e5e749df18..fc9c9b7bdbe 100644 --- a/scripts/freshvenvs.py +++ b/scripts/freshvenvs.py @@ -103,6 +103,7 @@ def _get_updatable_packages_implementing(contrib_modules: typing.Set[str]) -> ty all_venvs = _propagate_venv_names_to_child_venvs(all_venvs) packages_setting_latest = set() + def recurse_venvs(venvs: typing.List[riotfile.Venv]): for venv in venvs: # split venv name by ":" since some venvs are named after the integration:subintegration @@ -232,7 +233,7 @@ def get_integration_and_dependencies(venv_name: str) -> typing.Tuple[str, typing for line in lockfile_content: package, _, versions = line.partition("==") - package = package.split("[")[0] # strip optional package installs like flask[async] + package = package.split("[")[0] # strip optional package installs like flask[async] if package in dependencies or package == integration: lock_packages.append((package, versions)) return lock_packages diff --git a/tests/contrib/boto/test.py b/tests/contrib/boto/test.py index 76dd974692a..a826e55a4c8 100644 --- a/tests/contrib/boto/test.py +++ b/tests/contrib/boto/test.py @@ -5,9 +5,9 @@ import boto.awslambda import boto.ec2 import boto.elasticache -import boto.kms +import boto.kms # noqa: F401 import boto.s3 -import boto.sqs +import boto.sqs # noqa: F401 import boto.sts from moto import mock_ec2 from moto import mock_lambda diff --git a/tests/contrib/langgraph/conftest.py b/tests/contrib/langgraph/conftest.py index a2b01780354..5882325093f 100644 --- a/tests/contrib/langgraph/conftest.py +++ b/tests/contrib/langgraph/conftest.py @@ -33,7 +33,7 @@ def mock_tracer(): def langgraph(monkeypatch, mock_tracer): patch() import langgraph - import langgraph.prebuilt + import langgraph.prebuilt # noqa: F401 pin = Pin.get_from(langgraph) pin._override(langgraph, tracer=mock_tracer) diff --git a/tests/profiling/simple_program_fork.py b/tests/profiling/simple_program_fork.py index ad8c0541ccd..57f3bf81f64 100644 --- a/tests/profiling/simple_program_fork.py +++ b/tests/profiling/simple_program_fork.py @@ -3,9 +3,9 @@ import threading from ddtrace.internal import service -import ddtrace.profiling.auto +import ddtrace.profiling.auto # noqa: F401 import ddtrace.profiling.bootstrap -import ddtrace.profiling.profiler +import ddtrace.profiling.profiler # noqa: F401 lock = threading.Lock() diff --git a/tests/profiling_v2/simple_program_fork.py b/tests/profiling_v2/simple_program_fork.py index ad8c0541ccd..57f3bf81f64 100644 --- a/tests/profiling_v2/simple_program_fork.py +++ b/tests/profiling_v2/simple_program_fork.py @@ -3,9 +3,9 @@ import threading from ddtrace.internal import service -import ddtrace.profiling.auto +import ddtrace.profiling.auto # noqa: F401 import ddtrace.profiling.bootstrap -import ddtrace.profiling.profiler +import ddtrace.profiling.profiler # noqa: F401 lock = threading.Lock() diff --git a/tests/profiling_v2/simple_program_gevent.py b/tests/profiling_v2/simple_program_gevent.py index f50fa3aa2e0..1cc85d2c921 100644 --- a/tests/profiling_v2/simple_program_gevent.py +++ b/tests/profiling_v2/simple_program_gevent.py @@ -4,7 +4,7 @@ import ddtrace.profiling.auto # noqa:F401 -import gevent.monkey # noqa:F402 +import gevent.monkey # noqa:F402 gevent.monkey.patch_all() diff --git a/tests/profiling_v2/simple_program_pytorch_gpu.py b/tests/profiling_v2/simple_program_pytorch_gpu.py index 8d846c52de4..cecb90abc3a 100644 --- a/tests/profiling_v2/simple_program_pytorch_gpu.py +++ b/tests/profiling_v2/simple_program_pytorch_gpu.py @@ -4,7 +4,7 @@ from torch.profiler import ProfilerActivity import torch.utils.data import torchvision.datasets -import torchvision.models +import torchvision.models # noqa: F401 from torchvision.models import ResNet18_Weights from torchvision.models import resnet18 import torchvision.transforms as T diff --git a/tests/tracer/test_tracer.py b/tests/tracer/test_tracer.py index 2833f1c3778..65c85894a59 100644 --- a/tests/tracer/test_tracer.py +++ b/tests/tracer/test_tracer.py @@ -29,7 +29,7 @@ from ddtrace.constants import VERSION_KEY from ddtrace.contrib.internal.trace_utils import set_user from ddtrace.ext import user -import ddtrace.internal +import ddtrace.internal # noqa: F401 from ddtrace.internal.compat import PYTHON_VERSION_INFO from ddtrace.internal.rate_limiter import RateLimiter from ddtrace.internal.serverless import has_aws_lambda_agent_extension From a66e3ee994eb3a08445d027ae11881879dc72c71 Mon Sep 17 00:00:00 2001 From: brettlangdon Date: Thu, 13 Nov 2025 15:12:45 -0500 Subject: [PATCH 15/29] remove unused riot requirements.txt --- .riot/requirements/16ebde6.txt | 24 ------------------------ 1 file changed, 24 deletions(-) delete mode 100644 .riot/requirements/16ebde6.txt diff --git a/.riot/requirements/16ebde6.txt b/.riot/requirements/16ebde6.txt deleted file mode 100644 index 497357b5a92..00000000000 --- a/.riot/requirements/16ebde6.txt +++ /dev/null @@ -1,24 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.10 -# by the following command: -# -# pip-compile --no-annotate .riot/requirements/16ebde6.in -# -attrs==25.3.0 -click==8.2.1 -coverage[toml]==7.8.2 -exceptiongroup==1.3.0 -hypothesis==6.45.0 -iniconfig==2.1.0 -mock==5.2.0 -opentracing==2.4.0 -packaging==25.0 -pluggy==1.6.0 -pygments==2.19.1 -pytest==8.4.0 -pytest-cov==6.1.1 -pytest-mock==3.14.1 -slotscheck==0.17.0 -sortedcontainers==2.4.0 -tomli==2.2.1 -typing-extensions==4.14.0 From 29490632359d0fa02ef304c9cdd5f2e112285815 Mon Sep 17 00:00:00 2001 From: brettlangdon Date: Thu, 13 Nov 2025 15:24:45 -0500 Subject: [PATCH 16/29] get suitespec checks passing --- scripts/check_suitespec_coverage.py | 2 +- tests/contrib/suitespec.yml | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/scripts/check_suitespec_coverage.py b/scripts/check_suitespec_coverage.py index e2ad4fa28c4..f6ce7558408 100755 --- a/scripts/check_suitespec_coverage.py +++ b/scripts/check_suitespec_coverage.py @@ -25,7 +25,7 @@ # Ignore any embedded documentation IGNORE_PATTERNS.add("**/*.md") # The aioredis integration is deprecated and untested -IGNORE_PATTERNS.add("ddtrace/contrib/aioredis/*") +IGNORE_PATTERNS.add("ddtrace/contrib/internal/aioredis/*") # TODO(taegyunkim): remove these after merging profiling v2 tests back to profiling IGNORE_PATTERNS.add("tests/profiling/*.py") IGNORE_PATTERNS.add("tests/profiling/*/*.py") diff --git a/tests/contrib/suitespec.yml b/tests/contrib/suitespec.yml index ce4a3de89c3..0e7a714e238 100644 --- a/tests/contrib/suitespec.yml +++ b/tests/contrib/suitespec.yml @@ -20,10 +20,12 @@ components: - ddtrace/ext/aws.py azure_eventhubs: - ddtrace/contrib/internal/azure_eventhubs/* + - ddtrace/ext/azure_eventhubs.py azure_functions: - ddtrace/contrib/internal/azure_functions/* azure_servicebus: - ddtrace/contrib/internal/azure_servicebus/* + - ddtrace/ext/azure_servicebus.py botocore: - ddtrace/contrib/internal/botocore/* - ddtrace/contrib/internal/boto/* @@ -43,9 +45,13 @@ components: contrib: - ddtrace/contrib/__init__.py - ddtrace/contrib/trace_utils.py + - ddtrace/contrib/internal/__init__.py + - ddtrace/contrib/internal/trace_utils_base.py - ddtrace/contrib/internal/trace_utils_async.py - ddtrace/contrib/internal/trace_utils.py - ddtrace/contrib/internal/redis_utils.py + - ddtrace/contrib/ray.py + - ddtrace/contrib/valkey.py - ddtrace/ext/__init__.py - ddtrace/ext/http.py - ddtrace/ext/net.py @@ -53,6 +59,7 @@ components: - ddtrace/ext/sql.py - ddtrace/ext/test.py - ddtrace/ext/user.py + - ddtrace/ext/websocket.py - ddtrace/propagation/* - ddtrace/internal/settings/_database_monitoring.py - tests/contrib/patch.py From 75439a47882f5c9ab39405cb015562250d161f4a Mon Sep 17 00:00:00 2001 From: brettlangdon Date: Fri, 14 Nov 2025 09:13:09 -0500 Subject: [PATCH 17/29] add CI check for static version correctness --- ddtrace/version.py | 4 +- pyproject.toml | 2 +- scripts/gen_gitlab_config.py | 5 ++ scripts/verify-package-version | 158 +++++++++++++++++++++++++++++++++ 4 files changed, 166 insertions(+), 3 deletions(-) create mode 100755 scripts/verify-package-version diff --git a/ddtrace/version.py b/ddtrace/version.py index e7e5fe018cd..84520812315 100644 --- a/ddtrace/version.py +++ b/ddtrace/version.py @@ -19,5 +19,5 @@ __version_tuple__: VERSION_TUPLE version_tuple: VERSION_TUPLE -__version__ = version = "4.0.0rc1" -__version_tuple__ = version_tuple = (4, 0, 0, "rc1") +__version__ = version = "4.1.0dev" +__version_tuple__ = version_tuple = (4, 1, 0, "dev0") diff --git a/pyproject.toml b/pyproject.toml index 28ca84cc611..dce5514257c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ build-backend = "setuptools.build_meta" [project] name = "ddtrace" -version = "4.0.0rc1" +version = "4.1.0dev" description = "Datadog APM client library" readme = "README.md" license = { text = "LICENSE.BSD3" } diff --git a/scripts/gen_gitlab_config.py b/scripts/gen_gitlab_config.py index 3f018b687e8..0e9e70c8db7 100644 --- a/scripts/gen_gitlab_config.py +++ b/scripts/gen_gitlab_config.py @@ -271,6 +271,11 @@ def check(name: str, command: str, paths: t.Set[str]) -> None: command="scripts/check-dependency-bounds", paths={"pyproject.toml"}, ) + check( + name="Check package version", + command="scripts/verify-package-version", + paths={"pyproject.toml", "ddtrace/version.py"}, + ) if not checks: return diff --git a/scripts/verify-package-version b/scripts/verify-package-version new file mode 100755 index 00000000000..9ba9f4e355e --- /dev/null +++ b/scripts/verify-package-version @@ -0,0 +1,158 @@ +#!/usr/bin/env scripts/uv-run-script +# -*- mode: python -*- +# /// script +# requires-python = ">=3.9" +# dependencies = ["packaging"] +# /// +"""Verify and update static versions in pyproject.toml and ddtrace/version.py. + +Usage: + scripts/verify-package-version # Check versions match (exit 0 if OK, 1 if mismatch) + scripts/verify-package-version --fix # Update version.py from pyproject.toml +""" + +from pathlib import Path +import re +import sys + +from packaging.version import InvalidVersion +from packaging.version import Version + + +try: + import tomllib +except ModuleNotFoundError: + import tomli as tomllib # type: ignore[import-not-found] + + +def version_to_tuple(version_str: str) -> tuple: + """Convert a PEP 440 version string to a 4-element tuple. + + Returns a tuple of (major, minor, micro, pre_release_identifier) + where pre_release_identifier is a string like "rc1", "dev0", "post1", or "". + + Raises: + InvalidVersion: If the version string is not PEP 440 compliant. + """ + parsed = Version(version_str) + major, minor, micro = parsed.release[:3] + + # Build the pre-release identifier + pre_id_parts = [] + if parsed.pre: + pre_id_parts.append(f"{parsed.pre[0]}{parsed.pre[1]}") + if parsed.post is not None: + pre_id_parts.append(f"post{parsed.post}") + if parsed.dev is not None: + pre_id_parts.append(f"dev{parsed.dev}") + if parsed.local: + pre_id_parts.append(f"+{parsed.local}") + + pre_id = "".join(pre_id_parts) if pre_id_parts else "" + + return (major, minor, micro, pre_id) + + +def read_pyproject_version(pyproject_path: Path) -> str: + """Read version from pyproject.toml.""" + with open(pyproject_path, "rb") as f: + data = tomllib.load(f) + return data["project"]["version"] + + +def read_version_py(version_path: Path) -> tuple: + """Read version and version_tuple from ddtrace/version.py. + + Returns: + Tuple of (version_string, version_tuple) + """ + content = version_path.read_text() + + version_match = re.search(r'__version__\s*=\s*version\s*=\s*"([^"]+)"', content) + if not version_match: + raise ValueError("Could not find __version__ in version.py") + version = version_match.group(1) + + tuple_match = re.search(r"__version_tuple__\s*=\s*version_tuple\s*=\s*(\([^)]+\))", content) + if not tuple_match: + raise ValueError("Could not find __version_tuple__ in version.py") + version_tuple = eval(tuple_match.group(1)) + + return version, version_tuple + + +def format_tuple_with_double_quotes(tup: tuple) -> str: + """Format a tuple using double quotes for strings instead of single quotes.""" + elements = [] + for item in tup: + if isinstance(item, str): + elements.append(f'"{item}"') + else: + elements.append(repr(item)) + return f"({', '.join(elements)})" + + +def update_version_py(version_path: Path, new_version: str, new_version_tuple: tuple) -> None: + """Update version and version_tuple in ddtrace/version.py.""" + content = version_path.read_text() + + version_tuple_str = format_tuple_with_double_quotes(new_version_tuple) + + content = re.sub( + r'__version__\s*=\s*version\s*=\s*"[^"]+"', + f'__version__ = version = "{new_version}"', + content, + ) + + content = re.sub( + r"__version_tuple__\s*=\s*version_tuple\s*=\s*\([^)]+\)", + f"__version_tuple__ = version_tuple = {version_tuple_str}", + content, + ) + + version_path.write_text(content) + + +def main() -> int: + """Main entry point.""" + fix = "--fix" in sys.argv + + repo_root = Path(__file__).parent.parent + pyproject_path = repo_root / "pyproject.toml" + version_path = repo_root / "ddtrace" / "version.py" + + try: + pyproject_version = read_pyproject_version(pyproject_path) + + # Validate that the version is PEP 440 compliant + try: + pyproject_version_tuple = version_to_tuple(pyproject_version) + except InvalidVersion as e: + print(f"✗ Invalid version in pyproject.toml: {pyproject_version}", file=sys.stderr) + print(f" Error: {e}", file=sys.stderr) + return 1 + + current_version, current_version_tuple = read_version_py(version_path) + + if current_version == pyproject_version and current_version_tuple == pyproject_version_tuple: + print("✓ Versions match!") + return 0 + + if not fix: + print("✗ Version mismatch detected:") + print(f" pyproject.toml: {pyproject_version} {pyproject_version_tuple}") + print(f" ddtrace/version.py: {current_version} {current_version_tuple}") + print("\nRun with --fix to update ddtrace/version.py") + return 1 + + update_version_py(version_path, pyproject_version, pyproject_version_tuple) + print(f"✓ Updated ddtrace/version.py to {pyproject_version} {pyproject_version_tuple}") + return 0 + + except Exception as e: + print(f"✗ Error: {e}", file=sys.stderr) + return 1 + + +if __name__ == "__main__": + sys.exit(main()) From 8464924f6951bb92cc7931773824ea6c4536a808 Mon Sep 17 00:00:00 2001 From: brettlangdon Date: Fri, 14 Nov 2025 12:13:26 -0500 Subject: [PATCH 18/29] fix formatting --- benchmarks/appsec_iast_aspects_split/functions.py | 1 + 1 file changed, 1 insertion(+) diff --git a/benchmarks/appsec_iast_aspects_split/functions.py b/benchmarks/appsec_iast_aspects_split/functions.py index 14212792046..04d96c6b506 100644 --- a/benchmarks/appsec_iast_aspects_split/functions.py +++ b/benchmarks/appsec_iast_aspects_split/functions.py @@ -2,6 +2,7 @@ import os import re + try: from ddtrace import __version__ except ImportError: From 5b5eae9c81f5b6d359bb9a5e75133f9a1870bab4 Mon Sep 17 00:00:00 2001 From: brettlangdon Date: Mon, 17 Nov 2025 11:30:10 -0500 Subject: [PATCH 19/29] fix version parsing and drop version specific s3 endpoints --- .gitlab/package.yml | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/.gitlab/package.yml b/.gitlab/package.yml index 2c3aa1dd140..908e77c93f8 100644 --- a/.gitlab/package.yml +++ b/.gitlab/package.yml @@ -75,22 +75,20 @@ publish-wheels-to-s3: - printf ' - %s\n' "${WHEELS[@]}" - | - VERSION=$(unzip -p $(ls ./pywheels/*.whl | head -n 1) '*.dist-info/METADATA' | awk -F': ' '/^Version:/ { print $2; exit }') + VERSION=$(grep '^version = ' pyproject.toml | head -1 | sed -E 's/version = "(.+)"/\1/') if [ -z "${VERSION:-}" ]; then - echo "ERROR: VERSION is not defined or library-version/version.txt missing!" + echo "ERROR: VERSION is not defined in pyproject.toml!" exit 1 fi - printf 'Detected version %s\n' ${VERSION} - # Upload all wheels to versioned prefix and pipeline-id prefix - - aws s3 cp --recursive --exclude "*" --include "*.whl" pywheels "s3://${BUCKET}/${VERSION}/" + # Upload all wheels to pipeline-id prefix - aws s3 cp --recursive --exclude "*" --include "*.whl" pywheels "s3://${BUCKET}/${CI_PIPELINE_ID}/" - | VERSION_ENC="${VERSION//+/%2B}" - S3_BASE_VER="https://${BUCKET}.s3.amazonaws.com/${VERSION_ENC}" S3_BASE_PIPE="https://${BUCKET}.s3.amazonaws.com/${CI_PIPELINE_ID}" generate_index_html() { @@ -107,15 +105,11 @@ publish-wheels-to-s3: } # Generate both minimal indexes - generate_index_html "index.version.html" generate_index_html "index.pipeline.html" # Upload to each S3 prefix - aws s3 cp "index.version.html" "s3://${BUCKET}/${VERSION}/index.html" --content-type text/html aws s3 cp "index.pipeline.html" "s3://${BUCKET}/${CI_PIPELINE_ID}/index.html" --content-type text/html # Print the clickable URLs - VER_INDEX_URL="${S3_BASE_VER}/index.html" PIPE_INDEX_URL="${S3_BASE_PIPE}/index.html" - echo "S3 index (version): ${VER_INDEX_URL}" echo "S3 index (pipeline): ${PIPE_INDEX_URL}" From e17694171b7da8b9cffbf45c545f38cf62958117 Mon Sep 17 00:00:00 2001 From: brettlangdon Date: Mon, 17 Nov 2025 12:07:38 -0500 Subject: [PATCH 20/29] fix package version and add verification --- ddtrace/version.py | 2 +- pyproject.toml | 2 +- scripts/verify-package-version | 47 +++++++++++++++++++++++++++++++++- 3 files changed, 48 insertions(+), 3 deletions(-) diff --git a/ddtrace/version.py b/ddtrace/version.py index 84520812315..2c60fc8886a 100644 --- a/ddtrace/version.py +++ b/ddtrace/version.py @@ -19,5 +19,5 @@ __version_tuple__: VERSION_TUPLE version_tuple: VERSION_TUPLE -__version__ = version = "4.1.0dev" +__version__ = version = "4.1.0.dev0" __version_tuple__ = version_tuple = (4, 1, 0, "dev0") diff --git a/pyproject.toml b/pyproject.toml index dce5514257c..98b2c9bce5c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ build-backend = "setuptools.build_meta" [project] name = "ddtrace" -version = "4.1.0dev" +version = "4.1.0.dev0" description = "Datadog APM client library" readme = "README.md" license = { text = "LICENSE.BSD3" } diff --git a/scripts/verify-package-version b/scripts/verify-package-version index 9ba9f4e355e..70bb2cb61cd 100755 --- a/scripts/verify-package-version +++ b/scripts/verify-package-version @@ -6,6 +6,11 @@ # /// """Verify and update static versions in pyproject.toml and ddtrace/version.py. +Performs the following validations: +- Ensures version in pyproject.toml is PEP 440 compliant (e.g., "4.1.0.dev0" not "4.1.0.dev") +- Verifies __version__ in ddtrace/version.py matches __version_tuple__ when parsed +- Checks that versions in both files are consistent + Usage: scripts/verify-package-version # Check versions match (exit 0 if OK, 1 if mismatch) scripts/verify-package-version --fix # Update version.py from pyproject.toml @@ -25,6 +30,24 @@ except ModuleNotFoundError: import tomli as tomllib # type: ignore[import-not-found] +def is_pep440_compliant(version_str: str) -> tuple[bool, str]: + """Check if a version string is strictly PEP 440 compliant. + + Returns: + Tuple of (is_compliant, normalized_version) + """ + try: + parsed = Version(version_str) + normalized = str(parsed) + + # Check if the original matches the normalized form + # If they differ, the version had implicit defaults which is not strictly compliant + is_compliant = version_str == normalized + return is_compliant, normalized + except InvalidVersion: + return False, "" + + def version_to_tuple(version_str: str) -> tuple: """Convert a PEP 440 version string to a 4-element tuple. @@ -124,7 +147,15 @@ def main() -> int: try: pyproject_version = read_pyproject_version(pyproject_path) - # Validate that the version is PEP 440 compliant + # Check PEP 440 compliance + is_compliant, normalized_version = is_pep440_compliant(pyproject_version) + if not is_compliant: + print(f"✗ Version in pyproject.toml is not PEP 440 compliant: {pyproject_version}", file=sys.stderr) + print(f" PEP 440 requires explicit numbers for pre-release, post, and dev versions", file=sys.stderr) + print(f" Suggested fix: {normalized_version}", file=sys.stderr) + return 1 + + # Validate that the version is PEP 440 parseable try: pyproject_version_tuple = version_to_tuple(pyproject_version) except InvalidVersion as e: @@ -134,6 +165,20 @@ def main() -> int: current_version, current_version_tuple = read_version_py(version_path) + # Verify that __version__ and __version_tuple__ in version.py are consistent + try: + derived_tuple = version_to_tuple(current_version) + if derived_tuple != current_version_tuple: + print(f"✗ Version mismatch in ddtrace/version.py:", file=sys.stderr) + print(f" __version__ '{current_version}' converts to tuple {derived_tuple}", file=sys.stderr) + print(f" __version_tuple__ is {current_version_tuple}", file=sys.stderr) + print("\nRun with --fix to update ddtrace/version.py") + return 1 + except InvalidVersion as e: + print(f"✗ Invalid version in ddtrace/version.py: {current_version}", file=sys.stderr) + print(f" Error: {e}", file=sys.stderr) + return 1 + if current_version == pyproject_version and current_version_tuple == pyproject_version_tuple: print("✓ Versions match!") return 0 From af7dc82f677087c1f16143d04eef0cd53a440cf2 Mon Sep 17 00:00:00 2001 From: brettlangdon Date: Mon, 17 Nov 2025 12:53:49 -0500 Subject: [PATCH 21/29] fix appsec benchmarks --- benchmarks/appsec_iast_aspects/functions.py | 8 +++++--- benchmarks/appsec_iast_aspects_ospath/functions.py | 8 +++++--- benchmarks/appsec_iast_aspects_re_module/functions.py | 8 +++++--- benchmarks/appsec_iast_aspects_split/functions.py | 6 ++---- 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/benchmarks/appsec_iast_aspects/functions.py b/benchmarks/appsec_iast_aspects/functions.py index ebe92461e87..c1a5a7d3f2e 100644 --- a/benchmarks/appsec_iast_aspects/functions.py +++ b/benchmarks/appsec_iast_aspects/functions.py @@ -2,10 +2,12 @@ import os import re -from ddtrace import get_version +try: + from ddtrace import __version__ as version +except ImportError: + from ddtrace import get_version - -version = get_version() + version = get_version() # Some old versions could not have or export some symbols, so we import them dynamically and assign None if not found # which will make the aspect benchmark fail but not the entire benchmark diff --git a/benchmarks/appsec_iast_aspects_ospath/functions.py b/benchmarks/appsec_iast_aspects_ospath/functions.py index 1e7fbc324ff..da71f9c42d4 100644 --- a/benchmarks/appsec_iast_aspects_ospath/functions.py +++ b/benchmarks/appsec_iast_aspects_ospath/functions.py @@ -2,10 +2,12 @@ import os import re -from ddtrace import get_version +try: + from ddtrace import __version__ as version +except ImportError: + from ddtrace import get_version - -version = get_version() + version = get_version() # Some old versions could not have or export some symbols, so we import them dynamically and assign None if not found # which will make the aspect benchmark fail but not the entire benchmark diff --git a/benchmarks/appsec_iast_aspects_re_module/functions.py b/benchmarks/appsec_iast_aspects_re_module/functions.py index 4b2c2ebc8cc..08f0e3fcafe 100644 --- a/benchmarks/appsec_iast_aspects_re_module/functions.py +++ b/benchmarks/appsec_iast_aspects_re_module/functions.py @@ -2,10 +2,12 @@ import os import re -from ddtrace import get_version +try: + from ddtrace import __version__ as version +except ImportError: + from ddtrace import get_version - -version = get_version() + version = get_version() # Some old versions could not have or export some symbols, so we import them dynamically and assign None if not found # which will make the aspect benchmark fail but not the entire benchmark diff --git a/benchmarks/appsec_iast_aspects_split/functions.py b/benchmarks/appsec_iast_aspects_split/functions.py index 04d96c6b506..657c5e5237a 100644 --- a/benchmarks/appsec_iast_aspects_split/functions.py +++ b/benchmarks/appsec_iast_aspects_split/functions.py @@ -4,15 +4,13 @@ try: - from ddtrace import __version__ + from ddtrace import __version__ as version except ImportError: from ddtrace.version import get_version - __version__ = get_version() + version = get_version() -version = get_version() - # Some old versions could not have or export some symbols, so we import them dynamically and assign None if not found # which will make the aspect benchmark fail but not the entire benchmark symbols = [ From 85028aa3eb0f0a8da612a3b5e57b0e9070d57b2e Mon Sep 17 00:00:00 2001 From: brettlangdon Date: Mon, 17 Nov 2025 12:58:48 -0500 Subject: [PATCH 22/29] fix formatting --- benchmarks/appsec_iast_aspects/functions.py | 1 + benchmarks/appsec_iast_aspects_ospath/functions.py | 1 + benchmarks/appsec_iast_aspects_re_module/functions.py | 1 + benchmarks/appsec_iast_aspects_split/functions.py | 2 +- 4 files changed, 4 insertions(+), 1 deletion(-) diff --git a/benchmarks/appsec_iast_aspects/functions.py b/benchmarks/appsec_iast_aspects/functions.py index c1a5a7d3f2e..bd8f62d378e 100644 --- a/benchmarks/appsec_iast_aspects/functions.py +++ b/benchmarks/appsec_iast_aspects/functions.py @@ -2,6 +2,7 @@ import os import re + try: from ddtrace import __version__ as version except ImportError: diff --git a/benchmarks/appsec_iast_aspects_ospath/functions.py b/benchmarks/appsec_iast_aspects_ospath/functions.py index da71f9c42d4..2aa1936ca1e 100644 --- a/benchmarks/appsec_iast_aspects_ospath/functions.py +++ b/benchmarks/appsec_iast_aspects_ospath/functions.py @@ -2,6 +2,7 @@ import os import re + try: from ddtrace import __version__ as version except ImportError: diff --git a/benchmarks/appsec_iast_aspects_re_module/functions.py b/benchmarks/appsec_iast_aspects_re_module/functions.py index 08f0e3fcafe..5f45dc323da 100644 --- a/benchmarks/appsec_iast_aspects_re_module/functions.py +++ b/benchmarks/appsec_iast_aspects_re_module/functions.py @@ -2,6 +2,7 @@ import os import re + try: from ddtrace import __version__ as version except ImportError: diff --git a/benchmarks/appsec_iast_aspects_split/functions.py b/benchmarks/appsec_iast_aspects_split/functions.py index 657c5e5237a..bfdb30ae0ce 100644 --- a/benchmarks/appsec_iast_aspects_split/functions.py +++ b/benchmarks/appsec_iast_aspects_split/functions.py @@ -35,7 +35,7 @@ # print(f"Warning: {symbol} not found in the current version") if notfound_symbols: - print("Warning: symbols not found in the tested version [%s]: %s" % (__version__, str(notfound_symbols))) + print("Warning: symbols not found in the tested version [%s]: %s" % (version, str(notfound_symbols))) def iast_add_aspect(): From db4ae268ae020e1aa41dae9e2c132b8e62f2e3d8 Mon Sep 17 00:00:00 2001 From: brettlangdon Date: Mon, 17 Nov 2025 14:56:35 -0500 Subject: [PATCH 23/29] use importlib metadata to avoid duplicate locations for version --- ddtrace/__init__.py | 12 ++- ddtrace/version.py | 23 ------ scripts/gen_gitlab_config.py | 2 +- scripts/verify-package-version | 135 ++------------------------------- 4 files changed, 17 insertions(+), 155 deletions(-) delete mode 100644 ddtrace/version.py diff --git a/ddtrace/__init__.py b/ddtrace/__init__.py index 1d02cf93c0f..4ab3bb8fe15 100644 --- a/ddtrace/__init__.py +++ b/ddtrace/__init__.py @@ -1,3 +1,4 @@ +import importlib.metadata import os import sys @@ -21,12 +22,21 @@ from .internal.compat import PYTHON_VERSION_INFO # noqa: E402 from .internal.settings._config import config from .internal.utils.deprecations import DDTraceDeprecationWarning # noqa: E402 -from .version import __version__ + # TODO: Deprecate accessing tracer from ddtrace.__init__ module in v4.0 if os.environ.get("_DD_GLOBAL_TRACER_INIT", "true").lower() in ("1", "true"): from ddtrace.trace import tracer # noqa: F401 + +__version__: str + +try: + __version__ = version = importlib.metadata.version(__package__ or __name__) +except importlib.metadata.PackageNotFoundError: + __version__ = version = "0.0.0" + + __all__ = [ "__version__", "patch", diff --git a/ddtrace/version.py b/ddtrace/version.py deleted file mode 100644 index 2c60fc8886a..00000000000 --- a/ddtrace/version.py +++ /dev/null @@ -1,23 +0,0 @@ -__all__ = [ - "__version__", - "__version_tuple__", - "version", - "version_tuple", -] - -TYPE_CHECKING = False -if TYPE_CHECKING: - from typing import Tuple - from typing import Union - - VERSION_TUPLE = Tuple[Union[int, str], ...] -else: - VERSION_TUPLE = object - -version: str -__version__: str -__version_tuple__: VERSION_TUPLE -version_tuple: VERSION_TUPLE - -__version__ = version = "4.1.0.dev0" -__version_tuple__ = version_tuple = (4, 1, 0, "dev0") diff --git a/scripts/gen_gitlab_config.py b/scripts/gen_gitlab_config.py index 614ff90130d..99e392ee3da 100644 --- a/scripts/gen_gitlab_config.py +++ b/scripts/gen_gitlab_config.py @@ -274,7 +274,7 @@ def check(name: str, command: str, paths: t.Set[str]) -> None: check( name="Check package version", command="scripts/verify-package-version", - paths={"pyproject.toml", "ddtrace/version.py"}, + paths={"pyproject.toml"}, ) check( name="Check for namespace packages", diff --git a/scripts/verify-package-version b/scripts/verify-package-version index 70bb2cb61cd..d258c482c50 100755 --- a/scripts/verify-package-version +++ b/scripts/verify-package-version @@ -4,26 +4,21 @@ # requires-python = ">=3.9" # dependencies = ["packaging"] # /// -"""Verify and update static versions in pyproject.toml and ddtrace/version.py. +"""Verify that the version in pyproject.toml is PEP 440 compliant. -Performs the following validations: +Performs the following validation: - Ensures version in pyproject.toml is PEP 440 compliant (e.g., "4.1.0.dev0" not "4.1.0.dev") -- Verifies __version__ in ddtrace/version.py matches __version_tuple__ when parsed -- Checks that versions in both files are consistent Usage: - scripts/verify-package-version # Check versions match (exit 0 if OK, 1 if mismatch) - scripts/verify-package-version --fix # Update version.py from pyproject.toml + scripts/verify-package-version # Check version compliance (exit 0 if OK, 1 if not compliant) """ -from pathlib import Path -import re import sys +from pathlib import Path from packaging.version import InvalidVersion from packaging.version import Version - try: import tomllib except ModuleNotFoundError: @@ -48,34 +43,6 @@ def is_pep440_compliant(version_str: str) -> tuple[bool, str]: return False, "" -def version_to_tuple(version_str: str) -> tuple: - """Convert a PEP 440 version string to a 4-element tuple. - - Returns a tuple of (major, minor, micro, pre_release_identifier) - where pre_release_identifier is a string like "rc1", "dev0", "post1", or "". - - Raises: - InvalidVersion: If the version string is not PEP 440 compliant. - """ - parsed = Version(version_str) - major, minor, micro = parsed.release[:3] - - # Build the pre-release identifier - pre_id_parts = [] - if parsed.pre: - pre_id_parts.append(f"{parsed.pre[0]}{parsed.pre[1]}") - if parsed.post is not None: - pre_id_parts.append(f"post{parsed.post}") - if parsed.dev is not None: - pre_id_parts.append(f"dev{parsed.dev}") - if parsed.local: - pre_id_parts.append(f"+{parsed.local}") - - pre_id = "".join(pre_id_parts) if pre_id_parts else "" - - return (major, minor, micro, pre_id) - - def read_pyproject_version(pyproject_path: Path) -> str: """Read version from pyproject.toml.""" with open(pyproject_path, "rb") as f: @@ -83,66 +50,10 @@ def read_pyproject_version(pyproject_path: Path) -> str: return data["project"]["version"] -def read_version_py(version_path: Path) -> tuple: - """Read version and version_tuple from ddtrace/version.py. - - Returns: - Tuple of (version_string, version_tuple) - """ - content = version_path.read_text() - - version_match = re.search(r'__version__\s*=\s*version\s*=\s*"([^"]+)"', content) - if not version_match: - raise ValueError("Could not find __version__ in version.py") - version = version_match.group(1) - - tuple_match = re.search(r"__version_tuple__\s*=\s*version_tuple\s*=\s*(\([^)]+\))", content) - if not tuple_match: - raise ValueError("Could not find __version_tuple__ in version.py") - version_tuple = eval(tuple_match.group(1)) - - return version, version_tuple - - -def format_tuple_with_double_quotes(tup: tuple) -> str: - """Format a tuple using double quotes for strings instead of single quotes.""" - elements = [] - for item in tup: - if isinstance(item, str): - elements.append(f'"{item}"') - else: - elements.append(repr(item)) - return f"({', '.join(elements)})" - - -def update_version_py(version_path: Path, new_version: str, new_version_tuple: tuple) -> None: - """Update version and version_tuple in ddtrace/version.py.""" - content = version_path.read_text() - - version_tuple_str = format_tuple_with_double_quotes(new_version_tuple) - - content = re.sub( - r'__version__\s*=\s*version\s*=\s*"[^"]+"', - f'__version__ = version = "{new_version}"', - content, - ) - - content = re.sub( - r"__version_tuple__\s*=\s*version_tuple\s*=\s*\([^)]+\)", - f"__version_tuple__ = version_tuple = {version_tuple_str}", - content, - ) - - version_path.write_text(content) - - def main() -> int: """Main entry point.""" - fix = "--fix" in sys.argv - repo_root = Path(__file__).parent.parent pyproject_path = repo_root / "pyproject.toml" - version_path = repo_root / "ddtrace" / "version.py" try: pyproject_version = read_pyproject_version(pyproject_path) @@ -155,43 +66,7 @@ def main() -> int: print(f" Suggested fix: {normalized_version}", file=sys.stderr) return 1 - # Validate that the version is PEP 440 parseable - try: - pyproject_version_tuple = version_to_tuple(pyproject_version) - except InvalidVersion as e: - print(f"✗ Invalid version in pyproject.toml: {pyproject_version}", file=sys.stderr) - print(f" Error: {e}", file=sys.stderr) - return 1 - - current_version, current_version_tuple = read_version_py(version_path) - - # Verify that __version__ and __version_tuple__ in version.py are consistent - try: - derived_tuple = version_to_tuple(current_version) - if derived_tuple != current_version_tuple: - print(f"✗ Version mismatch in ddtrace/version.py:", file=sys.stderr) - print(f" __version__ '{current_version}' converts to tuple {derived_tuple}", file=sys.stderr) - print(f" __version_tuple__ is {current_version_tuple}", file=sys.stderr) - print("\nRun with --fix to update ddtrace/version.py") - return 1 - except InvalidVersion as e: - print(f"✗ Invalid version in ddtrace/version.py: {current_version}", file=sys.stderr) - print(f" Error: {e}", file=sys.stderr) - return 1 - - if current_version == pyproject_version and current_version_tuple == pyproject_version_tuple: - print("✓ Versions match!") - return 0 - - if not fix: - print("✗ Version mismatch detected:") - print(f" pyproject.toml: {pyproject_version} {pyproject_version_tuple}") - print(f" ddtrace/version.py: {current_version} {current_version_tuple}") - print("\nRun with --fix to update ddtrace/version.py") - return 1 - - update_version_py(version_path, pyproject_version, pyproject_version_tuple) - print(f"✓ Updated ddtrace/version.py to {pyproject_version} {pyproject_version_tuple}") + print(f"✓ Version {pyproject_version} is PEP 440 compliant") return 0 except Exception as e: From fcc1211d018a7e1bfbb81e77dc9eb214adac1077 Mon Sep 17 00:00:00 2001 From: brettlangdon Date: Mon, 17 Nov 2025 14:57:42 -0500 Subject: [PATCH 24/29] use ddtrace.__version__ not ddtrace.version.__version__ --- ddtrace/_trace/tracer.py | 2 +- ddtrace/internal/datastreams/processor.py | 2 +- ddtrace/internal/processor/stats.py | 2 +- ddtrace/internal/settings/dynamic_instrumentation.py | 2 +- ddtrace/internal/telemetry/data.py | 2 +- ddtrace/internal/utils/version.py | 2 +- tests/appsec/architectures/mini.py | 2 +- tests/contrib/patch.py | 2 +- tests/debugging/test_config.py | 2 +- tests/lib_injection/conftest.py | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/ddtrace/_trace/tracer.py b/ddtrace/_trace/tracer.py index 7ea9be3b4df..aa5e38a946b 100644 --- a/ddtrace/_trace/tracer.py +++ b/ddtrace/_trace/tracer.py @@ -59,7 +59,7 @@ from ddtrace.internal.utils.formats import format_trace_id from ddtrace.internal.writer import AgentWriterInterface from ddtrace.internal.writer import HTTPWriter -from ddtrace.version import __version__ +from ddtrace import __version__ log = get_logger(__name__) diff --git a/ddtrace/internal/datastreams/processor.py b/ddtrace/internal/datastreams/processor.py index 9f05ecfcc7c..72f4fc0e18b 100644 --- a/ddtrace/internal/datastreams/processor.py +++ b/ddtrace/internal/datastreams/processor.py @@ -22,7 +22,7 @@ from ddtrace.internal.settings._agent import config as agent_config from ddtrace.internal.settings._config import config from ddtrace.internal.utils.retry import fibonacci_backoff_with_jitter -from ddtrace.version import __version__ +from ddtrace import __version__ from .._encoding import packb from ..agent import get_connection diff --git a/ddtrace/internal/processor/stats.py b/ddtrace/internal/processor/stats.py index 0ad1397c1e6..8c5661cb755 100644 --- a/ddtrace/internal/processor/stats.py +++ b/ddtrace/internal/processor/stats.py @@ -14,7 +14,7 @@ from ddtrace.internal.native import DDSketch from ddtrace.internal.settings._config import config from ddtrace.internal.utils.retry import fibonacci_backoff_with_jitter -from ddtrace.version import __version__ +from ddtrace import __version__ from ...constants import _SPAN_MEASURED_KEY from .. import agent diff --git a/ddtrace/internal/settings/dynamic_instrumentation.py b/ddtrace/internal/settings/dynamic_instrumentation.py index 707f0758c95..29f0cb2e9ce 100644 --- a/ddtrace/internal/settings/dynamic_instrumentation.py +++ b/ddtrace/internal/settings/dynamic_instrumentation.py @@ -8,7 +8,7 @@ from ddtrace.internal.settings._agent import config as agent_config from ddtrace.internal.settings._core import DDConfig from ddtrace.internal.utils.config import get_application_name -from ddtrace.version import __version__ +from ddtrace import __version__ DEFAULT_MAX_PROBES = 100 diff --git a/ddtrace/internal/telemetry/data.py b/ddtrace/internal/telemetry/data.py index 6ac4689f29d..2791fbf9ab2 100644 --- a/ddtrace/internal/telemetry/data.py +++ b/ddtrace/internal/telemetry/data.py @@ -11,7 +11,7 @@ from ddtrace.internal.packages import get_module_distribution_versions from ddtrace.internal.runtime.container import get_container_info from ddtrace.internal.utils.cache import cached -from ddtrace.version import __version__ +from ddtrace import __version__ from ..hostname import get_hostname diff --git a/ddtrace/internal/utils/version.py b/ddtrace/internal/utils/version.py index be10973b63f..304ab9b54e0 100644 --- a/ddtrace/internal/utils/version.py +++ b/ddtrace/internal/utils/version.py @@ -2,7 +2,7 @@ from typing import Optional # noqa:F401 import ddtrace.vendor.packaging.version as packaging_version -from ddtrace.version import __version__ +from ddtrace import __version__ def parse_version(version): diff --git a/tests/appsec/architectures/mini.py b/tests/appsec/architectures/mini.py index be8990b5423..d3f17a8d02c 100644 --- a/tests/appsec/architectures/mini.py +++ b/tests/appsec/architectures/mini.py @@ -13,7 +13,7 @@ from ddtrace.internal.settings.asm import config as asm_config # noqa: E402 import ddtrace.internal.telemetry.writer # noqa: E402 -from ddtrace.version import __version__ # noqa: E402 +from ddtrace import __version__ # noqa: E402 app = Flask(__name__) diff --git a/tests/contrib/patch.py b/tests/contrib/patch.py index 1dad7472cd2..bf600efcc10 100644 --- a/tests/contrib/patch.py +++ b/tests/contrib/patch.py @@ -9,7 +9,7 @@ import unittest from ddtrace.internal.compat import is_wrapted -from ddtrace.version import __version__ +from ddtrace import __version__ from tests.subprocesstest import SubprocessTestCase from tests.subprocesstest import run_in_subprocess from tests.utils import call_program diff --git a/tests/debugging/test_config.py b/tests/debugging/test_config.py index 03a6679b29e..d90641e4a74 100644 --- a/tests/debugging/test_config.py +++ b/tests/debugging/test_config.py @@ -5,7 +5,7 @@ from ddtrace.internal.settings._agent import config as agent_config from ddtrace.internal.settings.dynamic_instrumentation import DynamicInstrumentationConfig from ddtrace.internal.utils.formats import parse_tags_str -from ddtrace.version import __version__ +from ddtrace import __version__ from tests.utils import override_env diff --git a/tests/lib_injection/conftest.py b/tests/lib_injection/conftest.py index 77e5ace1580..94ead44a978 100644 --- a/tests/lib_injection/conftest.py +++ b/tests/lib_injection/conftest.py @@ -13,7 +13,7 @@ import pytest -from ddtrace.version import __version__ +from ddtrace import __version__ HOST_DDTRACE_VERSION = __version__ From 0b886088980c5865213bc248d2329681f6f696c7 Mon Sep 17 00:00:00 2001 From: brettlangdon Date: Tue, 18 Nov 2025 11:41:32 -0500 Subject: [PATCH 25/29] don't use ddtrace.__version__ internally --- .sg/rules/ddtrace-version-import.yml | 49 ++++ .../ddtrace-version-import-snapshot.yml | 226 ++++++++++++++++++ .sg/tests/ddtrace-version-import-test.yml | 103 ++++++++ ddtrace/__init__.py | 10 +- ddtrace/_trace/tracer.py | 2 +- ddtrace/appsec/ai_guard/_api_client.py | 4 +- ddtrace/commands/ddtrace_run.py | 3 +- ddtrace/internal/ci_visibility/filters.py | 5 +- ddtrace/internal/ci_visibility/writer.py | 4 +- ddtrace/internal/datastreams/processor.py | 2 +- ddtrace/internal/debug.py | 3 +- ddtrace/internal/processor/stats.py | 2 +- ddtrace/internal/runtime/tag_collectors.py | 6 +- .../settings/dynamic_instrumentation.py | 2 +- ddtrace/internal/telemetry/data.py | 2 +- ddtrace/internal/utils/version.py | 2 +- ddtrace/internal/writer/writer.py | 6 +- ddtrace/llmobs/_experiment.py | 4 +- ddtrace/llmobs/_llmobs.py | 5 +- ddtrace/llmobs/_writer.py | 4 +- ddtrace/version.py | 12 + 21 files changed, 420 insertions(+), 36 deletions(-) create mode 100644 .sg/rules/ddtrace-version-import.yml create mode 100644 .sg/tests/__snapshots__/ddtrace-version-import-snapshot.yml create mode 100644 .sg/tests/ddtrace-version-import-test.yml create mode 100644 ddtrace/version.py diff --git a/.sg/rules/ddtrace-version-import.yml b/.sg/rules/ddtrace-version-import.yml new file mode 100644 index 00000000000..10451e7c664 --- /dev/null +++ b/.sg/rules/ddtrace-version-import.yml @@ -0,0 +1,49 @@ +id: ddtrace-version-import +message: Import `__version__` from `ddtrace.version` instead of `ddtrace` +severity: error +language: python +ignores: + - "ddtrace/version.py" +rule: + any: + # Match: from ddtrace import __version__ (using variable with constraint) + - pattern: from $MOD import __version__ + # Match: from ddtrace import __version__ as $ALIAS + - pattern: from $MOD import __version__ as $ALIAS + # Match: from ddtrace import $$$IMPORTS, __version__ + - pattern: from $MOD import $$$IMPORTS, __version__ + # Match: from ddtrace import $$$IMPORTS, __version__ as $ALIAS + - pattern: from $MOD import $$$IMPORTS, __version__ as $ALIAS + # Match: from ddtrace import __version__, $$$IMPORTS + - pattern: from $MOD import __version__, $$$IMPORTS + # Match: from ddtrace import __version__ as $ALIAS, $$$IMPORTS + - pattern: from $MOD import __version__ as $ALIAS, $$$IMPORTS + # Match: from ddtrace import $$$IMPORTS, __version__, $$$IMPORTS (middle case) + - pattern: from $MOD import $$$IMPORTS, __version__, $$$IMPORTS + # Match: from ddtrace import $$$IMPORTS, __version__ as $ALIAS, $$$IMPORTS (middle case with alias) + - pattern: from $MOD import $$$IMPORTS, __version__ as $ALIAS, $$$IMPORTS + # Match: ddtrace.__version__ usage + - pattern: ddtrace.__version__ +constraints: + MOD: + regex: "^ddtrace$" + ALIAS: + regex: ".*" +note: | + The `__version__` attribute should be imported from `ddtrace.version` to avoid circular dependencies and ensure proper version management. + + Change your imports as follows: + + **Before:** + ```python + from ddtrace import __version__ + import ddtrace + version = ddtrace.__version__ + ``` + + **After:** + ```python + from ddtrace.version import __version__ + ``` + + This ensures that `__version__` is accessed from the dedicated version module, preventing circular import issues and making version management more explicit. diff --git a/.sg/tests/__snapshots__/ddtrace-version-import-snapshot.yml b/.sg/tests/__snapshots__/ddtrace-version-import-snapshot.yml new file mode 100644 index 00000000000..994b6bf7d40 --- /dev/null +++ b/.sg/tests/__snapshots__/ddtrace-version-import-snapshot.yml @@ -0,0 +1,226 @@ +id: ddtrace-version-import +snapshots: + ddtrace.__version__: + labels: + - source: ddtrace.__version__ + style: primary + start: 0 + end: 19 + from ddtrace import __version__: + labels: + - source: from ddtrace import __version__ + style: primary + start: 0 + end: 31 + from ddtrace import __version__ as V: + labels: + - source: from ddtrace import __version__ as V + style: primary + start: 0 + end: 36 + from ddtrace import __version__ as V, config, tracer: + labels: + - source: from ddtrace import __version__ as V, config, tracer + style: primary + start: 0 + end: 52 + from ddtrace import __version__ as VERSION: + labels: + - source: from ddtrace import __version__ as VERSION + style: primary + start: 0 + end: 42 + from ddtrace import __version__ as VERSION, tracer: + labels: + - source: from ddtrace import __version__ as VERSION, tracer + style: primary + start: 0 + end: 50 + from ddtrace import __version__ as VERSION, tracer, config, pin: + labels: + - source: from ddtrace import __version__ as VERSION, tracer, config, pin + style: primary + start: 0 + end: 63 + from ddtrace import __version__, config: + labels: + - source: from ddtrace import __version__, config + style: primary + start: 0 + end: 39 + from ddtrace import __version__, config, tracer: + labels: + - source: from ddtrace import __version__, config, tracer + style: primary + start: 0 + end: 47 + from ddtrace import __version__, tracer: + labels: + - source: from ddtrace import __version__, tracer + style: primary + start: 0 + end: 39 + from ddtrace import __version__, tracer, config, pin: + labels: + - source: from ddtrace import __version__, tracer, config, pin + style: primary + start: 0 + end: 52 + from ddtrace import config, __version__: + labels: + - source: from ddtrace import config, __version__ + style: primary + start: 0 + end: 39 + from ddtrace import config, __version__ as V: + labels: + - source: from ddtrace import config, __version__ as V + style: primary + start: 0 + end: 44 + from ddtrace import config, __version__ as V, tracer, pin: + labels: + - source: from ddtrace import config, __version__ as V, tracer, pin + style: primary + start: 0 + end: 57 + from ddtrace import config, __version__ as VERSION, tracer: + labels: + - source: from ddtrace import config, __version__ as VERSION, tracer + style: primary + start: 0 + end: 58 + from ddtrace import config, __version__, tracer: + labels: + - source: from ddtrace import config, __version__, tracer + style: primary + start: 0 + end: 47 + from ddtrace import config, __version__, tracer, pin: + labels: + - source: from ddtrace import config, __version__, tracer, pin + style: primary + start: 0 + end: 52 + from ddtrace import config, tracer, __version__: + labels: + - source: from ddtrace import config, tracer, __version__ + style: primary + start: 0 + end: 47 + from ddtrace import config, tracer, __version__ as VERSION: + labels: + - source: from ddtrace import config, tracer, __version__ as VERSION + style: primary + start: 0 + end: 58 + from ddtrace import config, tracer, pin, __version__: + labels: + - source: from ddtrace import config, tracer, pin, __version__ + style: primary + start: 0 + end: 52 + from ddtrace import config, tracer, pin, __version__ as V: + labels: + - source: from ddtrace import config, tracer, pin, __version__ as V + style: primary + start: 0 + end: 57 + ? |- + from ddtrace import tracer + version = ddtrace.__version__ + : labels: + - source: ddtrace.__version__ + style: primary + start: 37 + end: 56 + ? | + from ddtrace import tracer + version = ddtrace.__version__ + : labels: + - source: ddtrace.__version__ + style: primary + start: 37 + end: 56 + from ddtrace import tracer, __version__: + labels: + - source: from ddtrace import tracer, __version__ + style: primary + start: 0 + end: 39 + from ddtrace import tracer, __version__ as VERSION: + labels: + - source: from ddtrace import tracer, __version__ as VERSION + style: primary + start: 0 + end: 50 + from ddtrace import tracer, config, __version__ as VERSION, pin, span: + labels: + - source: from ddtrace import tracer, config, __version__ as VERSION, pin, span + style: primary + start: 0 + end: 69 + from ddtrace import tracer, config, __version__, pin, span: + labels: + - source: from ddtrace import tracer, config, __version__, pin, span + style: primary + start: 0 + end: 58 + ? | + import ddtrace + compare = ddtrace.__version__ > "1.0" + : labels: + - source: ddtrace.__version__ + style: primary + start: 25 + end: 44 + ? | + import ddtrace + config = {"version": ddtrace.__version__} + : labels: + - source: ddtrace.__version__ + style: primary + start: 36 + end: 55 + ? | + import ddtrace + def get_version(): + return ddtrace.__version__ + : labels: + - source: ddtrace.__version__ + style: primary + start: 43 + end: 62 + ? | + import ddtrace + if ddtrace.__version__: + pass + : labels: + - source: ddtrace.__version__ + style: primary + start: 18 + end: 37 + ? | + import ddtrace + print(ddtrace.__version__) + : labels: + - source: ddtrace.__version__ + style: primary + start: 21 + end: 40 + ? |- + import ddtrace + version = ddtrace.__version__ + : labels: + - source: ddtrace.__version__ + style: primary + start: 25 + end: 44 + ? | + import ddtrace + version = ddtrace.__version__ + : labels: + - source: ddtrace.__version__ + style: primary + start: 25 + end: 44 diff --git a/.sg/tests/ddtrace-version-import-test.yml b/.sg/tests/ddtrace-version-import-test.yml new file mode 100644 index 00000000000..9e32a5aed8f --- /dev/null +++ b/.sg/tests/ddtrace-version-import-test.yml @@ -0,0 +1,103 @@ +id: ddtrace-version-import +valid: + # These should NOT trigger the rule (valid code) + # ============================================ + # Direct import from ddtrace.version (correct way) + - from ddtrace.version import __version__ + - from ddtrace.version import __version__ as VERSION + - from ddtrace.version import __version__ as VERSION, get_version + - from ddtrace.version import other, __version__ + # Import version module and access __version__ via attribute (correct way) + - | + from ddtrace import version + v = version.__version__ + - | + import ddtrace.version + v = ddtrace.version.__version__ + - | + import ddtrace.version as ver + v = ver.__version__ + - | + from ddtrace import version as ver + v = ver.__version__ + - | + from ddtrace import version + print(version.__version__) + - | + from ddtrace import version as v + def get_version(): + return v.__version__ + - | + import ddtrace.version + if ddtrace.version.__version__: + pass + # Other imports from ddtrace (not __version__) + - from ddtrace import tracer + - from ddtrace import config, tracer + - from ddtrace import config, tracer, pin + - from ddtrace import tracer, config, pin, span + # Using __version__ that's defined locally (not imported from ddtrace) + - | + __version__ = "1.0.0" + print(__version__) + +invalid: + # These should trigger the rule (errors) + # ====================================== + # Simple direct import cases + - from ddtrace import __version__ + - from ddtrace import __version__ as VERSION + - from ddtrace import __version__ as V + # __version__ at beginning with other items + - from ddtrace import __version__, tracer + - from ddtrace import __version__, config + - from ddtrace import __version__, config, tracer + - from ddtrace import __version__, tracer, config, pin + # __version__ with alias at beginning with other items + - from ddtrace import __version__ as VERSION, tracer + - from ddtrace import __version__ as V, config, tracer + - from ddtrace import __version__ as VERSION, tracer, config, pin + # __version__ at end with other items + - from ddtrace import tracer, __version__ + - from ddtrace import config, __version__ + - from ddtrace import config, tracer, __version__ + - from ddtrace import config, tracer, pin, __version__ + # __version__ with alias at end with other items + - from ddtrace import tracer, __version__ as VERSION + - from ddtrace import config, __version__ as V + - from ddtrace import config, tracer, __version__ as VERSION + - from ddtrace import config, tracer, pin, __version__ as V + # __version__ in middle with other items (2 on each side) + - from ddtrace import config, __version__, tracer + - from ddtrace import config, __version__, tracer, pin + - from ddtrace import tracer, config, __version__, pin, span + # __version__ with alias in middle + - from ddtrace import config, __version__ as VERSION, tracer + - from ddtrace import config, __version__ as V, tracer, pin + - from ddtrace import tracer, config, __version__ as VERSION, pin, span + # Attribute access on ddtrace module (direct __version__ access) + - ddtrace.__version__ + - | + import ddtrace + version = ddtrace.__version__ + - | + from ddtrace import tracer + version = ddtrace.__version__ + # ddtrace.__version__ in various contexts + - | + import ddtrace + print(ddtrace.__version__) + - | + import ddtrace + if ddtrace.__version__: + pass + - | + import ddtrace + compare = ddtrace.__version__ > "1.0" + - | + import ddtrace + config = {"version": ddtrace.__version__} + - | + import ddtrace + def get_version(): + return ddtrace.__version__ diff --git a/ddtrace/__init__.py b/ddtrace/__init__.py index 4ab3bb8fe15..353448295b9 100644 --- a/ddtrace/__init__.py +++ b/ddtrace/__init__.py @@ -1,4 +1,3 @@ -import importlib.metadata import os import sys @@ -22,6 +21,7 @@ from .internal.compat import PYTHON_VERSION_INFO # noqa: E402 from .internal.settings._config import config from .internal.utils.deprecations import DDTraceDeprecationWarning # noqa: E402 +from .version import __version__ # TODO: Deprecate accessing tracer from ddtrace.__init__ module in v4.0 @@ -29,14 +29,6 @@ from ddtrace.trace import tracer # noqa: F401 -__version__: str - -try: - __version__ = version = importlib.metadata.version(__package__ or __name__) -except importlib.metadata.PackageNotFoundError: - __version__ = version = "0.0.0" - - __all__ = [ "__version__", "patch", diff --git a/ddtrace/_trace/tracer.py b/ddtrace/_trace/tracer.py index aa5e38a946b..7ea9be3b4df 100644 --- a/ddtrace/_trace/tracer.py +++ b/ddtrace/_trace/tracer.py @@ -59,7 +59,7 @@ from ddtrace.internal.utils.formats import format_trace_id from ddtrace.internal.writer import AgentWriterInterface from ddtrace.internal.writer import HTTPWriter -from ddtrace import __version__ +from ddtrace.version import __version__ log = get_logger(__name__) diff --git a/ddtrace/appsec/ai_guard/_api_client.py b/ddtrace/appsec/ai_guard/_api_client.py index a551a8288dd..d24a7814dc0 100644 --- a/ddtrace/appsec/ai_guard/_api_client.py +++ b/ddtrace/appsec/ai_guard/_api_client.py @@ -7,7 +7,6 @@ from typing import Optional # noqa:F401 from typing import TypedDict -import ddtrace from ddtrace import config from ddtrace import tracer as ddtracer from ddtrace._trace.tracer import Tracer @@ -19,6 +18,7 @@ from ddtrace.internal.telemetry.metrics_namespaces import MetricTagType from ddtrace.internal.utils.http import Response from ddtrace.internal.utils.http import get_connection +from ddtrace.version import __version__ logger = ddlogger.get_logger(__name__) @@ -99,7 +99,7 @@ def __init__(self, endpoint: str, api_key: str, app_key: str, tracer: Tracer): "Content-Type": "application/json", "DD-API-KEY": api_key, "DD-APPLICATION-KEY": app_key, - "DD-AI-GUARD-VERSION": ddtrace.__version__, + "DD-AI-GUARD-VERSION": __version__, "DD-AI-GUARD-SOURCE": "SDK", "DD-AI-GUARD-LANGUAGE": "python", } diff --git a/ddtrace/commands/ddtrace_run.py b/ddtrace/commands/ddtrace_run.py index b88199b1e7f..e297134e877 100755 --- a/ddtrace/commands/ddtrace_run.py +++ b/ddtrace/commands/ddtrace_run.py @@ -8,6 +8,7 @@ import typing # noqa:F401 import ddtrace +from ddtrace.version import __version__ def _find_executable(args: typing.Optional[argparse.Namespace]) -> typing.Optional[str]: @@ -70,7 +71,7 @@ def _get_arg_parser() -> argparse.ArgumentParser: action="store_true", ) parser.add_argument("-p", "--profiling", help="enable profiling (disabled by default)", action="store_true") - parser.add_argument("-v", "--version", action="version", version="%(prog)s " + ddtrace.__version__) + parser.add_argument("-v", "--version", action="version", version="%(prog)s " + __version__) parser.add_argument("-nc", "--colorless", help="print output of command without color", action="store_true") return parser diff --git a/ddtrace/internal/ci_visibility/filters.py b/ddtrace/internal/ci_visibility/filters.py index f4b96fbe88e..04a1b5906d9 100644 --- a/ddtrace/internal/ci_visibility/filters.py +++ b/ddtrace/internal/ci_visibility/filters.py @@ -4,13 +4,12 @@ from typing import Optional # noqa:F401 from typing import Union # noqa:F401 -import ddtrace from ddtrace.ext import SpanTypes from ddtrace.ext import ci from ddtrace.internal.constants import SamplingMechanism from ddtrace.internal.sampling import _set_sampling_tags from ddtrace.trace import TraceFilter - +from ddtrace.version import __version__ if TYPE_CHECKING: from ddtrace.trace import Span # noqa:F401 @@ -35,6 +34,6 @@ def process_trace(self, trace): _set_sampling_tags(local_root, True, 1.0, SamplingMechanism.DEFAULT) for span in trace: span.set_tags(self._tags) - span._set_tag_str(ci.LIBRARY_VERSION, ddtrace.__version__) + span._set_tag_str(ci.LIBRARY_VERSION, __version__) return trace diff --git a/ddtrace/internal/ci_visibility/writer.py b/ddtrace/internal/ci_visibility/writer.py index a3a0ba9d217..ea8a060d13c 100644 --- a/ddtrace/internal/ci_visibility/writer.py +++ b/ddtrace/internal/ci_visibility/writer.py @@ -5,7 +5,6 @@ from typing import Dict from typing import Optional # noqa:F401 -import ddtrace from ddtrace import config from ddtrace.ext import SpanTypes from ddtrace.ext.test import TEST_SESSION_NAME @@ -15,6 +14,7 @@ from ddtrace.internal.settings._agent import config as agent_config from ddtrace.internal.utils.time import StopWatch from ddtrace.vendor.dogstatsd import DogStatsd # noqa:F401 +from ddtrace.version import __version__ from .. import service from ..evp_proxy.constants import EVP_PROXY_AGENT_ENDPOINT @@ -53,7 +53,7 @@ def __init__(self): "language": "python", "env": os.getenv("_CI_DD_ENV", config.env), "runtime-id": get_runtime_id(), - "library_version": ddtrace.__version__, + "library_version": __version__, "_dd.test.is_user_provided_service": "true" if config._is_user_provided_service else "false", }, ) diff --git a/ddtrace/internal/datastreams/processor.py b/ddtrace/internal/datastreams/processor.py index 72f4fc0e18b..9f05ecfcc7c 100644 --- a/ddtrace/internal/datastreams/processor.py +++ b/ddtrace/internal/datastreams/processor.py @@ -22,7 +22,7 @@ from ddtrace.internal.settings._agent import config as agent_config from ddtrace.internal.settings._config import config from ddtrace.internal.utils.retry import fibonacci_backoff_with_jitter -from ddtrace import __version__ +from ddtrace.version import __version__ from .._encoding import packb from ..agent import get_connection diff --git a/ddtrace/internal/debug.py b/ddtrace/internal/debug.py index 4d174d278a1..c8512d4d462 100644 --- a/ddtrace/internal/debug.py +++ b/ddtrace/internal/debug.py @@ -15,6 +15,7 @@ from ddtrace.internal.utils.cache import callonce from ddtrace.internal.writer import AgentWriterInterface from ddtrace.internal.writer import LogWriter +from ddtrace.version import __version__ from .logger import get_logger @@ -119,7 +120,7 @@ def collect(tracer): is_64_bit=sys.maxsize > 2**32, architecture=architecture()[0], vm=platform.python_implementation(), - version=ddtrace.__version__, + version=__version__, lang="python", lang_version=platform.python_version(), pip_version=pip_version, diff --git a/ddtrace/internal/processor/stats.py b/ddtrace/internal/processor/stats.py index 8c5661cb755..0ad1397c1e6 100644 --- a/ddtrace/internal/processor/stats.py +++ b/ddtrace/internal/processor/stats.py @@ -14,7 +14,7 @@ from ddtrace.internal.native import DDSketch from ddtrace.internal.settings._config import config from ddtrace.internal.utils.retry import fibonacci_backoff_with_jitter -from ddtrace import __version__ +from ddtrace.version import __version__ from ...constants import _SPAN_MEASURED_KEY from .. import agent diff --git a/ddtrace/internal/runtime/tag_collectors.py b/ddtrace/internal/runtime/tag_collectors.py index 9cbe857446d..bf0943e3ea0 100644 --- a/ddtrace/internal/runtime/tag_collectors.py +++ b/ddtrace/internal/runtime/tag_collectors.py @@ -63,16 +63,16 @@ class PlatformTagCollector(RuntimeTagCollector): - ``tracer_version`` e.g. ``0.29.0`` """ - required_modules = ["platform", "ddtrace"] + required_modules = ["platform", "ddtrace.version"] def collect_fn(self, keys): platform = self.modules.get("platform") - ddtrace = self.modules.get("ddtrace") + version = self.modules.get("ddtrace.version") tags = [ (LANG, "python"), (LANG_INTERPRETER, platform.python_implementation()), (LANG_VERSION, platform.python_version()), - (TRACER_VERSION, ddtrace.__version__), + (TRACER_VERSION, version.__version__), ] return tags diff --git a/ddtrace/internal/settings/dynamic_instrumentation.py b/ddtrace/internal/settings/dynamic_instrumentation.py index 29f0cb2e9ce..707f0758c95 100644 --- a/ddtrace/internal/settings/dynamic_instrumentation.py +++ b/ddtrace/internal/settings/dynamic_instrumentation.py @@ -8,7 +8,7 @@ from ddtrace.internal.settings._agent import config as agent_config from ddtrace.internal.settings._core import DDConfig from ddtrace.internal.utils.config import get_application_name -from ddtrace import __version__ +from ddtrace.version import __version__ DEFAULT_MAX_PROBES = 100 diff --git a/ddtrace/internal/telemetry/data.py b/ddtrace/internal/telemetry/data.py index 2791fbf9ab2..6ac4689f29d 100644 --- a/ddtrace/internal/telemetry/data.py +++ b/ddtrace/internal/telemetry/data.py @@ -11,7 +11,7 @@ from ddtrace.internal.packages import get_module_distribution_versions from ddtrace.internal.runtime.container import get_container_info from ddtrace.internal.utils.cache import cached -from ddtrace import __version__ +from ddtrace.version import __version__ from ..hostname import get_hostname diff --git a/ddtrace/internal/utils/version.py b/ddtrace/internal/utils/version.py index 304ab9b54e0..be10973b63f 100644 --- a/ddtrace/internal/utils/version.py +++ b/ddtrace/internal/utils/version.py @@ -2,7 +2,7 @@ from typing import Optional # noqa:F401 import ddtrace.vendor.packaging.version as packaging_version -from ddtrace import __version__ +from ddtrace.version import __version__ def parse_version(version): diff --git a/ddtrace/internal/writer/writer.py b/ddtrace/internal/writer/writer.py index 9aad9ca77fa..014e73f94d6 100644 --- a/ddtrace/internal/writer/writer.py +++ b/ddtrace/internal/writer/writer.py @@ -13,7 +13,6 @@ from typing import Optional from typing import TextIO -import ddtrace from ddtrace import config from ddtrace.internal.dist_computing.utils import in_ray_job from ddtrace.internal.hostname import get_hostname @@ -23,6 +22,7 @@ from ddtrace.internal.settings.asm import ai_guard_config from ddtrace.internal.settings.asm import config as asm_config from ddtrace.internal.utils.retry import fibonacci_backoff_with_jitter +from ddtrace.version import __version__ from ...constants import _KEEP_SPANS_RATE_KEY from .. import compat @@ -575,7 +575,7 @@ def __init__( "Datadog-Meta-Lang": "python", "Datadog-Meta-Lang-Version": compat.PYTHON_VERSION, "Datadog-Meta-Lang-Interpreter": compat.PYTHON_INTERPRETER, - "Datadog-Meta-Tracer-Version": ddtrace.__version__, + "Datadog-Meta-Tracer-Version": __version__, "Datadog-Client-Computed-Top-Level": "yes", } if headers: @@ -801,7 +801,7 @@ def _create_exporter(self) -> native.TraceExporter: .set_language("python") .set_language_version(compat.PYTHON_VERSION) .set_language_interpreter(compat.PYTHON_INTERPRETER) - .set_tracer_version(ddtrace.__version__) + .set_tracer_version(__version__) .set_git_commit_sha(commit_sha) .set_client_computed_top_level() .set_input_format(self._api_version) diff --git a/ddtrace/llmobs/_experiment.py b/ddtrace/llmobs/_experiment.py index d31a4680626..0640f85a523 100644 --- a/ddtrace/llmobs/_experiment.py +++ b/ddtrace/llmobs/_experiment.py @@ -16,7 +16,6 @@ from typing import overload import uuid -import ddtrace from ddtrace import config from ddtrace.constants import ERROR_MSG from ddtrace.constants import ERROR_STACK @@ -26,6 +25,7 @@ from ddtrace.llmobs._constants import EXPERIMENT_EXPECTED_OUTPUT from ddtrace.llmobs._utils import convert_tags_dict_to_list from ddtrace.llmobs._utils import safe_json +from ddtrace.version import __version__ if TYPE_CHECKING: @@ -338,7 +338,7 @@ def __init__( self._summary_evaluators = summary_evaluators or [] self._description = description self._tags: Dict[str, str] = tags or {} - self._tags["ddtrace.version"] = str(ddtrace.__version__) + self._tags["ddtrace.version"] = str(__version__) self._config: Dict[str, JSONType] = config or {} self._llmobs_instance = _llmobs_instance diff --git a/ddtrace/llmobs/_llmobs.py b/ddtrace/llmobs/_llmobs.py index c429714778c..2ae9a9f0905 100644 --- a/ddtrace/llmobs/_llmobs.py +++ b/ddtrace/llmobs/_llmobs.py @@ -120,6 +120,7 @@ from ddtrace.llmobs.utils import Messages from ddtrace.llmobs.utils import extract_tool_definitions from ddtrace.propagation.http import HTTPPropagator +from ddtrace.version import __version__ log = get_logger(__name__) @@ -464,7 +465,7 @@ def _llmobs_tags(span: Span, ml_app: str, session_id: Optional[str] = None) -> L "service": span.service or "", "source": "integration", "ml_app": ml_app, - "ddtrace.version": ddtrace.__version__, + "ddtrace.version": __version__, "language": "python", "error": span.error, } @@ -1712,7 +1713,7 @@ def submit_evaluation( ) evaluation_tags = { - "ddtrace.version": ddtrace.__version__, + "ddtrace.version": __version__, "ml_app": ml_app, } diff --git a/ddtrace/llmobs/_writer.py b/ddtrace/llmobs/_writer.py index 1b4d0f2462b..354acef628b 100644 --- a/ddtrace/llmobs/_writer.py +++ b/ddtrace/llmobs/_writer.py @@ -15,7 +15,6 @@ from urllib.parse import quote from urllib.parse import urlparse -import ddtrace from ddtrace import config from ddtrace.internal import agent from ddtrace.internal import forksafe @@ -49,6 +48,7 @@ from ddtrace.llmobs._utils import safe_json from ddtrace.llmobs.types import _Meta from ddtrace.llmobs.types import _SpanLink +from ddtrace.version import __version__ logger = get_logger(__name__) @@ -721,7 +721,7 @@ def _data(self, events: List[LLMObsSpanEvent]) -> List[Dict[str, Any]]: for event in events: event_data = { "_dd.stage": "raw", - "_dd.tracer_version": ddtrace.__version__, + "_dd.tracer_version": __version__, "event_type": "span", "spans": [event], } diff --git a/ddtrace/version.py b/ddtrace/version.py new file mode 100644 index 00000000000..f85182248bb --- /dev/null +++ b/ddtrace/version.py @@ -0,0 +1,12 @@ +"""Maintain a separate module for the version to avoid circular imports.""" + +import importlib.metadata + +__all__ = ["__version__"] + +__version__: str + +try: + __version__ = importlib.metadata.version(__package__ or __name__) +except importlib.metadata.PackageNotFoundError: + __version__ = "0.0.0" From 13ed54505255be6f04adb97cd679aeba89d3d21c Mon Sep 17 00:00:00 2001 From: brettlangdon Date: Tue, 18 Nov 2025 13:18:14 -0500 Subject: [PATCH 26/29] ignore tests and benchmarks --- .sg/rules/ddtrace-version-import.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.sg/rules/ddtrace-version-import.yml b/.sg/rules/ddtrace-version-import.yml index 10451e7c664..5c3b53d851b 100644 --- a/.sg/rules/ddtrace-version-import.yml +++ b/.sg/rules/ddtrace-version-import.yml @@ -3,7 +3,8 @@ message: Import `__version__` from `ddtrace.version` instead of `ddtrace` severity: error language: python ignores: - - "ddtrace/version.py" + - "tests/**" + - "benchmarks/**" rule: any: # Match: from ddtrace import __version__ (using variable with constraint) From fdd6101c57d2f2b1b1e15e64099775b0c44541ee Mon Sep 17 00:00:00 2001 From: brettlangdon Date: Tue, 18 Nov 2025 14:45:00 -0500 Subject: [PATCH 27/29] fix linting --- ddtrace/internal/ci_visibility/filters.py | 1 + ddtrace/version.py | 1 + tests/appsec/architectures/mini.py | 2 +- tests/contrib/patch.py | 2 +- tests/debugging/test_config.py | 2 +- 5 files changed, 5 insertions(+), 3 deletions(-) diff --git a/ddtrace/internal/ci_visibility/filters.py b/ddtrace/internal/ci_visibility/filters.py index 04a1b5906d9..88066635374 100644 --- a/ddtrace/internal/ci_visibility/filters.py +++ b/ddtrace/internal/ci_visibility/filters.py @@ -11,6 +11,7 @@ from ddtrace.trace import TraceFilter from ddtrace.version import __version__ + if TYPE_CHECKING: from ddtrace.trace import Span # noqa:F401 diff --git a/ddtrace/version.py b/ddtrace/version.py index f85182248bb..a688dcc6383 100644 --- a/ddtrace/version.py +++ b/ddtrace/version.py @@ -2,6 +2,7 @@ import importlib.metadata + __all__ = ["__version__"] __version__: str diff --git a/tests/appsec/architectures/mini.py b/tests/appsec/architectures/mini.py index d3f17a8d02c..3d7b8419273 100644 --- a/tests/appsec/architectures/mini.py +++ b/tests/appsec/architectures/mini.py @@ -11,9 +11,9 @@ from flask import request # noqa: E402 import requests # noqa: E402 F401 +from ddtrace import __version__ # noqa: E402 from ddtrace.internal.settings.asm import config as asm_config # noqa: E402 import ddtrace.internal.telemetry.writer # noqa: E402 -from ddtrace import __version__ # noqa: E402 app = Flask(__name__) diff --git a/tests/contrib/patch.py b/tests/contrib/patch.py index bf600efcc10..cc20c745c14 100644 --- a/tests/contrib/patch.py +++ b/tests/contrib/patch.py @@ -8,8 +8,8 @@ from textwrap import dedent import unittest -from ddtrace.internal.compat import is_wrapted from ddtrace import __version__ +from ddtrace.internal.compat import is_wrapted from tests.subprocesstest import SubprocessTestCase from tests.subprocesstest import run_in_subprocess from tests.utils import call_program diff --git a/tests/debugging/test_config.py b/tests/debugging/test_config.py index d90641e4a74..563df5f5eae 100644 --- a/tests/debugging/test_config.py +++ b/tests/debugging/test_config.py @@ -2,10 +2,10 @@ import pytest +from ddtrace import __version__ from ddtrace.internal.settings._agent import config as agent_config from ddtrace.internal.settings.dynamic_instrumentation import DynamicInstrumentationConfig from ddtrace.internal.utils.formats import parse_tags_str -from ddtrace import __version__ from tests.utils import override_env From b6bda498c5d71689fb0ed3fe951560ac01fb8264 Mon Sep 17 00:00:00 2001 From: Brett Langdon Date: Tue, 18 Nov 2025 14:55:12 -0500 Subject: [PATCH 28/29] Update pyproject.toml --- pyproject.toml | 3 --- 1 file changed, 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index dfdfffda384..46c1eb52a9c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -210,10 +210,7 @@ lint.ignore = [ "D413", "E203", "E231", - "E262", - "E266", "E721", - "F822", "G201", ] line-length = 120 From 659766cee17d3697930e4f74e604a24e8305f0e0 Mon Sep 17 00:00:00 2001 From: brettlangdon Date: Fri, 21 Nov 2025 15:12:37 -0500 Subject: [PATCH 29/29] add ci release check that wheel version matches tag --- .gitlab/package.yml | 16 +++++++++++++++ .gitlab/verify-package-versions.sh | 32 ++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100755 .gitlab/verify-package-versions.sh diff --git a/.gitlab/package.yml b/.gitlab/package.yml index 908e77c93f8..f988bc88d71 100644 --- a/.gitlab/package.yml +++ b/.gitlab/package.yml @@ -113,3 +113,19 @@ publish-wheels-to-s3: # Print the clickable URLs PIPE_INDEX_URL="${S3_BASE_PIPE}/index.html" echo "S3 index (pipeline): ${PIPE_INDEX_URL}" + + +# Fail if the downloaded package versions do not match the git tag version +verify_package_version: + image: registry.ddbuild.io/images/mirror/python:3.14.0 + tags: [ "arch:amd64" ] + stage: package + needs: [ download_ddtrace_artifacts ] + only: + # v2.10.0 + # v2.10.1 + # v2.10.0rc0 + # v2.10.0rc5 + - /^v[0-9]+\.[0-9]+\.[0-9]+(rc[0-9]+)?$/ + script: + - .gitlab/verify-package-versions.sh diff --git a/.gitlab/verify-package-versions.sh b/.gitlab/verify-package-versions.sh new file mode 100755 index 00000000000..9412ea5cc65 --- /dev/null +++ b/.gitlab/verify-package-versions.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +set -euo pipefail + +WHEEL_FILES=(pywheels/*.whl) +if [ ${#WHEEL_FILES[@]} -eq 0 ]; then + echo "No wheels found in pywheels/"; exit 1 +fi + +VERSION_TAG="${CI_COMMIT_TAG#v}" +echo "Verifying package version ${VERSION_TAG}" + +for wf in "${WHEEL_FILES[@]}"; do + echo "Checking wheel file: ${wf}" + WHEEL_VERSION=$(basename "${wf}" | awk -F '-' '{print $2}') + if [ "${WHEEL_VERSION}" != "${VERSION_TAG}" ]; then + echo "ERROR: Wheel version ${WHEEL_VERSION} does not match tag version ${VERSION_TAG}" + exit 1 + fi +done + +SDIST_FILES=(pywheels/*.tar.gz) +if [ ${#SDIST_FILES[@]} -eq 0 ]; then + echo "No sdist files found in pywheels/"; exit 1 +fi +for sf in "${SDIST_FILES[@]}"; do + echo "Checking sdist file: ${sf}" + SDIST_VERSION=$(basename "${sf}" | awk -F '-' '{print $2}' | sed 's/\.tar\.gz$//') + if [ "${SDIST_VERSION}" != "${VERSION_TAG}" ]; then + echo "ERROR: Sdist version ${SDIST_VERSION} does not match tag version ${VERSION_TAG}" + exit 1 + fi +done