From de723b7b595f34d83a81e627d06ed5a47b3eeb64 Mon Sep 17 00:00:00 2001 From: Kyle Oliver Date: Fri, 23 May 2025 20:50:08 -0400 Subject: [PATCH 01/31] fix: adjust path to .ruff.toml in .pre-commit-config.yaml --- {{cookiecutter.project_name}}/.pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/{{cookiecutter.project_name}}/.pre-commit-config.yaml b/{{cookiecutter.project_name}}/.pre-commit-config.yaml index 8556ff9..120eac3 100644 --- a/{{cookiecutter.project_name}}/.pre-commit-config.yaml +++ b/{{cookiecutter.project_name}}/.pre-commit-config.yaml @@ -17,7 +17,7 @@ repos: rev: v0.3.5 hooks: - id: ruff-format - args: [--config={{ cookiecutter.project_name }}/.ruff.toml] + args: [--config=.ruff.toml] - id: ruff - args: [--fix, --exit-non-zero-on-fix, --config={{ cookiecutter.project_name }}/.ruff.toml] + args: [--fix, --exit-non-zero-on-fix, --config=.ruff.toml] From e12c92d325023eae608ed5285509346ef52682e7 Mon Sep 17 00:00:00 2001 From: Kyle Oliver Date: Fri, 23 May 2025 20:52:27 -0400 Subject: [PATCH 02/31] feat: update version of ruff used in pre-commit hooks --- {{cookiecutter.project_name}}/.pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/{{cookiecutter.project_name}}/.pre-commit-config.yaml b/{{cookiecutter.project_name}}/.pre-commit-config.yaml index 120eac3..d466081 100644 --- a/{{cookiecutter.project_name}}/.pre-commit-config.yaml +++ b/{{cookiecutter.project_name}}/.pre-commit-config.yaml @@ -14,7 +14,7 @@ repos: - id: trailing-whitespace - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.3.5 + rev: v0.11.11 hooks: - id: ruff-format args: [--config=.ruff.toml] From 45c601fbdd8f7ff110667758083acac5f529feb1 Mon Sep 17 00:00:00 2001 From: Kyle Oliver Date: Fri, 23 May 2025 20:56:29 -0400 Subject: [PATCH 03/31] fix: correct arg error in precommit hook and adjust max size of files to 2000 kb --- {{cookiecutter.project_name}}/.pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/{{cookiecutter.project_name}}/.pre-commit-config.yaml b/{{cookiecutter.project_name}}/.pre-commit-config.yaml index d466081..1b5ea1f 100644 --- a/{{cookiecutter.project_name}}/.pre-commit-config.yaml +++ b/{{cookiecutter.project_name}}/.pre-commit-config.yaml @@ -7,7 +7,7 @@ repos: rev: v4.6.0 hooks: - id: check-added-large-files - args: ["--max-size=1000000"] + args: ["--maxkb=2000"] - id: check-yaml - id: check-toml - id: end-of-file-fixer From 624ecec4cb60857b817a3c5ef8216d27a30f48bd Mon Sep 17 00:00:00 2001 From: Kyle Oliver Date: Fri, 23 May 2025 21:30:57 -0400 Subject: [PATCH 04/31] fix: ensure package metadata is passed correctly and fix syntax errors in release-python.yml --- .../.github/workflows/release-python.yml | 2 +- {{cookiecutter.project_name}}/pyproject.toml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/{{cookiecutter.project_name}}/.github/workflows/release-python.yml b/{{cookiecutter.project_name}}/.github/workflows/release-python.yml index e3eed7b..73d17e6 100644 --- a/{{cookiecutter.project_name}}/.github/workflows/release-python.yml +++ b/{{cookiecutter.project_name}}/.github/workflows/release-python.yml @@ -61,7 +61,7 @@ jobs: env: # TestPyPI credentials stored as secrets in GitHub Settings -> Secrets TWINE_USERNAME: __token__ # Standard username when using API tokens - TWINE_PASSWORD: {% raw %}[{% raw %} {{ "${{ secrets.TESTPYPI_API_TOKEN }}" }} {% raw %}]{% endraw %} # Use GitHub Encrypted Secret + TWINE_PASSWORD: {% raw %}${{ secrets.TESTPYPI_API_TOKEN }} {% endraw } # Use GitHub Encrypted Secret # Optional: If uv publish requires different config for repository URL, pass TWINE_REPOSITORY or similar run: uvx nox -s publish-package -- --repository testpypi # Call the publish-package session, passing repository arg diff --git a/{{cookiecutter.project_name}}/pyproject.toml b/{{cookiecutter.project_name}}/pyproject.toml index 4bc1e10..33c4d6d 100644 --- a/{{cookiecutter.project_name}}/pyproject.toml +++ b/{{cookiecutter.project_name}}/pyproject.toml @@ -1,7 +1,7 @@ [project] -name = "{{cookiecutter.package_name}}" -version = "0.1.0" -description = "{{cookiecutter.package_name}}" +name = "{{cookiecutter.project_name}}" +version = "{{cookiecutter.version}}" +description = "{{cookiecutter.project_name}}" authors = [ { name = "{{cookiecutter.author}}", email = "{{cookiecutter.email}}" }, ] From f3fd8088292cd4b205cca29d9b8d3b16d02f63ab Mon Sep 17 00:00:00 2001 From: Kyle Oliver Date: Fri, 23 May 2025 21:37:24 -0400 Subject: [PATCH 05/31] feat: attempt at fixing syntax error for release-python.yml --- .../.github/workflows/release-python.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/{{cookiecutter.project_name}}/.github/workflows/release-python.yml b/{{cookiecutter.project_name}}/.github/workflows/release-python.yml index 73d17e6..fd7fadb 100644 --- a/{{cookiecutter.project_name}}/.github/workflows/release-python.yml +++ b/{{cookiecutter.project_name}}/.github/workflows/release-python.yml @@ -73,7 +73,7 @@ jobs: uses: simple-changelog/action@v3 # Action to parse CHANGELOG.md with: path: CHANGELOG.md # Path to your CHANGELOG.md - tag: {% raw %}${{ github.event_name == 'push' && github.ref_name || github.event.inputs.tag }}{% endraw %} # Pass the tag name + tag: { "${{ github.event_name == 'push' && github.ref_name || github.event.inputs.tag }}" } # Pass the tag name # Define outputs from this job that other jobs (like create_github_release) can use. outputs: From 9035534cd74766515a04f068bda8950c87f0d87e Mon Sep 17 00:00:00 2001 From: Kyle Oliver Date: Fri, 23 May 2025 21:44:42 -0400 Subject: [PATCH 06/31] feat: more changes hoping to get release-python.yml working --- .../.github/workflows/release-python.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/{{cookiecutter.project_name}}/.github/workflows/release-python.yml b/{{cookiecutter.project_name}}/.github/workflows/release-python.yml index fd7fadb..305b28f 100644 --- a/{{cookiecutter.project_name}}/.github/workflows/release-python.yml +++ b/{{cookiecutter.project_name}}/.github/workflows/release-python.yml @@ -61,7 +61,7 @@ jobs: env: # TestPyPI credentials stored as secrets in GitHub Settings -> Secrets TWINE_USERNAME: __token__ # Standard username when using API tokens - TWINE_PASSWORD: {% raw %}${{ secrets.TESTPYPI_API_TOKEN }} {% endraw } # Use GitHub Encrypted Secret + TWINE_PASSWORD: {% raw %}${{ secrets.TESTPYPI_API_TOKEN }}{% endraw } # Use GitHub Encrypted Secret # Optional: If uv publish requires different config for repository URL, pass TWINE_REPOSITORY or similar run: uvx nox -s publish-package -- --repository testpypi # Call the publish-package session, passing repository arg @@ -94,18 +94,18 @@ jobs: if: {% raw %}github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v'){% endraw %} steps: - - name: Download package artifacts # Get packages built in the previous job + - name: Download package artifacts uses: actions/download-artifact@v4 with: - name: distribution-packages # Must match the artifact name from build job - path: dist/ # Download into the 'dist' directory + name: distribution-packages + path: dist/ - - name: Set up Python # Needed to run uvx/publish task + - name: Set up Python uses: actions/setup-python@v5 with: - python-version: {% raw %}3.x{% endraw %} # Same version as build job for consistency + python-version-file: .python-version - - name: Set up uv # Install uv for this job + - name: Set up uv uses: astral-sh/setup-uv@v6 # --- Publish to Production PyPI Step --- @@ -116,7 +116,7 @@ jobs: env: # Production PyPI credentials stored as secrets in GitHub Settings -> Secrets TWINE_USERNAME: __token__ - TWINE_PASSWORD: {% raw %}[{% raw %} {{ "${{ secrets.PYPI_API_TOKEN }}" }} {% raw %}]{% endraw %} # Use GitHub Encrypted Secret + TWINE_PASSWORD: {% raw %}${{ secrets.PYPI_API_TOKEN }}{% endraw %} # Use GitHub Encrypted Secret # Optional: TWINE_REPOSITORY if publishing to a custom production index run: uvx nox -s publish-package # Call the publish-package session (defaults to pypi.org) @@ -129,7 +129,7 @@ jobs: needs: build_and_testpypi # Only run this job if triggered by a tag push - if: {% raw %}github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v'){% endraw %} + if: {% raw %}(github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v'){% endraw %} steps: - name: Download package artifacts # Get built artifacts for release assets From 397e48ef798af0ed5831751c0ed30a0ab64543c4 Mon Sep 17 00:00:00 2001 From: Kyle Oliver Date: Fri, 23 May 2025 21:46:20 -0400 Subject: [PATCH 07/31] feat: add target and .idea to .gitignore --- {{cookiecutter.project_name}}/.gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/{{cookiecutter.project_name}}/.gitignore b/{{cookiecutter.project_name}}/.gitignore index fad6f04..a8199bb 100644 --- a/{{cookiecutter.project_name}}/.gitignore +++ b/{{cookiecutter.project_name}}/.gitignore @@ -8,6 +8,8 @@ /docs/_build/ /src/*.egg-info/ *.egg-info/ +target +/.idea/ # Byte-code files *.pyc From f716c097dcd70cbda6ffa6fc69deeef9a2ab28b5 Mon Sep 17 00:00:00 2001 From: Kyle Oliver Date: Fri, 23 May 2025 22:15:19 -0400 Subject: [PATCH 08/31] feat: attempt to fix jinja escaping --- .../.github/workflows/release-python.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/{{cookiecutter.project_name}}/.github/workflows/release-python.yml b/{{cookiecutter.project_name}}/.github/workflows/release-python.yml index 305b28f..35e558d 100644 --- a/{{cookiecutter.project_name}}/.github/workflows/release-python.yml +++ b/{{cookiecutter.project_name}}/.github/workflows/release-python.yml @@ -70,10 +70,10 @@ jobs: # This action requires a standard CHANGELOG.md format. - name: Get Release Notes from Changelog id: changelog - uses: simple-changelog/action@v3 # Action to parse CHANGELOG.md + uses: simple-changelog/action@v3 with: path: CHANGELOG.md # Path to your CHANGELOG.md - tag: { "${{ github.event_name == 'push' && github.ref_name || github.event.inputs.tag }}" } # Pass the tag name + tag: {% raw %}${{ github.event_name == 'push' && github.ref_name || github.event.inputs.tag }}{% endraw %} # Define outputs from this job that other jobs (like create_github_release) can use. outputs: From 24b8f7fc5a533947cd722e496111114352d60c2d Mon Sep 17 00:00:00 2001 From: Kyle Oliver Date: Fri, 23 May 2025 22:18:37 -0400 Subject: [PATCH 09/31] Revert "feat: attempt to fix jinja escaping" This reverts commit f716c097dcd70cbda6ffa6fc69deeef9a2ab28b5. --- .../.github/workflows/release-python.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/{{cookiecutter.project_name}}/.github/workflows/release-python.yml b/{{cookiecutter.project_name}}/.github/workflows/release-python.yml index 35e558d..305b28f 100644 --- a/{{cookiecutter.project_name}}/.github/workflows/release-python.yml +++ b/{{cookiecutter.project_name}}/.github/workflows/release-python.yml @@ -70,10 +70,10 @@ jobs: # This action requires a standard CHANGELOG.md format. - name: Get Release Notes from Changelog id: changelog - uses: simple-changelog/action@v3 + uses: simple-changelog/action@v3 # Action to parse CHANGELOG.md with: path: CHANGELOG.md # Path to your CHANGELOG.md - tag: {% raw %}${{ github.event_name == 'push' && github.ref_name || github.event.inputs.tag }}{% endraw %} + tag: { "${{ github.event_name == 'push' && github.ref_name || github.event.inputs.tag }}" } # Pass the tag name # Define outputs from this job that other jobs (like create_github_release) can use. outputs: From 6efa88d939de3da62e902196ea4572fec30790e7 Mon Sep 17 00:00:00 2001 From: Kyle Oliver Date: Fri, 23 May 2025 22:20:47 -0400 Subject: [PATCH 10/31] feat: yet another attempt at fixing jinja madness --- .../.github/workflows/release-python.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/{{cookiecutter.project_name}}/.github/workflows/release-python.yml b/{{cookiecutter.project_name}}/.github/workflows/release-python.yml index 305b28f..15f95da 100644 --- a/{{cookiecutter.project_name}}/.github/workflows/release-python.yml +++ b/{{cookiecutter.project_name}}/.github/workflows/release-python.yml @@ -91,8 +91,7 @@ jobs: needs: build_and_testpypi # Only run on tag push events, NOT on manual dispatch for the final PyPI publish - if: {% raw %}github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v'){% endraw %} - + if: { "github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')" } steps: - name: Download package artifacts uses: actions/download-artifact@v4 From d335917a6ebcfca617f61c1b5090211406f01ca9 Mon Sep 17 00:00:00 2001 From: Kyle Oliver Date: Fri, 23 May 2025 22:36:08 -0400 Subject: [PATCH 11/31] feat: convert all manual uv executions in nox to just implicitly use it through the venv --- noxfile.py | 8 ++--- {{cookiecutter.project_name}}/noxfile.py | 44 ++++++++++++------------ 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/noxfile.py b/noxfile.py index 7562ccc..60c6728 100644 --- a/noxfile.py +++ b/noxfile.py @@ -155,10 +155,10 @@ def docs(session: Session): session.log(f"Cleaning template docs build directory: {docs_build_dir}") docs_build_dir.parent.mkdir(parents=True, exist_ok=True) - session.run("uv", "run", "sphinx-build", "-b", "html", "docs", str(docs_build_dir), "-E", external=True) + session.run("sphinx-build", "-b", "html", "docs", str(docs_build_dir), "-E") session.log("Building template documentation.") - session.run("uv", "run", "sphinx-build", "-b", "html", "docs", str(docs_build_dir), "-W", external=True) + session.run("sphinx-build", "-b", "html", "docs", str(docs_build_dir), "-W") session.log(f"Template documentation built in {docs_build_dir.resolve()}.") @@ -199,7 +199,7 @@ def test(session: Session) -> None: session.install("-e", ".", external=True) session.log("Running generated project's default checks...") - session.run("uv", "run", "nox", external=True) + session.run("nox") session.log(f"Cleaning up temporary directory: {temp_dir}") shutil.rmtree(temp_dir) @@ -221,7 +221,7 @@ def release_template(session: Session): session.skip("Git not available.") session.log("Checking Commitizen availability via uvx.") - session.run("uvx", "cz", "--version", successcodes=[0], external=True) + session.run("cz", "--version", successcodes=[0]) increment = session.posargs[0] if session.posargs else None session.log( diff --git a/{{cookiecutter.project_name}}/noxfile.py b/{{cookiecutter.project_name}}/noxfile.py index 3981360..ec3850d 100644 --- a/{{cookiecutter.project_name}}/noxfile.py +++ b/{{cookiecutter.project_name}}/noxfile.py @@ -34,51 +34,51 @@ def pre_commit(session: Session) -> None: def format_python(session: Session) -> None: """Run Python code formatter (Ruff format).""" session.log("Installing formatting dependencies...") - session.run("uv", "sync", "--locked", "--group", "dev", "--group", "lint", external=True) + session.install("-e", ".", "--group", "dev", "--group", "lint") session.log(f"Running Ruff formatter check with py{session.python}.") # Use --check, not fix. Fixing is done by pre-commit or manual run. - session.run("uv", "run", "ruff", "format", *session.posargs, external=True) + session.run("ruff", "format", *session.posargs) @nox.session(python=DEFAULT_PYTHON_VERSION, name="lint-python") def lint_python(session: Session) -> None: """Run Python code linters (Ruff check, Pydocstyle rules).""" session.log("Installing linting dependencies...") - session.run("uv", "sync", "--locked", "--group", "dev", "--group", "lint", external=True) + session.install("-e", ".", "--group", "dev", "--group", "lint") session.log(f"Running Ruff check with py{session.python}.") - session.run("uv", "run", "ruff", "check", "--verbose", external=True) + session.run("ruff", "check", "--verbose") @nox.session(python=PYTHON_VERSIONS) def typecheck(session: Session) -> None: """Run static type checking (Pyright) on Python code.""" session.log("Installing type checking dependencies...") - session.run("uv", "sync", "--locked", "--group", "dev", "--group", "typecheck", external=True) + session.install("-e", ".", "--group", "dev", "--group", "typecheck") session.log(f"Running Pyright check with py{session.python}.") - session.run("uv", "run", "pyright", external=True) + session.run("pyright") @nox.session(python=DEFAULT_PYTHON_VERSION, name="security-python") def security_python(session: Session) -> None: """Run code security checks (Bandit) on Python code.""" session.log("Installing security dependencies...") - session.run("uv", "sync", "--locked", "--group", "dev", "--group", "security", external=True) + session.install("-e", ".", "--group", "dev", "--group", "security") session.log(f"Running Bandit static security analysis with py{session.python}.") - session.run("uv", "run", "bandit", "-r", PACKAGE_NAME, "-c", ".bandit", "-ll", "-s", external=True) + session.run("bandit", "-r", PACKAGE_NAME, "-c", ".bandit", "-ll", "-s") session.log(f"Running pip-audit dependency security check with py{session.python}.") - session.run("uv", "run", "pip-audit", "--python", str(Path(session.python)), external=True) + session.run("pip-audit", "--python", str(Path(session.python))) @nox.session(python=PYTHON_VERSIONS, name="tests-python") def tests_python(session: Session) -> None: """Run the Python test suite (pytest with coverage).""" session.log("Installing test dependencies...") - session.run("uv", "sync", "--locked", "--group", "dev", "--group", "test", external=True) + session.install("-e", ".", "--group", "dev", "--group", "test") session.log(f"Running test suite with py{session.python}.") test_results_dir = Path("test-results") @@ -107,16 +107,16 @@ def tests_rust(session: Session) -> None: def docs_build(session: Session) -> None: """Build the project documentation (Sphinx).""" session.log("Installing documentation dependencies...") - session.run("uv", "sync", "--locked", "--group", "dev", "--group", "docs", external=True) + session.install("-e", ".", "--group", "dev", "--group", "docs") session.log(f"Building documentation with py{session.python}.") docs_build_dir = Path("docs") / "_build" / "html" session.log(f"Cleaning build directory: {docs_build_dir}") - session.run("uv", "run", "sphinx-build", "-b", "html", "docs", str(docs_build_dir), "-E", external=True) + session.run("sphinx-build", "-b", "html", "docs", str(docs_build_dir), "-E") session.log("Building documentation.") - session.run("uv", "run", "sphinx-build", "-b", "html", "docs", str(docs_build_dir), "-W", external=True) + session.run("sphinx-build", "-b", "html", "docs", str(docs_build_dir), "-W") @nox.session(python=DEFAULT_PYTHON_VERSION, name="build-python") @@ -130,7 +130,7 @@ def build_python(session: Session) -> None: {% if cookiecutter.add_rust_extension == 'y' -%} session.run("uv", "build", "--sdist", "--wheel", "--outdir", "dist/", external=True) {% else -%} - session.run("uvx", "maturin", "develop", "--uv", external=True) + session.run("maturin", "develop", "--uv") {% endif -%} session.log("Built packages in ./dist directory:") @@ -179,7 +179,7 @@ def publish_python(session: Session) -> None: session.run("uv", "sync", "--locked", "--group", "dev", external=True) session.log("Checking built packages with Twine.") - session.run("uvx", "twine", "check", "dist/*", external=True) + session.run("twine", "check", "dist/*") session.log("Publishing packages to PyPI.") session.run("uv", "publish", "dist/*", external=True) @@ -211,7 +211,7 @@ def release(session: Session) -> None: session.skip("Git not available.") session.log("Checking Commitizen availability via uvx.") - session.run("uvx", "cz", "--version", success_codes=[0], external=True) + session.run("cz", "--version", success_codes=[0]) increment = session.posargs[0] if session.posargs else None session.log( @@ -254,9 +254,9 @@ def tox(session: Session) -> None: session.skip("tox.ini not present.") session.log("Checking Tox availability via uvx.") - session.run("uvx", "tox", "--version", success_codes=[0], external=True) + session.run("tox", "--version", success_codes=[0]) - session.run("uvx", "tox", *session.posargs, external=True) + session.run("tox", *session.posargs) # --- COMBINED/ORCHESTRATION SESSIONS --- @@ -312,13 +312,13 @@ def coverage(session: Session) -> None: session.log("Note: Ensure 'nox -s test-python' was run across all desired Python versions first to generate coverage data.") session.log("Installing dependencies for coverage report session...") - session.run("uv", "sync", "--locked", "--group", "dev", "--group", "test", external=True) + session.install("-e", ".", "--group", "dev", "--group", "test") coverage_combined_file: Path = Path.cwd() / ".coverage" session.log("Combining coverage data.") try: - session.run("uv", "run", "coverage", "combine", external=True) + session.run("coverage", "combine") session.log(f"Combined coverage data into {coverage_combined_file.resolve()}") except CommandFailed as e: if e.returncode == 1: @@ -329,9 +329,9 @@ def coverage(session: Session) -> None: session.log("Generating HTML coverage report.") coverage_html_dir = Path("coverage-html") - session.run("uv", "run", "coverage", "html", "--directory", str(coverage_html_dir), external=True) + session.run("coverage", "html", "--directory", str(coverage_html_dir)) session.log("Running terminal coverage report.") - session.run("uv", "run", "coverage", "report", external=True) + session.run("coverage", "report") session.log(f"Coverage reports generated in ./{coverage_html_dir} and terminal.") From 8b72f9ce783fcd8b63bda8cee2b88704ccefa776 Mon Sep 17 00:00:00 2001 From: Kyle Oliver Date: Fri, 23 May 2025 22:39:38 -0400 Subject: [PATCH 12/31] feat: convert leftover uv sync calls in noxfile.py to install normally --- {{cookiecutter.project_name}}/noxfile.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/{{cookiecutter.project_name}}/noxfile.py b/{{cookiecutter.project_name}}/noxfile.py index ec3850d..381dec5 100644 --- a/{{cookiecutter.project_name}}/noxfile.py +++ b/{{cookiecutter.project_name}}/noxfile.py @@ -124,7 +124,7 @@ def build_python(session: Session) -> None: """Build sdist and wheel packages (uv build).""" session.log("Installing build dependencies...") # Sync core & dev deps are needed for accessing project source code. - session.run("uv", "sync", "--locked", "--group", "dev", external=True) + session.install("-e", ".", "--group", "dev") session.log(f"Building sdist and wheel packages with py{session.python}.") {% if cookiecutter.add_rust_extension == 'y' -%} @@ -176,7 +176,7 @@ def publish_python(session: Session) -> None: Requires packages to be built first (`nox -s build-python` or `nox -s build`). Requires TWINE_USERNAME/TWINE_PASSWORD or TWINE_API_KEY environment variables set (usually in CI). """ - session.run("uv", "sync", "--locked", "--group", "dev", external=True) + session.install("-e", ".", "--group", "dev") session.log("Checking built packages with Twine.") session.run("twine", "check", "dist/*") @@ -202,7 +202,7 @@ def release(session: Session) -> None: Optionally accepts increment (major, minor, patch) after '--'. """ session.log("Running release process using Commitizen...") - session.run("uv", "sync", "--locked", "--group", "dev", external=True) + session.install("-e", ".", "--group", "dev") try: session.run("git", "version", success_codes=[0], external=True, silent=True) @@ -246,7 +246,7 @@ def tox(session: Session) -> None: Accepts tox args after '--' (e.g., `nox -s tox -- -e py39`). """ session.log("Running Tox test matrix via uvx...") - session.run("uv", "sync", "--locked", "--group", "dev", external=True) + session.install("-e", ".", "--group", "dev") tox_ini_path = Path("tox.ini") if not tox_ini_path.exists(): From 014cba9d679b4e81a804a017d5e2e28d5bcff280 Mon Sep 17 00:00:00 2001 From: Kyle Oliver Date: Fri, 23 May 2025 22:43:31 -0400 Subject: [PATCH 13/31] feat: removes final uv sync calls --- {{cookiecutter.project_name}}/noxfile.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/{{cookiecutter.project_name}}/noxfile.py b/{{cookiecutter.project_name}}/noxfile.py index 381dec5..97487dd 100644 --- a/{{cookiecutter.project_name}}/noxfile.py +++ b/{{cookiecutter.project_name}}/noxfile.py @@ -27,7 +27,7 @@ def pre_commit(session: Session) -> None: """Run pre-commit checks.""" session.log("Installing pre-commit dependencies...") - session.run("uv", "sync", "--locked", "--group", "dev", "--group", "pre-commit", external=True) + session.install("-e", ".", "--group", "dev", "--group", "pre-commit") @nox.session(python=DEFAULT_PYTHON_VERSION, name="format-python") @@ -160,7 +160,7 @@ def build_container(session: Session) -> None: current_dir: Path = Path.cwd() session.log(f"Ensuring core dependencies are synced in {current_dir.resolve()} for build context...") - session.run("uv", "sync", "--locked", external=True) + session.run("-e", ".") session.log(f"Building Docker image using {container_cli}.") project_image_name = PACKAGE_NAME.replace("_", "-").lower() From fcaf0118ac14cad7aee374ba3255480c6abd62ae Mon Sep 17 00:00:00 2001 From: Kyle Oliver Date: Fri, 23 May 2025 22:45:51 -0400 Subject: [PATCH 14/31] feat: remove non-existent installation group --- {{cookiecutter.project_name}}/noxfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/{{cookiecutter.project_name}}/noxfile.py b/{{cookiecutter.project_name}}/noxfile.py index 97487dd..9a852cb 100644 --- a/{{cookiecutter.project_name}}/noxfile.py +++ b/{{cookiecutter.project_name}}/noxfile.py @@ -27,7 +27,7 @@ def pre_commit(session: Session) -> None: """Run pre-commit checks.""" session.log("Installing pre-commit dependencies...") - session.install("-e", ".", "--group", "dev", "--group", "pre-commit") + session.install("-e", ".", "--group", "dev") @nox.session(python=DEFAULT_PYTHON_VERSION, name="format-python") From 9ec490a4768d9c4022d19e3c1635ab0fde82b7d2 Mon Sep 17 00:00:00 2001 From: Kyle Oliver Date: Fri, 23 May 2025 22:49:05 -0400 Subject: [PATCH 15/31] feat: add pre-commit migration change --- {{cookiecutter.project_name}}/.pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/{{cookiecutter.project_name}}/.pre-commit-config.yaml b/{{cookiecutter.project_name}}/.pre-commit-config.yaml index 1b5ea1f..2cf9c12 100644 --- a/{{cookiecutter.project_name}}/.pre-commit-config.yaml +++ b/{{cookiecutter.project_name}}/.pre-commit-config.yaml @@ -1,6 +1,6 @@ # .pre-commit-config.yaml # See https://pre-commit.com/ -default_stages: [commit] +default_stages: [pre-commit] repos: - repo: https://github.com/pre-commit/pre-commit-hooks From 107564d473d226a363811983485b2d362224606e Mon Sep 17 00:00:00 2001 From: Kyle Oliver Date: Fri, 23 May 2025 22:54:22 -0400 Subject: [PATCH 16/31] feat: remove .cruft.json from the .gitignore --- .gitignore | 3 --- 1 file changed, 3 deletions(-) diff --git a/.gitignore b/.gitignore index fad6f04..3266791 100644 --- a/.gitignore +++ b/.gitignore @@ -60,6 +60,3 @@ Thumbs.db # Windows thumbnail cache # Development logs/outputs debug.log nohup.out - -# Cookiecutter/Cruft metadata (for template updates) -.cruft.json From 613898bb898d357d4834861f5e51d6585317352a Mon Sep 17 00:00:00 2001 From: Kyle Oliver Date: Fri, 23 May 2025 23:42:31 -0400 Subject: [PATCH 17/31] feat: add hypermodern cookiecutter logic for installing pre-commit-hooks --- {{cookiecutter.project_name}}/noxfile.py | 85 +++++++++++++++++++- {{cookiecutter.project_name}}/pyproject.toml | 1 + 2 files changed, 84 insertions(+), 2 deletions(-) diff --git a/{{cookiecutter.project_name}}/noxfile.py b/{{cookiecutter.project_name}}/noxfile.py index 9a852cb..feec625 100644 --- a/{{cookiecutter.project_name}}/noxfile.py +++ b/{{cookiecutter.project_name}}/noxfile.py @@ -1,5 +1,10 @@ """Noxfile for the {{cookiecutter.project_name}} project.""" + +import os +import shlex + from pathlib import Path +from textwrap import dedent from typing import List import nox @@ -23,12 +28,88 @@ PACKAGE_NAME: str = "{{cookiecutter.package_name}}" +def activate_virtualenv_in_precommit_hooks(session: Session) -> None: + """Activate virtualenv in hooks installed by pre-commit. + + This function patches git hooks installed by pre-commit to activate the + session's virtual environment. This allows pre-commit to locate hooks in + that environment when invoked from git. + + Args: + session: The Session object. + """ + assert session.bin is not None # nosec + + # Only patch hooks containing a reference to this session's bindir. Support + # quoting rules for Python and bash, but strip the outermost quotes so we + # can detect paths within the bindir, like /python. + bindirs = [ + bindir[1:-1] if bindir[0] in "'\"" else bindir for bindir in (repr(session.bin), shlex.quote(session.bin)) + ] + + virtualenv = session.env.get("VIRTUAL_ENV") + if virtualenv is None: + return + + headers = { + # pre-commit < 2.16.0 + "python": f"""\ + import os + os.environ["VIRTUAL_ENV"] = {virtualenv!r} + os.environ["PATH"] = os.pathsep.join(( + {session.bin!r}, + os.environ.get("PATH", ""), + )) + """, + # pre-commit >= 2.16.0 + "bash": f"""\ + VIRTUAL_ENV={shlex.quote(virtualenv)} + PATH={shlex.quote(session.bin)}"{os.pathsep}$PATH" + """, + # pre-commit >= 2.17.0 on Windows forces sh shebang + "/bin/sh": f"""\ + VIRTUAL_ENV={shlex.quote(virtualenv)} + PATH={shlex.quote(session.bin)}"{os.pathsep}$PATH" + """, + } + + hookdir: Path = Path(".git") / "hooks" + if not hookdir.is_dir(): + return + + for hook in hookdir.iterdir(): + if hook.name.endswith(".sample") or not hook.is_file(): + continue + + if not hook.read_bytes().startswith(b"#!"): + continue + + text: str = hook.read_text() + + if not any((Path("A") == Path("a") and bindir.lower() in text.lower()) or bindir in text for bindir in bindirs): + continue + + lines: list[str] = text.splitlines() + + for executable, header in headers.items(): + if executable in lines[0].lower(): + lines.insert(1, dedent(header)) + hook.write_text("\n".join(lines)) + break + + @nox.session(python=DEFAULT_PYTHON_VERSION, name="pre-commit") -def pre_commit(session: Session) -> None: - """Run pre-commit checks.""" +def precommit(session: Session) -> None: + """Lint using pre-commit.""" + args: list[str] = session.posargs or ["run", "--all-files", "--hook-stage=manual", "--show-diff-on-failure"] + session.log("Installing pre-commit dependencies...") session.install("-e", ".", "--group", "dev") + session.run("pre-commit", *args) + if args and args[0] == "install": + activate_virtualenv_in_precommit_hooks(session) + @nox.session(python=DEFAULT_PYTHON_VERSION, name="format-python") def format_python(session: Session) -> None: diff --git a/{{cookiecutter.project_name}}/pyproject.toml b/{{cookiecutter.project_name}}/pyproject.toml index 33c4d6d..3c714ab 100644 --- a/{{cookiecutter.project_name}}/pyproject.toml +++ b/{{cookiecutter.project_name}}/pyproject.toml @@ -27,6 +27,7 @@ dev = [ "commitizen>=4.7.0", "nox>={{cookiecutter.copyright_year}}.5.1", "pre-commit>=4.2.0", + "pre-commit-hooks>=5.0.0", ] docs = [ "furo>=2024.8.6", From 55765722dee998adde0d6552ffc321a63478d557 Mon Sep 17 00:00:00 2001 From: Kyle Oliver Date: Fri, 23 May 2025 23:44:31 -0400 Subject: [PATCH 18/31] feat: remove .cruft.json from .gitignore --- {{cookiecutter.project_name}}/.gitignore | 3 --- 1 file changed, 3 deletions(-) diff --git a/{{cookiecutter.project_name}}/.gitignore b/{{cookiecutter.project_name}}/.gitignore index a8199bb..1d65b34 100644 --- a/{{cookiecutter.project_name}}/.gitignore +++ b/{{cookiecutter.project_name}}/.gitignore @@ -62,6 +62,3 @@ Thumbs.db # Windows thumbnail cache # Development logs/outputs debug.log nohup.out - -# Cookiecutter/Cruft metadata (for template updates) -.cruft.json From d0d42c8a1a8ce57dedc34c0e964b07ea8caa812d Mon Sep 17 00:00:00 2001 From: Kyle Oliver Date: Fri, 23 May 2025 23:57:47 -0400 Subject: [PATCH 19/31] feat: add a DEBUG flag to pyrightconfig.json --- {{cookiecutter.project_name}}/pyrightconfig.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/{{cookiecutter.project_name}}/pyrightconfig.json b/{{cookiecutter.project_name}}/pyrightconfig.json index cd288a4..569ab4b 100644 --- a/{{cookiecutter.project_name}}/pyrightconfig.json +++ b/{{cookiecutter.project_name}}/pyrightconfig.json @@ -29,6 +29,9 @@ "extraPaths": [] } ], + "defineConstant": { + "DEBUG": true + }, "reportMissingImports": true, "reportMissingTypeStubs": true, From c56f813281468ecfaa1b33f41c50cf4c64754a8f Mon Sep 17 00:00:00 2001 From: Kyle Oliver Date: Sat, 24 May 2025 00:16:12 -0400 Subject: [PATCH 20/31] feat: remove execution environments from pyrightconfig.json to allow for proper import resolution --- {{cookiecutter.project_name}}/pyrightconfig.json | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/{{cookiecutter.project_name}}/pyrightconfig.json b/{{cookiecutter.project_name}}/pyrightconfig.json index 569ab4b..daf74b1 100644 --- a/{{cookiecutter.project_name}}/pyrightconfig.json +++ b/{{cookiecutter.project_name}}/pyrightconfig.json @@ -21,11 +21,7 @@ "venv": ".venv", "executionEnvironments": [ { - "root": "src", - "extraPaths": [] - }, - { - "root": "tests", + "root": ".", "extraPaths": [] } ], From e57a9a89e77f82e940cf02877707a4dfd6b7ac99 Mon Sep 17 00:00:00 2001 From: Kyle Oliver Date: Sat, 24 May 2025 00:39:29 -0400 Subject: [PATCH 21/31] fix: remove invalid kwarg from nox session security-python --- {{cookiecutter.project_name}}/noxfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/{{cookiecutter.project_name}}/noxfile.py b/{{cookiecutter.project_name}}/noxfile.py index feec625..f4d7e90 100644 --- a/{{cookiecutter.project_name}}/noxfile.py +++ b/{{cookiecutter.project_name}}/noxfile.py @@ -149,7 +149,7 @@ def security_python(session: Session) -> None: session.install("-e", ".", "--group", "dev", "--group", "security") session.log(f"Running Bandit static security analysis with py{session.python}.") - session.run("bandit", "-r", PACKAGE_NAME, "-c", ".bandit", "-ll", "-s") + session.run("bandit", "-r", PACKAGE_NAME, "-c", ".bandit", "-ll") session.log(f"Running pip-audit dependency security check with py{session.python}.") session.run("pip-audit", "--python", str(Path(session.python))) From cc004b8633099141b8b65da5e97ab9a3d2e2b6b8 Mon Sep 17 00:00:00 2001 From: Kyle Oliver Date: Sat, 24 May 2025 11:31:47 -0400 Subject: [PATCH 22/31] feat: add commitizen pre-commit hook and edit settings --- {{cookiecutter.project_name}}/.cz.toml | 2 ++ {{cookiecutter.project_name}}/.pre-commit-config.yaml | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/{{cookiecutter.project_name}}/.cz.toml b/{{cookiecutter.project_name}}/.cz.toml index c18146c..5548a61 100644 --- a/{{cookiecutter.project_name}}/.cz.toml +++ b/{{cookiecutter.project_name}}/.cz.toml @@ -11,6 +11,8 @@ write_version_files = [ "src/{{ cookiecutter.package_name }}/__init__.py:__version__", ] commit_msg_file = ".git/COMMIT_EDITMSG" +retry_after_failure = true +update_changelog_on_bump = true [tool.commitizen.github] release = true diff --git a/{{cookiecutter.project_name}}/.pre-commit-config.yaml b/{{cookiecutter.project_name}}/.pre-commit-config.yaml index 2cf9c12..d94b0a3 100644 --- a/{{cookiecutter.project_name}}/.pre-commit-config.yaml +++ b/{{cookiecutter.project_name}}/.pre-commit-config.yaml @@ -21,3 +21,10 @@ repos: - id: ruff args: [--fix, --exit-non-zero-on-fix, --config=.ruff.toml] + + - repo: https://github.com/commitizen-tools/commitizen + rev: v1.17.0 + hooks: + - id: commitizen + - id: commitizen-branch + stages: [ commit-msg ] From b5cdf7b9db61646b4cb6ef89bfee7586be155e4b Mon Sep 17 00:00:00 2001 From: Kyle Oliver Date: Sat, 24 May 2025 11:36:04 -0400 Subject: [PATCH 23/31] chore: add .idea to .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 3266791..076666a 100644 --- a/.gitignore +++ b/.gitignore @@ -60,3 +60,5 @@ Thumbs.db # Windows thumbnail cache # Development logs/outputs debug.log nohup.out + +/.idea/ From c95b15a811dc07eff289399e0b302f159c6b9986 Mon Sep 17 00:00:00 2001 From: Kyle Oliver Date: Sat, 24 May 2025 11:38:50 -0400 Subject: [PATCH 24/31] feat: update commitizen version in .pre-commit-config.yaml --- {{cookiecutter.project_name}}/.pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/{{cookiecutter.project_name}}/.pre-commit-config.yaml b/{{cookiecutter.project_name}}/.pre-commit-config.yaml index d94b0a3..eff2767 100644 --- a/{{cookiecutter.project_name}}/.pre-commit-config.yaml +++ b/{{cookiecutter.project_name}}/.pre-commit-config.yaml @@ -4,7 +4,7 @@ default_stages: [pre-commit] repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.6.0 + rev: v5.0.0 hooks: - id: check-added-large-files args: ["--maxkb=2000"] @@ -23,7 +23,7 @@ repos: args: [--fix, --exit-non-zero-on-fix, --config=.ruff.toml] - repo: https://github.com/commitizen-tools/commitizen - rev: v1.17.0 + rev: v4.8.2 hooks: - id: commitizen - id: commitizen-branch From 3ce5bfd1d2a48144e874e37c0512094bb0dea2fd Mon Sep 17 00:00:00 2001 From: Kyle Oliver Date: Sun, 25 May 2025 02:25:19 -0400 Subject: [PATCH 25/31] build: update typer and commitizen dependencies --- pyproject.toml | 4 ++-- uv.lock | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 6dd11b9..4882d9e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,12 +10,12 @@ dependencies = [ "loguru>=0.7.3", "platformdirs>=4.3.8", "retrocookie>=0.4.3", - "typer>=0.15.3", + "typer>=0.15.4", ] [dependency-groups] dev = [ - "commitizen>=4.7.0", + "commitizen>=4.8.2", "nox>=2025.5.1", "pre-commit>=4.2.0", ] diff --git a/uv.lock b/uv.lock index 6ea43ff..fd8f794 100644 --- a/uv.lock +++ b/uv.lock @@ -346,7 +346,7 @@ wheels = [ [[package]] name = "commitizen" -version = "4.7.0" +version = "4.8.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "argcomplete" }, @@ -362,9 +362,9 @@ dependencies = [ { name = "tomlkit" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f3/7a/2c2a781b3e227f528b19e1144efcfdd61f7414bf9cf84ba6bbdc215f3427/commitizen-4.7.0.tar.gz", hash = "sha256:ef95f2ef354b438dce7c6164e5d47d10cc377df666ee65a116bcfcb146bb0c0a", size = 53254, upload-time = "2025-05-10T13:47:33.585Z" } +sdist = { url = "https://files.pythonhosted.org/packages/64/15/c2fe85c0224886109b5061419acea2e20539be1b4bff619a16d7295fe0f2/commitizen-4.8.2.tar.gz", hash = "sha256:4fc73126c7300f715f11b85242550677722c57767b579100e869ccd45143e2c5", size = 53235, upload-time = "2025-05-22T03:16:39.915Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/27/2d/0ae2129dd1071f16fffa373f9223971f84808a7fee85c0cefc1db2ffaec8/commitizen-4.7.0-py3-none-any.whl", hash = "sha256:2d274e013a6e09bc69f97fe64d6a6389926e2a22f5a4a19b16571d5b4c31083c", size = 76010, upload-time = "2025-05-10T13:47:31.75Z" }, + { url = "https://files.pythonhosted.org/packages/0e/40/2b81df1b3ec24c41004512feba0884895b84748775d21642690120539a30/commitizen-4.8.2-py3-none-any.whl", hash = "sha256:86cae0bd8e1da889389d828b30a5acb79b62f9290f9274b127ee9d8c189eb16c", size = 76074, upload-time = "2025-05-22T03:16:38.431Z" }, ] [[package]] @@ -441,12 +441,12 @@ requires-dist = [ { name = "loguru", specifier = ">=0.7.3" }, { name = "platformdirs", specifier = ">=4.3.8" }, { name = "retrocookie", specifier = ">=0.4.3" }, - { name = "typer", specifier = ">=0.15.3" }, + { name = "typer", specifier = ">=0.15.4" }, ] [package.metadata.requires-dev] dev = [ - { name = "commitizen", specifier = ">=4.7.0" }, + { name = "commitizen", specifier = ">=4.8.2" }, { name = "nox", specifier = ">=2025.5.1" }, { name = "pre-commit", specifier = ">=4.2.0" }, ] @@ -1831,7 +1831,7 @@ wheels = [ [[package]] name = "typer" -version = "0.15.3" +version = "0.15.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, @@ -1839,9 +1839,9 @@ dependencies = [ { name = "shellingham" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/98/1a/5f36851f439884bcfe8539f6a20ff7516e7b60f319bbaf69a90dc35cc2eb/typer-0.15.3.tar.gz", hash = "sha256:818873625d0569653438316567861899f7e9972f2e6e0c16dab608345ced713c", size = 101641, upload-time = "2025-04-28T21:40:59.204Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/89/c527e6c848739be8ceb5c44eb8208c52ea3515c6cf6406aa61932887bf58/typer-0.15.4.tar.gz", hash = "sha256:89507b104f9b6a0730354f27c39fae5b63ccd0c95b1ce1f1a6ba0cfd329997c3", size = 101559, upload-time = "2025-05-14T16:34:57.704Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/48/20/9d953de6f4367163d23ec823200eb3ecb0050a2609691e512c8b95827a9b/typer-0.15.3-py3-none-any.whl", hash = "sha256:c86a65ad77ca531f03de08d1b9cb67cd09ad02ddddf4b34745b5008f43b239bd", size = 45253, upload-time = "2025-04-28T21:40:56.269Z" }, + { url = "https://files.pythonhosted.org/packages/c9/62/d4ba7afe2096d5659ec3db8b15d8665bdcb92a3c6ff0b95e99895b335a9c/typer-0.15.4-py3-none-any.whl", hash = "sha256:eb0651654dcdea706780c466cf06d8f174405a659ffff8f163cfbfee98c0e173", size = 45258, upload-time = "2025-05-14T16:34:55.583Z" }, ] [[package]] From 90a9a588925c996fc9be67c1ccfa0289e6e47daf Mon Sep 17 00:00:00 2001 From: Kyle Oliver Date: Sun, 25 May 2025 02:41:39 -0400 Subject: [PATCH 26/31] chore: fix grammar in README.md --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index ba41e93..19851a5 100644 --- a/README.md +++ b/README.md @@ -12,10 +12,10 @@ Any and all advice, support, PR's, etc are welcome and would be greatly apprecia # Why does this project exist? -Unfortunately the [Hypermodern Python Cookiecutter] is no longer maintained nor modern. +Unfortunately, the [Hypermodern Python Cookiecutter] is no longer maintained nor modern. While it will always have a place in my heart, there have been far too many improvements in Python tooling to keep using it as is. -For a whle I maintained [a personal fork](https://github.com/56kyle/cookiecutter-hypermodern-python) that I would update, however when it came time to switch +For a while I maintained [a personal fork](https://github.com/56kyle/cookiecutter-hypermodern-python) that I would update, however when it came time to switch to new tooling such as [ruff], [uv], [maturin], etc, I found the process of updating the existing tooling to be extremly painful. The [Hypermodern Python Cookiecutter] remains as a fantastic sendoff point for devs interested in building a 2021 style Python Package, but there were @@ -34,7 +34,7 @@ The [Robust Python Cookiecutter] exists to solve a few main concerns One of the main issues I encountered with [my personal fork] of the [Hypermodern Python Cookiecutter] was that any change I made to my repos would mean a later conflict if I tried to rerun [cookiecutter] to sync a change from a different project. -Thankfully [cruft] exists specifically to help with this issue. It enables us to periodically create PR's to add in any fixes +Thankfully, [cruft] exists specifically to help with this issue. It enables us to periodically create PR's to add in any fixes the [Robust Python Cookiecutter] may have added. Additionally, extra care is put in to use tooling specific config files whenever possible to help reduce merge conflicts occurring @@ -76,12 +76,12 @@ Overall it's rather rare that people debate over tooling for no reason. Most thi ## CI/CD Vendor Lock Now don't get me wrong, I love [github-actions] and do pretty much everything in my power to avoid [bitbucket-pipelines]. -However not all jobs have the luxury of github, and I would love to be able to just use the same template for both my personal and professional projects. +However, not all jobs have the luxury of github, and I would love to be able to just use the same template for both my personal and professional projects. The [Robust Python Cookiecutter] focuses on being as modular as possible for areas that connect to the CI/CD pipeline. Additionally, there will always be either alternative CI/CD options or at a minimum basic examples of what the translated CI/CD pipeline would look like. -Finally the main reason that this task is even possible is that the [Robust Python Cookiecutter] mirrors all of the CI/CD steps in it's local dev tooling. +Finally, the main reason that this task is even possible is that the [Robust Python Cookiecutter] mirrors all of the CI/CD steps in it's local dev tooling. The local [noxfile] is designed to match up directly with the CI/CD each step of the way. The [Hypermodern Python Cookiecutter] did this where it could afford to also, however the lack of [uv] meant it would significantly increase CI/CD times if done everywhere. From fe597e3f83e2b790af159da1d0b0984def1b51c1 Mon Sep 17 00:00:00 2001 From: Kyle Oliver Date: Sun, 25 May 2025 03:01:04 -0400 Subject: [PATCH 27/31] chore: add exported .editorconfig settings from pycharm --- {{cookiecutter.project_name}}/.editorconfig | 100 +++++++++++++++++++- 1 file changed, 99 insertions(+), 1 deletion(-) diff --git a/{{cookiecutter.project_name}}/.editorconfig b/{{cookiecutter.project_name}}/.editorconfig index a8faee7..58f5d49 100644 --- a/{{cookiecutter.project_name}}/.editorconfig +++ b/{{cookiecutter.project_name}}/.editorconfig @@ -5,11 +5,109 @@ charset = utf-8 end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true +max_line_length = 120 +tab_width = 4 +ij_continuation_indent_size = 8 +ij_formatter_off_tag = @formatter:off +ij_formatter_on_tag = @formatter:on +ij_formatter_tags_enabled = true +ij_smart_tabs = false +ij_visual_guides = +ij_wrap_on_typing = false -[*.{py,toml}] +[*.toml] indent_style = space indent_size = 4 [*.yml,yaml,json] indent_style = space indent_size = 2 + +[{*.py,*.pyw,*.whl}] +indent_style = space +indent_size = 4 +ij_continuation_indent_size = 4 +ij_python_add_indent_inside_injections = false +ij_python_align_collections_and_comprehensions = true +ij_python_align_multiline_imports = true +ij_python_align_multiline_parameters = false +ij_python_align_multiline_parameters_in_calls = false +ij_python_blank_line_at_file_end = true +ij_python_blank_lines_after_imports = 2 +ij_python_blank_lines_after_local_imports = 0 +ij_python_blank_lines_around_class = 1 +ij_python_blank_lines_around_method = 1 +ij_python_blank_lines_around_top_level_classes_functions = 2 +ij_python_blank_lines_before_first_method = 0 +ij_python_call_parameters_new_line_after_left_paren = true +ij_python_call_parameters_right_paren_on_new_line = true +ij_python_call_parameters_wrap = on_every_item +ij_python_dict_alignment = 0 +ij_python_dict_new_line_after_left_brace = true +ij_python_dict_new_line_before_right_brace = true +ij_python_dict_wrapping = on_every_item +ij_python_format_injected_fragments = true +ij_python_from_import_new_line_after_left_parenthesis = true +ij_python_from_import_new_line_before_right_parenthesis = true +ij_python_from_import_parentheses_force_if_multiline = true +ij_python_from_import_trailing_comma_if_multiline = false +ij_python_from_import_wrapping = on_every_item +ij_python_hang_closing_brackets = false +ij_python_keep_blank_lines_in_code = 1 +ij_python_keep_blank_lines_in_declarations = 1 +ij_python_keep_indents_on_empty_lines = false +ij_python_keep_line_breaks = true +ij_python_list_new_line_after_left_bracket = true +ij_python_list_new_line_before_right_bracket = true +ij_python_list_wrapping = on_every_item +ij_python_method_parameters_new_line_after_left_paren = true +ij_python_method_parameters_right_paren_on_new_line = true +ij_python_method_parameters_wrap = on_every_item +ij_python_new_line_after_colon = true +ij_python_new_line_after_colon_multi_clause = true +ij_python_optimize_imports_always_split_from_imports = true +ij_python_optimize_imports_case_insensitive_order = false +ij_python_optimize_imports_join_from_imports_with_same_source = false +ij_python_optimize_imports_sort_by_type_first = true +ij_python_optimize_imports_sort_imports = true +ij_python_optimize_imports_sort_names_in_from_imports = false +ij_python_set_new_line_after_left_brace = true +ij_python_set_new_line_before_right_brace = true +ij_python_set_wrapping = on_every_item +ij_python_space_after_comma = true +ij_python_space_after_number_sign = true +ij_python_space_after_py_colon = true +ij_python_space_before_backslash = true +ij_python_space_before_comma = false +ij_python_space_before_for_semicolon = false +ij_python_space_before_lbracket = false +ij_python_space_before_method_call_parentheses = false +ij_python_space_before_method_parentheses = false +ij_python_space_before_number_sign = true +ij_python_space_before_py_colon = false +ij_python_space_within_empty_method_call_parentheses = false +ij_python_space_within_empty_method_parentheses = false +ij_python_spaces_around_additive_operators = true +ij_python_spaces_around_assignment_operators = true +ij_python_spaces_around_bitwise_operators = true +ij_python_spaces_around_eq_in_keyword_argument = false +ij_python_spaces_around_eq_in_named_parameter = false +ij_python_spaces_around_equality_operators = true +ij_python_spaces_around_multiplicative_operators = true +ij_python_spaces_around_power_operator = true +ij_python_spaces_around_relational_operators = true +ij_python_spaces_around_shift_operators = true +ij_python_spaces_within_braces = false +ij_python_spaces_within_brackets = false +ij_python_spaces_within_method_call_parentheses = false +ij_python_spaces_within_method_parentheses = false +ij_python_tuple_new_line_after_left_parenthesis = true +ij_python_tuple_new_line_before_right_parenthesis = true +ij_python_tuple_wrapping = on_every_item +ij_python_use_continuation_indent_for_arguments = false +ij_python_use_continuation_indent_for_collection_and_comprehensions = false +ij_python_use_continuation_indent_for_parameters = true +ij_python_use_trailing_comma_in_arguments_list = false +ij_python_use_trailing_comma_in_collections = false +ij_python_use_trailing_comma_in_parameter_list = false +ij_python_wrap_long_lines = false From 76a58277962e6d31fc62d956a91baa9091ed2299 Mon Sep 17 00:00:00 2001 From: Kyle Oliver Date: Sun, 25 May 2025 14:55:07 -0400 Subject: [PATCH 28/31] fix: adjust bandit.yml path in nox session security-python --- {{cookiecutter.project_name}}/noxfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/{{cookiecutter.project_name}}/noxfile.py b/{{cookiecutter.project_name}}/noxfile.py index f4d7e90..7393738 100644 --- a/{{cookiecutter.project_name}}/noxfile.py +++ b/{{cookiecutter.project_name}}/noxfile.py @@ -149,7 +149,7 @@ def security_python(session: Session) -> None: session.install("-e", ".", "--group", "dev", "--group", "security") session.log(f"Running Bandit static security analysis with py{session.python}.") - session.run("bandit", "-r", PACKAGE_NAME, "-c", ".bandit", "-ll") + session.run("bandit", "-r", PACKAGE_NAME, "-c", "bandit.yml", "-ll") session.log(f"Running pip-audit dependency security check with py{session.python}.") session.run("pip-audit", "--python", str(Path(session.python))) From 6ed3b21066697ede9e250eb2798077f9e549c5b9 Mon Sep 17 00:00:00 2001 From: Kyle Oliver Date: Sun, 25 May 2025 15:01:16 -0400 Subject: [PATCH 29/31] fix: remove faulty kwarg from nox session security-python --- {{cookiecutter.project_name}}/noxfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/{{cookiecutter.project_name}}/noxfile.py b/{{cookiecutter.project_name}}/noxfile.py index 7393738..d9a97a4 100644 --- a/{{cookiecutter.project_name}}/noxfile.py +++ b/{{cookiecutter.project_name}}/noxfile.py @@ -152,7 +152,7 @@ def security_python(session: Session) -> None: session.run("bandit", "-r", PACKAGE_NAME, "-c", "bandit.yml", "-ll") session.log(f"Running pip-audit dependency security check with py{session.python}.") - session.run("pip-audit", "--python", str(Path(session.python))) + session.run("pip-audit") @nox.session(python=PYTHON_VERSIONS, name="tests-python") From 5624f06dc26a5e3a8b90d1d5c649c5048aa9a6f8 Mon Sep 17 00:00:00 2001 From: Kyle Oliver Date: Sun, 25 May 2025 16:13:43 -0400 Subject: [PATCH 30/31] fix: remove happenstance retrocookie insertion into nox version specification --- {{cookiecutter.project_name}}/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/{{cookiecutter.project_name}}/pyproject.toml b/{{cookiecutter.project_name}}/pyproject.toml index 3c714ab..e98158e 100644 --- a/{{cookiecutter.project_name}}/pyproject.toml +++ b/{{cookiecutter.project_name}}/pyproject.toml @@ -25,7 +25,7 @@ dependencies = [ [dependency-groups] dev = [ "commitizen>=4.7.0", - "nox>={{cookiecutter.copyright_year}}.5.1", + "nox>=2025.5.1", "pre-commit>=4.2.0", "pre-commit-hooks>=5.0.0", ] From 873a80e29090ec35f5e5247413d88faa157721ec Mon Sep 17 00:00:00 2001 From: Kyle Oliver Date: Sun, 25 May 2025 17:30:58 -0400 Subject: [PATCH 31/31] feat: copy .editorconfig from the inner portion to the template Ensures the template scripts and hooks follow the same style guide --- .editorconfig | 113 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..58f5d49 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,113 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +max_line_length = 120 +tab_width = 4 +ij_continuation_indent_size = 8 +ij_formatter_off_tag = @formatter:off +ij_formatter_on_tag = @formatter:on +ij_formatter_tags_enabled = true +ij_smart_tabs = false +ij_visual_guides = +ij_wrap_on_typing = false + +[*.toml] +indent_style = space +indent_size = 4 + +[*.yml,yaml,json] +indent_style = space +indent_size = 2 + +[{*.py,*.pyw,*.whl}] +indent_style = space +indent_size = 4 +ij_continuation_indent_size = 4 +ij_python_add_indent_inside_injections = false +ij_python_align_collections_and_comprehensions = true +ij_python_align_multiline_imports = true +ij_python_align_multiline_parameters = false +ij_python_align_multiline_parameters_in_calls = false +ij_python_blank_line_at_file_end = true +ij_python_blank_lines_after_imports = 2 +ij_python_blank_lines_after_local_imports = 0 +ij_python_blank_lines_around_class = 1 +ij_python_blank_lines_around_method = 1 +ij_python_blank_lines_around_top_level_classes_functions = 2 +ij_python_blank_lines_before_first_method = 0 +ij_python_call_parameters_new_line_after_left_paren = true +ij_python_call_parameters_right_paren_on_new_line = true +ij_python_call_parameters_wrap = on_every_item +ij_python_dict_alignment = 0 +ij_python_dict_new_line_after_left_brace = true +ij_python_dict_new_line_before_right_brace = true +ij_python_dict_wrapping = on_every_item +ij_python_format_injected_fragments = true +ij_python_from_import_new_line_after_left_parenthesis = true +ij_python_from_import_new_line_before_right_parenthesis = true +ij_python_from_import_parentheses_force_if_multiline = true +ij_python_from_import_trailing_comma_if_multiline = false +ij_python_from_import_wrapping = on_every_item +ij_python_hang_closing_brackets = false +ij_python_keep_blank_lines_in_code = 1 +ij_python_keep_blank_lines_in_declarations = 1 +ij_python_keep_indents_on_empty_lines = false +ij_python_keep_line_breaks = true +ij_python_list_new_line_after_left_bracket = true +ij_python_list_new_line_before_right_bracket = true +ij_python_list_wrapping = on_every_item +ij_python_method_parameters_new_line_after_left_paren = true +ij_python_method_parameters_right_paren_on_new_line = true +ij_python_method_parameters_wrap = on_every_item +ij_python_new_line_after_colon = true +ij_python_new_line_after_colon_multi_clause = true +ij_python_optimize_imports_always_split_from_imports = true +ij_python_optimize_imports_case_insensitive_order = false +ij_python_optimize_imports_join_from_imports_with_same_source = false +ij_python_optimize_imports_sort_by_type_first = true +ij_python_optimize_imports_sort_imports = true +ij_python_optimize_imports_sort_names_in_from_imports = false +ij_python_set_new_line_after_left_brace = true +ij_python_set_new_line_before_right_brace = true +ij_python_set_wrapping = on_every_item +ij_python_space_after_comma = true +ij_python_space_after_number_sign = true +ij_python_space_after_py_colon = true +ij_python_space_before_backslash = true +ij_python_space_before_comma = false +ij_python_space_before_for_semicolon = false +ij_python_space_before_lbracket = false +ij_python_space_before_method_call_parentheses = false +ij_python_space_before_method_parentheses = false +ij_python_space_before_number_sign = true +ij_python_space_before_py_colon = false +ij_python_space_within_empty_method_call_parentheses = false +ij_python_space_within_empty_method_parentheses = false +ij_python_spaces_around_additive_operators = true +ij_python_spaces_around_assignment_operators = true +ij_python_spaces_around_bitwise_operators = true +ij_python_spaces_around_eq_in_keyword_argument = false +ij_python_spaces_around_eq_in_named_parameter = false +ij_python_spaces_around_equality_operators = true +ij_python_spaces_around_multiplicative_operators = true +ij_python_spaces_around_power_operator = true +ij_python_spaces_around_relational_operators = true +ij_python_spaces_around_shift_operators = true +ij_python_spaces_within_braces = false +ij_python_spaces_within_brackets = false +ij_python_spaces_within_method_call_parentheses = false +ij_python_spaces_within_method_parentheses = false +ij_python_tuple_new_line_after_left_parenthesis = true +ij_python_tuple_new_line_before_right_parenthesis = true +ij_python_tuple_wrapping = on_every_item +ij_python_use_continuation_indent_for_arguments = false +ij_python_use_continuation_indent_for_collection_and_comprehensions = false +ij_python_use_continuation_indent_for_parameters = true +ij_python_use_trailing_comma_in_arguments_list = false +ij_python_use_trailing_comma_in_collections = false +ij_python_use_trailing_comma_in_parameter_list = false +ij_python_wrap_long_lines = false