From 3e94188767e6eaad140b349f9329434c3bfc2edd Mon Sep 17 00:00:00 2001 From: Eli Fine Date: Mon, 15 Dec 2025 13:26:27 +0000 Subject: [PATCH 01/19] copier --- .copier-answers.yml | 4 +- .devcontainer/devcontainer.json | 9 +-- .devcontainer/install-ci-tooling.py | 4 +- .devcontainer/windows-host-helper.sh | 44 ++++++------- .github/actions/install_deps/action.yml | 6 +- .../update-devcontainer-hash/action.yml | 2 +- .../build-docker-image.yaml | 4 +- .github/workflows/ci.yaml | 22 +++++-- .github/workflows/get-values.yaml | 5 +- .github/workflows/hash_git_files.py | 11 ++-- .github/workflows/pre-commit.yaml | 15 +++-- .github/workflows/tag-on-merge.yaml | 2 +- .gitignore | 3 +- .pre-commit-config.yaml | 20 +++--- copier.yml | 9 +++ .../python_asyncio/asyncio_fixtures.py | 36 +++++++++++ .../python_asyncio/background_tasks.py | 41 ++++++++++++ copier_template_resources/vcrpy_fixtures.py | 64 +++++++++++++++++++ extensions/context.py | 56 ++++++++-------- pyproject.toml | 2 +- pyrightconfig.json | 5 +- .../.devcontainer/devcontainer.json.jinja | 11 ++-- template/.devcontainer/windows-host-helper.sh | 44 ++++++------- .../.github/actions/install_deps/action.yml | 6 +- .../update-devcontainer-hash/action.yml | 2 +- template/.github/workflows/get-values.yaml | 5 +- template/.github/workflows/hash_git_files.py | 11 ++-- template/.github/workflows/pre-commit.yaml | 15 +++-- template/.gitignore | 3 +- template/.pre-commit-config.yaml | 20 +++--- template/pyrightconfig.json | 5 +- tests/copier_data/data1.yaml | 2 + tests/copier_data/data2.yaml | 2 + tests/copier_data/data3.yaml | 2 + uv.lock | 10 +-- 35 files changed, 334 insertions(+), 168 deletions(-) create mode 100644 copier_template_resources/python_asyncio/asyncio_fixtures.py create mode 100644 copier_template_resources/python_asyncio/background_tasks.py create mode 100644 copier_template_resources/vcrpy_fixtures.py diff --git a/.copier-answers.yml b/.copier-answers.yml index ba57bf3d..956911e5 100644 --- a/.copier-answers.yml +++ b/.copier-answers.yml @@ -1,5 +1,5 @@ # Changes here will be overwritten by Copier -_commit: v0.0.85 +_commit: v0.0.89 _src_path: gh:LabAutomationAndScreening/copier-base-template.git description: Copier template for creating Python libraries and executables install_claude_cli: false @@ -12,6 +12,8 @@ repo_org_name: LabAutomationAndScreening repo_org_name_for_copyright: Lab Automation & Screening ssh_port_number: 55874 template_might_want_to_install_aws_ssm_port_forwarding_plugin: true +template_might_want_to_use_python_asyncio: true +template_might_want_to_use_vcrpy: true template_uses_javascript: false template_uses_pulumi: false template_uses_python: true diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 0438d274..25e01643 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -3,11 +3,6 @@ "service": "devcontainer", "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}", "features": { - "ghcr.io/devcontainers/features/aws-cli:1.1.2": { - // https://github.com/devcontainers/features/blob/main/src/aws-cli/devcontainer-feature.json - // view latest version https://raw.githubusercontent.com/aws/aws-cli/v2/CHANGELOG.rst - "version": "2.31.11" - }, "ghcr.io/devcontainers/features/python:1.7.1": { // https://github.com/devcontainers/features/blob/main/src/python/devcontainer-feature.json "version": "3.12.7", @@ -21,7 +16,7 @@ "extensions": [ // basic tooling // "eamodio.gitlens@15.5.1", - "coderabbit.coderabbit-vscode@0.16.0", + "coderabbit.coderabbit-vscode@0.16.1", "ms-vscode.live-server@0.5.2025051301", "MS-vsliveshare.vsliveshare@1.0.5905", "github.copilot@1.388.0", @@ -63,5 +58,5 @@ "initializeCommand": "sh .devcontainer/initialize-command.sh", "onCreateCommand": "sh .devcontainer/on-create-command.sh", "postStartCommand": "sh .devcontainer/post-start-command.sh" - // Devcontainer context hash (do not manually edit this, it's managed by a pre-commit hook): eeccb984 # spellchecker:disable-line + // Devcontainer context hash (do not manually edit this, it's managed by a pre-commit hook): 99b3f7c4 # spellchecker:disable-line } diff --git a/.devcontainer/install-ci-tooling.py b/.devcontainer/install-ci-tooling.py index 006ffe97..0de75239 100644 --- a/.devcontainer/install-ci-tooling.py +++ b/.devcontainer/install-ci-tooling.py @@ -7,8 +7,8 @@ import tempfile from pathlib import Path -UV_VERSION = "0.9.11" -PNPM_VERSION = "10.23.0" +UV_VERSION = "0.9.17" +PNPM_VERSION = "10.25.0" COPIER_VERSION = "9.11.0" COPIER_TEMPLATE_EXTENSIONS_VERSION = "0.3.3" PRE_COMMIT_VERSION = "4.5.0" diff --git a/.devcontainer/windows-host-helper.sh b/.devcontainer/windows-host-helper.sh index b3d1d876..a071d4f7 100644 --- a/.devcontainer/windows-host-helper.sh +++ b/.devcontainer/windows-host-helper.sh @@ -27,38 +27,30 @@ repoName=$(basename "$gitUrl" .git) echo "Repo name extracted as '$repoName'" -# Remove any existing subfolder with the repository name and recreate it -rm -rf "./$repoName" || true # sometimes deleting the .venv folder fails -rm -rf "./$repoName/*.md" # for some reason, sometimes md files are left behind +sudo rm -rf "./$repoName" || true +sudo rm -rf "./$repoName/*.md" mkdir -p "./$repoName" +sudo chown -R "$(whoami):$(whoami)" "./$repoName" # TODO: see if this alone is enough to fix everything # Create a temporary directory for cloning tmpdir=$(mktemp -d) -# Clone the repository into a subfolder inside the temporary directory. -# This creates "$tmpdir/$repoName" with the repository's contents. +# Clone the repository into a subfolder inside the temporary directory git clone "$gitUrl" "$tmpdir/$repoName" - -SRC="$(realpath "$tmpdir/$repoName")" -DST="$(realpath "./$repoName")" - -# 1) Recreate directory tree under $DST -while IFS= read -r -d '' dir; do - rel="${dir#$SRC/}" # strip leading $SRC/ → e.g. "sub/dir" - mkdir -p "$DST/$rel" -done < <(find "$SRC" -type d -print0) - -# 2) Move all files into that mirror -while IFS= read -r -d '' file; do - rel="${file#$SRC/}" # e.g. "sub/dir/file.txt" - # ensure parent exists (though step 1 already did) - mkdir -p "$(dirname "$DST/$rel")" - mv "$file" "$DST/$rel" -done < <(find "$SRC" -type f -print0) - -# 3) Clean up now‑empty dirs and the tmp clone -find "$SRC" -depth -type d -empty -delete +# Use rsync to merge all contents (including hidden files) from cloned repo to target +# -a: archive mode (preserves permissions, timestamps, etc.) +# -v: verbose +# --exclude: skip volume mount directories that should not be overwritten +echo "Syncing repository contents..." +rsync -av \ + --exclude='node_modules' \ + --exclude='.pnpm-store' \ + --exclude='.venv' \ + "$tmpdir/$repoName/" "./$repoName/" + +# Clean up: remove the temporary directory rm -rf "$tmpdir" -echo "Repository '$repoName' has been synced into '$DST'." +echo "Repository '$repoName' has been updated." +echo "Note: Volume mounts (node_modules, .pnpm-store, .venv) were preserved." diff --git a/.github/actions/install_deps/action.yml b/.github/actions/install_deps/action.yml index 0e9f0206..48796359 100644 --- a/.github/actions/install_deps/action.yml +++ b/.github/actions/install_deps/action.yml @@ -58,13 +58,13 @@ runs: - name: Setup python if: ${{ inputs.python-version != 'notUsing' }} - uses: actions/setup-python@v6.0.0 + uses: actions/setup-python@v6.1.0 with: python-version: ${{ env.PYTHON_VERSION }} - name: Setup node if: ${{ inputs.node-version != 'notUsing' }} - uses: actions/setup-node@v6.0.0 + uses: actions/setup-node@v6.1.0 with: node-version: ${{ inputs.node-version }} @@ -75,7 +75,7 @@ runs: - name: OIDC Auth for CodeArtifact if: ${{ inputs.code-artifact-auth-role-name != 'no-code-artifact' }} - uses: aws-actions/configure-aws-credentials@v5.1.0 + uses: aws-actions/configure-aws-credentials@v5.1.1 with: role-to-assume: arn:aws:iam::${{ inputs.code-artifact-auth-role-account-id }}:role/${{ inputs.code-artifact-auth-role-name }} aws-region: ${{ inputs.code-artifact-auth-region }} diff --git a/.github/actions/update-devcontainer-hash/action.yml b/.github/actions/update-devcontainer-hash/action.yml index fb64cc81..4e6d434c 100644 --- a/.github/actions/update-devcontainer-hash/action.yml +++ b/.github/actions/update-devcontainer-hash/action.yml @@ -27,7 +27,7 @@ runs: shell: bash - name: Checkout code - uses: actions/checkout@v5.0.0 + uses: actions/checkout@v6.0.1 with: persist-credentials: true fetch-depth: 1 diff --git a/.github/reusable_workflows/build-docker-image.yaml b/.github/reusable_workflows/build-docker-image.yaml index 50fafeb5..f4f4a7a0 100644 --- a/.github/reusable_workflows/build-docker-image.yaml +++ b/.github/reusable_workflows/build-docker-image.yaml @@ -66,13 +66,13 @@ jobs: shell: bash - name: Checkout code - uses: actions/checkout@v5.0.0 + uses: actions/checkout@v6.0.1 with: persist-credentials: false - name: OIDC Auth for ECR if: ${{ inputs.push-role-name != 'no-push' }} - uses: aws-actions/configure-aws-credentials@v5.1.0 + uses: aws-actions/configure-aws-credentials@v5.1.1 with: role-to-assume: arn:aws:iam::${{ steps.parse_ecr_url.outputs.aws_account_id }}:role/${{ inputs.push-role-name }} aws-region: ${{ steps.parse_ecr_url.outputs.aws_region }} diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 59811b57..d49e7666 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -20,7 +20,8 @@ jobs: contents: write # needed for updating dependabot branches pre-commit: - needs: [ get-values ] + needs: + - get-values uses: ./.github/workflows/pre-commit.yaml permissions: contents: write # needed for mutex @@ -29,7 +30,8 @@ jobs: python-version: 3.12.7 lint-matrix: - needs: [ pre-commit ] + needs: + - pre-commit strategy: matrix: os: @@ -54,7 +56,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v5.0.0 + uses: actions/checkout@v6.0.1 with: persist-credentials: false @@ -111,7 +113,7 @@ jobs: timeout-minutes: 8 # this is the amount of time this action will wait to attempt to acquire the mutex lock before failing, e.g. if other jobs are queued up in front of it - name: Cache Pre-commit hooks - uses: actions/cache@v4.2.4 + uses: actions/cache@v4.3.0 env: cache-name: cache-pre-commit-hooks with: @@ -123,7 +125,13 @@ jobs: - name: Run pre-commit run: | # skip devcontainer context hash because the template instantiation may make it different every time - SKIP=git-dirty,compute-devcontainer-context-hash pre-commit run -a + SKIP=git-dirty,compute-devcontainer-context-hash pre-commit run -a || PRE_COMMIT_EXIT_CODE=$? + if [ -n "$PRE_COMMIT_EXIT_CODE" ]; then + echo "Pre-commit failed with exit code $PRE_COMMIT_EXIT_CODE" + echo "Showing git diff:" + git --no-pager diff + exit $PRE_COMMIT_EXIT_CODE + fi - name: Upload pre-commit log if failure if: ${{ failure() }} @@ -135,7 +143,9 @@ jobs: required-check: runs-on: ubuntu-24.04 timeout-minutes: 2 - needs: [ lint-matrix, get-values ] + needs: + - lint-matrix + - get-values permissions: statuses: write # needed for updating status on Dependabot PRs if: always() diff --git a/.github/workflows/get-values.yaml b/.github/workflows/get-values.yaml index c7126d34..0be45c49 100644 --- a/.github/workflows/get-values.yaml +++ b/.github/workflows/get-values.yaml @@ -9,6 +9,9 @@ on: dependabot-commit-created: description: whether or not a commit was created on a dependabot branch value: ${{ jobs.get-values.outputs.dependabot-commit-created }} + pr-short-num: + description: the last two digits of the PR number (to be used for fixed width naming, like Pulumi stacks) + value: ${{ jobs.get-values.outputs.pr-short-num }} env: PYTHONUNBUFFERED: True @@ -32,7 +35,7 @@ jobs: JSON - name: Checkout code - uses: actions/checkout@v5.0.0 + uses: actions/checkout@v6.0.1 with: persist-credentials: false diff --git a/.github/workflows/hash_git_files.py b/.github/workflows/hash_git_files.py index 1da0a3f7..40576d44 100644 --- a/.github/workflows/hash_git_files.py +++ b/.github/workflows/hash_git_files.py @@ -65,19 +65,16 @@ def compute_adler32(repo_path: Path, files: list[str]) -> int: if not chunk: break checksum = zlib.adler32(chunk, checksum) - except Exception as e: - if "[Errno 21] Is a directory" in str(e): - # Ignore symlinks that on windows sometimes get confused as being directories - continue - print(f"Error reading file {file}: {e}", file=sys.stderr) # noqa: T201 # this just runs as a simple script, so using print instead of log - raise + except IsADirectoryError: + # Ignore symlinks that on windows sometimes get confused as being directories + continue return checksum def find_devcontainer_hash_line(lines: list[str]) -> tuple[int, str | None]: """Find the line index and current hash in the devcontainer.json file.""" - for i in range(len(lines) - 1, -1, -1): + for i in reversed(range(len(lines))): if lines[i].strip() == "}": # Check the line above it if i > 0: diff --git a/.github/workflows/pre-commit.yaml b/.github/workflows/pre-commit.yaml index 6d6cf4ca..40c0a513 100644 --- a/.github/workflows/pre-commit.yaml +++ b/.github/workflows/pre-commit.yaml @@ -33,14 +33,14 @@ jobs: steps: - name: Checkout code during push if: ${{ github.event_name == 'push' }} - uses: actions/checkout@v5.0.0 + uses: actions/checkout@v6.0.1 with: ref: ${{ github.ref_name }} # explicitly get the head of the branch, which will include any new commits pushed if this is a dependabot branch persist-credentials: false - name: Checkout code not during push if: ${{ github.event_name != 'push' }} - uses: actions/checkout@v5.0.0 + uses: actions/checkout@v6.0.1 with: persist-credentials: false @@ -59,7 +59,7 @@ jobs: timeout-minutes: 8 # this is the amount of time this action will wait to attempt to acquire the mutex lock before failing, e.g. if other jobs are queued up in front of it - name: Cache Pre-commit hooks - uses: actions/cache@v4.2.4 + uses: actions/cache@v4.3.0 env: cache-name: cache-pre-commit-hooks with: @@ -69,4 +69,11 @@ jobs: ubuntu-24.04-py${{ inputs.python-version }}-node-${{ inputs.node-version}}-${{ env.cache-name }}- - name: Run pre-commit - run: pre-commit run -a + run: | + pre-commit run -a || PRE_COMMIT_EXIT_CODE=$? + if [ -n "$PRE_COMMIT_EXIT_CODE" ]; then + echo "Pre-commit failed with exit code $PRE_COMMIT_EXIT_CODE" + echo "Showing git diff:" + git --no-pager diff + exit $PRE_COMMIT_EXIT_CODE + fi diff --git a/.github/workflows/tag-on-merge.yaml b/.github/workflows/tag-on-merge.yaml index 3c208ca7..2e9b2277 100644 --- a/.github/workflows/tag-on-merge.yaml +++ b/.github/workflows/tag-on-merge.yaml @@ -14,7 +14,7 @@ jobs: permissions: contents: write steps: - - uses: actions/checkout@v5.0.0 + - uses: actions/checkout@v6.0.1 with: ref: ${{ github.event.pull_request.merge_commit_sha }} fetch-depth: '0' diff --git a/.gitignore b/.gitignore index fa36212d..72004381 100644 --- a/.gitignore +++ b/.gitignore @@ -77,7 +77,6 @@ dist **/logs/*.log.* # macOS dev cleanliness -*.DS_Store -.DS_Store +**/.DS_Store # Ignores specific to this repository diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 86348a0b..a9e1bbd5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -42,7 +42,7 @@ repos: # Reformatting (should generally come before any file format or other checks, because reformatting can change things) - repo: https://github.com/crate-ci/typos - rev: 6573587991823ef75e4d6ca97fe895f45e9f14e4 # frozen: v1 + rev: 802d5794ff9cf7b15610c47eca99cd1ab757d8d4 # frozen: v1 hooks: - id: typos exclude: | @@ -108,7 +108,7 @@ repos: )$ - repo: https://github.com/rbubley/mirrors-prettier - rev: 5ba47274f9b181bce26a5150a725577f3c336011 # frozen: v3.6.2 + rev: 14abee445aea04b39069c19b4bd54efff6775819 # frozen: v3.7.4 hooks: - id: prettier # TODO: get template YAML and MD files more in line with prettier expectations so we can start using prettier on those too @@ -195,7 +195,7 @@ repos: - id: check-case-conflict - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 83b816d020105076daac266dbf6bfed199a2da93 # frozen: 0.34.1 + rev: 16a6ad2fead09286ee6eb6b0a3fab55655a6c22a # frozen: 0.35.0 hooks: - id: check-github-workflows @@ -249,7 +249,7 @@ repos: description: Runs hadolint to lint Dockerfiles - repo: https://github.com/astral-sh/ruff-pre-commit - rev: 3db93a2be6f214ed722bf7bce095ec1b1715422a # frozen: v0.14.2 + rev: 1a1f58ba4c35362efe8fed2279715a905baee93d # frozen: v0.14.8 hooks: - id: ruff name: ruff-src @@ -257,7 +257,7 @@ repos: files: src/.+\.py$ exclude: | (?x)^( - .*/graphql_codegen/.*| + .*/generated/graphql/.*| .*/generated/open[-_]api/.*| template/.*| )$ @@ -267,26 +267,26 @@ repos: files: tests?/.+\.py$ exclude: | (?x)^( - .*/graphql_codegen/.*| + .*/generated/graphql/.*| .*/generated/open[-_]api/.*| template/.*| )$ - id: ruff-format exclude: | (?x)^( - .*/graphql_codegen/.*| + .*/generated/graphql/.*| .*/generated/open[-_]api/.*| )$ - repo: https://github.com/pylint-dev/pylint - rev: 0eb92d25fd38ba5bad2f8d2ea7df63ad23e18ae3 # frozen: v4.0.2 + rev: e16f942166511d6fb4427e503a734152fae0c4fe # frozen: v4.0.4 hooks: - id: pylint name: pylint # exclude the template files---duplication within them will be discovered during CI of that template instantiation exclude: | (?x)^( - .*/graphql_codegen/.*| + .*/generated/graphql/.*| .*/generated/open[-_]api/.*| template/.*| )$ @@ -302,7 +302,7 @@ repos: files: '.+\.py$' exclude: | (?x)^( - .*/graphql_codegen/.*| + .*/generated/graphql/.*| .*/generated/open[-_]api/.*| )$ # don't pass filenames else the command line sees them twice diff --git a/copier.yml b/copier.yml index a7ae287e..7750ac88 100644 --- a/copier.yml +++ b/copier.yml @@ -37,6 +37,15 @@ install_aws_ssm_port_forwarding_plugin: help: Should the AWS SSM Port Forwarding Plugin be installed? default: no +configure_vcrpy: + type: bool + help: Should VCRpy be configured for use during unit testing in Python? + default: no +configure_python_asyncio: + type: bool + help: Will python code be using asyncio? + default: no + python_version: type: str help: What version of Python is used for development? diff --git a/copier_template_resources/python_asyncio/asyncio_fixtures.py b/copier_template_resources/python_asyncio/asyncio_fixtures.py new file mode 100644 index 00000000..43bb4405 --- /dev/null +++ b/copier_template_resources/python_asyncio/asyncio_fixtures.py @@ -0,0 +1,36 @@ +import asyncio + +import pytest +from backend_api.background_tasks import background_task_exceptions +from backend_api.background_tasks import background_tasks_set + + +async def _wait_for_tasks(tasks_list: list[asyncio.Task[None]]): + _, pending = await asyncio.wait(tasks_list, timeout=5.0) + if pending: + raise RuntimeError(f"There are still pending tasks: {pending}") + + +@pytest.fixture(autouse=True) +def fail_on_background_task_errors(): + """Automatically fail tests if ANY background task raises an exception.""" + background_task_exceptions.clear() + + yield + + # Wait for background tasks to complete (using asyncio.run for sync fixture) + if background_tasks_set: + tasks_list = list(background_tasks_set) + try: + loop = asyncio.get_running_loop() + except RuntimeError: + asyncio.run(_wait_for_tasks(tasks_list)) + else: + loop.run_until_complete(_wait_for_tasks(tasks_list)) + + # Fail if any exceptions occurred + if background_task_exceptions: + pytest.fail( + f"Background tasks raised {len(background_task_exceptions)} exception(s):\n" + + "\n\n".join(f"{type(e).__name__}: {e}" for e in background_task_exceptions) + ) diff --git a/copier_template_resources/python_asyncio/background_tasks.py b/copier_template_resources/python_asyncio/background_tasks.py new file mode 100644 index 00000000..815f1721 --- /dev/null +++ b/copier_template_resources/python_asyncio/background_tasks.py @@ -0,0 +1,41 @@ +import asyncio +import logging +import traceback +from collections import deque +from weakref import WeakSet + +logger = logging.getLogger(__name__) +background_tasks_set: WeakSet[asyncio.Task[None]] = WeakSet() +background_task_exceptions: deque[Exception] = deque( + maxlen=100 # don't grow infinitely in production +) +# Store creation tracebacks for debugging +_task_creation_tracebacks: dict[int, str] = {} + + +def _task_done_callback(task: asyncio.Task[None]): + task_id = id(task) + background_tasks_set.discard(task) + try: + task.result() + except ( # pragma: no cover # hard to unit test this, but it'd be good to think of a way to do so + asyncio.CancelledError + ): + _ = _task_creation_tracebacks.pop(task_id, None) + return + except Exception as e: # pragma: no cover # hard to unit test this, but it'd be good to think of a way to do so + creation_tb = _task_creation_tracebacks.pop(task_id, "No traceback available") + logger.exception(f"Unhandled exception in background task\nTask was created from:\n{creation_tb}") + background_task_exceptions.append(e) + else: + # Clean up on successful completion + _ = _task_creation_tracebacks.pop(task_id, None) + + +def register_task(task: asyncio.Task[None]) -> None: + # Capture the stack trace at task creation time (excluding this function) + creation_stack = "".join(traceback.format_stack()[:-1]) + _task_creation_tracebacks[id(task)] = creation_stack + + background_tasks_set.add(task) + task.add_done_callback(_task_done_callback) diff --git a/copier_template_resources/vcrpy_fixtures.py b/copier_template_resources/vcrpy_fixtures.py new file mode 100644 index 00000000..cb33e4ff --- /dev/null +++ b/copier_template_resources/vcrpy_fixtures.py @@ -0,0 +1,64 @@ +import os +from typing import cast + +import pytest +from pydantic import JsonValue +from vcr import VCR + +UNREACHABLE_IP_ADDRESS = "192.0.2.1" # RFC 5737 TEST-NET-1 +IGNORED_HOSTS = [ + "testserver", # Skip recording any requests to our own server - let them run live + UNREACHABLE_IP_ADDRESS, # allow this through VCR in order to be able to test network failure handling +] +ALLOWED_HOSTS: list[str] = [] + +CUSTOM_IGNORED_HOSTS: tuple[str, ...] = () + +IGNORED_HOSTS.extend(CUSTOM_IGNORED_HOSTS) +if ( + os.name == "nt" +): # on Windows (in CI), the network calls happen at a lower level socket connection even to our FastAPI test client, and can get automatically blocked. This disables that automatic network guard, which isn't great...but since it's still in place on Linux, any actual problems would hopefully get caught before pushing to CI. + ALLOWED_HOSTS.extend(["127.0.0.1", "localhost", "::1"]) + + +@pytest.fixture(autouse=True) +def vcr_config() -> dict[str, list[str]]: + cfg: dict[str, list[str]] = { + "ignore_hosts": IGNORED_HOSTS, + "filter_headers": ["User-Agent"], + } + if ALLOWED_HOSTS: + cfg["allowed_hosts"] = ALLOWED_HOSTS + return cfg + + +def pytest_recording_configure( + config: pytest.Config, # noqa: ARG001 # the config argument MUST be present (even when unused) or pytest-recording throws an error + vcr: VCR, +): + vcr.match_on = cast(tuple[str, ...], vcr.match_on) # pyright: ignore[reportUnknownMemberType] # I know vcr.match_on is unknown, that's why I'm casting and isinstance-ing it...not sure if there's a different approach pyright prefers + assert isinstance(vcr.match_on, tuple), ( + f"vcr.match_on is not a tuple, it is a {type(vcr.match_on)} with value {vcr.match_on}" + ) + vcr.match_on += ("body",) # body is not included by default, but it seems relevant + + def before_record_response(response: dict[str, JsonValue]) -> dict[str, JsonValue]: + headers_to_filter = ( + "Transfer-Encoding", + "Date", + "Server", + ) # none of these headers in the response matter for unit testing, so might as well make the cassette files smaller + headers = response["headers"] + assert isinstance(headers, dict), ( + f"Expected response['headers'] to be a dict, got {type(headers)} with value {headers}" + ) + for header in headers_to_filter: + if header in headers: + del headers[header] + if ( + header.lower() in headers + ): # some headers are lowercased by the server in the response (e.g. Date, Server) + del headers[header.lower()] + return response + + vcr.before_record_response = before_record_response diff --git a/extensions/context.py b/extensions/context.py index 8a38b56b..7a83b8ac 100644 --- a/extensions/context.py +++ b/extensions/context.py @@ -10,11 +10,11 @@ class ContextUpdater(ContextHook): @override def hook(self, context: dict[Any, Any]) -> dict[Any, Any]: - context["uv_version"] = "0.9.11" - context["pnpm_version"] = "10.23.0" + context["uv_version"] = "0.9.17" + context["pnpm_version"] = "10.25.0" context["pre_commit_version"] = "4.5.0" context["pyright_version"] = "1.1.407" - context["pytest_version"] = "9.0.1" + context["pytest_version"] = "9.0.2" context["pytest_randomly_version"] = "4.0.1" context["pytest_cov_version"] = "7.0.0" context["copier_version"] = "9.11.0" @@ -28,29 +28,32 @@ def hook(self, context: dict[Any, Any]) -> dict[Any, Any]: context["pulumi_okta_version"] = "6.1.0" context["boto3_version"] = "1.41.2" context["ephemeral_pulumi_deploy_version"] = "0.0.5" - context["pydantic_version"] = "2.12.4" - context["pyinstaller_version"] = "6.16.0" + context["pydantic_version"] = "2.12.5" + context["pyinstaller_version"] = "6.17.0" context["setuptools_version"] = "80.7.1" - context["strawberry_graphql_version"] = "0.287.0" - context["fastapi_version"] = "0.121.0" - context["fastapi_offline_version"] = "1.7.4" - context["uvicorn_version"] = "0.38.0" + context["strawberry_graphql_version"] = ">=0.287.0" + context["fastapi_version"] = ">=0.124.2" + context["fastapi_offline_version"] = ">=1.7.4" + context["uvicorn_version"] = ">=0.38.0" context["lab_auto_pulumi_version"] = "0.1.17" - context["ariadne_codegen_version"] = "0.15.2" + context["ariadne_codegen_version"] = ">=0.17.0" context["pytest_mock_version"] = "3.15.1" - context["uuid_utils_version"] = "0.11.0" - context["syrupy_version"] = "5.0.0" - context["structlog_version"] = "25.5.0" + context["uuid_utils_version"] = ">=0.12.0" + context["syrupy_version"] = ">=5.0.0" + context["structlog_version"] = ">=25.5.0" context["httpx_version"] = "0.28.1" - context["python_kiota_bundle_version"] = "1.9.7" + context["python_kiota_bundle_version"] = ">=1.9.7" + context["vcrpy_version"] = ">=8.1.0" + context["pytest_recording_version"] = ">=0.13.4" + context["pytest_asyncio_version"] = ">=1.3.0" - context["node_version"] = "24.7.0" + context["node_version"] = "24.11.1" context["nuxt_ui_version"] = "^4.2.1" - context["nuxt_version"] = "^4.2.0" + context["nuxt_version"] = "^4.2.2" context["nuxt_icon_version"] = "^2.1.0" context["typescript_version"] = "^5.9.3" - context["playwright_version"] = "^1.56.0" - context["vue_version"] = "^3.5.22" + context["playwright_version"] = "^1.57.0" + context["vue_version"] = "^3.5.25" context["vue_tsc_version"] = "^3.1.2" context["vue_devtools_api_version"] = "^8.0.0" context["vue_router_version"] = "^4.6.0" @@ -61,11 +64,12 @@ def hook(self, context: dict[Any, Any]) -> dict[Any, Any]: context["nuxt_eslint_version"] = "^1.10.0" context["zod_version"] = "^4.1.12" context["zod_from_json_schema_version"] = "^0.5.1" - context["types_node_version"] = "^24.10.1" + context["types_node_version"] = "^25.0.0" context["nuxt_apollo_version"] = "5.0.0-alpha.15" context["graphql_codegen_cli_version"] = "^6.0.0" context["graphql_codegen_typescript_version"] = "^5.0.0" context["graphql_codegen_typescript_operations_version"] = "^5.0.0" + context["graphql_tools_mock_version"] = "^9.1.0" context["tailwindcss_version"] = "^4.1.11" context["iconify_vue_version"] = "^5.0.0" context["iconify_json_lucide_version"] = "^1.2.71" @@ -77,18 +81,18 @@ def hook(self, context: dict[Any, Any]) -> dict[Any, Any]: context["happy_dom_version"] = "^20.0.2" context["node_kiota_bundle_version"] = "1.0.0-preview.99" - context["gha_checkout"] = "v5.0.0" - context["gha_setup_python"] = "v6.0.0" - context["gha_cache"] = "v4.2.4" + context["gha_checkout"] = "v6.0.1" + context["gha_setup_python"] = "v6.1.0" + context["gha_cache"] = "v4.3.0" context["gha_upload_artifact"] = "v5.0.0" context["gha_download_artifact"] = "v6.0.0" context["gha_github_script"] = "v7.0.1" context["gha_setup_buildx"] = "v3.11.1" context["buildx_version"] = "v0.27.0" context["gha_docker_build_push"] = "v6.18.0" - context["gha_configure_aws_credentials"] = "v5.1.0" + context["gha_configure_aws_credentials"] = "v5.1.1" context["gha_amazon_ecr_login"] = "v2.0.1" - context["gha_setup_node"] = "v6.0.0" + context["gha_setup_node"] = "v6.1.0" context["gha_action_gh_release"] = "v2.2.1" context["gha_mutex"] = "1ebad517141198e08d47cf72f3c0975316620a65 # v1.0.0-alpha.10" context["gha_pypi_publish"] = "v1.13.0" @@ -101,8 +105,8 @@ def hook(self, context: dict[Any, Any]) -> dict[Any, Any]: context["gha_xlong_timeout_minutes"] = "45" context["debian_release_name"] = "bookworm" - context["alpine_image_version"] = "3.22" - context["nginx_image_version"] = "1.29.1" + context["alpine_image_version"] = "3.23" + context["nginx_image_version"] = "1.29.4" context["kiota_cli_version"] = "1.29.0" diff --git a/pyproject.toml b/pyproject.toml index c538181d..1866b074 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ readme = "README.md" requires-python = ">=3.12.7" dependencies = [ # Managed by upstream template - "pytest>=9.0.1", + "pytest>=9.0.2", "pytest-cov>=7.0.0", "pytest-randomly>=4.0.1", "pyright[nodejs]>=1.1.407", diff --git a/pyrightconfig.json b/pyrightconfig.json index 1e26d9d5..10ed1516 100644 --- a/pyrightconfig.json +++ b/pyrightconfig.json @@ -10,10 +10,11 @@ "**/.pipx_cache", "**/__pycache__", "**/vendor_files", - "**/graphql_codegen", + "**/generated/graphql", "**/generated/open_api", "**/.venv", - "**/venv" + "**/venv", + "**/copier_template_resources" ], "strictListInference": true, "strictDictionaryInference": true, diff --git a/template/.devcontainer/devcontainer.json.jinja b/template/.devcontainer/devcontainer.json.jinja index 33839d91..12fc0580 100644 --- a/template/.devcontainer/devcontainer.json.jinja +++ b/template/.devcontainer/devcontainer.json.jinja @@ -2,12 +2,12 @@ "dockerComposeFile": "docker-compose.yml", "service": "devcontainer", "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}", - "features": { + "features": {{% endraw %}{% if is_child_of_copier_base_template is not defined %}{% raw %} "ghcr.io/devcontainers/features/aws-cli:1.1.2": { // https://github.com/devcontainers/features/blob/main/src/aws-cli/devcontainer-feature.json // view latest version https://raw.githubusercontent.com/aws/aws-cli/v2/CHANGELOG.rst - "version": "2.31.11" - }, + "version": "2.32.6", + },{% endraw %}{% endif %}{% raw %} "ghcr.io/devcontainers/features/python:1.7.1": { // https://github.com/devcontainers/features/blob/main/src/python/devcontainer-feature.json "version": "{% endraw %}{{ python_version }}{% raw %}",{% endraw %}{% if is_frozen_executable %}{% raw %} @@ -25,10 +25,11 @@ "customizations": { "vscode": { // Add the IDs of extensions you want installed when the container is created. - "extensions": [ + "extensions": [{% endraw %}{% if is_child_of_copier_base_template is not defined %}{% raw %} + "-AmazonWebServices.aws-toolkit-vscode", // the AWS CLI feature installs this automatically, but it's causing problems in VS Code{% endraw %}{% endif %}{% raw %} // basic tooling // "eamodio.gitlens@15.5.1", - "coderabbit.coderabbit-vscode@0.16.0", + "coderabbit.coderabbit-vscode@0.16.1", "ms-vscode.live-server@0.5.2025051301", "MS-vsliveshare.vsliveshare@1.0.5905", "github.copilot@1.388.0", diff --git a/template/.devcontainer/windows-host-helper.sh b/template/.devcontainer/windows-host-helper.sh index b3d1d876..a071d4f7 100644 --- a/template/.devcontainer/windows-host-helper.sh +++ b/template/.devcontainer/windows-host-helper.sh @@ -27,38 +27,30 @@ repoName=$(basename "$gitUrl" .git) echo "Repo name extracted as '$repoName'" -# Remove any existing subfolder with the repository name and recreate it -rm -rf "./$repoName" || true # sometimes deleting the .venv folder fails -rm -rf "./$repoName/*.md" # for some reason, sometimes md files are left behind +sudo rm -rf "./$repoName" || true +sudo rm -rf "./$repoName/*.md" mkdir -p "./$repoName" +sudo chown -R "$(whoami):$(whoami)" "./$repoName" # TODO: see if this alone is enough to fix everything # Create a temporary directory for cloning tmpdir=$(mktemp -d) -# Clone the repository into a subfolder inside the temporary directory. -# This creates "$tmpdir/$repoName" with the repository's contents. +# Clone the repository into a subfolder inside the temporary directory git clone "$gitUrl" "$tmpdir/$repoName" - -SRC="$(realpath "$tmpdir/$repoName")" -DST="$(realpath "./$repoName")" - -# 1) Recreate directory tree under $DST -while IFS= read -r -d '' dir; do - rel="${dir#$SRC/}" # strip leading $SRC/ → e.g. "sub/dir" - mkdir -p "$DST/$rel" -done < <(find "$SRC" -type d -print0) - -# 2) Move all files into that mirror -while IFS= read -r -d '' file; do - rel="${file#$SRC/}" # e.g. "sub/dir/file.txt" - # ensure parent exists (though step 1 already did) - mkdir -p "$(dirname "$DST/$rel")" - mv "$file" "$DST/$rel" -done < <(find "$SRC" -type f -print0) - -# 3) Clean up now‑empty dirs and the tmp clone -find "$SRC" -depth -type d -empty -delete +# Use rsync to merge all contents (including hidden files) from cloned repo to target +# -a: archive mode (preserves permissions, timestamps, etc.) +# -v: verbose +# --exclude: skip volume mount directories that should not be overwritten +echo "Syncing repository contents..." +rsync -av \ + --exclude='node_modules' \ + --exclude='.pnpm-store' \ + --exclude='.venv' \ + "$tmpdir/$repoName/" "./$repoName/" + +# Clean up: remove the temporary directory rm -rf "$tmpdir" -echo "Repository '$repoName' has been synced into '$DST'." +echo "Repository '$repoName' has been updated." +echo "Note: Volume mounts (node_modules, .pnpm-store, .venv) were preserved." diff --git a/template/.github/actions/install_deps/action.yml b/template/.github/actions/install_deps/action.yml index 0e9f0206..48796359 100644 --- a/template/.github/actions/install_deps/action.yml +++ b/template/.github/actions/install_deps/action.yml @@ -58,13 +58,13 @@ runs: - name: Setup python if: ${{ inputs.python-version != 'notUsing' }} - uses: actions/setup-python@v6.0.0 + uses: actions/setup-python@v6.1.0 with: python-version: ${{ env.PYTHON_VERSION }} - name: Setup node if: ${{ inputs.node-version != 'notUsing' }} - uses: actions/setup-node@v6.0.0 + uses: actions/setup-node@v6.1.0 with: node-version: ${{ inputs.node-version }} @@ -75,7 +75,7 @@ runs: - name: OIDC Auth for CodeArtifact if: ${{ inputs.code-artifact-auth-role-name != 'no-code-artifact' }} - uses: aws-actions/configure-aws-credentials@v5.1.0 + uses: aws-actions/configure-aws-credentials@v5.1.1 with: role-to-assume: arn:aws:iam::${{ inputs.code-artifact-auth-role-account-id }}:role/${{ inputs.code-artifact-auth-role-name }} aws-region: ${{ inputs.code-artifact-auth-region }} diff --git a/template/.github/actions/update-devcontainer-hash/action.yml b/template/.github/actions/update-devcontainer-hash/action.yml index fb64cc81..4e6d434c 100644 --- a/template/.github/actions/update-devcontainer-hash/action.yml +++ b/template/.github/actions/update-devcontainer-hash/action.yml @@ -27,7 +27,7 @@ runs: shell: bash - name: Checkout code - uses: actions/checkout@v5.0.0 + uses: actions/checkout@v6.0.1 with: persist-credentials: true fetch-depth: 1 diff --git a/template/.github/workflows/get-values.yaml b/template/.github/workflows/get-values.yaml index c7126d34..0be45c49 100644 --- a/template/.github/workflows/get-values.yaml +++ b/template/.github/workflows/get-values.yaml @@ -9,6 +9,9 @@ on: dependabot-commit-created: description: whether or not a commit was created on a dependabot branch value: ${{ jobs.get-values.outputs.dependabot-commit-created }} + pr-short-num: + description: the last two digits of the PR number (to be used for fixed width naming, like Pulumi stacks) + value: ${{ jobs.get-values.outputs.pr-short-num }} env: PYTHONUNBUFFERED: True @@ -32,7 +35,7 @@ jobs: JSON - name: Checkout code - uses: actions/checkout@v5.0.0 + uses: actions/checkout@v6.0.1 with: persist-credentials: false diff --git a/template/.github/workflows/hash_git_files.py b/template/.github/workflows/hash_git_files.py index 1da0a3f7..40576d44 100644 --- a/template/.github/workflows/hash_git_files.py +++ b/template/.github/workflows/hash_git_files.py @@ -65,19 +65,16 @@ def compute_adler32(repo_path: Path, files: list[str]) -> int: if not chunk: break checksum = zlib.adler32(chunk, checksum) - except Exception as e: - if "[Errno 21] Is a directory" in str(e): - # Ignore symlinks that on windows sometimes get confused as being directories - continue - print(f"Error reading file {file}: {e}", file=sys.stderr) # noqa: T201 # this just runs as a simple script, so using print instead of log - raise + except IsADirectoryError: + # Ignore symlinks that on windows sometimes get confused as being directories + continue return checksum def find_devcontainer_hash_line(lines: list[str]) -> tuple[int, str | None]: """Find the line index and current hash in the devcontainer.json file.""" - for i in range(len(lines) - 1, -1, -1): + for i in reversed(range(len(lines))): if lines[i].strip() == "}": # Check the line above it if i > 0: diff --git a/template/.github/workflows/pre-commit.yaml b/template/.github/workflows/pre-commit.yaml index 6d6cf4ca..40c0a513 100644 --- a/template/.github/workflows/pre-commit.yaml +++ b/template/.github/workflows/pre-commit.yaml @@ -33,14 +33,14 @@ jobs: steps: - name: Checkout code during push if: ${{ github.event_name == 'push' }} - uses: actions/checkout@v5.0.0 + uses: actions/checkout@v6.0.1 with: ref: ${{ github.ref_name }} # explicitly get the head of the branch, which will include any new commits pushed if this is a dependabot branch persist-credentials: false - name: Checkout code not during push if: ${{ github.event_name != 'push' }} - uses: actions/checkout@v5.0.0 + uses: actions/checkout@v6.0.1 with: persist-credentials: false @@ -59,7 +59,7 @@ jobs: timeout-minutes: 8 # this is the amount of time this action will wait to attempt to acquire the mutex lock before failing, e.g. if other jobs are queued up in front of it - name: Cache Pre-commit hooks - uses: actions/cache@v4.2.4 + uses: actions/cache@v4.3.0 env: cache-name: cache-pre-commit-hooks with: @@ -69,4 +69,11 @@ jobs: ubuntu-24.04-py${{ inputs.python-version }}-node-${{ inputs.node-version}}-${{ env.cache-name }}- - name: Run pre-commit - run: pre-commit run -a + run: | + pre-commit run -a || PRE_COMMIT_EXIT_CODE=$? + if [ -n "$PRE_COMMIT_EXIT_CODE" ]; then + echo "Pre-commit failed with exit code $PRE_COMMIT_EXIT_CODE" + echo "Showing git diff:" + git --no-pager diff + exit $PRE_COMMIT_EXIT_CODE + fi diff --git a/template/.gitignore b/template/.gitignore index fa36212d..72004381 100644 --- a/template/.gitignore +++ b/template/.gitignore @@ -77,7 +77,6 @@ dist **/logs/*.log.* # macOS dev cleanliness -*.DS_Store -.DS_Store +**/.DS_Store # Ignores specific to this repository diff --git a/template/.pre-commit-config.yaml b/template/.pre-commit-config.yaml index 86348a0b..a9e1bbd5 100644 --- a/template/.pre-commit-config.yaml +++ b/template/.pre-commit-config.yaml @@ -42,7 +42,7 @@ repos: # Reformatting (should generally come before any file format or other checks, because reformatting can change things) - repo: https://github.com/crate-ci/typos - rev: 6573587991823ef75e4d6ca97fe895f45e9f14e4 # frozen: v1 + rev: 802d5794ff9cf7b15610c47eca99cd1ab757d8d4 # frozen: v1 hooks: - id: typos exclude: | @@ -108,7 +108,7 @@ repos: )$ - repo: https://github.com/rbubley/mirrors-prettier - rev: 5ba47274f9b181bce26a5150a725577f3c336011 # frozen: v3.6.2 + rev: 14abee445aea04b39069c19b4bd54efff6775819 # frozen: v3.7.4 hooks: - id: prettier # TODO: get template YAML and MD files more in line with prettier expectations so we can start using prettier on those too @@ -195,7 +195,7 @@ repos: - id: check-case-conflict - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 83b816d020105076daac266dbf6bfed199a2da93 # frozen: 0.34.1 + rev: 16a6ad2fead09286ee6eb6b0a3fab55655a6c22a # frozen: 0.35.0 hooks: - id: check-github-workflows @@ -249,7 +249,7 @@ repos: description: Runs hadolint to lint Dockerfiles - repo: https://github.com/astral-sh/ruff-pre-commit - rev: 3db93a2be6f214ed722bf7bce095ec1b1715422a # frozen: v0.14.2 + rev: 1a1f58ba4c35362efe8fed2279715a905baee93d # frozen: v0.14.8 hooks: - id: ruff name: ruff-src @@ -257,7 +257,7 @@ repos: files: src/.+\.py$ exclude: | (?x)^( - .*/graphql_codegen/.*| + .*/generated/graphql/.*| .*/generated/open[-_]api/.*| template/.*| )$ @@ -267,26 +267,26 @@ repos: files: tests?/.+\.py$ exclude: | (?x)^( - .*/graphql_codegen/.*| + .*/generated/graphql/.*| .*/generated/open[-_]api/.*| template/.*| )$ - id: ruff-format exclude: | (?x)^( - .*/graphql_codegen/.*| + .*/generated/graphql/.*| .*/generated/open[-_]api/.*| )$ - repo: https://github.com/pylint-dev/pylint - rev: 0eb92d25fd38ba5bad2f8d2ea7df63ad23e18ae3 # frozen: v4.0.2 + rev: e16f942166511d6fb4427e503a734152fae0c4fe # frozen: v4.0.4 hooks: - id: pylint name: pylint # exclude the template files---duplication within them will be discovered during CI of that template instantiation exclude: | (?x)^( - .*/graphql_codegen/.*| + .*/generated/graphql/.*| .*/generated/open[-_]api/.*| template/.*| )$ @@ -302,7 +302,7 @@ repos: files: '.+\.py$' exclude: | (?x)^( - .*/graphql_codegen/.*| + .*/generated/graphql/.*| .*/generated/open[-_]api/.*| )$ # don't pass filenames else the command line sees them twice diff --git a/template/pyrightconfig.json b/template/pyrightconfig.json index 1e26d9d5..10ed1516 100644 --- a/template/pyrightconfig.json +++ b/template/pyrightconfig.json @@ -10,10 +10,11 @@ "**/.pipx_cache", "**/__pycache__", "**/vendor_files", - "**/graphql_codegen", + "**/generated/graphql", "**/generated/open_api", "**/.venv", - "**/venv" + "**/venv", + "**/copier_template_resources" ], "strictListInference": true, "strictDictionaryInference": true, diff --git a/tests/copier_data/data1.yaml b/tests/copier_data/data1.yaml index 20fb4737..cc5b46e2 100644 --- a/tests/copier_data/data1.yaml +++ b/tests/copier_data/data1.yaml @@ -7,6 +7,8 @@ install_claude_cli: false ssh_port_number: 12345 use_windows_in_ci: false install_aws_ssm_port_forwarding_plugin: true +configure_vcrpy: true +configure_python_asyncio: true python_package_registry: PyPI diff --git a/tests/copier_data/data2.yaml b/tests/copier_data/data2.yaml index 05e6e4a4..19e929c0 100644 --- a/tests/copier_data/data2.yaml +++ b/tests/copier_data/data2.yaml @@ -7,6 +7,8 @@ install_claude_cli: true ssh_port_number: 54321 use_windows_in_ci: true install_aws_ssm_port_forwarding_plugin: false +configure_vcrpy: false +configure_python_asyncio: false python_package_registry: AWS CodeArtifact diff --git a/tests/copier_data/data3.yaml b/tests/copier_data/data3.yaml index 0bf6f907..095dfbf7 100644 --- a/tests/copier_data/data3.yaml +++ b/tests/copier_data/data3.yaml @@ -7,6 +7,8 @@ install_claude_cli: false ssh_port_number: 12345 use_windows_in_ci: false install_aws_ssm_port_forwarding_plugin: false +configure_vcrpy: false +configure_python_asyncio: true python_package_registry: PyPI diff --git a/uv.lock b/uv.lock index c2e6c865..9f07ae0a 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 2 +revision = 3 requires-python = ">=3.12.7" [[package]] @@ -62,7 +62,7 @@ requires-dist = [ { name = "copier", specifier = ">=9.11.0" }, { name = "copier-template-extensions", specifier = ">=0.3.3" }, { name = "pyright", extras = ["nodejs"], specifier = ">=1.1.407" }, - { name = "pytest", specifier = ">=9.0.1" }, + { name = "pytest", specifier = ">=9.0.2" }, { name = "pytest-cov", specifier = ">=7.0.0" }, { name = "pytest-randomly", specifier = ">=4.0.1" }, ] @@ -402,7 +402,7 @@ nodejs = [ [[package]] name = "pytest" -version = "9.0.1" +version = "9.0.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -411,9 +411,9 @@ dependencies = [ { name = "pluggy" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/07/56/f013048ac4bc4c1d9be45afd4ab209ea62822fb1598f40687e6bf45dcea4/pytest-9.0.1.tar.gz", hash = "sha256:3e9c069ea73583e255c3b21cf46b8d3c56f6e3a1a8f6da94ccb0fcf57b9d73c8", size = 1564125, upload-time = "2025-11-12T13:05:09.333Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/8b/6300fb80f858cda1c51ffa17075df5d846757081d11ab4aa35cef9e6258b/pytest-9.0.1-py3-none-any.whl", hash = "sha256:67be0030d194df2dfa7b556f2e56fb3c3315bd5c8822c6951162b92b32ce7dad", size = 373668, upload-time = "2025-11-12T13:05:07.379Z" }, + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, ] [[package]] From 3cec5204991fef2181147d573035b114f5d71c30 Mon Sep 17 00:00:00 2001 From: Eli Fine Date: Mon, 15 Dec 2025 13:28:29 +0000 Subject: [PATCH 02/19] vcr --- template/pyproject.toml.jinja | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/template/pyproject.toml.jinja b/template/pyproject.toml.jinja index 2cdf7477..a6d8bffc 100644 --- a/template/pyproject.toml.jinja +++ b/template/pyproject.toml.jinja @@ -28,9 +28,11 @@ dev = [ "pyright>={% endraw %}{{ pyright_version }}{% raw %}", "pytest>={% endraw %}{{ pytest_version }}{% raw %}", "pytest-cov>={% endraw %}{{ pytest_cov_version }}{% raw %}", - "pytest-randomly>={% endraw %}{{ pytest_randomly_version }}{% raw %}", -{% endraw %}{% if create_docs %}{% raw %} "sphinx=={% endraw %}{{ sphinx_version }}{% raw %}",{% endraw %}{% endif %}{% raw %} -{% endraw %}{% if is_frozen_executable %}{% raw %} "pyinstaller>={% endraw %}{{ pyinstaller_version }}{% raw %}",{% endraw %}{% endif %}{% raw %} + "pytest-randomly>={% endraw %}{{ pytest_randomly_version }}{% raw %}",{% endraw %}{% if configure_vcrpy %}{% raw %} + "pytest-recording{% endraw %}{{ pytest_recording_version }}{% raw %}", + "vcrpy{% endraw %}{{ vcrpy_version }}{% raw %}",{% endraw %}{% endif %}{% raw %}{% endraw %}{% if create_docs %}{% raw %} + "sphinx=={% endraw %}{{ sphinx_version }}{% raw %}",{% endraw %}{% endif %}{% raw %}{% endraw %}{% if is_frozen_executable %}{% raw %} + "pyinstaller>={% endraw %}{{ pyinstaller_version }}{% raw %}",{% endraw %}{% endif %}{% raw %} ] [tool.setuptools] From 97fb793bc36448ce697fb4fcd9ade2cc722c11ab Mon Sep 17 00:00:00 2001 From: Eli Fine Date: Mon, 15 Dec 2025 13:30:26 +0000 Subject: [PATCH 03/19] fixture --- .../unit/{% if configure_vcrpy %}vcrpy_fixtures.py{% endif %} | 1 + 1 file changed, 1 insertion(+) create mode 120000 template/tests/unit/{% if configure_vcrpy %}vcrpy_fixtures.py{% endif %} diff --git a/template/tests/unit/{% if configure_vcrpy %}vcrpy_fixtures.py{% endif %} b/template/tests/unit/{% if configure_vcrpy %}vcrpy_fixtures.py{% endif %} new file mode 120000 index 00000000..006332d2 --- /dev/null +++ b/template/tests/unit/{% if configure_vcrpy %}vcrpy_fixtures.py{% endif %} @@ -0,0 +1 @@ +../../../copier_template_resources/vcrpy_fixtures.py \ No newline at end of file From 2bfd4909033d4f1bc71b39de162407b37de9750b Mon Sep 17 00:00:00 2001 From: Eli Fine Date: Mon, 15 Dec 2025 13:43:44 +0000 Subject: [PATCH 04/19] phrasing --- template/tests/unit/conftest.py.jinja | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 template/tests/unit/conftest.py.jinja diff --git a/template/tests/unit/conftest.py.jinja b/template/tests/unit/conftest.py.jinja new file mode 100644 index 00000000..61f0528f --- /dev/null +++ b/template/tests/unit/conftest.py.jinja @@ -0,0 +1,10 @@ +{% raw %}import logging + +import pytest + + +{% endraw %}{% if configure_python_asyncio %}{% raw %}from .asyncio_fixtures import fail_on_background_task_errors # noqa: F401 # this is an autouse fixture{% endraw %}{% endif %}{% if configure_vcrpy %}{% raw %} +from .vcrpy_fixtures import pytest_recording_configure # noqa: F401 # this is configuration we need in conftest scope +from .vcrpy_fixtures import vcr_config # noqa: F401 # this is an autouse fixture{% endraw %}{% endif %}{% raw %} + +logger = logging.getLogger(__name__) From 174b75d76d72a458eccf800bf7c35188f9463b64 Mon Sep 17 00:00:00 2001 From: Eli Fine Date: Mon, 15 Dec 2025 13:46:59 +0000 Subject: [PATCH 05/19] endraw --- template/tests/unit/conftest.py.jinja | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/template/tests/unit/conftest.py.jinja b/template/tests/unit/conftest.py.jinja index 61f0528f..e7cd6682 100644 --- a/template/tests/unit/conftest.py.jinja +++ b/template/tests/unit/conftest.py.jinja @@ -7,4 +7,4 @@ import pytest from .vcrpy_fixtures import pytest_recording_configure # noqa: F401 # this is configuration we need in conftest scope from .vcrpy_fixtures import vcr_config # noqa: F401 # this is an autouse fixture{% endraw %}{% endif %}{% raw %} -logger = logging.getLogger(__name__) +logger = logging.getLogger(__name__){% endraw %} From ce2e0ab9316987fb6c0ce5d28cecd8fe62db0b70 Mon Sep 17 00:00:00 2001 From: Eli Fine Date: Mon, 15 Dec 2025 13:49:16 +0000 Subject: [PATCH 06/19] configuri --- template/tests/unit/conftest.py.jinja | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/template/tests/unit/conftest.py.jinja b/template/tests/unit/conftest.py.jinja index e7cd6682..44161a0c 100644 --- a/template/tests/unit/conftest.py.jinja +++ b/template/tests/unit/conftest.py.jinja @@ -7,4 +7,11 @@ import pytest from .vcrpy_fixtures import pytest_recording_configure # noqa: F401 # this is configuration we need in conftest scope from .vcrpy_fixtures import vcr_config # noqa: F401 # this is an autouse fixture{% endraw %}{% endif %}{% raw %} -logger = logging.getLogger(__name__){% endraw %} +logger = logging.getLogger(__name__) + +def pytest_configure( + config: pytest.Config, # noqa: ARG001 # the config argument MUST be present (even when unused) or pytest throws an error +): + # force the vcr.cassette logger to WARNING+ because otherwise the logs get super noisy with the playback of all the cassettes + vcr_logger = logging.getLogger("vcr.cassette") + vcr_logger.setLevel(logging.WARNING){% endraw %} From e993c3e67607ccfba3e087455c1a0c998d37da45 Mon Sep 17 00:00:00 2001 From: Eli Fine Date: Mon, 15 Dec 2025 13:51:03 +0000 Subject: [PATCH 07/19] whitespace --- template/tests/unit/conftest.py.jinja | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/template/tests/unit/conftest.py.jinja b/template/tests/unit/conftest.py.jinja index 44161a0c..ea447a43 100644 --- a/template/tests/unit/conftest.py.jinja +++ b/template/tests/unit/conftest.py.jinja @@ -1,9 +1,7 @@ {% raw %}import logging -import pytest - - -{% endraw %}{% if configure_python_asyncio %}{% raw %}from .asyncio_fixtures import fail_on_background_task_errors # noqa: F401 # this is an autouse fixture{% endraw %}{% endif %}{% if configure_vcrpy %}{% raw %} +import pytest{% endraw %}{% if configure_python_asyncio %}{% raw %} +from .asyncio_fixtures import fail_on_background_task_errors # noqa: F401 # this is an autouse fixture{% endraw %}{% endif %}{% if configure_vcrpy %}{% raw %} from .vcrpy_fixtures import pytest_recording_configure # noqa: F401 # this is configuration we need in conftest scope from .vcrpy_fixtures import vcr_config # noqa: F401 # this is an autouse fixture{% endraw %}{% endif %}{% raw %} From b92d6db6d12890ce0005b3bd1b70d4a2a7001841 Mon Sep 17 00:00:00 2001 From: Eli Fine Date: Mon, 15 Dec 2025 14:11:44 +0000 Subject: [PATCH 08/19] more whitespace --- template/tests/unit/conftest.py.jinja | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/template/tests/unit/conftest.py.jinja b/template/tests/unit/conftest.py.jinja index ea447a43..0db73cc5 100644 --- a/template/tests/unit/conftest.py.jinja +++ b/template/tests/unit/conftest.py.jinja @@ -1,12 +1,14 @@ {% raw %}import logging -import pytest{% endraw %}{% if configure_python_asyncio %}{% raw %} +import pytest{% endraw %}{% if configure_python_asyncio or configure_vcrpy %} +{% endif %}{% if configure_python_asyncio %}{% raw %} from .asyncio_fixtures import fail_on_background_task_errors # noqa: F401 # this is an autouse fixture{% endraw %}{% endif %}{% if configure_vcrpy %}{% raw %} from .vcrpy_fixtures import pytest_recording_configure # noqa: F401 # this is configuration we need in conftest scope from .vcrpy_fixtures import vcr_config # noqa: F401 # this is an autouse fixture{% endraw %}{% endif %}{% raw %} logger = logging.getLogger(__name__) + def pytest_configure( config: pytest.Config, # noqa: ARG001 # the config argument MUST be present (even when unused) or pytest throws an error ): From a89e6be648d6a7174881068d8cb9b26168274224 Mon Sep 17 00:00:00 2001 From: Eli Fine Date: Mon, 15 Dec 2025 14:37:54 +0000 Subject: [PATCH 09/19] async --- ...f configure_python_asyncio %}background_tasks.py{% endif %} | 1 + template/tests/unit/conftest.py.jinja | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) create mode 120000 template/src/{{ package_name.replace('-', '_') }}/{% if configure_python_asyncio %}background_tasks.py{% endif %} diff --git a/template/src/{{ package_name.replace('-', '_') }}/{% if configure_python_asyncio %}background_tasks.py{% endif %} b/template/src/{{ package_name.replace('-', '_') }}/{% if configure_python_asyncio %}background_tasks.py{% endif %} new file mode 120000 index 00000000..1520fbcd --- /dev/null +++ b/template/src/{{ package_name.replace('-', '_') }}/{% if configure_python_asyncio %}background_tasks.py{% endif %} @@ -0,0 +1 @@ +../../../../copier-python-package-template/copier_template_resources/python_asyncio/background_tasks.py \ No newline at end of file diff --git a/template/tests/unit/conftest.py.jinja b/template/tests/unit/conftest.py.jinja index 0db73cc5..d7ae6756 100644 --- a/template/tests/unit/conftest.py.jinja +++ b/template/tests/unit/conftest.py.jinja @@ -1,8 +1,7 @@ {% raw %}import logging import pytest{% endraw %}{% if configure_python_asyncio or configure_vcrpy %} -{% endif %}{% if configure_python_asyncio %}{% raw %} -from .asyncio_fixtures import fail_on_background_task_errors # noqa: F401 # this is an autouse fixture{% endraw %}{% endif %}{% if configure_vcrpy %}{% raw %} +{% endif %}{% if configure_vcrpy %}{% raw %} from .vcrpy_fixtures import pytest_recording_configure # noqa: F401 # this is configuration we need in conftest scope from .vcrpy_fixtures import vcr_config # noqa: F401 # this is an autouse fixture{% endraw %}{% endif %}{% raw %} From 2f4a63714b71d6a88090ec6c300e912a9cadc772 Mon Sep 17 00:00:00 2001 From: Eli Fine Date: Mon, 15 Dec 2025 15:02:53 +0000 Subject: [PATCH 10/19] no background --- ... if configure_python_asyncio %}background_tasks.py{% endif %} | 1 - 1 file changed, 1 deletion(-) delete mode 120000 template/src/{{ package_name.replace('-', '_') }}/{% if configure_python_asyncio %}background_tasks.py{% endif %} diff --git a/template/src/{{ package_name.replace('-', '_') }}/{% if configure_python_asyncio %}background_tasks.py{% endif %} b/template/src/{{ package_name.replace('-', '_') }}/{% if configure_python_asyncio %}background_tasks.py{% endif %} deleted file mode 120000 index 1520fbcd..00000000 --- a/template/src/{{ package_name.replace('-', '_') }}/{% if configure_python_asyncio %}background_tasks.py{% endif %} +++ /dev/null @@ -1 +0,0 @@ -../../../../copier-python-package-template/copier_template_resources/python_asyncio/background_tasks.py \ No newline at end of file From 247b1abd99c4d5d98051d18a2b33cca93bfd1d2c Mon Sep 17 00:00:00 2001 From: Eli Fine Date: Mon, 15 Dec 2025 18:19:53 +0000 Subject: [PATCH 11/19] sphinx --- .copier-answers.yml | 2 +- copier_template_resources/vcrpy_fixtures.py | 4 ++-- extensions/context.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.copier-answers.yml b/.copier-answers.yml index 956911e5..b9bab48c 100644 --- a/.copier-answers.yml +++ b/.copier-answers.yml @@ -1,5 +1,5 @@ # Changes here will be overwritten by Copier -_commit: v0.0.89 +_commit: v0.0.89-2-g243766a _src_path: gh:LabAutomationAndScreening/copier-base-template.git description: Copier template for creating Python libraries and executables install_claude_cli: false diff --git a/copier_template_resources/vcrpy_fixtures.py b/copier_template_resources/vcrpy_fixtures.py index cb33e4ff..4283fe1c 100644 --- a/copier_template_resources/vcrpy_fixtures.py +++ b/copier_template_resources/vcrpy_fixtures.py @@ -1,8 +1,8 @@ import os +from typing import Any from typing import cast import pytest -from pydantic import JsonValue from vcr import VCR UNREACHABLE_IP_ADDRESS = "192.0.2.1" # RFC 5737 TEST-NET-1 @@ -42,7 +42,7 @@ def pytest_recording_configure( ) vcr.match_on += ("body",) # body is not included by default, but it seems relevant - def before_record_response(response: dict[str, JsonValue]) -> dict[str, JsonValue]: + def before_record_response(response: dict[str, str | dict[str, Any]]) -> dict[str, str | dict[str, Any]]: headers_to_filter = ( "Transfer-Encoding", "Date", diff --git a/extensions/context.py b/extensions/context.py index 7a83b8ac..9c9bcffb 100644 --- a/extensions/context.py +++ b/extensions/context.py @@ -19,7 +19,7 @@ def hook(self, context: dict[Any, Any]) -> dict[Any, Any]: context["pytest_cov_version"] = "7.0.0" context["copier_version"] = "9.11.0" context["copier_template_extensions_version"] = "0.3.3" - context["sphinx_version"] = "8.1.3" + context["sphinx_version"] = "9.0.4" context["pulumi_version"] = "3.208.0" context["pulumi_aws_version"] = "7.12.0" context["pulumi_aws_native_version"] = "1.38.0" From c403fb2940737ea4bf2b7e84416ec02d02f4c26a Mon Sep 17 00:00:00 2001 From: Eli Fine Date: Mon, 15 Dec 2025 18:26:55 +0000 Subject: [PATCH 12/19] whiespace --- template/tests/unit/conftest.py.jinja | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/template/tests/unit/conftest.py.jinja b/template/tests/unit/conftest.py.jinja index d7ae6756..5cac1ed2 100644 --- a/template/tests/unit/conftest.py.jinja +++ b/template/tests/unit/conftest.py.jinja @@ -1,8 +1,8 @@ {% raw %}import logging import pytest{% endraw %}{% if configure_python_asyncio or configure_vcrpy %} -{% endif %}{% if configure_vcrpy %}{% raw %} -from .vcrpy_fixtures import pytest_recording_configure # noqa: F401 # this is configuration we need in conftest scope + +{% endif %}{% if configure_vcrpy %}{% raw %}from .vcrpy_fixtures import pytest_recording_configure # noqa: F401 # this is configuration we need in conftest scope from .vcrpy_fixtures import vcr_config # noqa: F401 # this is an autouse fixture{% endraw %}{% endif %}{% raw %} logger = logging.getLogger(__name__) From 3e52a6ee24d5f02368089f53c2237b1d1ca83502 Mon Sep 17 00:00:00 2001 From: Eli Fine Date: Mon, 15 Dec 2025 19:23:39 +0000 Subject: [PATCH 13/19] less async --- template/tests/unit/conftest.py.jinja | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/template/tests/unit/conftest.py.jinja b/template/tests/unit/conftest.py.jinja index 5cac1ed2..22b386ce 100644 --- a/template/tests/unit/conftest.py.jinja +++ b/template/tests/unit/conftest.py.jinja @@ -1,6 +1,6 @@ {% raw %}import logging -import pytest{% endraw %}{% if configure_python_asyncio or configure_vcrpy %} +import pytest{% endraw %}{% if configure_vcrpy %} {% endif %}{% if configure_vcrpy %}{% raw %}from .vcrpy_fixtures import pytest_recording_configure # noqa: F401 # this is configuration we need in conftest scope from .vcrpy_fixtures import vcr_config # noqa: F401 # this is an autouse fixture{% endraw %}{% endif %}{% raw %} From 22d2253797baef154e29e59b13f899b76a1d96ad Mon Sep 17 00:00:00 2001 From: Eli Fine Date: Mon, 15 Dec 2025 19:48:29 +0000 Subject: [PATCH 14/19] tag --- .copier-answers.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.copier-answers.yml b/.copier-answers.yml index b9bab48c..c151c79c 100644 --- a/.copier-answers.yml +++ b/.copier-answers.yml @@ -1,5 +1,5 @@ # Changes here will be overwritten by Copier -_commit: v0.0.89-2-g243766a +_commit: v0.0.90 _src_path: gh:LabAutomationAndScreening/copier-base-template.git description: Copier template for creating Python libraries and executables install_claude_cli: false From 3d469a5537ab9a4a65f7aa3320318d602912f895 Mon Sep 17 00:00:00 2001 From: Eli Fine Date: Mon, 15 Dec 2025 19:59:02 +0000 Subject: [PATCH 15/19] conditinal codecov --- template/README.md.jinja | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/template/README.md.jinja b/template/README.md.jinja index 55fa0641..62c23983 100644 --- a/template/README.md.jinja +++ b/template/README.md.jinja @@ -6,8 +6,8 @@ [![Open in Dev Containers](https://img.shields.io/static/v1?label=Dev%20Containers&message=Open&color=blue)](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url={% endraw %}{{ full_repo_url }}{% raw %}){% endraw %}{% if not is_frozen_executable %}{% raw %} [![PyPI Version](https://img.shields.io/pypi/v/{% endraw %}{{ package_name }}{% raw %}.svg)](https://pypi.org/project/{% endraw %}{{ package_name }}{% raw %}/) [![Downloads](https://pepy.tech/badge/{% endraw %}{{ package_name }}{% raw %})](https://pepy.tech/project/{% endraw %}{{ package_name }}{% raw %}) -[![Python Versions](https://img.shields.io/pypi/pyversions/{% endraw %}{{ package_name }}{% raw %}.svg)](https://pypi.org/project/{% endraw %}{{ package_name }}{% raw %}/) -{% endraw %}{% endif %}{% raw %}[![Codecov](https://codecov.io/gh/{% endraw %}{{ full_repo_url | replace("https://github.com/", "") }}{% raw %}/branch/main/graph/badge.svg)](https://codecov.io/gh/{% endraw %}{{ full_repo_url | replace("https://github.com/", "") }}{% raw %}){% endraw %}{% if create_docs %}{% raw %} +[![Python Versions](https://img.shields.io/pypi/pyversions/{% endraw %}{{ package_name }}{% raw %}.svg)](https://pypi.org/project/{% endraw %}{{ package_name }}{% raw %}/){% endraw %}{% endif %}{% if use_codecov %}{% raw %} +[![Codecov](https://codecov.io/gh/{% endraw %}{{ full_repo_url | replace("https://github.com/", "") }}{% raw %}/branch/main/graph/badge.svg)](https://codecov.io/gh/{% endraw %}{{ full_repo_url | replace("https://github.com/", "") }}{% raw %}){% endraw %}{% endif %}{% if create_docs %}{% raw %} [![Documentation Status](https://readthedocs.org/projects/{% endraw %}{{ package_name }}{% raw %}/badge/?version=latest)](https://{% endraw %}{{ package_name }}{% raw %}.readthedocs.io/en/latest/?badge=latest){% endraw %}{% endif %}{% raw %}{% endraw %}{% if is_open_source%}{% raw %} [![OpenIssues](https://isitmaintained.com/badge/open/{% endraw %}{{ repo_org_name }}/{{ repo_name }}{% raw %}.svg)](https://isitmaintained.com/project/{% endraw %}{{ repo_org_name }}/{{ repo_name }}{% raw %}){% endraw %}{% endif %}{% raw %} From 61d611f190e013d6dea94b35578156fbac43da76 Mon Sep 17 00:00:00 2001 From: Eli Fine Date: Mon, 15 Dec 2025 20:28:13 +0000 Subject: [PATCH 16/19] logging --- template/tests/unit/conftest.py.jinja | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/template/tests/unit/conftest.py.jinja b/template/tests/unit/conftest.py.jinja index 22b386ce..aa2b9108 100644 --- a/template/tests/unit/conftest.py.jinja +++ b/template/tests/unit/conftest.py.jinja @@ -11,6 +11,7 @@ logger = logging.getLogger(__name__) def pytest_configure( config: pytest.Config, # noqa: ARG001 # the config argument MUST be present (even when unused) or pytest throws an error ): + """Configuration for pytest itself, such as logging levels."""{% endraw %}{% if configure_vcrpy %}{% raw %} # force the vcr.cassette logger to WARNING+ because otherwise the logs get super noisy with the playback of all the cassettes vcr_logger = logging.getLogger("vcr.cassette") - vcr_logger.setLevel(logging.WARNING){% endraw %} + vcr_logger.setLevel(logging.WARNING){% endraw %}{% endif %}{% raw %}{% endraw %} From 4ef67b3af86ae7589d1fb38475ece5fc913b774f Mon Sep 17 00:00:00 2001 From: Eli Fine Date: Mon, 15 Dec 2025 20:29:29 +0000 Subject: [PATCH 17/19] docstring --- template/tests/unit/conftest.py.jinja | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/template/tests/unit/conftest.py.jinja b/template/tests/unit/conftest.py.jinja index aa2b9108..b78b5b9d 100644 --- a/template/tests/unit/conftest.py.jinja +++ b/template/tests/unit/conftest.py.jinja @@ -11,7 +11,7 @@ logger = logging.getLogger(__name__) def pytest_configure( config: pytest.Config, # noqa: ARG001 # the config argument MUST be present (even when unused) or pytest throws an error ): - """Configuration for pytest itself, such as logging levels."""{% endraw %}{% if configure_vcrpy %}{% raw %} + """Configure pytest itself, such as logging levels."""{% endraw %}{% if configure_vcrpy %}{% raw %} # force the vcr.cassette logger to WARNING+ because otherwise the logs get super noisy with the playback of all the cassettes vcr_logger = logging.getLogger("vcr.cassette") vcr_logger.setLevel(logging.WARNING){% endraw %}{% endif %}{% raw %}{% endraw %} From 52b80bc7e91a912ccc03f5628a7d4beb59b03de6 Mon Sep 17 00:00:00 2001 From: Eli Fine Date: Mon, 15 Dec 2025 20:30:58 +0000 Subject: [PATCH 18/19] raw --- template/tests/unit/conftest.py.jinja | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/template/tests/unit/conftest.py.jinja b/template/tests/unit/conftest.py.jinja index b78b5b9d..51d86abf 100644 --- a/template/tests/unit/conftest.py.jinja +++ b/template/tests/unit/conftest.py.jinja @@ -9,7 +9,7 @@ logger = logging.getLogger(__name__) def pytest_configure( - config: pytest.Config, # noqa: ARG001 # the config argument MUST be present (even when unused) or pytest throws an error + config: pytest.Config,{% endraw %}{% if not configure_vcrpy %}{% raw %} # noqa: ARG001 # the config argument MUST be present (even when unused) or pytest throws an error{% endraw %}{% endif %}{% raw %} ): """Configure pytest itself, such as logging levels."""{% endraw %}{% if configure_vcrpy %}{% raw %} # force the vcr.cassette logger to WARNING+ because otherwise the logs get super noisy with the playback of all the cassettes From 4d3b5bc83d7bfa1c5b10850b293909aa06eeab84 Mon Sep 17 00:00:00 2001 From: Eli Fine Date: Mon, 15 Dec 2025 20:32:19 +0000 Subject: [PATCH 19/19] nit --- template/tests/unit/conftest.py.jinja | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/template/tests/unit/conftest.py.jinja b/template/tests/unit/conftest.py.jinja index 51d86abf..6bc122ab 100644 --- a/template/tests/unit/conftest.py.jinja +++ b/template/tests/unit/conftest.py.jinja @@ -9,7 +9,7 @@ logger = logging.getLogger(__name__) def pytest_configure( - config: pytest.Config,{% endraw %}{% if not configure_vcrpy %}{% raw %} # noqa: ARG001 # the config argument MUST be present (even when unused) or pytest throws an error{% endraw %}{% endif %}{% raw %} + config: pytest.Config,{% endraw %}{% if configure_vcrpy %}{% raw %} # noqa: ARG001 # the config argument MUST be present (even when unused) or pytest throws an error{% endraw %}{% endif %}{% raw %} ): """Configure pytest itself, such as logging levels."""{% endraw %}{% if configure_vcrpy %}{% raw %} # force the vcr.cassette logger to WARNING+ because otherwise the logs get super noisy with the playback of all the cassettes