From 126118632b6a5466298958f6deb22250f8d35b10 Mon Sep 17 00:00:00 2001 From: Eli Fine Date: Mon, 3 Nov 2025 14:18:01 +0000 Subject: [PATCH 1/4] copier --- .coderabbit.yaml | 26 ++++++++ .copier-answers.yml | 2 +- .devcontainer/devcontainer.json | 6 +- .devcontainer/install-ci-tooling.py | 16 ++--- .devcontainer/manual-setup-deps.py | 37 ++++++++++- .github/actions/install_deps/action.yml | 11 +++- .../build-docker-image.yaml | 6 +- .github/workflows/ci.yaml | 2 +- .pre-commit-config.yaml | 24 ++++---- extensions/context.py | 61 ++++++++++--------- pyproject.toml | 4 +- pyrightconfig.json | 3 +- template/.coderabbit.yaml | 26 ++++++++ .../.devcontainer/devcontainer.json.jinja | 6 +- .../.devcontainer/install-ci-tooling.py.jinja | 10 +-- template/.devcontainer/manual-setup-deps.py | 37 ++++++++++- .../.github/actions/install_deps/action.yml | 11 +++- template/.pre-commit-config.yaml | 24 ++++---- template/pyrightconfig.json | 3 +- uv.lock | 16 ++--- 20 files changed, 219 insertions(+), 112 deletions(-) create mode 100644 .coderabbit.yaml create mode 100644 template/.coderabbit.yaml diff --git a/.coderabbit.yaml b/.coderabbit.yaml new file mode 100644 index 00000000..213afdd5 --- /dev/null +++ b/.coderabbit.yaml @@ -0,0 +1,26 @@ +# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json +reviews: + profile: assertive + path_instructions: + - path: "**/vendor_files/**" + instructions: "These files came from a vendor and we're not allowed to change them. Refer to it if you need to understand how the main code interacts with it, but do not make comments about it." + tools: + eslint: # when the code contains typescript, eslint will be run by pre-commit, and coderabbit often generates false positives + enabled: false + ruff: # when the code contains python, ruff will be run by pre-commit, and coderabbit often generates false positives + enabled: false + pylint: # when the code contains python, pylint will be run by pre-commit, and coderabbit often generates false positives + enabled: false + flake8: # we use ruff instead (when we use Python) + enabled: false + poem: false + # the commit status is driven by our repository config and required checks, we don't want CodeRabbit messing with it + commit_status: false + auto_review: + # a main purpose of opening a draft PR might be to get CodeRabbit feedback early + drafts: true + finishing_touches: + docstrings: + enabled: false # if we wanted AI to generate docstrings, it would be via CLI, not in the GitHub interface + unit_tests: + enabled: false # Quis custodiet ipsos custodes? not something we want AI doing, especially not via the GitHub interface diff --git a/.copier-answers.yml b/.copier-answers.yml index 6dce6305..ab61ea84 100644 --- a/.copier-answers.yml +++ b/.copier-answers.yml @@ -1,5 +1,5 @@ # Changes here will be overwritten by Copier -_commit: v0.0.77 +_commit: v0.0.80-8-g27328d4 _src_path: gh:LabAutomationAndScreening/copier-base-template.git description: Copier template for creating Python libraries and executables install_claude_cli: false diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 7a98cb14..d8fd4c11 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -24,8 +24,8 @@ "coderabbit.coderabbit-vscode@0.15.2", "ms-vscode.live-server@0.5.2025051301", "MS-vsliveshare.vsliveshare@1.0.5905", - "github.copilot@1.380.1802", - "github.copilot-chat@0.33.2025101401", + "github.copilot@1.388.0", + "github.copilot-chat@0.33.2025102701", // Python "ms-python.python@2025.17.2025100201", @@ -63,5 +63,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): a5dbeab3 # spellchecker:disable-line + // Devcontainer context hash (do not manually edit this, it's managed by a pre-commit hook): 2c365de4 # spellchecker:disable-line } diff --git a/.devcontainer/install-ci-tooling.py b/.devcontainer/install-ci-tooling.py index 5c6e684a..c208bbf8 100644 --- a/.devcontainer/install-ci-tooling.py +++ b/.devcontainer/install-ci-tooling.py @@ -7,9 +7,9 @@ import tempfile from pathlib import Path -UV_VERSION = "0.9.2" -PNPM_VERSION = "10.18.3" -COPIER_VERSION = "9.10.2" +UV_VERSION = "0.9.7" +PNPM_VERSION = "10.20.0" +COPIER_VERSION = "9.10.3" COPIER_TEMPLATE_EXTENSIONS_VERSION = "0.3.3" PRE_COMMIT_VERSION = "4.3.0" GITHUB_WINDOWS_RUNNER_BIN_PATH = r"C:\Users\runneradmin\.local\bin" @@ -36,21 +36,13 @@ default=False, help="Skip installing the SSM plugin for AWS CLI", ) -_ = parser.add_argument( - "--allow-uv-to-install-python", - action="store_true", - default=False, - help="Allow uv to install new versions of Python on the fly. This is typically only needed when instantiating the copier template.", -) def main(): args = parser.parse_args(sys.argv[1:]) is_windows = platform.system() == "Windows" uv_env = dict(os.environ) - uv_env.update({"UV_PYTHON": args.python_version}) - if not args.allow_uv_to_install_python: - uv_env.update({"UV_PYTHON_PREFERENCE": "only-system"}) + uv_env.update({"UV_PYTHON": args.python_version, "UV_PYTHON_PREFERENCE": "only-system"}) uv_path = ((GITHUB_WINDOWS_RUNNER_BIN_PATH + "\\") if is_windows else "") + "uv" if is_windows: pwsh = shutil.which("pwsh") or shutil.which("powershell") diff --git a/.devcontainer/manual-setup-deps.py b/.devcontainer/manual-setup-deps.py index ffc9f48a..e145d68b 100644 --- a/.devcontainer/manual-setup-deps.py +++ b/.devcontainer/manual-setup-deps.py @@ -15,8 +15,8 @@ _ = parser.add_argument( "--python-version", type=str, - default=f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}", - help="What version to install.", + default=None, + help="What version to install. This will override anything in .python-version files.", ) _ = parser.add_argument("--skip-check-lock", action="store_true", default=False, help="Skip the lock file check step") _ = parser.add_argument( @@ -34,6 +34,15 @@ _ = parser.add_argument( "--no-node", action="store_true", default=False, help="Do not process any environments using node package managers" ) +_ = parser.add_argument( + "--skip-updating-devcontainer-hash", action="store_true", default=False, help="Do not update the devcontainer hash" +) +_ = parser.add_argument( + "--allow-uv-to-install-python", + action="store_true", + default=False, + help="Allow uv to install new versions of Python on the fly. This is typically only needed when instantiating the copier template.", +) class PackageManager(str, enum.Enum): @@ -60,7 +69,8 @@ def main(): args = parser.parse_args(sys.argv[1:]) is_windows = platform.system() == "Windows" uv_env = dict(os.environ) - uv_env.update({"UV_PYTHON_PREFERENCE": "only-system", "UV_PYTHON": args.python_version}) + if not args.allow_uv_to_install_python: + uv_env.update({"UV_PYTHON_PREFERENCE": "only-system"}) generate_lock_file_only = args.only_create_lock check_lock_file = not (args.skip_check_lock or args.optionally_check_lock or generate_lock_file_only) if args.skip_check_lock and args.optionally_check_lock: @@ -78,6 +88,17 @@ def main(): if args.no_node and env.package_manager == PackageManager.PNPM: print(f"Skipping environment {env.path} as it uses a Node package manager and --no-node is set") continue + if env.package_manager == PackageManager.UV: + if args.python_version is not None: + uv_env.update({"UV_PYTHON": args.python_version}) + else: + python_version_path = env.lock_file.parent / ".python-version" + python_version_path_in_repo_root = REPO_ROOT_DIR / ".python-version" + if python_version_path.exists(): + uv_env.update({"UV_PYTHON": python_version_path.read_text().strip()}) + elif python_version_path_in_repo_root.exists(): + uv_env.update({"UV_PYTHON": python_version_path_in_repo_root.read_text().strip()}) + env_check_lock = check_lock_file if args.optionally_check_lock and env.lock_file.exists(): env_check_lock = True @@ -126,6 +147,16 @@ def main(): ) else: raise NotImplementedError(f"Package manager {env.package_manager} is not supported for installation") + if args.skip_updating_devcontainer_hash: + return + result = subprocess.run( # update the devcontainer hash after changing lock files + ["python3", ".github/workflows/hash_git_files.py", ".", "--for-devcontainer-config-update", "--exit-zero"], + capture_output=True, + text=True, + check=True, + cwd=REPO_ROOT_DIR, + ) + print(result.stdout) if __name__ == "__main__": diff --git a/.github/actions/install_deps/action.yml b/.github/actions/install_deps/action.yml index ddfe8b45..0e9f0206 100644 --- a/.github/actions/install_deps/action.yml +++ b/.github/actions/install_deps/action.yml @@ -39,6 +39,11 @@ inputs: type: string description: What region should the role use? required: false + skip-updating-devcontainer-hash: + type: boolean + description: Whether to skip updating the hash when running manual-setup-deps.py + default: true + required: false runs: @@ -59,7 +64,7 @@ runs: - name: Setup node if: ${{ inputs.node-version != 'notUsing' }} - uses: actions/setup-node@v5.0.0 + uses: actions/setup-node@v6.0.0 with: node-version: ${{ inputs.node-version }} @@ -70,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.0.0 + uses: aws-actions/configure-aws-credentials@v5.1.0 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 }} @@ -78,5 +83,5 @@ runs: - name: Install dependencies # the funky syntax is github action ternary if: ${{ inputs.install-deps }} - run: python .devcontainer/manual-setup-deps.py ${{ inputs.python-version == 'notUsing' && '--no-python' || '' }} ${{ inputs.node-version == 'notUsing' && '--no-node' || '' }} + run: python .devcontainer/manual-setup-deps.py ${{ inputs.python-version == 'notUsing' && '--no-python' || '' }} ${{ inputs.node-version == 'notUsing' && '--no-node' || '' }} ${{ inputs.skip-updating-devcontainer-hash && '--skip-updating-devcontainer-hash' || '' }} shell: pwsh diff --git a/.github/reusable_workflows/build-docker-image.yaml b/.github/reusable_workflows/build-docker-image.yaml index 3e0a15ee..cb73dbd3 100644 --- a/.github/reusable_workflows/build-docker-image.yaml +++ b/.github/reusable_workflows/build-docker-image.yaml @@ -71,7 +71,7 @@ jobs: - name: OIDC Auth for ECR if: ${{ inputs.push-role-name != 'no-push' }} - uses: aws-actions/configure-aws-credentials@v5.0.0 + uses: aws-actions/configure-aws-credentials@v5.1.0 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 }} @@ -128,7 +128,7 @@ jobs: - name: Build Docker Image if: ${{ (inputs.save-as-artifact && inputs.push-role-name == 'no-push') || steps.check-if-exists.outputs.status == 'notfound' }} - uses: docker/build-push-action@v6.16.0 + uses: docker/build-push-action@v6.18.0 with: context: ${{ inputs.context }} push: ${{ inputs.push-role-name != 'no-push' && steps.check-if-exists.outputs.status == 'notfound' }} @@ -155,7 +155,7 @@ jobs: - name: Upload Docker Image Artifact if: ${{ inputs.save-as-artifact }} - uses: actions/upload-artifact@v4.6.2 + uses: actions/upload-artifact@v5.0.0 with: name: ${{ steps.calculate-build-context-hash.outputs.image_name_no_slashes }} path: ${{ steps.calculate-build-context-hash.outputs.image_name_no_slashes }}.tar diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index fc82d003..0c92e04b 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -97,7 +97,7 @@ jobs: run: | # Remove any specification of a Python repository having a default other than PyPI...because in this CI pipeline we can only install from PyPI python $RUNNER_TEMP/replace_private_package_registries.py - python .devcontainer/manual-setup-deps.py --skip-check-lock + python .devcontainer/manual-setup-deps.py --skip-check-lock --skip-updating-devcontainer-hash # Add everything to git so that pre-commit recognizes the files and runs on them git add . git status diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 66531bc6..22ab55ae 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -59,7 +59,7 @@ repos: (?x)^( .*/vendor_files/.*| .*tests/.*/__snapshots__/.*| - .*/openapi_codegen/.*| + .*/generated/open[-_]api/.*| )$ - id: end-of-file-fixer # the XML formatter hook doesn't leave a blank line at the end, so excluding XML files from this hook to avoid conflicts @@ -71,7 +71,7 @@ repos: template/template/.copier-answers.yml.jinja| template/.copier-answers.yml.jinja| .*generated/graphql.ts| - .*/openapi_codegen/.*| + .*/generated/open[-_]api/.*| .*tests/.*/__snapshots__/.*| .devcontainer/devcontainer-lock.json| .copier-answers.yml| @@ -88,7 +88,7 @@ repos: .*pyrightconfig.json| .*tsconfig.json| .*biome.jsonc| - .*/openapi_codegen/.*| + .*/generated/open[-_]api/.*| .*tests/.*/__snapshots__/.*| .*/vendor_files/.*| )$ @@ -102,7 +102,7 @@ repos: exclude: | (?x)^( .*generated/graphql.ts| - .*/openapi_codegen/.*| + .*/generated/open[-_]api/.*| .*/schema.graphql| .*pyrightconfig\.json| )$ @@ -129,7 +129,7 @@ repos: .*.jsonc| .*/vendor_files/.*| .*/schema.graphql| - .*/openapi_codegen/.*| + .*/generated/open[-_]api/.*| .*generated/graphql.ts| template/.*| )$ @@ -243,7 +243,7 @@ repos: description: Runs hadolint to lint Dockerfiles - repo: https://github.com/astral-sh/ruff-pre-commit - rev: f9351c924055bf6c7b4a4670237d3ce141e9f57c # frozen: v0.14.0 + rev: 3db93a2be6f214ed722bf7bce095ec1b1715422a # frozen: v0.14.2 hooks: - id: ruff name: ruff-src @@ -252,7 +252,7 @@ repos: exclude: | (?x)^( .*/graphql_codegen/.*| - .*/openapi_codegen/.*| + .*/generated/open[-_]api/.*| template/.*| )$ - id: ruff @@ -262,18 +262,18 @@ repos: exclude: | (?x)^( .*/graphql_codegen/.*| - .*/openapi_codegen/.*| + .*/generated/open[-_]api/.*| template/.*| )$ - id: ruff-format exclude: | (?x)^( .*/graphql_codegen/.*| - .*/openapi_codegen/.*| + .*/generated/open[-_]api/.*| )$ - repo: https://github.com/pylint-dev/pylint - rev: 9a3035053154ba0c3ca3b300d6bc9fa72b95d552 # frozen: v4.0.1 + rev: 0eb92d25fd38ba5bad2f8d2ea7df63ad23e18ae3 # frozen: v4.0.2 hooks: - id: pylint name: pylint @@ -281,7 +281,7 @@ repos: exclude: | (?x)^( .*/graphql_codegen/.*| - .*/openapi_codegen/.*| + .*/generated/open[-_]api/.*| template/.*| )$ args: @@ -297,7 +297,7 @@ repos: exclude: | (?x)^( .*/graphql_codegen/.*| - .*/openapi_codegen/.*| + .*/generated/open[-_]api/.*| )$ # don't pass filenames else the command line sees them twice pass_filenames: false diff --git a/extensions/context.py b/extensions/context.py index ab7081a1..0490f7b9 100644 --- a/extensions/context.py +++ b/extensions/context.py @@ -10,62 +10,63 @@ class ContextUpdater(ContextHook): @override def hook(self, context: dict[Any, Any]) -> dict[Any, Any]: - context["uv_version"] = "0.9.2" - context["pnpm_version"] = "10.18.3" + context["uv_version"] = "0.9.7" + context["pnpm_version"] = "10.20.0" context["pre_commit_version"] = "4.3.0" - context["pyright_version"] = "1.1.406" + context["pyright_version"] = "1.1.407" context["pytest_version"] = "8.4.2" context["pytest_randomly_version"] = "4.0.1" context["pytest_cov_version"] = "7.0.0" - context["copier_version"] = "9.10.2" + context["copier_version"] = "9.10.3" context["copier_template_extensions_version"] = "0.3.3" context["sphinx_version"] = "8.1.3" - context["pulumi_version"] = "3.203.0" - context["pulumi_aws_version"] = "7.8.0" + context["pulumi_version"] = "3.205.0" + context["pulumi_aws_version"] = "7.10.0" context["pulumi_aws_native_version"] = "1.37.0" context["pulumi_command_version"] = "1.1.3" - context["pulumi_github_version"] = "6.7.4" - context["pulumi_okta_version"] = "6.0.0" - context["boto3_version"] = "1.40.51" + context["pulumi_github_version"] = "6.8.0" + context["pulumi_okta_version"] = "6.1.0" + context["boto3_version"] = "1.40.57" context["ephemeral_pulumi_deploy_version"] = "0.0.5" - context["pydantic_version"] = "2.12.2" + context["pydantic_version"] = "2.12.3" context["pyinstaller_version"] = "6.16.0" context["setuptools_version"] = "80.7.1" - context["strawberry_graphql_version"] = "0.283.3" - context["fastapi_version"] = "0.119.0" + context["strawberry_graphql_version"] = "0.284.1" + context["fastapi_version"] = "0.121.0" context["fastapi_offline_version"] = "1.7.4" - context["uvicorn_version"] = "0.37.0" + context["uvicorn_version"] = "0.38.0" context["lab_auto_pulumi_version"] = "0.1.17" context["ariadne_codegen_version"] = "0.15.2" 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["node_version"] = "24.7.0" - context["nuxt_ui_version"] = "^4.0.1" - context["nuxt_version"] = "^4.1.0" - context["nuxt_icon_version"] = "^2.0.0" + context["nuxt_ui_version"] = "^4.1.0" + context["nuxt_version"] = "^4.2.0" + context["nuxt_icon_version"] = "^2.1.0" context["typescript_version"] = "^5.9.3" context["playwright_version"] = "^1.56.0" - context["vue_version"] = "^3.5.21" - context["vue_tsc_version"] = "^3.1.1" + context["vue_version"] = "^3.5.22" + context["vue_tsc_version"] = "^3.1.2" context["vue_devtools_api_version"] = "^8.0.0" context["vue_router_version"] = "^4.6.0" - context["dotenv_cli_version"] = "^10.0.0" + context["dotenv_cli_version"] = "^11.0.0" context["faker_version"] = "^10.1.0" context["vitest_version"] = "^3.2.4" - context["eslint_version"] = "^9.37.0" - context["nuxt_eslint_version"] = "^1.9.0" - context["zod_version"] = "^4.1.5" - context["zod_from_json_schema_version"] = "^0.5.0" - context["types_node_version"] = "^24.7.2" + context["eslint_version"] = "~9.38.0" + 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.9.2" 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["tailwindcss_version"] = "^4.1.11" context["iconify_vue_version"] = "^5.0.0" - context["iconify_json_lucide_version"] = "^1.2.68" + context["iconify_json_lucide_version"] = "^1.2.71" context["nuxt_fonts_version"] = "^0.11.4" context["nuxtjs_color_mode_version"] = "^3.5.2" context["vue_test_utils_version"] = "^2.4.6" @@ -77,15 +78,15 @@ def hook(self, context: dict[Any, Any]) -> dict[Any, Any]: context["gha_checkout"] = "v5.0.0" context["gha_setup_python"] = "v6.0.0" context["gha_cache"] = "v4.2.4" - context["gha_upload_artifact"] = "v4.6.2" - context["gha_download_artifact"] = "v5.0.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.16.0" - context["gha_configure_aws_credentials"] = "v5.0.0" + context["gha_docker_build_push"] = "v6.18.0" + context["gha_configure_aws_credentials"] = "v5.1.0" context["gha_amazon_ecr_login"] = "v2.0.1" - context["gha_setup_node"] = "v5.0.0" + context["gha_setup_node"] = "v6.0.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" diff --git a/pyproject.toml b/pyproject.toml index 6e0f8e16..30bbf2ec 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,8 +9,8 @@ dependencies = [ "pytest>=8.4.2", "pytest-cov>=7.0.0", "pytest-randomly>=4.0.1", - "pyright[nodejs]>=1.1.406", - "copier>=9.10.2", + "pyright[nodejs]>=1.1.407", + "copier>=9.10.3", "copier-template-extensions>=0.3.3" # Specific to this template diff --git a/pyrightconfig.json b/pyrightconfig.json index b817e638..1e26d9d5 100644 --- a/pyrightconfig.json +++ b/pyrightconfig.json @@ -11,7 +11,7 @@ "**/__pycache__", "**/vendor_files", "**/graphql_codegen", - "**/openapi_codegen", + "**/generated/open_api", "**/.venv", "**/venv" ], @@ -99,7 +99,6 @@ "reportUnnecessaryTypeIgnoreComment": true, "reportMatchNotExhaustive": true, "reportImplicitOverride": true, - "reportShadowedImports": true, "pythonPlatform": "Linux", "executionEnvironments": [ { diff --git a/template/.coderabbit.yaml b/template/.coderabbit.yaml new file mode 100644 index 00000000..213afdd5 --- /dev/null +++ b/template/.coderabbit.yaml @@ -0,0 +1,26 @@ +# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json +reviews: + profile: assertive + path_instructions: + - path: "**/vendor_files/**" + instructions: "These files came from a vendor and we're not allowed to change them. Refer to it if you need to understand how the main code interacts with it, but do not make comments about it." + tools: + eslint: # when the code contains typescript, eslint will be run by pre-commit, and coderabbit often generates false positives + enabled: false + ruff: # when the code contains python, ruff will be run by pre-commit, and coderabbit often generates false positives + enabled: false + pylint: # when the code contains python, pylint will be run by pre-commit, and coderabbit often generates false positives + enabled: false + flake8: # we use ruff instead (when we use Python) + enabled: false + poem: false + # the commit status is driven by our repository config and required checks, we don't want CodeRabbit messing with it + commit_status: false + auto_review: + # a main purpose of opening a draft PR might be to get CodeRabbit feedback early + drafts: true + finishing_touches: + docstrings: + enabled: false # if we wanted AI to generate docstrings, it would be via CLI, not in the GitHub interface + unit_tests: + enabled: false # Quis custodiet ipsos custodes? not something we want AI doing, especially not via the GitHub interface diff --git a/template/.devcontainer/devcontainer.json.jinja b/template/.devcontainer/devcontainer.json.jinja index 123279c8..5d5b73f3 100644 --- a/template/.devcontainer/devcontainer.json.jinja +++ b/template/.devcontainer/devcontainer.json.jinja @@ -31,9 +31,9 @@ "coderabbit.coderabbit-vscode@0.15.2", "ms-vscode.live-server@0.5.2025051301", "MS-vsliveshare.vsliveshare@1.0.5905", - "github.copilot@1.380.1802", - "github.copilot-chat@0.33.2025101401",{% endraw %}{% if install_claude_cli %}{% raw %} - "anthropic.claude-code@2.0.10",{% endraw %}{% endif %}{% raw %} + "github.copilot@1.388.0", + "github.copilot-chat@0.33.2025102701",{% endraw %}{% if install_claude_cli %}{% raw %} + "anthropic.claude-code@2.0.27",{% endraw %}{% endif %}{% raw %} // Python "ms-python.python@2025.17.2025100201", diff --git a/template/.devcontainer/install-ci-tooling.py.jinja b/template/.devcontainer/install-ci-tooling.py.jinja index 0623c0c9..d0e4d0db 100644 --- a/template/.devcontainer/install-ci-tooling.py.jinja +++ b/template/.devcontainer/install-ci-tooling.py.jinja @@ -36,21 +36,13 @@ _ = parser.add_argument( default=False, help="Skip installing the SSM plugin for AWS CLI", ) -_ = parser.add_argument( - "--allow-uv-to-install-python", - action="store_true", - default=False, - help="Allow uv to install new versions of Python on the fly. This is typically only needed when instantiating the copier template.", -) def main(): args = parser.parse_args(sys.argv[1:]) is_windows = platform.system() == "Windows" uv_env = dict(os.environ) - uv_env.update({"UV_PYTHON": args.python_version}) - if not args.allow_uv_to_install_python: - uv_env.update({"UV_PYTHON_PREFERENCE": "only-system"}) + uv_env.update({"UV_PYTHON": args.python_version, "UV_PYTHON_PREFERENCE": "only-system"}) uv_path = ((GITHUB_WINDOWS_RUNNER_BIN_PATH + "\\") if is_windows else "") + "uv" if is_windows: pwsh = shutil.which("pwsh") or shutil.which("powershell") diff --git a/template/.devcontainer/manual-setup-deps.py b/template/.devcontainer/manual-setup-deps.py index ffc9f48a..e145d68b 100644 --- a/template/.devcontainer/manual-setup-deps.py +++ b/template/.devcontainer/manual-setup-deps.py @@ -15,8 +15,8 @@ _ = parser.add_argument( "--python-version", type=str, - default=f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}", - help="What version to install.", + default=None, + help="What version to install. This will override anything in .python-version files.", ) _ = parser.add_argument("--skip-check-lock", action="store_true", default=False, help="Skip the lock file check step") _ = parser.add_argument( @@ -34,6 +34,15 @@ _ = parser.add_argument( "--no-node", action="store_true", default=False, help="Do not process any environments using node package managers" ) +_ = parser.add_argument( + "--skip-updating-devcontainer-hash", action="store_true", default=False, help="Do not update the devcontainer hash" +) +_ = parser.add_argument( + "--allow-uv-to-install-python", + action="store_true", + default=False, + help="Allow uv to install new versions of Python on the fly. This is typically only needed when instantiating the copier template.", +) class PackageManager(str, enum.Enum): @@ -60,7 +69,8 @@ def main(): args = parser.parse_args(sys.argv[1:]) is_windows = platform.system() == "Windows" uv_env = dict(os.environ) - uv_env.update({"UV_PYTHON_PREFERENCE": "only-system", "UV_PYTHON": args.python_version}) + if not args.allow_uv_to_install_python: + uv_env.update({"UV_PYTHON_PREFERENCE": "only-system"}) generate_lock_file_only = args.only_create_lock check_lock_file = not (args.skip_check_lock or args.optionally_check_lock or generate_lock_file_only) if args.skip_check_lock and args.optionally_check_lock: @@ -78,6 +88,17 @@ def main(): if args.no_node and env.package_manager == PackageManager.PNPM: print(f"Skipping environment {env.path} as it uses a Node package manager and --no-node is set") continue + if env.package_manager == PackageManager.UV: + if args.python_version is not None: + uv_env.update({"UV_PYTHON": args.python_version}) + else: + python_version_path = env.lock_file.parent / ".python-version" + python_version_path_in_repo_root = REPO_ROOT_DIR / ".python-version" + if python_version_path.exists(): + uv_env.update({"UV_PYTHON": python_version_path.read_text().strip()}) + elif python_version_path_in_repo_root.exists(): + uv_env.update({"UV_PYTHON": python_version_path_in_repo_root.read_text().strip()}) + env_check_lock = check_lock_file if args.optionally_check_lock and env.lock_file.exists(): env_check_lock = True @@ -126,6 +147,16 @@ def main(): ) else: raise NotImplementedError(f"Package manager {env.package_manager} is not supported for installation") + if args.skip_updating_devcontainer_hash: + return + result = subprocess.run( # update the devcontainer hash after changing lock files + ["python3", ".github/workflows/hash_git_files.py", ".", "--for-devcontainer-config-update", "--exit-zero"], + capture_output=True, + text=True, + check=True, + cwd=REPO_ROOT_DIR, + ) + print(result.stdout) if __name__ == "__main__": diff --git a/template/.github/actions/install_deps/action.yml b/template/.github/actions/install_deps/action.yml index ddfe8b45..0e9f0206 100644 --- a/template/.github/actions/install_deps/action.yml +++ b/template/.github/actions/install_deps/action.yml @@ -39,6 +39,11 @@ inputs: type: string description: What region should the role use? required: false + skip-updating-devcontainer-hash: + type: boolean + description: Whether to skip updating the hash when running manual-setup-deps.py + default: true + required: false runs: @@ -59,7 +64,7 @@ runs: - name: Setup node if: ${{ inputs.node-version != 'notUsing' }} - uses: actions/setup-node@v5.0.0 + uses: actions/setup-node@v6.0.0 with: node-version: ${{ inputs.node-version }} @@ -70,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.0.0 + uses: aws-actions/configure-aws-credentials@v5.1.0 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 }} @@ -78,5 +83,5 @@ runs: - name: Install dependencies # the funky syntax is github action ternary if: ${{ inputs.install-deps }} - run: python .devcontainer/manual-setup-deps.py ${{ inputs.python-version == 'notUsing' && '--no-python' || '' }} ${{ inputs.node-version == 'notUsing' && '--no-node' || '' }} + run: python .devcontainer/manual-setup-deps.py ${{ inputs.python-version == 'notUsing' && '--no-python' || '' }} ${{ inputs.node-version == 'notUsing' && '--no-node' || '' }} ${{ inputs.skip-updating-devcontainer-hash && '--skip-updating-devcontainer-hash' || '' }} shell: pwsh diff --git a/template/.pre-commit-config.yaml b/template/.pre-commit-config.yaml index 66531bc6..22ab55ae 100644 --- a/template/.pre-commit-config.yaml +++ b/template/.pre-commit-config.yaml @@ -59,7 +59,7 @@ repos: (?x)^( .*/vendor_files/.*| .*tests/.*/__snapshots__/.*| - .*/openapi_codegen/.*| + .*/generated/open[-_]api/.*| )$ - id: end-of-file-fixer # the XML formatter hook doesn't leave a blank line at the end, so excluding XML files from this hook to avoid conflicts @@ -71,7 +71,7 @@ repos: template/template/.copier-answers.yml.jinja| template/.copier-answers.yml.jinja| .*generated/graphql.ts| - .*/openapi_codegen/.*| + .*/generated/open[-_]api/.*| .*tests/.*/__snapshots__/.*| .devcontainer/devcontainer-lock.json| .copier-answers.yml| @@ -88,7 +88,7 @@ repos: .*pyrightconfig.json| .*tsconfig.json| .*biome.jsonc| - .*/openapi_codegen/.*| + .*/generated/open[-_]api/.*| .*tests/.*/__snapshots__/.*| .*/vendor_files/.*| )$ @@ -102,7 +102,7 @@ repos: exclude: | (?x)^( .*generated/graphql.ts| - .*/openapi_codegen/.*| + .*/generated/open[-_]api/.*| .*/schema.graphql| .*pyrightconfig\.json| )$ @@ -129,7 +129,7 @@ repos: .*.jsonc| .*/vendor_files/.*| .*/schema.graphql| - .*/openapi_codegen/.*| + .*/generated/open[-_]api/.*| .*generated/graphql.ts| template/.*| )$ @@ -243,7 +243,7 @@ repos: description: Runs hadolint to lint Dockerfiles - repo: https://github.com/astral-sh/ruff-pre-commit - rev: f9351c924055bf6c7b4a4670237d3ce141e9f57c # frozen: v0.14.0 + rev: 3db93a2be6f214ed722bf7bce095ec1b1715422a # frozen: v0.14.2 hooks: - id: ruff name: ruff-src @@ -252,7 +252,7 @@ repos: exclude: | (?x)^( .*/graphql_codegen/.*| - .*/openapi_codegen/.*| + .*/generated/open[-_]api/.*| template/.*| )$ - id: ruff @@ -262,18 +262,18 @@ repos: exclude: | (?x)^( .*/graphql_codegen/.*| - .*/openapi_codegen/.*| + .*/generated/open[-_]api/.*| template/.*| )$ - id: ruff-format exclude: | (?x)^( .*/graphql_codegen/.*| - .*/openapi_codegen/.*| + .*/generated/open[-_]api/.*| )$ - repo: https://github.com/pylint-dev/pylint - rev: 9a3035053154ba0c3ca3b300d6bc9fa72b95d552 # frozen: v4.0.1 + rev: 0eb92d25fd38ba5bad2f8d2ea7df63ad23e18ae3 # frozen: v4.0.2 hooks: - id: pylint name: pylint @@ -281,7 +281,7 @@ repos: exclude: | (?x)^( .*/graphql_codegen/.*| - .*/openapi_codegen/.*| + .*/generated/open[-_]api/.*| template/.*| )$ args: @@ -297,7 +297,7 @@ repos: exclude: | (?x)^( .*/graphql_codegen/.*| - .*/openapi_codegen/.*| + .*/generated/open[-_]api/.*| )$ # don't pass filenames else the command line sees them twice pass_filenames: false diff --git a/template/pyrightconfig.json b/template/pyrightconfig.json index b817e638..1e26d9d5 100644 --- a/template/pyrightconfig.json +++ b/template/pyrightconfig.json @@ -11,7 +11,7 @@ "**/__pycache__", "**/vendor_files", "**/graphql_codegen", - "**/openapi_codegen", + "**/generated/open_api", "**/.venv", "**/venv" ], @@ -99,7 +99,6 @@ "reportUnnecessaryTypeIgnoreComment": true, "reportMatchNotExhaustive": true, "reportImplicitOverride": true, - "reportShadowedImports": true, "pythonPlatform": "Linux", "executionEnvironments": [ { diff --git a/uv.lock b/uv.lock index 8c21b321..04a4b0c6 100644 --- a/uv.lock +++ b/uv.lock @@ -22,7 +22,7 @@ wheels = [ [[package]] name = "copier" -version = "9.10.2" +version = "9.10.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama" }, @@ -39,9 +39,9 @@ dependencies = [ { name = "pyyaml" }, { name = "questionary" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fc/88/11f7279c994526855576c3d1860fb9bfb950baf4d545b172089352a806de/copier-9.10.2.tar.gz", hash = "sha256:495159b796bc745a90238b2097053555f9e4283efc7181ee3b5402a5cbe21614", size = 586648, upload-time = "2025-09-09T19:36:09.381Z" } +sdist = { url = "https://files.pythonhosted.org/packages/82/c6/1f61fe2b011347c0b9d7637d02ef3dc1c2874640a4fd5fb059ea8b5493f2/copier-9.10.3.tar.gz", hash = "sha256:6e965d8f719678ee3bc5e611ef0d1b182d6b01a3d5385a5f1ba43aaade51caf7", size = 598697, upload-time = "2025-10-17T18:26:04.217Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/56/10/402af0f5db39e62cbae4bff55abad1ac9547a63430120685de4f3383d7fb/copier-9.10.2-py3-none-any.whl", hash = "sha256:85b37839e2bac93b8b48d17dc438f362fdf8787a07d821d8263a427e8be8a522", size = 56001, upload-time = "2025-09-09T19:36:08.083Z" }, + { url = "https://files.pythonhosted.org/packages/ed/f2/58ee35dbd55d0ffa13197365ec4725c36da2f7ec72eaa1dd154bfbd2676e/copier-9.10.3-py3-none-any.whl", hash = "sha256:7165239566f68e9e36c148f71e0552bf1e4911eef96bfa7cf33e6a0dbfed3c96", size = 56186, upload-time = "2025-10-17T18:26:02.856Z" }, ] [[package]] @@ -59,9 +59,9 @@ dependencies = [ [package.metadata] requires-dist = [ - { name = "copier", specifier = ">=9.10.2" }, + { name = "copier", specifier = ">=9.10.3" }, { name = "copier-template-extensions", specifier = ">=0.3.3" }, - { name = "pyright", extras = ["nodejs"], specifier = ">=1.1.406" }, + { name = "pyright", extras = ["nodejs"], specifier = ">=1.1.407" }, { name = "pytest", specifier = ">=8.4.2" }, { name = "pytest-cov", specifier = ">=7.0.0" }, { name = "pytest-randomly", specifier = ">=4.0.1" }, @@ -384,15 +384,15 @@ wheels = [ [[package]] name = "pyright" -version = "1.1.406" +version = "1.1.407" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "nodeenv" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f7/16/6b4fbdd1fef59a0292cbb99f790b44983e390321eccbc5921b4d161da5d1/pyright-1.1.406.tar.gz", hash = "sha256:c4872bc58c9643dac09e8a2e74d472c62036910b3bd37a32813989ef7576ea2c", size = 4113151, upload-time = "2025-10-02T01:04:45.488Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/1b/0aa08ee42948b61745ac5b5b5ccaec4669e8884b53d31c8ec20b2fcd6b6f/pyright-1.1.407.tar.gz", hash = "sha256:099674dba5c10489832d4a4b2d302636152a9a42d317986c38474c76fe562262", size = 4122872, upload-time = "2025-10-24T23:17:15.145Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f6/a2/e309afbb459f50507103793aaef85ca4348b66814c86bc73908bdeb66d12/pyright-1.1.406-py3-none-any.whl", hash = "sha256:1d81fb43c2407bf566e97e57abb01c811973fdb21b2df8df59f870f688bdca71", size = 5980982, upload-time = "2025-10-02T01:04:43.137Z" }, + { url = "https://files.pythonhosted.org/packages/dc/93/b69052907d032b00c40cb656d21438ec00b3a471733de137a3f65a49a0a0/pyright-1.1.407-py3-none-any.whl", hash = "sha256:6dd419f54fcc13f03b52285796d65e639786373f433e243f8b94cf93a7444d21", size = 5997008, upload-time = "2025-10-24T23:17:13.159Z" }, ] [package.optional-dependencies] From d38c07a85e715d7043208464c54f8fbffbea9402 Mon Sep 17 00:00:00 2001 From: Eli Fine Date: Mon, 3 Nov 2025 14:25:01 +0000 Subject: [PATCH 2/4] more copier --- .copier-answers.yml | 2 +- .devcontainer/devcontainer.json | 2 +- .devcontainer/manual-setup-deps.py | 5 +++-- template/.devcontainer/manual-setup-deps.py | 5 +++-- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.copier-answers.yml b/.copier-answers.yml index ab61ea84..7ce6f29a 100644 --- a/.copier-answers.yml +++ b/.copier-answers.yml @@ -1,5 +1,5 @@ # Changes here will be overwritten by Copier -_commit: v0.0.80-8-g27328d4 +_commit: v0.0.80-9-g7d13d91 _src_path: gh:LabAutomationAndScreening/copier-base-template.git description: Copier template for creating Python libraries and executables install_claude_cli: false diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index d8fd4c11..66ed3a9a 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -63,5 +63,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): 2c365de4 # spellchecker:disable-line + // Devcontainer context hash (do not manually edit this, it's managed by a pre-commit hook): f350a1b4 # spellchecker:disable-line } diff --git a/.devcontainer/manual-setup-deps.py b/.devcontainer/manual-setup-deps.py index e145d68b..f4d04195 100644 --- a/.devcontainer/manual-setup-deps.py +++ b/.devcontainer/manual-setup-deps.py @@ -11,12 +11,13 @@ REPO_ROOT_DIR = Path(__file__).parent.parent.resolve() ENVS_CONFIG = REPO_ROOT_DIR / ".devcontainer" / "envs.json" +UV_PYTHON_ALREADY_CONFIGURED = "UV_PYTHON" in os.environ parser = argparse.ArgumentParser(description="Manual setup for dependencies in the repo") _ = parser.add_argument( "--python-version", type=str, default=None, - help="What version to install. This will override anything in .python-version files.", + help="What version to install. This will override anything in .python-version files. But if the UV_PYTHON envvar is set prior to starting the script, that will take precedence over everything.", ) _ = parser.add_argument("--skip-check-lock", action="store_true", default=False, help="Skip the lock file check step") _ = parser.add_argument( @@ -88,7 +89,7 @@ def main(): if args.no_node and env.package_manager == PackageManager.PNPM: print(f"Skipping environment {env.path} as it uses a Node package manager and --no-node is set") continue - if env.package_manager == PackageManager.UV: + if env.package_manager == PackageManager.UV and not UV_PYTHON_ALREADY_CONFIGURED: if args.python_version is not None: uv_env.update({"UV_PYTHON": args.python_version}) else: diff --git a/template/.devcontainer/manual-setup-deps.py b/template/.devcontainer/manual-setup-deps.py index e145d68b..f4d04195 100644 --- a/template/.devcontainer/manual-setup-deps.py +++ b/template/.devcontainer/manual-setup-deps.py @@ -11,12 +11,13 @@ REPO_ROOT_DIR = Path(__file__).parent.parent.resolve() ENVS_CONFIG = REPO_ROOT_DIR / ".devcontainer" / "envs.json" +UV_PYTHON_ALREADY_CONFIGURED = "UV_PYTHON" in os.environ parser = argparse.ArgumentParser(description="Manual setup for dependencies in the repo") _ = parser.add_argument( "--python-version", type=str, default=None, - help="What version to install. This will override anything in .python-version files.", + help="What version to install. This will override anything in .python-version files. But if the UV_PYTHON envvar is set prior to starting the script, that will take precedence over everything.", ) _ = parser.add_argument("--skip-check-lock", action="store_true", default=False, help="Skip the lock file check step") _ = parser.add_argument( @@ -88,7 +89,7 @@ def main(): if args.no_node and env.package_manager == PackageManager.PNPM: print(f"Skipping environment {env.path} as it uses a Node package manager and --no-node is set") continue - if env.package_manager == PackageManager.UV: + if env.package_manager == PackageManager.UV and not UV_PYTHON_ALREADY_CONFIGURED: if args.python_version is not None: uv_env.update({"UV_PYTHON": args.python_version}) else: From 8c612f687bf86d7ae1a9f90de758f86eb8047880 Mon Sep 17 00:00:00 2001 From: Eli Fine Date: Mon, 3 Nov 2025 14:47:48 +0000 Subject: [PATCH 3/4] copier --- .copier-answers.yml | 2 +- .devcontainer/devcontainer.json | 2 +- .devcontainer/manual-setup-deps.py | 2 +- template/.devcontainer/manual-setup-deps.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.copier-answers.yml b/.copier-answers.yml index 7ce6f29a..d76020e0 100644 --- a/.copier-answers.yml +++ b/.copier-answers.yml @@ -1,5 +1,5 @@ # Changes here will be overwritten by Copier -_commit: v0.0.80-9-g7d13d91 +_commit: v0.0.80-10-gbbb2ef4 _src_path: gh:LabAutomationAndScreening/copier-base-template.git description: Copier template for creating Python libraries and executables install_claude_cli: false diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 66ed3a9a..ac94b75c 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -63,5 +63,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): f350a1b4 # spellchecker:disable-line + // Devcontainer context hash (do not manually edit this, it's managed by a pre-commit hook): ac3ca44a # spellchecker:disable-line } diff --git a/.devcontainer/manual-setup-deps.py b/.devcontainer/manual-setup-deps.py index f4d04195..6f6fe0d8 100644 --- a/.devcontainer/manual-setup-deps.py +++ b/.devcontainer/manual-setup-deps.py @@ -151,7 +151,7 @@ def main(): if args.skip_updating_devcontainer_hash: return result = subprocess.run( # update the devcontainer hash after changing lock files - ["python3", ".github/workflows/hash_git_files.py", ".", "--for-devcontainer-config-update", "--exit-zero"], + [sys.executable, ".github/workflows/hash_git_files.py", ".", "--for-devcontainer-config-update", "--exit-zero"], capture_output=True, text=True, check=True, diff --git a/template/.devcontainer/manual-setup-deps.py b/template/.devcontainer/manual-setup-deps.py index f4d04195..6f6fe0d8 100644 --- a/template/.devcontainer/manual-setup-deps.py +++ b/template/.devcontainer/manual-setup-deps.py @@ -151,7 +151,7 @@ def main(): if args.skip_updating_devcontainer_hash: return result = subprocess.run( # update the devcontainer hash after changing lock files - ["python3", ".github/workflows/hash_git_files.py", ".", "--for-devcontainer-config-update", "--exit-zero"], + [sys.executable, ".github/workflows/hash_git_files.py", ".", "--for-devcontainer-config-update", "--exit-zero"], capture_output=True, text=True, check=True, From 48096ef6c4330900d6439adc3c2ffef7b7fcae6b Mon Sep 17 00:00:00 2001 From: Eli Fine Date: Mon, 3 Nov 2025 15:02:14 +0000 Subject: [PATCH 4/4] tag --- .copier-answers.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.copier-answers.yml b/.copier-answers.yml index d76020e0..a3eb2264 100644 --- a/.copier-answers.yml +++ b/.copier-answers.yml @@ -1,5 +1,5 @@ # Changes here will be overwritten by Copier -_commit: v0.0.80-10-gbbb2ef4 +_commit: v0.0.81 _src_path: gh:LabAutomationAndScreening/copier-base-template.git description: Copier template for creating Python libraries and executables install_claude_cli: false