From 2058ac9ebc701d1c43bbc12f883d74d1a45a40b8 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Fri, 15 Nov 2024 12:17:31 +0100 Subject: [PATCH 01/23] Minor version updates. --- recipe/meta.yaml | 10 +++++----- tests/requirements.txt | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/recipe/meta.yaml b/recipe/meta.yaml index e0dda43..d954c77 100644 --- a/recipe/meta.yaml +++ b/recipe/meta.yaml @@ -19,16 +19,16 @@ requirements: host: - python >=3.9 - pip - - hatchling - - hatch-vcs + - hatchling >=1.12.2 + - hatch-vcs >=0.2.0 run: - python >=3.9 - - conda-canary/label/dev::conda + - conda >24.9.2 test: requires: - - conda-canary/label/dev::conda - pip + - conda >24.9.2 commands: - conda --version - pip check @@ -41,7 +41,7 @@ about: summary: Anaconda Telemetry conda plugin description: Anaconda Telemetry for conda adds helps us understand how conda is being used. license: BSD-3-Clause - dev_url: https://github.com/anaconda/{{ name }} + license_file: LICENSE extra: recipe-maintainers: diff --git a/tests/requirements.txt b/tests/requirements.txt index 8ad35ea..b57d43f 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -1,2 +1,2 @@ -conda-canary/label/dev::conda +conda >24.9.2 python >=3.9 From da0935fda8ca0de86c16a74fd72478b65f657a66 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Fri, 15 Nov 2024 12:22:04 +0100 Subject: [PATCH 02/23] Update hatch version mechanism to match conda's. --- .git_archival.txt | 4 ++++ .gitattributes | 2 ++ conda_anaconda_telemetry/__init__.py | 32 ++++++++++++++++++++++++++++ pyproject.toml | 6 ++++++ recipe/meta.yaml | 2 +- 5 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 .git_archival.txt create mode 100644 .gitattributes diff --git a/.git_archival.txt b/.git_archival.txt new file mode 100644 index 0000000..8fb235d --- /dev/null +++ b/.git_archival.txt @@ -0,0 +1,4 @@ +node: $Format:%H$ +node-date: $Format:%cI$ +describe-name: $Format:%(describe:tags=true,match=*[0-9]*)$ +ref-names: $Format:%D$ diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..cf9df9b --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +* text=auto eol=lf +.git_archival.txt export-subst diff --git a/conda_anaconda_telemetry/__init__.py b/conda_anaconda_telemetry/__init__.py index e69de29..02509fe 100644 --- a/conda_anaconda_telemetry/__init__.py +++ b/conda_anaconda_telemetry/__init__.py @@ -0,0 +1,32 @@ +# Copyright (C) 2024 Anaconda, Inc +# SPDX-License-Identifier: BSD-3-Clause +"""Conda channel's ToS management plugin.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Final + +#: Application name. +APP_NAME: Final = "anaconda-conda-telemetry" + +try: + from ._version import __version__ +except ImportError: + # _version.py is only created after running `pip install` + try: + from setuptools_scm import get_version + + __version__ = get_version(root="..", relative_to=__file__) + except (ImportError, OSError, LookupError): + # ImportError: setuptools_scm isn't installed + # OSError: git isn't installed + # LookupError: setuptools_scm unable to detect version + # anaconda-conda-tos follows SemVer, so the dev version is: + # MJ.MN.MICRO.devN+gHASH[.dirty] + __version__ = "0.0.0.dev0+placeholder" + +#: Application version. +APP_VERSION: Final = __version__ diff --git a/pyproject.toml b/pyproject.toml index cf9b219..dac20bf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,6 +31,12 @@ conda-anaconda-telemetry = "conda_anaconda_telemetry.hooks" [tool.setuptools.packages] find = {} +[tool.hatch.build.hooks.vcs] +version-file = "anaconda_conda_telemetry/_version.py" [tool.hatch.version] source = "vcs" + +[tool.hatch.version.raw-options] +local_scheme = "dirty-tag" + diff --git a/recipe/meta.yaml b/recipe/meta.yaml index d954c77..07fd3f7 100644 --- a/recipe/meta.yaml +++ b/recipe/meta.yaml @@ -2,7 +2,7 @@ package: name: {{ name|lower }} - version: "{{ GIT_DESCRIBE_TAG }}.{{ GIT_BUILD_STR }}" + version: {{ os.getenv("VERSION_OVERRIDE") or GIT_DESCRIBE_TAG }}.{{ GIT_BUILD_STR }} source: # git_url only captures committed code From 43ba71fcba73a3ebbd536b18db3012a329fd246f Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Fri, 15 Nov 2024 12:22:26 +0100 Subject: [PATCH 03/23] Update some of the Python setup. --- .pre-commit-config.yaml | 90 ++++++++++++++++++++++++++++++++--------- pyproject.toml | 69 ++++++++++++++++++++++++++++++- setup.cfg | 3 -- 3 files changed, 138 insertions(+), 24 deletions(-) delete mode 100644 setup.cfg diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6671160..712a9dd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,27 +1,79 @@ repos: + # generic verification and formatting - repo: https://github.com/pre-commit/pre-commit-hooks rev: v5.0.0 hooks: - - id: check-yaml - exclude: "(mkdocs.yml|recipe/meta.yaml)" - - id: end-of-file-fixer - - id: trailing-whitespace - - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v1.13.0' # Use the sha / tag you want to point at + # standard end of line/end of file cleanup + - id: mixed-line-ending + - id: end-of-file-fixer + - id: trailing-whitespace + # ensure syntaxes are valid + - id: check-toml + - id: check-yaml + exclude: | + (?x)^( + recipe/meta.yaml + ) + - id: check-json + # catch git merge/rebase problems + - id: check-merge-conflict + # Python verification and formatting + - repo: https://github.com/Lucas-C/pre-commit-hooks + rev: v1.5.5 + hooks: + # auto inject license blurb + - id: insert-license + files: \.py$ + args: [--license-filepath, .github/disclaimer.txt, --no-extra-eol, --use-current-year] + - repo: https://github.com/adamchainz/blacken-docs + rev: 1.19.1 + hooks: + # auto format Python codes within docstrings + - id: blacken-docs + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.7.3 + hooks: + # lint & attempt to correct failures (e.g. pyupgrade) + - id: ruff + args: [--fix] + # compatible replacement for black + - id: ruff-format + - repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks + rev: v2.14.0 hooks: - - id: mypy - additional_dependencies: ['types-requests'] - - repo: https://github.com/asottile/pyupgrade - rev: v3.19.0 + - id: pretty-format-toml + args: [--autofix, --trailing-commas] + - repo: https://github.com/jumanjihouse/pre-commit-hook-yamlfmt + rev: 0.2.3 + hooks: + - id: yamlfmt + # ruamel.yaml doesn't line wrap correctly (?) so set width to 1M to avoid issues + args: [--mapping=2, --offset=2, --sequence=4, --width=1000000, --implicit_start] + exclude: | + (?x)^( + recipe/meta.yaml + ) + - repo: https://github.com/python-jsonschema/check-jsonschema + rev: 0.29.4 + hooks: + # verify github syntaxes + - id: check-github-workflows + - id: check-renovate + - repo: https://github.com/pre-commit/mirrors-mypy + rev: v1.13.0 hooks: - - id: pyupgrade - args: ["--py310-plus"] - - repo: https://github.com/akaihola/darker - rev: v2.1.1 + - id: mypy + additional_dependencies: [types-requests] + - repo: meta + # see https://pre-commit.com/#meta-hooks hooks: - - id: darker - additional_dependencies: [black==22.10.0] - - repo: https://github.com/PyCQA/flake8 - rev: 7.1.1 + - id: check-hooks-apply + - id: check-useless-excludes + - repo: local hooks: - - id: flake8 + - id: git-diff + name: git diff + entry: git diff --exit-code + language: system + pass_filenames: false + always_run: true diff --git a/pyproject.toml b/pyproject.toml index dac20bf..1764b5d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,8 +29,22 @@ dynamic = [ [project.entry-points.conda] conda-anaconda-telemetry = "conda_anaconda_telemetry.hooks" -[tool.setuptools.packages] -find = {} +[tool.coverage.report] +exclude_lines = [ + "pragma: no cover", + "if TYPE_CHECKING:", # ignore type checking imports +] +omit = [ + "tests/*", +] +show_missing = true +skip_covered = true +sort = "Miss" + +[tool.coverage.run] +# store relative paths in coverage information +relative_files = true + [tool.hatch.build.hooks.vcs] version-file = "anaconda_conda_telemetry/_version.py" @@ -40,3 +54,54 @@ source = "vcs" [tool.hatch.version.raw-options] local_scheme = "dirty-tag" +[tool.pytest.ini_options] +addopts = [ + "--color=yes", + # "--cov=anaconda_conda_telemetry", # passed in test runner scripts instead (avoid debugger) + "--cov-report=term", # print summary table to screen + "--cov-report=xml", # for codecov/codecov-action upload + "--tb=native", + "-vv", +] +testpaths = ["tests"] + +[tool.ruff] +target-version = "py39" + +[tool.ruff.lint] +extend-per-file-ignores = {"tests/*" = ["D", "S101"]} +ignore = ["D203", "D213"] +# see https://docs.astral.sh/ruff/rules/ +select = [ + "A", # flake8-builtins + "ANN", # flake8-annotations + "ARG", # flake8-unused-arguments + "B", # flake8-bugbear + "C", # flake8-commas + "C4", # flake8-comprehensions + "C90", # mccabe + "D", # pydocstyle + "DTZ", # flake8-datetimez + "E", # pycodestyle errors + "ERA", # eradicate + "F", # pyflakes + "FA", # flake8-future-annotations + "G", # flake8-logging-format + "I", # isort + "ISC", # flake8-implicit-str-concat + "LOG", # flake8-logging + "N", # pep8-naming + "PIE", # flake8-pie + "PTH", # flake8-use-pathlib + "RUF", # Ruff-specific rules + "S", # flake8-bandit + "SIM", # flake8-simplify + "T10", # flake8-debugger + "TCH", # flake8-type-checking + "UP", # pyupgrade + "W", # pycodestyle warnings + "YTT", # flake8-2020 +] + +[tool.setuptools] +packages = ["anaconda_conda_telemetry"] diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index e10f006..0000000 --- a/setup.cfg +++ /dev/null @@ -1,3 +0,0 @@ -[flake8] -max-line-length = 99 -ignore = E126,E133,E226,E241,E242,E302,E704,E731,E722,W503,E402,W504,F821,E203 From dbe98f60445bd0dc1d8a9142cf4ee6da5b300e43 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Fri, 15 Nov 2024 12:22:41 +0100 Subject: [PATCH 04/23] Sync with conda's GitHub action setup. --- .github/workflows/tests.yml | 218 ++++++++++++++++++++++-------------- 1 file changed, 137 insertions(+), 81 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 2af336a..1bb6212 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -9,6 +9,9 @@ on: # https://docs.github.com/en/webhooks-and-events/webhooks/webhook-events-and-payloads#pull_request pull_request: + # https://docs.github.com/en/webhooks/webhook-events-and-payloads#merge_group + merge_group: + # https://docs.github.com/en/webhooks-and-events/webhooks/webhook-events-and-payloads#workflow_dispatch workflow_dispatch: @@ -63,13 +66,40 @@ jobs: tests: needs: changes - if: needs.changes.outputs.code == 'true' + if: github.event_name == 'schedule' || needs.changes.outputs.code == 'true' + + defaults: + run: + # https://github.com/conda-incubator/setup-miniconda#use-a-default-shell + shell: bash -el {0} # bash exit immediately on error + login shell - runs-on: ${{ matrix.os }} strategy: matrix: - python-version: ["3.9", "3.12"] - os: ["macos-latest", "ubuntu-latest", "windows-latest"] + python-version: ['3.9', '3.10', '3.11', '3.12'] + os: [ubuntu-latest, windows-latest, macos-13, macos-latest] + exclude: + # Windows: only test lowest and highest Python versions + - os: windows-latest + python-version: '3.10' + - os: windows-latest + python-version: '3.11' + # macOS x86_64: only test lowest Python version + - os: macos-13 + python-version: '3.10' + - os: macos-13 + python-version: '3.11' + - os: macos-13 + python-version: '3.12' + # macOS arm64: only test highest Python version + - os: macos-14 + python-version: '3.9' + - os: macos-14 + python-version: '3.10' + - os: macos-14 + python-version: '3.11' + runs-on: ${{ matrix.os }} + env: + ErrorActionPreference: Stop # powershell exit immediately on error steps: # Clean checkout of specific git ref needed for package metadata version @@ -90,10 +120,12 @@ jobs: path: ~/conda_pkgs_dir key: cache-${{ env.HASH }} - - uses: conda-incubator/setup-miniconda@d2e6a045a86077fb6cad6f5adf368e9076ddaa8d # v3 - name: Setup Miniconda + - name: Setup Miniconda + uses: conda-incubator/setup-miniconda@d2e6a045a86077fb6cad6f5adf368e9076ddaa8d # v3.1.0 with: - python-version: ${{ matrix.python-version }} + # conda not preinstalled in arm64 runners + miniconda-version: ${{ runner.arch == 'ARM64' && 'latest' || null }} + architecture: ${{ runner.arch }} channels: defaults run-post: false # skip post cleanup @@ -108,16 +140,17 @@ jobs: # TODO: how can we remove this step? - name: Install Self - run: conda run --name test pip install --no-build-isolation --no-deps e . + run: pip install -e . - name: Conda Info - run: conda info --verbose + # view test env info (not base) + run: python -m conda info --verbose - name: Conda Config run: conda config --show-sources - name: Conda List - run: conda list --show-channel-urls --name test + run: conda list --show-channel-urls - name: Run Tests # Windows is sensitive to long paths, using `--basetemp=${{ runner.temp }} to @@ -130,21 +163,63 @@ jobs: --basetemp=${{ runner.temp }} -n auto - build: + # required check + analyze: needs: [tests] + if: '!cancelled()' + + runs-on: ubuntu-latest + steps: + - name: Determine Success + uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # v1.2.2 + id: alls-green + with: + # permit jobs to be skipped if there are no code changes (see changes job) + allowed-skips: ${{ toJSON(needs) }} + jobs: ${{ toJSON(needs) }} + + - name: Checkout Source + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + # source code is needed to report failures + if: always() && github.event_name != 'pull_request' && steps.alls-green.outputs.result == 'failure' + + - name: Report Failures + if: always() && github.event_name != 'pull_request' && steps.alls-green.outputs.result == 'failure' + uses: JasonEtco/create-an-issue@1b14a70e4d8dc185e5cc76d3bec9eab20257b2c5 # v2.9.2 + env: + GITHUB_TOKEN: ${{ secrets.AUTO_REPORT_TEST_FAILURE }} + RUN_ID: ${{ github.run_id }} + TITLE: Tests failed + with: + filename: .github/TEST_FAILURE_REPORT_TEMPLATE.md + update_existing: false + + # canary builds + build: + needs: [analyze] # only build canary build if # - prior steps succeeded, - # - this is the main branch + # - this is the main repo, and + # - we are on the main, feature, or release branch if: >- - success() + !cancelled() && !github.event.repository.fork + && ( + github.ref_name == 'main' + || startsWith(github.ref_name, 'feature/') + || endsWith(github.ref_name, '.x') + ) strategy: matrix: include: - runner: ubuntu-latest - subdir: noarch - python-version: 3.12 - + subdir: linux-64 + - runner: macos-13 + subdir: osx-64 + - runner: macos-latest + subdir: osx-arm64 + - runner: windows-latest + subdir: win-64 runs-on: ${{ matrix.runner }} steps: # Clean checkout of specific git ref needed for package metadata version @@ -156,71 +231,52 @@ jobs: clean: true fetch-depth: 0 - - name: Hash + Timestamp - run: echo "HASH=${{ runner.os }}-${{ runner.arch }}-Py${{ matrix.python-version }}-$(date -u "+%Y%m")" >> $GITHUB_ENV - - - name: Cache Conda - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + # Explicitly use Python 3.11 since each of the OSes has a different default Python + - name: Setup Python + uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 with: - path: ~/conda_pkgs_dir - key: cache-${{ env.HASH }} - - - uses: conda-incubator/setup-miniconda@d2e6a045a86077fb6cad6f5adf368e9076ddaa8d # v3 - name: Setup Miniconda - with: - python-version: ${{ matrix.python-version }} - channels: defaults - run-post: false # skip post cleanup - # conda not preinstalled in arm64 runners - miniconda-version: ${{ runner.arch == 'ARM64' && 'latest' || null }} - architecture: ${{ runner.arch }} + python-version: '3.11' - - name: Build package - # make sure we don't run on forks - if: github.repository_owner == 'anaconda' - id: build - shell: bash -l {0} - env: - # Run conda-build in isolated activation to properly package the software - _CONDA_BUILD_ISOLATED_ACTIVATION: 1 - run: | - set -euo pipefail - conda activate test - conda install --yes --quiet conda-build anaconda-client - # git needs to be installed after conda-build - # see https://github.com/conda/conda/issues/11758 - # see https://github.com/conda/actions/pull/47 - conda install --yes --quiet git - conda info - conda config --show-sources - conda list - conda build --croot=${{ runner.temp }}/pkgs --override-channels -c conda-canary/label/dev -c defaults recipe - - - name: Set variables - id: set-vars + - name: Detect Label + shell: python run: | - echo "PACKAGE_LABEL=${{ github.ref_name == 'main' && 'dev' || format('{0}-{1}', github.event.repository.name, github.ref_name) }}" >> $GITHUB_ENV - echo "PACKAGE_NAME=${{ github.event.repository.name }}" >> $GITHUB_ENV - - - name: Upload package - # make sure we don't run in branches - if: github.ref_name == 'main' - id: upload - shell: bash -l {0} - run: | - echo "::group::Uploading package" - anaconda \ - --token="${{ secrets.ANACONDA_ORG_TOKEN }}" \ - upload \ - --force \ - --register \ - --no-progress \ - --user="distribution-plugins" \ - --label="$PACKAGE_LABEL" \ - ${{ runner.temp }}/pkgs/${{ matrix.subdir }}/$PACKAGE_NAME-*.tar.bz2 - echo "Uploaded the following files:" - basename -a ./pkgs/${{ matrix.subdir }}/$PACKAGE_NAME-*.tar.bz2 - echo "::endgroup::" - - echo "Use this command to try out the build:" - echo "conda install -c distribution-plugins/label/$PACKAGE_LABEL $PACKAGE_NAME" + import re + from pathlib import Path + from os import environ + from subprocess import check_output + + # unless otherwise specified, commits are uploaded to the dev label + # e.g., `main` branch commits + envs = {"ANACONDA_ORG_LABEL": "dev"} + + if "${{ github.ref_name }}".startswith("feature/"): + # feature branch commits are uploaded to a custom label + envs["ANACONDA_ORG_LABEL"] = "${{ github.ref_name }}" + elif re.match(r"\d+(\.\d+)+\.x", "${{ github.ref_name }}"): + # release branch commits are added to the rc label + # see https://github.com/conda/infrastructure/issues/760 + _, name = "${{ github.repository }}".split("/") + envs["ANACONDA_ORG_LABEL"] = f"rc-{name}-${{ github.ref_name }}" + + # if no releases have occurred on this branch yet then `git describe --tag` + # will misleadingly produce a version number relative to the last release + # and not relative to the current release branch, if this is the case we need + # to override the version with a derivative of the branch name + + # override the version if `git describe --tag` does not start with the branch version + last_release = check_output(["git", "describe", "--tag"]) + prefix = "${{ github.ref_name }}"[:-1] # without x suffix + if not last_release.startswith(prefix): + envs["VERSION_OVERRIDE"] = "${{ github.ref_name }}" + + Path(environ["GITHUB_ENV"]).write_text("\n".join(f"{name}={value}" for name, value in envs.items())) + + - name: Create & Upload + uses: conda/actions/canary-release@6e72e0db87e72f0020e493aeb02f864363bd9258 # v24.11.1 + with: + package-name: ${{ github.event.repository.name }} + subdir: ${{ matrix.subdir }} + anaconda-org-channel: distribution-plugins + anaconda-org-label: ${{ env.ANACONDA_ORG_LABEL }} + anaconda-org-token: ${{ secrets.ANACONDA_ORG_TOKEN }} + conda-build-argumetns: --channel conda-canary/label/dev From 65db3f4aac10c1889e7be59fc739bde48da79d36 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Fri, 15 Nov 2024 12:24:49 +0100 Subject: [PATCH 05/23] Install from canary channel. --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1bb6212..c21cc02 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -132,7 +132,7 @@ jobs: - name: Conda Install run: > conda install - --name test + --channel conda-canary/label/dev --yes --file tests/requirements.txt --file tests/requirements-ci.txt From 145ce45937dfbe28fe3d4a8de4b41f043a627e2d Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Fri, 15 Nov 2024 12:29:42 +0100 Subject: [PATCH 06/23] Fix pip deps. --- .github/workflows/tests.yml | 2 +- tests/requirements-ci.txt | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c21cc02..868adec 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -123,11 +123,11 @@ jobs: - name: Setup Miniconda uses: conda-incubator/setup-miniconda@d2e6a045a86077fb6cad6f5adf368e9076ddaa8d # v3.1.0 with: + run-post: false # skip post cleanup # conda not preinstalled in arm64 runners miniconda-version: ${{ runner.arch == 'ARM64' && 'latest' || null }} architecture: ${{ runner.arch }} channels: defaults - run-post: false # skip post cleanup - name: Conda Install run: > diff --git a/tests/requirements-ci.txt b/tests/requirements-ci.txt index 52a48f0..bc392d6 100644 --- a/tests/requirements-ci.txt +++ b/tests/requirements-ci.txt @@ -1,11 +1,9 @@ # renovate: datasource=conda depName=main/conda-build conda-build >=24.9.0 # renovate: datasource=conda depName=main/hatchling -hatchling ==1.25.0 +hatchling >=1.12.2 # renovate: datasource=conda depName=main/hatch-vcs -hatch-vcs ==0.3.0 -# renovate: datasource=conda depName=main/pip -pip ==24.2 +hatch-vcs >=0.2.0 # renovate: datasource=conda depName=main/pytest pytest ==7.4.4 # renovate: datasource=conda depName=main/pytest-cov From 06ab396b01b59d9c353d8eec8be8267b951cb38d Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Fri, 15 Nov 2024 12:31:00 +0100 Subject: [PATCH 07/23] Fixing name. --- conda_anaconda_telemetry/__init__.py | 4 ++-- pyproject.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/conda_anaconda_telemetry/__init__.py b/conda_anaconda_telemetry/__init__.py index 02509fe..7b65086 100644 --- a/conda_anaconda_telemetry/__init__.py +++ b/conda_anaconda_telemetry/__init__.py @@ -1,6 +1,6 @@ # Copyright (C) 2024 Anaconda, Inc # SPDX-License-Identifier: BSD-3-Clause -"""Conda channel's ToS management plugin.""" +"""A conda plugin for Anaconda Telemetry.""" from __future__ import annotations @@ -24,7 +24,7 @@ # ImportError: setuptools_scm isn't installed # OSError: git isn't installed # LookupError: setuptools_scm unable to detect version - # anaconda-conda-tos follows SemVer, so the dev version is: + # conda-anaconda-telemetry follows SemVer, so the dev version is: # MJ.MN.MICRO.devN+gHASH[.dirty] __version__ = "0.0.0.dev0+placeholder" diff --git a/pyproject.toml b/pyproject.toml index 1764b5d..3a485b2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "conda-anaconda-telemetry" -description = "A conda plugin for Anaconda telemetry" +description = "A conda plugin for Anaconda Telemetry" readme = "README.md" license = {file = "LICENSE"} classifiers = [ From fe286e7b5dcc8981180e5ab219e2c31cfdab627a Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Fri, 15 Nov 2024 12:35:11 +0100 Subject: [PATCH 08/23] Require it on python level. --- pyproject.toml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 3a485b2..1d79448 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,9 @@ [build-system] -requires = ["hatchling", "hatch-vcs"] build-backend = "hatchling.build" +requires = [ + "hatchling >=1.12.2", + "hatch-vcs >=0.2.0", +] [project] name = "conda-anaconda-telemetry" From cdb2a4dcd760b09cb7cf2793c0e1b40510b1e1a4 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Fri, 15 Nov 2024 12:38:34 +0100 Subject: [PATCH 09/23] More config. --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 1d79448..ffb5444 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,6 +48,9 @@ sort = "Miss" # store relative paths in coverage information relative_files = true +[tool.hatch.build.targets.wheel] +packages = ["anaconda_conda_telemetry"] + [tool.hatch.build.hooks.vcs] version-file = "anaconda_conda_telemetry/_version.py" From 25a9d63a29ae3a4ccb474a6dc9621d3831b57b82 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Fri, 15 Nov 2024 12:44:25 +0100 Subject: [PATCH 10/23] Fix name, d'oh. --- conda_anaconda_telemetry/__init__.py | 2 +- develop.sh | 2 +- pyproject.toml | 9 +++------ 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/conda_anaconda_telemetry/__init__.py b/conda_anaconda_telemetry/__init__.py index 7b65086..8c97011 100644 --- a/conda_anaconda_telemetry/__init__.py +++ b/conda_anaconda_telemetry/__init__.py @@ -10,7 +10,7 @@ from typing import Final #: Application name. -APP_NAME: Final = "anaconda-conda-telemetry" +APP_NAME: Final = "conda-anaconda-telemetry" try: from ._version import __version__ diff --git a/develop.sh b/develop.sh index bb52982..5af3d86 100755 --- a/develop.sh +++ b/develop.sh @@ -8,7 +8,7 @@ fi CONDA_ENV_DIR="./env" -conda create -p "$CONDA_ENV_DIR" --file tests/requirements.txt --file tests/requirements-ci.txt --yes +conda create -p "$CONDA_ENV_DIR" --channel conda-canary/label/dev --file tests/requirements.txt --file tests/requirements-ci.txt --yes conda activate "$CONDA_ENV_DIR" pip install --no-deps --no-index --no-build-isolation -e . diff --git a/pyproject.toml b/pyproject.toml index ffb5444..4154ca0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,11 +48,8 @@ sort = "Miss" # store relative paths in coverage information relative_files = true -[tool.hatch.build.targets.wheel] -packages = ["anaconda_conda_telemetry"] - [tool.hatch.build.hooks.vcs] -version-file = "anaconda_conda_telemetry/_version.py" +version-file = "conda_anaconda_telemetry/_version.py" [tool.hatch.version] source = "vcs" @@ -63,7 +60,7 @@ local_scheme = "dirty-tag" [tool.pytest.ini_options] addopts = [ "--color=yes", - # "--cov=anaconda_conda_telemetry", # passed in test runner scripts instead (avoid debugger) + # "--cov=conda_anaconda_telemetry", # passed in test runner scripts instead (avoid debugger) "--cov-report=term", # print summary table to screen "--cov-report=xml", # for codecov/codecov-action upload "--tb=native", @@ -110,4 +107,4 @@ select = [ ] [tool.setuptools] -packages = ["anaconda_conda_telemetry"] +packages = ["conda_anaconda_telemetry"] From 0dadc354ac93ffe46fc52c7b37b6f48313815af0 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Fri, 15 Nov 2024 12:47:32 +0100 Subject: [PATCH 11/23] Ignore generated _version.py. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index cce9b0a..b88ac40 100644 --- a/.gitignore +++ b/.gitignore @@ -164,3 +164,4 @@ cython_debug/ # conda development environment ./env .channel/ +_version.py From d33335fb3f0f2da643e1c15bbac038c389c6057a Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Fri, 15 Nov 2024 12:47:38 +0100 Subject: [PATCH 12/23] Add pre-commit workflow. --- .github/workflows/pre_commit.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .github/workflows/pre_commit.yml diff --git a/.github/workflows/pre_commit.yml b/.github/workflows/pre_commit.yml new file mode 100644 index 0000000..031f487 --- /dev/null +++ b/.github/workflows/pre_commit.yml @@ -0,0 +1,26 @@ +name: Pre-Commit + +on: + push: + branches: + - main + pull_request: + branches: + - main + merge_group: + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: true + +jobs: + pre-commit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 + with: + python-version: '3.11' + cache: pip + - run: pip install pre-commit + - run: pre-commit run --all-files From 7ffea91b9b550923609469c635632a3bd85df523 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Fri, 15 Nov 2024 13:21:48 +0100 Subject: [PATCH 13/23] More typing and having ruff pass. --- conda_anaconda_telemetry/hooks.py | 91 +++++++++++++------------------ docker-compose.yaml | 14 +---- pyproject.toml | 16 +++--- tests/test_hooks.py | 66 +++++++++++++--------- 4 files changed, 90 insertions(+), 97 deletions(-) diff --git a/conda_anaconda_telemetry/hooks.py b/conda_anaconda_telemetry/hooks.py index 5bc520e..c25af40 100644 --- a/conda_anaconda_telemetry/hooks.py +++ b/conda_anaconda_telemetry/hooks.py @@ -1,3 +1,5 @@ +"""Conda plugin that adds telemetry headers to requests made by conda.""" + from __future__ import annotations import functools @@ -6,19 +8,20 @@ import typing from conda.base.context import context -from conda.common.configuration import PrimitiveParameter from conda.cli.main_list import list_packages +from conda.common.configuration import PrimitiveParameter from conda.common.url import mask_anaconda_token from conda.models.channel import all_channel_urls -from conda.plugins import hookimpl, CondaRequestHeader, CondaSetting +from conda.plugins import CondaRequestHeader, CondaSetting, hookimpl try: - from conda_build import __version__ as CONDA_BUILD_VERSION + from conda_build import __version__ as conda_build_version except ImportError: - CONDA_BUILD_VERSION = "n/a" + conda_build_version = "n/a" if typing.TYPE_CHECKING: from collections.abc import Iterator + from typing import Callable logger = logging.getLogger(__name__) @@ -53,16 +56,21 @@ REQUEST_HEADER_HOSTS = {"repo.anaconda.com", "conda.anaconda.org"} -def timer(func): +def timer(func: Callable) -> Callable: + """Log the duration of a function call.""" + @functools.wraps(func) - def wrapper_timer(*args, **kwargs): + def wrapper_timer(*args: tuple, **kwargs: dict) -> Callable: + """Wrap the given function.""" if logger.getEffectiveLevel() <= logging.INFO: tic = time.perf_counter() value = func(*args, **kwargs) toc = time.perf_counter() elapsed_time = toc - tic logger.info( - f"function: {func.__name__}; duration (seconds): {elapsed_time:0.4f}" + "function: %s; duration (seconds): %0.4f", + func.__name__, + elapsed_time=elapsed_time, ) return value @@ -72,9 +80,7 @@ def wrapper_timer(*args, **kwargs): def get_virtual_packages() -> tuple[str, ...]: - """ - Uses the ``conda.base.context.context`` object to retrieve registered virtual packages - """ + """Retrieve the registered virtual packages from conda's context.""" return tuple( f"{package.name}={package.version}={package.build}" for package in context.plugin_manager.get_virtual_package_records() @@ -82,24 +88,18 @@ def get_virtual_packages() -> tuple[str, ...]: def get_channel_urls() -> tuple[str, ...]: - """ - Returns a list of currently configured channel URLs with tokens masked - """ + """Return a list of currently configured channel URLs with tokens masked.""" channels = list(all_channel_urls(context.channels)) return tuple(mask_anaconda_token(c) for c in channels) def get_conda_command() -> str: - """ - Use ``sys.argv`` to determine the conda command that is current being run - """ + """Use ``sys.argv`` to determine the conda command that is current being run.""" return context._argparse_args.cmd def get_package_list() -> tuple[str, ...]: - """ - Retrieve the list of packages in the current environment - """ + """Retrieve the list of packages in the current environment.""" prefix = context.active_prefix or context.root_prefix _, packages = list_packages(prefix, format="canonical") @@ -107,27 +107,21 @@ def get_package_list() -> tuple[str, ...]: def get_search_term() -> str: - """ - Retrieve the search term being used when search command is run - """ + """Retrieve the search term being used when search command is run.""" return context._argparse_args.match_spec def get_install_arguments() -> tuple[str, ...]: - """ - Get the position argument which have specified via the ``install`` or ``create`` commands - """ + """Get the parsed position argument.""" return context._argparse_args.packages @timer @functools.lru_cache(None) def get_sys_info_header_value() -> str: - """ - Return ``;`` delimited string of extra system information - """ + """Return ``;`` delimited string of extra system information.""" telemetry_data = { - "conda_build_version": CONDA_BUILD_VERSION, + "conda_build_version": conda_build_version, "conda_command": get_conda_command(), } @@ -139,43 +133,33 @@ def get_sys_info_header_value() -> str: @timer @functools.lru_cache(None) def get_channel_urls_header_value() -> str: - """ - Return ``FIELD_SEPARATOR`` delimited string of channel URLs - """ + """Return ``FIELD_SEPARATOR`` delimited string of channel URLs.""" return FIELD_SEPARATOR.join(get_channel_urls()) @timer @functools.lru_cache(None) def get_virtual_packages_header_value() -> str: - """ - Return ``FIELD_SEPARATOR`` delimited string of virtual packages - """ + """Return ``FIELD_SEPARATOR`` delimited string of virtual packages.""" return FIELD_SEPARATOR.join(get_virtual_packages()) @timer @functools.lru_cache(None) def get_install_arguments_header_value() -> str: - """ - Return ``FIELD_SEPARATOR`` delimited string of channel URLs - """ + """Return ``FIELD_SEPARATOR`` delimited string of channel URLs.""" return FIELD_SEPARATOR.join(get_install_arguments()) @timer @functools.lru_cache(None) def get_installed_packages_header_value() -> str: - """ - Return ``FIELD_SEPARATOR`` delimited string of install arguments - """ + """Return ``FIELD_SEPARATOR`` delimited string of install arguments.""" return FIELD_SEPARATOR.join(get_package_list()) class HeaderWrapper(typing.NamedTuple): - """ - Object that wraps ``CondaRequestHeader`` and adds a ``size_limit`` field - """ + """Object that wraps ``CondaRequestHeader`` and adds a ``size_limit`` field.""" header: CondaRequestHeader size_limit: int @@ -184,22 +168,23 @@ class HeaderWrapper(typing.NamedTuple): def validate_headers( custom_headers: list[HeaderWrapper], ) -> Iterator[CondaRequestHeader]: - """ - Makes sure that all headers combined are not larger than ``SIZE_LIMIT``. + """Make sure that all headers combined are not larger than ``SIZE_LIMIT``. Any headers over their individual limits will be truncated. """ total_max_size = sum(header.size_limit for header in custom_headers) - assert ( - total_max_size <= SIZE_LIMIT - ), f"Total header size limited to {SIZE_LIMIT}. Exceeded with {total_max_size=}" + if total_max_size <= SIZE_LIMIT: + raise ValueError( + f"Total header size limited to {SIZE_LIMIT}. " + f"Exceeded with {total_max_size=}" + ) for wrapper in custom_headers: wrapper.header.value = wrapper.header.value[: wrapper.size_limit] yield wrapper.header -def _conda_request_headers(): +def _conda_request_headers() -> Iterator[HeaderWrapper] | None: if not context.plugins.anaconda_telemetry: return @@ -262,7 +247,8 @@ def _conda_request_headers(): @hookimpl -def conda_session_headers(host: str): +def conda_session_headers(host: str) -> Iterator[CondaRequestHeader]: + """Return a list of custom headers to be included in the request.""" try: if host in REQUEST_HEADER_HOSTS: yield from _conda_request_headers() @@ -271,7 +257,8 @@ def conda_session_headers(host: str): @hookimpl -def conda_settings(): +def conda_settings() -> Iterator[CondaSetting]: + """Return a list of settings that can be configured by the user.""" yield CondaSetting( name="anaconda_telemetry", description="Whether Anaconda Telemetry is enabled", diff --git a/docker-compose.yaml b/docker-compose.yaml index 2d19d68..1c91bd8 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -19,11 +19,7 @@ services: soft: -1 hard: -1 healthcheck: - test: - [ - "CMD-SHELL", - "curl --output /dev/null --silent --head --fail -u elastic:${ES_LOCAL_PASSWORD} http://elasticsearch:${ES_LOCAL_PORT}", - ] + test: [CMD-SHELL, 'curl --output /dev/null --silent --head --fail -u elastic:${ES_LOCAL_PASSWORD} http://elasticsearch:${ES_LOCAL_PORT}'] interval: 5s timeout: 5s retries: 10 @@ -34,7 +30,7 @@ services: condition: service_healthy image: docker.elastic.co/elasticsearch/elasticsearch:${ES_LOCAL_VERSION} container_name: ${KIBANA_SETTINGS_LOCAL_CONTAINER_NAME} - restart: 'no' + restart: no command: > bash -c ' echo "Setup the kibana_system password"; @@ -59,11 +55,7 @@ services: - XPACK_ENCRYPTEDSAVEDOBJECTS_ENCRYPTIONKEY=${KIBANA_ENCRYPTION_KEY} - ELASTICSEARCH_PUBLICBASEURL=http://localhost:${ES_LOCAL_PORT} healthcheck: - test: - [ - "CMD-SHELL", - "curl -s -I http://kibana:5601 | grep -q 'HTTP/1.1 302 Found'", - ] + test: [CMD-SHELL, curl -s -I http://kibana:5601 | grep -q 'HTTP/1.1 302 Found'] interval: 10s timeout: 10s retries: 20 diff --git a/pyproject.toml b/pyproject.toml index 4154ca0..d6c58ce 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,10 +6,6 @@ requires = [ ] [project] -name = "conda-anaconda-telemetry" -description = "A conda plugin for Anaconda Telemetry" -readme = "README.md" -license = {file = "LICENSE"} classifiers = [ "Operating System :: OS Independent", "Programming Language :: Python :: 3", @@ -19,15 +15,19 @@ classifiers = [ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: Implementation :: CPython", - "Programming Language :: Python :: Implementation :: PyPy" + "Programming Language :: Python :: Implementation :: PyPy", ] -requires-python = ">=3.9" dependencies = [ "conda >=24.9", ] +description = "A conda plugin for Anaconda Telemetry" dynamic = [ - "version" + "version", ] +license = {file = "LICENSE"} +name = "conda-anaconda-telemetry" +readme = "README.md" +requires-python = ">=3.9" [project.entry-points.conda] conda-anaconda-telemetry = "conda_anaconda_telemetry.hooks" @@ -73,7 +73,7 @@ target-version = "py39" [tool.ruff.lint] extend-per-file-ignores = {"tests/*" = ["D", "S101"]} -ignore = ["D203", "D213"] +ignore = ["D203", "D213", "ISC001"] # see https://docs.astral.sh/ruff/rules/ select = [ "A", # flake8-builtins diff --git a/tests/test_hooks.py b/tests/test_hooks.py index 31fc1e8..95fd545 100644 --- a/tests/test_hooks.py +++ b/tests/test_hooks.py @@ -1,43 +1,51 @@ +from __future__ import annotations + import logging +from typing import TYPE_CHECKING import pytest from conda_anaconda_telemetry.hooks import ( - conda_session_headers, - conda_settings, - HEADER_INSTALL, HEADER_CHANNELS, - HEADER_SYS_INFO, - HEADER_VIRTUAL_PACKAGES, + HEADER_INSTALL, HEADER_PACKAGES, HEADER_SEARCH, + HEADER_SYS_INFO, + HEADER_VIRTUAL_PACKAGES, + conda_session_headers, + conda_settings, timer, ) +if TYPE_CHECKING: + from pytest import CaptureFixture, MonkeyPatch + from pytest_mock import MockerFixture + + #: Host used across all tests TEST_HOST = "repo.anaconda.com" +TEST_PACKAGES = [ + "defaults/osx-arm64::sqlite-3.45.3-h80987f9_0", + "defaults/osx-arm64::pcre2-10.42-hb066dcc_1", + "defaults/osx-arm64::libxml2-2.13.1-h0b34f26_2", +] + + +def mock_list_packages(*args: tuple, **kwargs: dict) -> tuple: # noqa: ARG001 + return 0, TEST_PACKAGES + @pytest.fixture(autouse=True) -def packages(mocker): +def packages(mocker: MockerFixture) -> list: """ Mocks ``conda_anaconda_telemetry.hooks.list_packages`` """ - packages = [ - "defaults/osx-arm64::sqlite-3.45.3-h80987f9_0", - "defaults/osx-arm64::pcre2-10.42-hb066dcc_1", - "defaults/osx-arm64::libxml2-2.13.1-h0b34f26_2", - ] - - def mock_list_packages(*args, **kwargs): - return 0, packages - mocker.patch("conda_anaconda_telemetry.hooks.list_packages", mock_list_packages) - - return packages + return TEST_PACKAGES -def test_conda_request_header_default_headers(mocker): +def test_conda_request_header_default_headers(mocker: MockerFixture) -> None: """ Ensure default headers are returned """ @@ -62,7 +70,9 @@ def test_conda_request_header_default_headers(mocker): ) -def test_conda_request_header_with_search(monkeypatch, mocker): +def test_conda_request_header_with_search( + monkeypatch: MonkeyPatch, mocker: MockerFixture +) -> None: """ Ensure default headers are returned when conda search is invoked """ @@ -86,7 +96,9 @@ def test_conda_request_header_with_search(monkeypatch, mocker): ) -def test_conda_request_header_with_install(monkeypatch, mocker): +def test_conda_request_header_with_install( + monkeypatch: MonkeyPatch, mocker: MockerFixture +) -> None: """ Ensure default headers are returned when conda search is invoked """ @@ -110,7 +122,7 @@ def test_conda_request_header_with_install(monkeypatch, mocker): ) -def test_conda_request_header_when_disabled(monkeypatch, mocker): +def test_conda_request_header_when_disabled(mocker: MockerFixture) -> None: """ Make sure that nothing is returned when the plugin is disabled via settings """ @@ -120,14 +132,14 @@ def test_conda_request_header_when_disabled(monkeypatch, mocker): assert not tuple(conda_session_headers(TEST_HOST)) -def test_timer_in_info_mode(caplog): +def test_timer_in_info_mode(caplog: CaptureFixture) -> None: """ Ensure the timer decorator works and logs the time taken in INFO mode """ caplog.set_level(logging.INFO) @timer - def test(): + def test() -> int: return 1 assert test() == 1 @@ -138,7 +150,7 @@ def test(): assert "function: test; duration (seconds):" in caplog.text -def test_conda_settings(): +def test_conda_settings() -> None: """ Ensure the correct conda settings are returned """ @@ -150,7 +162,9 @@ def test_conda_settings(): assert settings[0].parameter.default.value is True -def test_conda_session_headers_with_exception(mocker, caplog): +def test_conda_session_headers_with_exception( + mocker: MockerFixture, caplog: CaptureFixture +) -> None: """ When any exception is encountered, ``conda_session_headers`` should return nothing and log a debug message. @@ -167,7 +181,7 @@ def test_conda_session_headers_with_exception(mocker, caplog): assert "Exception: Boom" in caplog.text -def test_conda_session_headers_with_non_matching_url(mocker, caplog): +def test_conda_session_headers_with_non_matching_url() -> None: """ When any exception is encountered, ``conda_session_headers`` should return nothing and log a debug message. From ba99a7373430cb305b60cfc9b0b1079451e95350 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Fri, 15 Nov 2024 13:24:57 +0100 Subject: [PATCH 14/23] Add missing files. --- .github/TEST_FAILURE_REPORT_TEMPLATE.md | 10 ++++++++++ .github/disclaimer.txt | 2 ++ 2 files changed, 12 insertions(+) create mode 100644 .github/TEST_FAILURE_REPORT_TEMPLATE.md create mode 100644 .github/disclaimer.txt diff --git a/.github/TEST_FAILURE_REPORT_TEMPLATE.md b/.github/TEST_FAILURE_REPORT_TEMPLATE.md new file mode 100644 index 0000000..95b4851 --- /dev/null +++ b/.github/TEST_FAILURE_REPORT_TEMPLATE.md @@ -0,0 +1,10 @@ +--- +title: '{{ env.TITLE }} ({{ date | date("YYYY-MM-DD") }})' +labels: ['type::bug', 'type::testing', 'source::auto'] +--- + +The {{ workflow }} workflow failed on {{ date | date("YYYY-MM-DD HH:mm") }} UTC + +Full run: https://github.com/anaconda/anaconda-conda-tos/actions/runs/{{ env.RUN_ID }} + +(This post will be updated if another test fails today, as long as this issue remains open.) diff --git a/.github/disclaimer.txt b/.github/disclaimer.txt new file mode 100644 index 0000000..fd50654 --- /dev/null +++ b/.github/disclaimer.txt @@ -0,0 +1,2 @@ +Copyright (C) 2024 Anaconda, Inc +SPDX-License-Identifier: BSD-3-Clause From 292e69c81be2dafdf10a09e141a1b542f490da4c Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Fri, 15 Nov 2024 13:25:04 +0100 Subject: [PATCH 15/23] Fix minor issue. --- tests/test_hooks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_hooks.py b/tests/test_hooks.py index 95fd545..61f1a74 100644 --- a/tests/test_hooks.py +++ b/tests/test_hooks.py @@ -72,7 +72,7 @@ def test_conda_request_header_default_headers(mocker: MockerFixture) -> None: def test_conda_request_header_with_search( monkeypatch: MonkeyPatch, mocker: MockerFixture -) -> None: +) -> None: """ Ensure default headers are returned when conda search is invoked """ From 468dce9078eae5ada9af6867050cb1b26242f2b0 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Fri, 15 Nov 2024 13:26:44 +0100 Subject: [PATCH 16/23] Fix logging call. --- conda_anaconda_telemetry/hooks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conda_anaconda_telemetry/hooks.py b/conda_anaconda_telemetry/hooks.py index c25af40..2c297ff 100644 --- a/conda_anaconda_telemetry/hooks.py +++ b/conda_anaconda_telemetry/hooks.py @@ -70,7 +70,7 @@ def wrapper_timer(*args: tuple, **kwargs: dict) -> Callable: logger.info( "function: %s; duration (seconds): %0.4f", func.__name__, - elapsed_time=elapsed_time, + elapsed_time, ) return value From 93fe6e6985fc2369534c7b1b11262352cef416a3 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Fri, 15 Nov 2024 13:30:08 +0100 Subject: [PATCH 17/23] Fixes. --- conda_anaconda_telemetry/hooks.py | 8 ++++++-- tests/test_hooks.py | 2 ++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/conda_anaconda_telemetry/hooks.py b/conda_anaconda_telemetry/hooks.py index 2c297ff..8210545 100644 --- a/conda_anaconda_telemetry/hooks.py +++ b/conda_anaconda_telemetry/hooks.py @@ -1,3 +1,5 @@ +# Copyright (C) 2024 Anaconda, Inc +# SPDX-License-Identifier: BSD-3-Clause """Conda plugin that adds telemetry headers to requests made by conda.""" from __future__ import annotations @@ -186,7 +188,7 @@ def validate_headers( def _conda_request_headers() -> Iterator[HeaderWrapper] | None: if not context.plugins.anaconda_telemetry: - return + return None custom_headers = [ HeaderWrapper( @@ -251,7 +253,9 @@ def conda_session_headers(host: str) -> Iterator[CondaRequestHeader]: """Return a list of custom headers to be included in the request.""" try: if host in REQUEST_HEADER_HOSTS: - yield from _conda_request_headers() + iterator = _conda_request_headers() + if iterator is not None: + yield from iterator except Exception as exc: logger.debug("Failed to collect telemetry data", exc_info=exc) diff --git a/tests/test_hooks.py b/tests/test_hooks.py index 61f1a74..71632e4 100644 --- a/tests/test_hooks.py +++ b/tests/test_hooks.py @@ -1,3 +1,5 @@ +# Copyright (C) 2024 Anaconda, Inc +# SPDX-License-Identifier: BSD-3-Clause from __future__ import annotations import logging From e170c505b884a3990590440b9499b007ab425740 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Fri, 15 Nov 2024 13:37:35 +0100 Subject: [PATCH 18/23] Ignore the docker-compose file. --- .pre-commit-config.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 712a9dd..0b7307d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,7 +12,8 @@ repos: - id: check-yaml exclude: | (?x)^( - recipe/meta.yaml + recipe/meta.yaml | + docker-compose.yaml ) - id: check-json # catch git merge/rebase problems From 9019ddb7ee17d3bb8a39dc5a142249bdf22f32e2 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Fri, 15 Nov 2024 13:43:18 +0100 Subject: [PATCH 19/23] Move the condition into the plugin hook implementation. --- conda_anaconda_telemetry/hooks.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/conda_anaconda_telemetry/hooks.py b/conda_anaconda_telemetry/hooks.py index 8210545..6c0632a 100644 --- a/conda_anaconda_telemetry/hooks.py +++ b/conda_anaconda_telemetry/hooks.py @@ -186,10 +186,7 @@ def validate_headers( yield wrapper.header -def _conda_request_headers() -> Iterator[HeaderWrapper] | None: - if not context.plugins.anaconda_telemetry: - return None - +def _conda_request_headers() -> Iterator[HeaderWrapper]: custom_headers = [ HeaderWrapper( header=CondaRequestHeader( @@ -251,13 +248,12 @@ def _conda_request_headers() -> Iterator[HeaderWrapper] | None: @hookimpl def conda_session_headers(host: str) -> Iterator[CondaRequestHeader]: """Return a list of custom headers to be included in the request.""" - try: - if host in REQUEST_HEADER_HOSTS: - iterator = _conda_request_headers() - if iterator is not None: - yield from iterator - except Exception as exc: - logger.debug("Failed to collect telemetry data", exc_info=exc) + if context.plugins.anaconda_telemetry: + try: + if host in REQUEST_HEADER_HOSTS: + yield from _conda_request_headers() + except Exception as exc: + logger.debug("Failed to collect telemetry data", exc_info=exc) @hookimpl From aac1b4cfe8440b8aefc2cbd09a03fb4d2a6a4d28 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Fri, 15 Nov 2024 14:38:41 +0100 Subject: [PATCH 20/23] Fix name. --- .github/TEST_FAILURE_REPORT_TEMPLATE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/TEST_FAILURE_REPORT_TEMPLATE.md b/.github/TEST_FAILURE_REPORT_TEMPLATE.md index 95b4851..d232cba 100644 --- a/.github/TEST_FAILURE_REPORT_TEMPLATE.md +++ b/.github/TEST_FAILURE_REPORT_TEMPLATE.md @@ -5,6 +5,6 @@ labels: ['type::bug', 'type::testing', 'source::auto'] The {{ workflow }} workflow failed on {{ date | date("YYYY-MM-DD HH:mm") }} UTC -Full run: https://github.com/anaconda/anaconda-conda-tos/actions/runs/{{ env.RUN_ID }} +Full run: https://github.com/anaconda/conda-anaconda-telemetry/actions/runs/{{ env.RUN_ID }} (This post will be updated if another test fails today, as long as this issue remains open.) From 20d220f77bcc56e14b67aeccd6b56f886d1c5989 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Fri, 15 Nov 2024 15:02:22 +0100 Subject: [PATCH 21/23] Fix develop.sh. --- develop.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/develop.sh b/develop.sh index 5af3d86..70df2f3 100755 --- a/develop.sh +++ b/develop.sh @@ -1,3 +1,5 @@ +#!/bin/bash + # Used to switch CONDA_EXE to the one located in development environment # To use this script, run `source develop.sh` @@ -10,6 +12,6 @@ CONDA_ENV_DIR="./env" conda create -p "$CONDA_ENV_DIR" --channel conda-canary/label/dev --file tests/requirements.txt --file tests/requirements-ci.txt --yes conda activate "$CONDA_ENV_DIR" -pip install --no-deps --no-index --no-build-isolation -e . +pip install -e . CONDA_EXE="$CONDA_PREFIX/condabin/conda" From 0754da8c725ab998376823180693eb7243043bff Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Fri, 15 Nov 2024 15:03:15 +0100 Subject: [PATCH 22/23] Fix ValueError. --- conda_anaconda_telemetry/hooks.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/conda_anaconda_telemetry/hooks.py b/conda_anaconda_telemetry/hooks.py index 6c0632a..567c8ab 100644 --- a/conda_anaconda_telemetry/hooks.py +++ b/conda_anaconda_telemetry/hooks.py @@ -175,7 +175,7 @@ def validate_headers( Any headers over their individual limits will be truncated. """ total_max_size = sum(header.size_limit for header in custom_headers) - if total_max_size <= SIZE_LIMIT: + if total_max_size > SIZE_LIMIT: raise ValueError( f"Total header size limited to {SIZE_LIMIT}. " f"Exceeded with {total_max_size=}" @@ -231,7 +231,7 @@ def _conda_request_headers() -> Iterator[HeaderWrapper]: ) ) - if command in {"install", "create"}: + elif command in {"install", "create"}: custom_headers.append( HeaderWrapper( header=CondaRequestHeader( From e30ab1ef18a1e066a792705b1b7737f1e32594e7 Mon Sep 17 00:00:00 2001 From: Travis Hathaway Date: Fri, 15 Nov 2024 16:06:44 +0100 Subject: [PATCH 23/23] adding new tests and removing an unnecessary raise exception statement --- conda_anaconda_telemetry/hooks.py | 19 ++++++----------- tests/test_hooks.py | 34 +++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 13 deletions(-) diff --git a/conda_anaconda_telemetry/hooks.py b/conda_anaconda_telemetry/hooks.py index 567c8ab..74fc6a7 100644 --- a/conda_anaconda_telemetry/hooks.py +++ b/conda_anaconda_telemetry/hooks.py @@ -22,7 +22,7 @@ conda_build_version = "n/a" if typing.TYPE_CHECKING: - from collections.abc import Iterator + from collections.abc import Iterator, Sequence from typing import Callable logger = logging.getLogger(__name__) @@ -168,25 +168,18 @@ class HeaderWrapper(typing.NamedTuple): def validate_headers( - custom_headers: list[HeaderWrapper], + header_wrappers: Sequence[HeaderWrapper], ) -> Iterator[CondaRequestHeader]: """Make sure that all headers combined are not larger than ``SIZE_LIMIT``. Any headers over their individual limits will be truncated. """ - total_max_size = sum(header.size_limit for header in custom_headers) - if total_max_size > SIZE_LIMIT: - raise ValueError( - f"Total header size limited to {SIZE_LIMIT}. " - f"Exceeded with {total_max_size=}" - ) - - for wrapper in custom_headers: + for wrapper in header_wrappers: wrapper.header.value = wrapper.header.value[: wrapper.size_limit] yield wrapper.header -def _conda_request_headers() -> Iterator[HeaderWrapper]: +def _conda_request_headers() -> Sequence[HeaderWrapper]: custom_headers = [ HeaderWrapper( header=CondaRequestHeader( @@ -242,7 +235,7 @@ def _conda_request_headers() -> Iterator[HeaderWrapper]: ) ) - yield from validate_headers(custom_headers) + return custom_headers @hookimpl @@ -251,7 +244,7 @@ def conda_session_headers(host: str) -> Iterator[CondaRequestHeader]: if context.plugins.anaconda_telemetry: try: if host in REQUEST_HEADER_HOSTS: - yield from _conda_request_headers() + yield from validate_headers(_conda_request_headers()) except Exception as exc: logger.debug("Failed to collect telemetry data", exc_info=exc) diff --git a/tests/test_hooks.py b/tests/test_hooks.py index 71632e4..5f36570 100644 --- a/tests/test_hooks.py +++ b/tests/test_hooks.py @@ -4,6 +4,7 @@ import logging from typing import TYPE_CHECKING +from unittest.mock import MagicMock import pytest @@ -14,6 +15,8 @@ HEADER_SEARCH, HEADER_SYS_INFO, HEADER_VIRTUAL_PACKAGES, + SIZE_LIMIT, + _conda_request_headers, conda_session_headers, conda_settings, timer, @@ -189,3 +192,34 @@ def test_conda_session_headers_with_non_matching_url() -> None: and log a debug message. """ assert list(conda_session_headers("https://example.com")) == [] + + +@pytest.mark.parametrize( + "command,argparse_mock", + ( + ( + ["conda", "install", "package"], + MagicMock(packages=["package"], cmd="install"), + ), + ( + ["conda", "search", "package"], + MagicMock(match_spec=["package"], cmd="search"), + ), + (["conda", "update", "package"], MagicMock(packages=["package"], cmd="update")), + ), +) +def test_header_wrapper_size_limit_constraint( + monkeypatch: MonkeyPatch, + mocker: MockerFixture, + command: list[str], + argparse_mock: MagicMock, +) -> None: + """ + Ensures that the size limit is being adhered to when all ``HeaderWrapper`` + objects are combined + """ + monkeypatch.setattr("sys.argv", command) + mocker.patch("conda_anaconda_telemetry.hooks.context._argparse_args", argparse_mock) + + headers = _conda_request_headers() + assert sum(header.size_limit for header in headers) <= SIZE_LIMIT