From 082b7feacd92eb9be144982571b4efeb4715437f Mon Sep 17 00:00:00 2001 From: Eric Fischer Date: Sat, 1 Aug 2026 09:51:28 -0400 Subject: [PATCH 01/19] ADR changes for uv migration --- meta/adr/ADR-003-use_poetry.md | 4 +- meta/adr/ADR-015-use_uv.md | 91 ++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 2 deletions(-) create mode 100644 meta/adr/ADR-015-use_uv.md diff --git a/meta/adr/ADR-003-use_poetry.md b/meta/adr/ADR-003-use_poetry.md index 06930e9..9c631ba 100644 --- a/meta/adr/ADR-003-use_poetry.md +++ b/meta/adr/ADR-003-use_poetry.md @@ -1,7 +1,7 @@ --- title: "ADR-003: Use Poetry for Dependency Management" -status: "Accepted" -date: "2026-03-31" +status: "Superseded" +date: "2026-08-01" tags: - "python" - "dependencies" diff --git a/meta/adr/ADR-015-use_uv.md b/meta/adr/ADR-015-use_uv.md new file mode 100644 index 0000000..4ea324a --- /dev/null +++ b/meta/adr/ADR-015-use_uv.md @@ -0,0 +1,91 @@ +--- +title: "ADR-015: Use uv for Dependency Management" +status: "Accepted" +date: "2026-08-01" +supersedes: "ADR-003" +tags: + - "python" + - "dependencies" + - "core-tooling" +--- + +## Context + +- **Problem:** ADR-003 selected Poetry for dependency management. Since + that decision, the Python packaging ecosystem has matured significantly. + `uv` (by Astral, the creators of Ruff) has evolved from an experimental + pip replacement into a full-featured project manager with lock file + support, virtual environment management, and `pyproject.toml`-native + workflows. Given that this project is greenfield, now is the ideal time + to adopt the faster, simpler tool before any Poetry lock files or + workflows are established. +- **Constraints:** The tool must support monorepo workflows (per ADR-007), + produce reproducible builds, integrate with CI/CD pipelines (ADR-014), + and work alongside Ruff (ADR-005) without conflict. It must support + publishing to PyPI. + +## Decision + +We will use **[uv](https://docs.astral.sh/uv/)** for dependency +management across all Python applications and libraries in this project, +superseding ADR-003 (Poetry). + +Each application in `apps/` and each library in `libs/` will have its own +`pyproject.toml` managed by uv. This provides: + +- **Deterministic dependency resolution** via `uv.lock` files. +- **Standardized project metadata** in `pyproject.toml` (PEP 621 + compliant). +- **Virtual environment management** — uv creates and manages isolated + `.venv` environments per project. +- **Build and publish support** — `uv build` and `uv publish` handle the + full package lifecycle. +- **Extreme speed** — dependency resolution and installation are + 10-100x faster than Poetry due to uv's Rust implementation. + +### Key Conventions + +- Applications **commit** their `uv.lock` files for reproducible + deployments. +- Libraries **may** choose to exclude `uv.lock` from version control + to test against the latest compatible versions. +- Use `uv sync` to install dependencies into the project's `.venv`. +- Use `uv run ` to execute commands within the virtual + environment (e.g., `uv run pytest`, `uv run ruff check .`). + +## Considered Options + +1. **uv (Chosen):** Ultra-fast Rust-based Python package and project + manager by Astral. + - *Pros:* 10-100x faster than Poetry, native `pyproject.toml` support + (PEP 621), built-in lock files, virtual environment management, + pip-compatible, same vendor as Ruff (consistent tooling ecosystem), + single binary install, excellent monorepo support. + - *Cons:* Younger project than Poetry, though now stable and + widely adopted. Some advanced Poetry features (e.g., plugin system) + have no direct equivalent. +2. **Poetry (Previous choice, ADR-003):** Modern dependency management + with lock files and `pyproject.toml` support. + - *Pros:* Mature ecosystem, large community, strong documentation. + - *Cons:* Slower dependency resolution, heavier installation footprint, + custom metadata format (not fully PEP 621), separate tool from + the linting ecosystem. +3. **pip + requirements.txt:** The traditional Python approach. + - *Pros:* Zero additional tooling, universally understood. + - *Cons:* No lock file by default, manual virtual environment + management, no dependency resolution guarantees. + +## Consequences + +- **Positive:** Faster CI/CD pipelines due to dramatically reduced + dependency resolution time. Consistent Astral tooling ecosystem + (uv + Ruff). Simpler installation (single binary). Native PEP 621 + compliance means `pyproject.toml` is portable across tools. +- **Negative:** Contributors familiar with Poetry will need to learn uv + commands. CI workflows (ADR-014) must be updated to use uv instead of + Poetry. The composite action `setup-python-poetry` will need to be + replaced with a uv-based equivalent. +- **Future Implications:** Aligning on Astral tooling (uv for packaging, + Ruff for linting) creates a cohesive, high-performance development + experience. Docker build stages (ADR-006) should be updated to use + `uv sync` instead of `poetry export`. From 5abb9db09f15250c660a963451def16075100782 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Aug 2026 14:00:07 +0000 Subject: [PATCH 02/19] Complete migration from poetry to uv across all files --- .../action.yml | 21 ++++++-------- .github/copilot-instructions.md | 18 ++++++------ .github/workflows/ci.yml | 10 +++---- .github/workflows/publish.yml | 11 ++++---- .gitignore | 5 ++-- README.md | 12 ++++---- apps/example-app/Dockerfile | 20 ++++++------- apps/example-app/README.md | 16 +++++------ docker-compose.test.yml | 4 +-- docs-src/LOCAL_DEVELOPMENT_DEPENDENCIES.md | 28 +++++++++---------- docs-src/SHARED_LIBRARY_VERSIONING.md | 13 +++++---- libs/example-lib/README.md | 18 ++++++------ meta/adr/ADR-004-use_pytest.md | 6 ++-- meta/adr/ADR-006-use_docker.md | 10 +++---- meta/adr/ADR-009-shared_library_versioning.md | 8 ++++-- meta/adr/ADR-014-cicd_strategy.md | 19 ++++++------- scripts/local-ci-check.sh | 12 ++++---- scripts/setup-local-deps.py | 6 ++-- .../{{cookiecutter.app_name}}/README.md | 12 ++++---- .../{{cookiecutter.lib_name}}/README.md | 14 ++++++---- 20 files changed, 133 insertions(+), 130 deletions(-) rename .github/actions/{setup-python-poetry => setup-python-uv}/action.yml (57%) diff --git a/.github/actions/setup-python-poetry/action.yml b/.github/actions/setup-python-uv/action.yml similarity index 57% rename from .github/actions/setup-python-poetry/action.yml rename to .github/actions/setup-python-uv/action.yml index 66c336f..80c7276 100644 --- a/.github/actions/setup-python-poetry/action.yml +++ b/.github/actions/setup-python-uv/action.yml @@ -1,7 +1,7 @@ -name: Setup Python and Poetry +name: Setup Python and uv description: > - Reusable composite action to set up Python, install Poetry, and restore - the project virtual environment from cache (keyed on poetry.lock hash). + Reusable composite action to set up Python, install uv, and restore + the project virtual environment from cache (keyed on uv.lock hash). inputs: python-version: @@ -9,7 +9,7 @@ inputs: required: false default: "3.12" working-directory: - description: Directory containing pyproject.toml and poetry.lock + description: Directory containing pyproject.toml and uv.lock required: false default: "." @@ -21,21 +21,16 @@ runs: with: python-version: ${{ inputs.python-version }} - - name: Install Poetry + - name: Install uv shell: bash - run: pip install --quiet poetry - - - name: Configure Poetry to create in-project virtual environments - shell: bash - working-directory: ${{ inputs.working-directory }} - run: poetry config virtualenvs.in-project true + run: pip install --quiet uv - name: Cache virtual environment id: cache-venv uses: actions/cache@v4 with: path: ${{ inputs.working-directory }}/.venv - key: venv-${{ runner.os }}-${{ inputs.python-version }}-${{ hashFiles(format('{0}/poetry.lock', inputs.working-directory)) }} + key: venv-${{ runner.os }}-${{ inputs.python-version }}-${{ hashFiles(format('{0}/uv.lock', inputs.working-directory)) }} restore-keys: | venv-${{ runner.os }}-${{ inputs.python-version }}- @@ -43,4 +38,4 @@ runs: if: steps.cache-venv.outputs.cache-hit != 'true' shell: bash working-directory: ${{ inputs.working-directory }} - run: poetry install --no-interaction + run: uv sync --frozen diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index fe4d8dd..e4f3524 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -44,8 +44,8 @@ Before submitting any pull request, ensure your changes pass the same quality ch This project uses the following Python tooling: -* **[Poetry](https://python-poetry.org/)** for dependency management - (see [ADR-003](../meta/adr/ADR-003-use_poetry.md)) +* **[uv](https://docs.astral.sh/uv/)** for dependency management + (see [ADR-015](../meta/adr/ADR-015-use_uv.md)) * **[Ruff](https://docs.astral.sh/ruff/)** for linting and formatting (see [ADR-005](../meta/adr/ADR-005-use_ruff.md)) * **[pytest](https://docs.pytest.org/)** for testing @@ -57,23 +57,23 @@ This project uses the following Python tooling: # Navigate to an app or lib directory cd apps/ -# Install dependencies with Poetry -poetry install +# Install dependencies with uv +uv sync # Run linting -poetry run ruff check . +uv run ruff check . # Run formatting -poetry run ruff format . +uv run ruff format . # Run tests -poetry run pytest +uv run pytest # Run tests with coverage -poetry run pytest --cov +uv run pytest --cov # Run type checking (if mypy is configured) -poetry run mypy . +uv run mypy . ``` ### Pre-Commit Checks diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 09e9db3..87e70b4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -109,19 +109,19 @@ jobs: - name: Checkout code uses: actions/checkout@v7 - - name: Set up Python and Poetry - uses: ./.github/actions/setup-python-poetry + - name: Set up Python and uv + uses: ./.github/actions/setup-python-uv with: working-directory: ${{ matrix.project }} - name: Ruff format check working-directory: ${{ matrix.project }} - run: poetry run ruff format --check . + run: uv run ruff format --check . - name: Ruff lint working-directory: ${{ matrix.project }} - run: poetry run ruff check . + run: uv run ruff check . - name: pytest working-directory: ${{ matrix.project }} - run: poetry run pytest + run: uv run pytest diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 9ac250a..f2c103c 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -21,22 +21,22 @@ jobs: - name: Checkout code uses: actions/checkout@v7 - - name: Set up Python and Poetry - uses: ./.github/actions/setup-python-poetry + - name: Set up Python and uv + uses: ./.github/actions/setup-python-uv with: working-directory: apps/example-app - name: Build package - run: poetry build + run: uv build - name: Verify build artifacts run: ls -lh dist/ # Uncomment the following to enable PyPI publishing: # - name: Publish to PyPI - # run: poetry publish --build + # run: uv publish # env: - # POETRY_PYPI_TOKEN_PYPI: ${{ secrets.PYPI_TOKEN }} + # UV_PUBLISH_TOKEN: ${{ secrets.PYPI_TOKEN }} # # Prerequisites for publishing: # 1. Add PYPI_TOKEN to your repository secrets @@ -44,3 +44,4 @@ jobs: # 2. Create an account on https://pypi.org and generate an API token # 3. Uncomment the "Publish to PyPI" step above # 4. Remove the "Verify build artifacts" step if desired + diff --git a/.gitignore b/.gitignore index 8a1407e..617bd03 100644 --- a/.gitignore +++ b/.gitignore @@ -80,11 +80,12 @@ htmlcov/ .ruff_cache/ # ============================================================================= -# Poetry +# uv # ============================================================================= -# poetry.lock is intentionally NOT ignored — lock files should be committed +# uv.lock is intentionally NOT ignored — lock files should be committed # for applications. Libraries may choose to ignore them. + # ============================================================================= # Build artifacts and logs # ============================================================================= diff --git a/README.md b/README.md index 9a82812..0f85834 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # python-project-blueprint -> A template for Python monorepo projects targeting Python 3.12+ with Poetry +> A template for Python monorepo projects targeting Python 3.12+ with uv > dependency management. ## What Is This? @@ -44,7 +44,7 @@ For more details on GitHub template repositories, see the | ADR | Decision | | :--- | :--- | | [ADR-002](meta/adr/ADR-002-use_python312.md) | Python 3.12+ as minimum version | -| [ADR-003](meta/adr/ADR-003-use_poetry.md) | Poetry for dependency management | +| [ADR-015](meta/adr/ADR-015-use_uv.md) | uv for dependency management | | [ADR-004](meta/adr/ADR-004-use_pytest.md) | pytest for testing | | [ADR-005](meta/adr/ADR-005-use_ruff.md) | Ruff for linting and formatting | | [ADR-006](meta/adr/ADR-006-use_docker.md) | Docker for containerization | @@ -83,8 +83,8 @@ values appropriate for your project: pyenv install 3.12 pyenv local 3.12 -# Install Poetry -curl -sSL https://install.python-poetry.org | python3 - +# Install uv +pip install uv # Install pre-commit hooks pip install pre-commit @@ -103,7 +103,7 @@ pip install -r docs-requirements.txt ```bash mkdir -p apps/my-app cd apps/my-app -poetry init +uv init mkdir -p src/my_app tests ``` @@ -116,7 +116,7 @@ passes in your new repository. * **Python 3.12+ only.** Take advantage of modern Python features and performance improvements. -* **Poetry everywhere.** Consistent dependency management across all apps +* **uv everywhere.** Fast, reliable dependency management across all apps and libraries. * **Ruff for speed.** Fast linting and formatting that replaces multiple tools. diff --git a/apps/example-app/Dockerfile b/apps/example-app/Dockerfile index e1362cd..fb3862f 100644 --- a/apps/example-app/Dockerfile +++ b/apps/example-app/Dockerfile @@ -11,7 +11,7 @@ # Resolves dependencies and pre-builds wheels for reproducible installs. FROM python:3.12-slim AS builder -RUN pip install --no-cache-dir poetry poetry-plugin-export +RUN pip install --no-cache-dir uv WORKDIR /build @@ -19,26 +19,26 @@ WORKDIR /build COPY libs/example-lib ./libs/example-lib # Copy application manifests before source code for Docker layer cache efficiency -COPY apps/example-app/pyproject.toml apps/example-app/poetry.lock \ +COPY apps/example-app/pyproject.toml apps/example-app/uv.lock \ ./apps/example-app/ -# Export pinned runtime dependencies from the Poetry lock file. +# Use uv to generate requirements from the lock file for reproducible builds. # The local path dependency (example-lib) is excluded and handled separately. RUN cd apps/example-app \ - && poetry export \ - --without dev \ - --without-hashes \ - --format requirements.txt \ - --output /tmp/requirements.txt \ + && uv export \ + --frozen \ + --no-dev \ + --no-hashes \ + --output-file /tmp/requirements.txt \ && grep -vE "^(-e |example-lib)" /tmp/requirements.txt \ - > /tmp/app-requirements.txt || true + > /tmp/app-requirements.txt || true # Pre-build wheels for reproducible, offline installation in the runtime stage RUN pip wheel --no-cache-dir --wheel-dir /wheels -r /tmp/app-requirements.txt RUN pip wheel --no-cache-dir --wheel-dir /wheels ./libs/example-lib # ── Stage 2: Runtime ──────────────────────────────────────────────────────── -# Minimal production image — no Poetry, no build tools, no source trees. +# Minimal production image — no uv, no build tools, no source trees. FROM python:3.12-slim AS runtime WORKDIR /app diff --git a/apps/example-app/README.md b/apps/example-app/README.md index 473db76..a6b093b 100644 --- a/apps/example-app/README.md +++ b/apps/example-app/README.md @@ -7,7 +7,7 @@ This application provides a minimal example of how applications work in the monorepo. It demonstrates: -* Application structure with `pyproject.toml` and Poetry +* Application structure with `pyproject.toml` and uv * Click-based CLI (per [ADR-011](../../meta/adr/ADR-011-use_click.md)) * Path dependency on a shared library (`example-lib`) * Testing CLI commands with `click.testing.CliRunner` @@ -16,17 +16,17 @@ monorepo. It demonstrates: ```bash cd apps/example-app -poetry install +uv sync ``` ## Usage ```bash # Run the CLI -poetry run example-app hello +uv run example-app hello # Output: Hello, World! -poetry run example-app hello --name Python +uv run example-app hello --name Python # Output: Hello, Python! ``` @@ -34,10 +34,10 @@ poetry run example-app hello --name Python ```bash cd apps/example-app -poetry install -poetry run pytest -poetry run ruff check . -poetry run ruff format --check . +uv sync +uv run pytest +uv run ruff check . +uv run ruff format --check . ``` ## Dependencies diff --git a/docker-compose.test.yml b/docker-compose.test.yml index 7af6231..0f2596d 100644 --- a/docker-compose.test.yml +++ b/docker-compose.test.yml @@ -15,7 +15,7 @@ services: build: context: . dockerfile: apps/example-app/Dockerfile - command: ["poetry", "run", "pytest"] + command: ["uv", "run", "pytest"] working_dir: /app volumes: - ./apps/example-app:/app @@ -25,7 +25,7 @@ services: build: context: . dockerfile: libs/example-lib/Dockerfile - command: ["poetry", "run", "pytest"] + command: ["uv", "run", "pytest"] working_dir: /app volumes: - ./libs/example-lib:/app diff --git a/docs-src/LOCAL_DEVELOPMENT_DEPENDENCIES.md b/docs-src/LOCAL_DEVELOPMENT_DEPENDENCIES.md index 3c43a15..f1296e6 100644 --- a/docs-src/LOCAL_DEVELOPMENT_DEPENDENCIES.md +++ b/docs-src/LOCAL_DEVELOPMENT_DEPENDENCIES.md @@ -7,7 +7,7 @@ | Tool | Version | Purpose | Install | | :--- | :--- | :--- | :--- | | **Python** | 3.12+ | Runtime | [python.org](https://www.python.org/) or `pyenv install 3.12` | -| **Poetry** | 2.x | Dependency management | [install.python-poetry.org](https://install.python-poetry.org) | +| **uv** | latest | Dependency management | [docs.astral.sh/uv](https://docs.astral.sh/uv/getting-started/) | | **Git** | 2.x | Version control | [git-scm.com](https://git-scm.com/) | ## Recommended Tools @@ -26,8 +26,8 @@ pyenv install 3.12 pyenv local 3.12 -# 2. Install Poetry -curl -sSL https://install.python-poetry.org | python3 - +# 2. Install uv +pip install uv # 3. Install pre-commit hooks pip install pre-commit @@ -54,22 +54,22 @@ pyenv local 3.12 python --version # Should output Python 3.12.x ``` -## Poetry +## uv -Poetry manages dependencies, virtual environments, and package metadata for +uv manages dependencies, virtual environments, and package metadata for each application and library in the monorepo. See -[ADR-003](https://github.com/{{GITHUB_OWNER}}/{{PROJECT_NAME}}/blob/main/meta/adr/ADR-003-use_poetry.md). +[ADR-015](https://github.com/{{GITHUB_OWNER}}/{{PROJECT_NAME}}/blob/main/meta/adr/ADR-015-use_uv.md). ```bash -# Install Poetry -curl -sSL https://install.python-poetry.org | python3 - +# Install uv +pip install uv # Install a project's dependencies cd apps/example-app -poetry install +uv sync # Run a command in the project's virtual environment -poetry run pytest +uv run pytest ``` ## Ruff @@ -79,16 +79,16 @@ Ruff handles both linting and formatting for Python code. See ```bash # Check formatting -poetry run ruff format --check . +uv run ruff format --check . # Auto-format -poetry run ruff format . +uv run ruff format . # Lint -poetry run ruff check . +uv run ruff check . # Lint with auto-fix -poetry run ruff check --fix . +uv run ruff check --fix . ``` ## Pre-commit diff --git a/docs-src/SHARED_LIBRARY_VERSIONING.md b/docs-src/SHARED_LIBRARY_VERSIONING.md index 581130f..4991bb4 100644 --- a/docs-src/SHARED_LIBRARY_VERSIONING.md +++ b/docs-src/SHARED_LIBRARY_VERSIONING.md @@ -24,7 +24,7 @@ All library versions use the `MAJOR.MINOR.PATCH` format: New libraries start at version `0.1.0`: ```toml -[tool.poetry] +[project] name = "my-lib" version = "0.1.0" ``` @@ -40,13 +40,14 @@ as documentation of the API contract rather than a resolution constraint. ```toml # In apps/my-app/pyproject.toml -[tool.poetry.dependencies] -my-lib = { path = "../../libs/my-lib", develop = true } +[project] +dependencies = [ + "my-lib @ file://../../libs/my-lib" +] ``` -The `develop = true` flag installs the library in editable mode so that -changes to the library source are immediately reflected in the application -without reinstalling. +This installs the library in editable mode so that changes to the library +source are immediately reflected in the application without reinstalling. ## When to Bump Versions diff --git a/libs/example-lib/README.md b/libs/example-lib/README.md index e0cda64..a7050b7 100644 --- a/libs/example-lib/README.md +++ b/libs/example-lib/README.md @@ -7,7 +7,7 @@ This library provides a minimal example of how shared libraries work in the monorepo. It demonstrates: -* Library structure with `pyproject.toml` and Poetry +* Library structure with `pyproject.toml` and uv * Snake\_case Python package naming (`example_lib`) * Kebab-case directory naming (`example-lib`) * Path dependency pattern for monorepo consumers @@ -18,14 +18,16 @@ monorepo. It demonstrates: From an application in the monorepo, add a path dependency: ```toml -[tool.poetry.dependencies] -example-lib = { path = "../../libs/example-lib", develop = true } +[project] +dependencies = [ + "example-lib @ file://../../libs/example-lib" +] ``` Then install: ```bash -poetry install +uv sync ``` ## Usage @@ -50,8 +52,8 @@ Returns a greeting message for the given name. ```bash cd libs/example-lib -poetry install -poetry run pytest -poetry run ruff check . -poetry run ruff format --check . +uv sync +uv run pytest +uv run ruff check . +uv run ruff format --check . ``` diff --git a/meta/adr/ADR-004-use_pytest.md b/meta/adr/ADR-004-use_pytest.md index 1a2a4f5..27e1cc4 100644 --- a/meta/adr/ADR-004-use_pytest.md +++ b/meta/adr/ADR-004-use_pytest.md @@ -39,13 +39,13 @@ for all Python applications and libraries in this project. ```bash # Run all tests in a project cd apps/ -poetry run pytest +uv run pytest # Run with verbose output -poetry run pytest -v +uv run pytest -v # Run with coverage -poetry run pytest --cov=src +uv run pytest --cov=src ``` ## Considered Options diff --git a/meta/adr/ADR-006-use_docker.md b/meta/adr/ADR-006-use_docker.md index 1cae8bf..12f0940 100644 --- a/meta/adr/ADR-006-use_docker.md +++ b/meta/adr/ADR-006-use_docker.md @@ -16,7 +16,7 @@ tags: developer machines, CI runners, and production servers cause "works on my machine" problems. * **Constraints:** The containerization approach must support Python - applications with Poetry-managed dependencies, multi-stage builds for + applications with uv-managed dependencies, multi-stage builds for small production images, and work across both x86_64 and ARM architectures. @@ -29,7 +29,7 @@ project. * Each deployable application in `apps/` may include a `Dockerfile`. * Use **multi-stage builds** to keep production images small: - * **Builder stage:** Install Poetry, resolve dependencies, build wheels. + * **Builder stage:** Install uv, resolve dependencies, build wheels. * **Runtime stage:** Copy only the built artifacts and runtime dependencies. * Base images should use `python:3.12-slim` (Debian-based slim images) @@ -43,10 +43,10 @@ project. ```dockerfile # Builder stage FROM python:3.12-slim AS builder -RUN pip install poetry +RUN pip install uv WORKDIR /app -COPY pyproject.toml poetry.lock ./ -RUN poetry export -f requirements.txt --output requirements.txt +COPY pyproject.toml uv.lock ./ +RUN uv export --frozen --output requirements.txt RUN pip wheel --no-cache-dir --wheel-dir /wheels -r requirements.txt # Runtime stage diff --git a/meta/adr/ADR-009-shared_library_versioning.md b/meta/adr/ADR-009-shared_library_versioning.md index 07f2a12..fc242eb 100644 --- a/meta/adr/ADR-009-shared_library_versioning.md +++ b/meta/adr/ADR-009-shared_library_versioning.md @@ -36,7 +36,7 @@ all shared libraries in `libs/`. ### Key Conventions * Library versions are defined in their `pyproject.toml` under - `[tool.poetry] version`. + `[project] version`. * All libraries start at version `0.1.0` until their API stabilizes. * While at `0.x.y`, minor version bumps may include breaking changes (per SemVer spec). @@ -53,8 +53,10 @@ all shared libraries in `libs/`. ```toml # In apps/my-app/pyproject.toml -[tool.poetry.dependencies] -my-lib = { path = "../../libs/my-lib", develop = true } +[project] +dependencies = [ + "my-lib @ file://../../libs/my-lib" +] ``` ## Considered Options diff --git a/meta/adr/ADR-014-cicd_strategy.md b/meta/adr/ADR-014-cicd_strategy.md index e80497c..1d12d84 100644 --- a/meta/adr/ADR-014-cicd_strategy.md +++ b/meta/adr/ADR-014-cicd_strategy.md @@ -15,8 +15,8 @@ tags: validates code quality, runs tests, demonstrates packaging for PyPI, and builds Docker images — while remaining simple enough to serve as a learning blueprint for downstream projects. -* **Constraints:** The strategy must work with the existing Poetry-based - dependency management (ADR-003), use pytest for tests (ADR-004), use +* **Constraints:** The strategy must work with the existing uv-based + dependency management (ADR-015), use pytest for tests (ADR-004), use Ruff for linting (ADR-005), and support Docker-based deployment (ADR-006). It must not require credentials for external registries in the template repository itself. @@ -26,15 +26,14 @@ tags: We will use **GitHub Actions** to implement a multi-workflow CI/CD pipeline with the following components: -### Reusable Composite Action (`setup-python-poetry`) +### Reusable Composite Action (`setup-python-uv`) -A composite action at `.github/actions/setup-python-poetry/action.yml` -standardizes Python and Poetry setup across all workflows. It: +A composite action at `.github/actions/setup-python-uv/action.yml` +standardizes Python and uv setup across all workflows. It: * Installs the specified Python version via `actions/setup-python` -* Installs Poetry via `pip` -* Configures in-project virtual environments (`virtualenvs.in-project true`) -* Caches the `.venv` directory keyed on `os + python-version + poetry.lock hash` +* Installs uv via `pip` +* Caches the `.venv` directory keyed on `os + python-version + uv.lock hash` ### Continuous Integration (`ci.yml`) @@ -51,8 +50,8 @@ Runs on every pull request and push to `main`. Jobs include: Runs on version tags (`v*`) or manual dispatch. It: -* Builds the package with `poetry build` -* Includes the `poetry publish` step **commented out** with clear +* Builds the package with `uv build` +* Includes the `uv publish` step **commented out** with clear instructions for enabling it * Requires a `PYPI_TOKEN` repository secret to activate publishing diff --git a/scripts/local-ci-check.sh b/scripts/local-ci-check.sh index 224b104..7fe2df6 100755 --- a/scripts/local-ci-check.sh +++ b/scripts/local-ci-check.sh @@ -6,7 +6,7 @@ # # Prerequisites: # pip install pre-commit -# Poetry installed (https://python-poetry.org/) +# uv installed (https://docs.astral.sh/uv/getting-started/) # # Usage: # ./scripts/local-ci-check.sh # Run repo-wide checks only @@ -75,19 +75,19 @@ if [ -n "$TARGET_DIR" ]; then echo "▶ Running Python quality checks for $TARGET_DIR..." cd "$PROJECT_PATH" - echo " ▸ poetry install..." - poetry install --quiet + echo " ▸ uv sync --frozen..." + uv sync --frozen echo " ▸ ruff format --check..." - poetry run ruff format --check . + uv run ruff format --check . echo " ✅ Formatting OK" echo " ▸ ruff check..." - poetry run ruff check . + uv run ruff check . echo " ✅ Linting OK" echo " ▸ pytest..." - poetry run pytest + uv run pytest echo " ✅ Tests passed" echo "" diff --git a/scripts/setup-local-deps.py b/scripts/setup-local-deps.py index a080019..91b97c4 100644 --- a/scripts/setup-local-deps.py +++ b/scripts/setup-local-deps.py @@ -2,7 +2,7 @@ """Install local dependencies across the monorepo. Walks through all apps/ and libs/ directories that contain a pyproject.toml -and runs ``poetry install`` in each one. This ensures every project has its +and runs ``uv sync`` in each one. This ensures every project has its dependencies (including path dependencies on sibling libraries) installed and ready for development. @@ -36,11 +36,11 @@ def find_projects() -> list[Path]: def install_project(project: Path) -> bool: - """Run ``poetry install`` in *project*. Return True on success.""" + """Run ``uv sync`` in *project*. Return True on success.""" relative = project.relative_to(REPO_ROOT) print(f"\n▶ Installing {relative} ...") result = subprocess.run( - ["poetry", "install"], + ["uv", "sync"], cwd=project, capture_output=True, text=True, diff --git a/templates/python-app-template/{{cookiecutter.app_name}}/README.md b/templates/python-app-template/{{cookiecutter.app_name}}/README.md index 8633949..7353fb4 100644 --- a/templates/python-app-template/{{cookiecutter.app_name}}/README.md +++ b/templates/python-app-template/{{cookiecutter.app_name}}/README.md @@ -6,20 +6,20 @@ ```bash cd apps/{{ cookiecutter.app_name }} -poetry install +uv sync ``` ## Usage ```bash -poetry run {{ cookiecutter.app_name }} hello +uv run {{ cookiecutter.app_name }} hello ``` ## Development ```bash -poetry install -poetry run pytest -poetry run ruff check . -poetry run ruff format --check . +uv sync +uv run pytest +uv run ruff check . +uv run ruff format --check . ``` diff --git a/templates/python-lib-template/{{cookiecutter.lib_name}}/README.md b/templates/python-lib-template/{{cookiecutter.lib_name}}/README.md index aafa287..7b257f0 100644 --- a/templates/python-lib-template/{{cookiecutter.lib_name}}/README.md +++ b/templates/python-lib-template/{{cookiecutter.lib_name}}/README.md @@ -7,16 +7,18 @@ Add a path dependency from your application: ```toml -[tool.poetry.dependencies] -{{ cookiecutter.lib_name }} = { path = "../../libs/{{ cookiecutter.lib_name }}", develop = true } +[project] +dependencies = [ + "{{ cookiecutter.lib_name }} @ file://../../libs/{{ cookiecutter.lib_name }}" +] ``` ## Development ```bash cd libs/{{ cookiecutter.lib_name }} -poetry install -poetry run pytest -poetry run ruff check . -poetry run ruff format --check . +uv sync +uv run pytest +uv run ruff check . +uv run ruff format --check . ``` From 14461ac2dbf86e59965d194890a18f0cf09f2ee1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Aug 2026 14:01:39 +0000 Subject: [PATCH 03/19] Update pyproject.toml files to use PEP 621 format compatible with uv --- apps/example-app/pyproject.toml | 39 ++++++++++--------- libs/example-lib/pyproject.toml | 28 ++++++------- .../{{cookiecutter.app_name}}/pyproject.toml | 37 ++++++++++-------- .../{{cookiecutter.lib_name}}/pyproject.toml | 28 ++++++------- 4 files changed, 71 insertions(+), 61 deletions(-) diff --git a/apps/example-app/pyproject.toml b/apps/example-app/pyproject.toml index 173cdf4..55147f6 100644 --- a/apps/example-app/pyproject.toml +++ b/apps/example-app/pyproject.toml @@ -1,27 +1,30 @@ -[tool.poetry] +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] name = "example-app" version = "0.1.0" description = "Example application demonstrating the monorepo app pattern" -authors = ["{{GITHUB_OWNER}}"] readme = "README.md" -packages = [{include = "app"}] - -[tool.poetry.dependencies] -python = "^3.12" -click = "^8.4" -example-lib = { path = "../../libs/example-lib", develop = true } - -[tool.poetry.group.dev.dependencies] -pytest = "^9.1" -ruff = "^0.16" - -[tool.poetry.scripts] +requires-python = ">=3.12" +authors = [ + {name = "{{GITHUB_OWNER}}"} +] +dependencies = [ + "click>=8.4", + "example-lib @ file://../../libs/example-lib" +] + +[project.optional-dependencies] +dev = [ + "pytest>=9.1", + "ruff>=0.16", +] + +[project.scripts] example-app = "app.main:cli" -[build-system] -requires = ["poetry-core"] -build-backend = "poetry.core.masonry.api" - [tool.ruff] target-version = "py312" line-length = 88 diff --git a/libs/example-lib/pyproject.toml b/libs/example-lib/pyproject.toml index 5ade804..9186349 100644 --- a/libs/example-lib/pyproject.toml +++ b/libs/example-lib/pyproject.toml @@ -1,21 +1,23 @@ -[tool.poetry] +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] name = "example-lib" version = "0.1.0" description = "Example shared library demonstrating the monorepo library pattern" -authors = ["{{GITHUB_OWNER}}"] readme = "README.md" -packages = [{include = "example_lib"}] - -[tool.poetry.dependencies] -python = "^3.12" +requires-python = ">=3.12" +authors = [ + {name = "{{GITHUB_OWNER}}"} +] +dependencies = [] -[tool.poetry.group.dev.dependencies] -pytest = "^9.0" -ruff = ">=0.15,<0.17" - -[build-system] -requires = ["poetry-core"] -build-backend = "poetry.core.masonry.api" +[project.optional-dependencies] +dev = [ + "pytest>=9.0", + "ruff>=0.15,<0.17", +] [tool.ruff] target-version = "py312" diff --git a/templates/python-app-template/{{cookiecutter.app_name}}/pyproject.toml b/templates/python-app-template/{{cookiecutter.app_name}}/pyproject.toml index 6dd1bbd..fa935b6 100644 --- a/templates/python-app-template/{{cookiecutter.app_name}}/pyproject.toml +++ b/templates/python-app-template/{{cookiecutter.app_name}}/pyproject.toml @@ -1,26 +1,29 @@ -[tool.poetry] +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] name = "{{ cookiecutter.app_name }}" version = "0.1.0" description = "{{ cookiecutter.description }}" -authors = ["{{ cookiecutter.author }}"] readme = "README.md" -packages = [{include = "{{ cookiecutter.package_name }}"}] - -[tool.poetry.dependencies] -python = "^3.12" -click = "^8.1" - -[tool.poetry.group.dev.dependencies] -pytest = "^8.0" -ruff = "^0.11" - -[tool.poetry.scripts] +requires-python = ">=3.12" +authors = [ + {name = "{{ cookiecutter.author }}"} +] +dependencies = [ + "click>=8.1", +] + +[project.optional-dependencies] +dev = [ + "pytest>=8.0", + "ruff>=0.11", +] + +[project.scripts] {{ cookiecutter.app_name }} = "{{ cookiecutter.package_name }}.main:cli" -[build-system] -requires = ["poetry-core"] -build-backend = "poetry.core.masonry.api" - [tool.ruff] target-version = "py312" line-length = 88 diff --git a/templates/python-lib-template/{{cookiecutter.lib_name}}/pyproject.toml b/templates/python-lib-template/{{cookiecutter.lib_name}}/pyproject.toml index 0380f1b..d13870f 100644 --- a/templates/python-lib-template/{{cookiecutter.lib_name}}/pyproject.toml +++ b/templates/python-lib-template/{{cookiecutter.lib_name}}/pyproject.toml @@ -1,21 +1,23 @@ -[tool.poetry] +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] name = "{{ cookiecutter.lib_name }}" version = "0.1.0" description = "{{ cookiecutter.description }}" -authors = ["{{ cookiecutter.author }}"] readme = "README.md" -packages = [{include = "{{ cookiecutter.package_name }}"}] - -[tool.poetry.dependencies] -python = "^3.12" +requires-python = ">=3.12" +authors = [ + {name = "{{ cookiecutter.author }}"} +] +dependencies = [] -[tool.poetry.group.dev.dependencies] -pytest = "^8.0" -ruff = "^0.11" - -[build-system] -requires = ["poetry-core"] -build-backend = "poetry.core.masonry.api" +[project.optional-dependencies] +dev = [ + "pytest>=8.0", + "ruff>=0.11", +] [tool.ruff] target-version = "py312" From d367f1a46947f65ec4466cb92fff1705dce00ec5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Aug 2026 16:52:34 +0000 Subject: [PATCH 04/19] Generate uv.lock files and configure hatchling build targets --- apps/example-app/pyproject.toml | 3 + apps/example-app/uv.lock | 137 ++++++++++++++++++++++++++++++++ libs/example-lib/pyproject.toml | 3 + libs/example-lib/uv.lock | 107 +++++++++++++++++++++++++ 4 files changed, 250 insertions(+) create mode 100644 apps/example-app/uv.lock create mode 100644 libs/example-lib/uv.lock diff --git a/apps/example-app/pyproject.toml b/apps/example-app/pyproject.toml index 55147f6..9f466c4 100644 --- a/apps/example-app/pyproject.toml +++ b/apps/example-app/pyproject.toml @@ -35,5 +35,8 @@ select = ["E", "F", "I", "UP", "B", "SIM"] [tool.ruff.format] quote-style = "double" +[tool.hatch.build.targets.wheel] +packages = ["app"] + [tool.pytest.ini_options] testpaths = ["tests"] diff --git a/apps/example-app/uv.lock b/apps/example-app/uv.lock new file mode 100644 index 0000000..0974fc4 --- /dev/null +++ b/apps/example-app/uv.lock @@ -0,0 +1,137 @@ +version = 1 +revision = 3 +requires-python = ">=3.12" + +[[package]] +name = "click" +version = "8.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/76/d4/81420972a676e8ffea40450d8c8c92943e7218a78fe9b64359836cc9876b/click-8.4.2.tar.gz", hash = "sha256:9a6cea6e60b17ebe0a44c5cc636d94f09bd66142c1cd7d8b4cd731c4917a15f6", size = 338000, upload-time = "2026-06-24T17:45:15.148Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/e2/79c688af8b210d232694e31e59da9f6ec747bae31c3f5946e4e9b98860d5/click-8.4.2-py3-none-any.whl", hash = "sha256:e6f9f66136c816745b9d65817da91d61d957fb16e02e4dcd0552553c5a197b76", size = 119243, upload-time = "2026-06-24T17:45:13.73Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "example-app" +version = "0.1.0" +source = { editable = "." } +dependencies = [ + { name = "click" }, + { name = "example-lib" }, +] + +[package.optional-dependencies] +dev = [ + { name = "pytest" }, + { name = "ruff" }, +] + +[package.metadata] +requires-dist = [ + { name = "click", specifier = ">=8.4" }, + { name = "example-lib", directory = "/home/runner/work/python-project-blueprint/python-project-blueprint/libs/example-lib" }, + { name = "pytest", marker = "extra == 'dev'", specifier = ">=9.1" }, + { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.16" }, +] +provides-extras = ["dev"] + +[[package]] +name = "example-lib" +version = "0.1.0" +source = { directory = "/home/runner/work/python-project-blueprint/python-project-blueprint/libs/example-lib" } + +[package.metadata] +requires-dist = [ + { name = "pytest", marker = "extra == 'dev'", specifier = ">=9.0" }, + { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.15,<0.17" }, +] +provides-extras = ["dev"] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "packaging" +version = "26.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "pygments" +version = "2.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, +] + +[[package]] +name = "pytest" +version = "9.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" }, +] + +[[package]] +name = "ruff" +version = "0.16.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/70/25/7113f6d5498888c5fb7db34081cba7d5971c4cb1bfb26819966eee68f003/ruff-0.16.1.tar.gz", hash = "sha256:fedad7c801dabd3fb9741d76aca39246e6ddd9ca446a015875207bf19f1e6bc7", size = 4877500, upload-time = "2026-07-30T19:37:01.379Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1b/bd/694da69368e0973de65df2ddc73ab18d43c469d5963d9b150911de6bc513/ruff-0.16.1-py3-none-linux_armv6l.whl", hash = "sha256:58edb313b88f0c5460a26adf5f39a37a3be789494a15e3e411e35fa78b89f9a0", size = 10839126, upload-time = "2026-07-30T19:36:13.697Z" }, + { url = "https://files.pythonhosted.org/packages/3f/f0/b626e5d5bd0dd9576263658ef12885e2288afd1029a48e26ffed65ec1ac1/ruff-0.16.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:fde5a99e2f97479af66edd6622c6d5a2a7592c77cf4153d9e4428f5eeb55b60c", size = 11070253, upload-time = "2026-07-30T19:36:17.14Z" }, + { url = "https://files.pythonhosted.org/packages/83/63/f40acfb6b35b88623e71684942b552c3edd96035f5d98f313815f7b277de/ruff-0.16.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e0d4c20532fca4f7fa609369161d968dd28f65d83dabbd61d8e9c7edbf7001f6", size = 10561425, upload-time = "2026-07-30T19:36:20.04Z" }, + { url = "https://files.pythonhosted.org/packages/aa/dd/14ec0e9c2b4d315547dd38765004b4863e354e1b52cb308272215d9f6f6d/ruff-0.16.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30affbcedf59ad5703d9c91f82266e02b47739f797e1a7b6e158e5526a6dae38", size = 10948879, upload-time = "2026-07-30T19:36:22.476Z" }, + { url = "https://files.pythonhosted.org/packages/33/e9/9d870cbae575030fdef595f04b4b97573c525b5497cce4f4498cf2f85446/ruff-0.16.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:24e9c631573cbca9d20f1283f8f479b2afa4a8503504822bd71a293889f16743", size = 10643691, upload-time = "2026-07-30T19:36:24.914Z" }, + { url = "https://files.pythonhosted.org/packages/c4/09/12743d544e2173f53ecd27217c65f90d2bc0f8424a66a60339e56bbc0457/ruff-0.16.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b41bdd48fb420987a9b5212e4957c26ad4abce401fa9ea9d4d85843727945f4f", size = 11435354, upload-time = "2026-07-30T19:36:28.447Z" }, + { url = "https://files.pythonhosted.org/packages/7f/89/a1652b2daee52083c9554a6333b678a8b01d0400f976827bb87857f9449a/ruff-0.16.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b0d1e1393b7648079e13669de1c1f4fde06d4583e84d8fd5c1551e0a77a2aa75", size = 12259033, upload-time = "2026-07-30T19:36:31.326Z" }, + { url = "https://files.pythonhosted.org/packages/16/96/ecdcb8c54ee7b123b487f807eb014e6e019155a0b81dfb669acd52f28ce3/ruff-0.16.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:07bf434b1c95f4e093be4532068ef4fcf00924eb2ade8796075980902d6fd54a", size = 11667981, upload-time = "2026-07-30T19:36:34.394Z" }, + { url = "https://files.pythonhosted.org/packages/cd/90/c52e12e0d862e9572f2a33aa227409143520abe53111e9a6babbac7b4af8/ruff-0.16.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39897739f112253ee4fdd2e8aa9a4f9ded99fb2be367d5f31dfa4ded6025584c", size = 11468183, upload-time = "2026-07-30T19:36:37.339Z" }, + { url = "https://files.pythonhosted.org/packages/2c/6b/4ffb7ad1d83eb16cf8cbb3c8815d3f11c88460fd162d4b372a2059be1c2a/ruff-0.16.1-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:82ae3c0c0d74daf17b968a10b7b3bb3ef297ab7de0c1f749646b25e690ccb150", size = 11470071, upload-time = "2026-07-30T19:36:39.91Z" }, + { url = "https://files.pythonhosted.org/packages/9c/72/32ae7db4c0b5e32ab611787caa19d1546800676d79f7483b7100a3561bf4/ruff-0.16.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:4d5f2ed10f8242d83fc08d521301089364e3375375705356f20c0e31606ef3ef", size = 10919503, upload-time = "2026-07-30T19:36:42.65Z" }, + { url = "https://files.pythonhosted.org/packages/f7/ca/3d901ba6ad6fc38da39c3448fc6c59ac945679293a17c3ceb6d6c1cba13e/ruff-0.16.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a4665b309891f83f3e3c25447935f1213e9abbd4b5640af7a1f2def9f8d413c1", size = 10649861, upload-time = "2026-07-30T19:36:45.18Z" }, + { url = "https://files.pythonhosted.org/packages/92/79/894ef1ced26552d5f8c9cf6d85b0687840e1128c55aeab7b9c2d54a0d880/ruff-0.16.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:26e9ca5c9bc3971f20d3cf18a957f52ffd6a5f6564ff15c4912a144dcac22494", size = 11148137, upload-time = "2026-07-30T19:36:47.936Z" }, + { url = "https://files.pythonhosted.org/packages/2d/69/3609a09fa1cb46cc28b762363e440a354204e5dff01bd0c8d7437874d6b9/ruff-0.16.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:67e1e1e3fa4f0c82f0e36d4cd61e661f6e7a6196cb1aa92fe0828fa7b8f257cd", size = 11559211, upload-time = "2026-07-30T19:36:50.448Z" }, + { url = "https://files.pythonhosted.org/packages/fc/8a/fb22af2fd78a736e241fabf67e30ce1799a64244026377a49e133af90762/ruff-0.16.1-py3-none-win32.whl", hash = "sha256:d31765e131295b8445caf301e3e8a85b34d1b9b211b4109b7ba457888b051806", size = 10838258, upload-time = "2026-07-30T19:36:53.298Z" }, + { url = "https://files.pythonhosted.org/packages/d4/35/e57fd9fb5d423961df087a00b12d42c0a830288dc2f3b45ecca299158b4f/ruff-0.16.1-py3-none-win_amd64.whl", hash = "sha256:09b05e8b90c2cb06ad63464350e7a45e8e44a2dfe52072ebfba6666ca8d3f596", size = 11961111, upload-time = "2026-07-30T19:36:56.107Z" }, + { url = "https://files.pythonhosted.org/packages/cb/46/240ea004bf6dc4feb40e9832f2205a476a47dd5b8a3f8211a5fc5f95e20e/ruff-0.16.1-py3-none-win_arm64.whl", hash = "sha256:dbaadaac38c70239f056d306b7476f246b0bf000fa6b3876402acbf5b227eaf8", size = 11309414, upload-time = "2026-07-30T19:36:58.79Z" }, +] diff --git a/libs/example-lib/pyproject.toml b/libs/example-lib/pyproject.toml index 9186349..ef1cdb5 100644 --- a/libs/example-lib/pyproject.toml +++ b/libs/example-lib/pyproject.toml @@ -29,5 +29,8 @@ select = ["E", "F", "I", "UP", "B", "SIM"] [tool.ruff.format] quote-style = "double" +[tool.hatch.build.targets.wheel] +packages = ["example_lib"] + [tool.pytest.ini_options] testpaths = ["tests"] diff --git a/libs/example-lib/uv.lock b/libs/example-lib/uv.lock new file mode 100644 index 0000000..724eef8 --- /dev/null +++ b/libs/example-lib/uv.lock @@ -0,0 +1,107 @@ +version = 1 +revision = 3 +requires-python = ">=3.12" + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "example-lib" +version = "0.1.0" +source = { editable = "." } + +[package.optional-dependencies] +dev = [ + { name = "pytest" }, + { name = "ruff" }, +] + +[package.metadata] +requires-dist = [ + { name = "pytest", marker = "extra == 'dev'", specifier = ">=9.0" }, + { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.15,<0.17" }, +] +provides-extras = ["dev"] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "packaging" +version = "26.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "pygments" +version = "2.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, +] + +[[package]] +name = "pytest" +version = "9.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" }, +] + +[[package]] +name = "ruff" +version = "0.16.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/70/25/7113f6d5498888c5fb7db34081cba7d5971c4cb1bfb26819966eee68f003/ruff-0.16.1.tar.gz", hash = "sha256:fedad7c801dabd3fb9741d76aca39246e6ddd9ca446a015875207bf19f1e6bc7", size = 4877500, upload-time = "2026-07-30T19:37:01.379Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1b/bd/694da69368e0973de65df2ddc73ab18d43c469d5963d9b150911de6bc513/ruff-0.16.1-py3-none-linux_armv6l.whl", hash = "sha256:58edb313b88f0c5460a26adf5f39a37a3be789494a15e3e411e35fa78b89f9a0", size = 10839126, upload-time = "2026-07-30T19:36:13.697Z" }, + { url = "https://files.pythonhosted.org/packages/3f/f0/b626e5d5bd0dd9576263658ef12885e2288afd1029a48e26ffed65ec1ac1/ruff-0.16.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:fde5a99e2f97479af66edd6622c6d5a2a7592c77cf4153d9e4428f5eeb55b60c", size = 11070253, upload-time = "2026-07-30T19:36:17.14Z" }, + { url = "https://files.pythonhosted.org/packages/83/63/f40acfb6b35b88623e71684942b552c3edd96035f5d98f313815f7b277de/ruff-0.16.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e0d4c20532fca4f7fa609369161d968dd28f65d83dabbd61d8e9c7edbf7001f6", size = 10561425, upload-time = "2026-07-30T19:36:20.04Z" }, + { url = "https://files.pythonhosted.org/packages/aa/dd/14ec0e9c2b4d315547dd38765004b4863e354e1b52cb308272215d9f6f6d/ruff-0.16.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30affbcedf59ad5703d9c91f82266e02b47739f797e1a7b6e158e5526a6dae38", size = 10948879, upload-time = "2026-07-30T19:36:22.476Z" }, + { url = "https://files.pythonhosted.org/packages/33/e9/9d870cbae575030fdef595f04b4b97573c525b5497cce4f4498cf2f85446/ruff-0.16.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:24e9c631573cbca9d20f1283f8f479b2afa4a8503504822bd71a293889f16743", size = 10643691, upload-time = "2026-07-30T19:36:24.914Z" }, + { url = "https://files.pythonhosted.org/packages/c4/09/12743d544e2173f53ecd27217c65f90d2bc0f8424a66a60339e56bbc0457/ruff-0.16.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b41bdd48fb420987a9b5212e4957c26ad4abce401fa9ea9d4d85843727945f4f", size = 11435354, upload-time = "2026-07-30T19:36:28.447Z" }, + { url = "https://files.pythonhosted.org/packages/7f/89/a1652b2daee52083c9554a6333b678a8b01d0400f976827bb87857f9449a/ruff-0.16.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b0d1e1393b7648079e13669de1c1f4fde06d4583e84d8fd5c1551e0a77a2aa75", size = 12259033, upload-time = "2026-07-30T19:36:31.326Z" }, + { url = "https://files.pythonhosted.org/packages/16/96/ecdcb8c54ee7b123b487f807eb014e6e019155a0b81dfb669acd52f28ce3/ruff-0.16.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:07bf434b1c95f4e093be4532068ef4fcf00924eb2ade8796075980902d6fd54a", size = 11667981, upload-time = "2026-07-30T19:36:34.394Z" }, + { url = "https://files.pythonhosted.org/packages/cd/90/c52e12e0d862e9572f2a33aa227409143520abe53111e9a6babbac7b4af8/ruff-0.16.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39897739f112253ee4fdd2e8aa9a4f9ded99fb2be367d5f31dfa4ded6025584c", size = 11468183, upload-time = "2026-07-30T19:36:37.339Z" }, + { url = "https://files.pythonhosted.org/packages/2c/6b/4ffb7ad1d83eb16cf8cbb3c8815d3f11c88460fd162d4b372a2059be1c2a/ruff-0.16.1-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:82ae3c0c0d74daf17b968a10b7b3bb3ef297ab7de0c1f749646b25e690ccb150", size = 11470071, upload-time = "2026-07-30T19:36:39.91Z" }, + { url = "https://files.pythonhosted.org/packages/9c/72/32ae7db4c0b5e32ab611787caa19d1546800676d79f7483b7100a3561bf4/ruff-0.16.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:4d5f2ed10f8242d83fc08d521301089364e3375375705356f20c0e31606ef3ef", size = 10919503, upload-time = "2026-07-30T19:36:42.65Z" }, + { url = "https://files.pythonhosted.org/packages/f7/ca/3d901ba6ad6fc38da39c3448fc6c59ac945679293a17c3ceb6d6c1cba13e/ruff-0.16.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a4665b309891f83f3e3c25447935f1213e9abbd4b5640af7a1f2def9f8d413c1", size = 10649861, upload-time = "2026-07-30T19:36:45.18Z" }, + { url = "https://files.pythonhosted.org/packages/92/79/894ef1ced26552d5f8c9cf6d85b0687840e1128c55aeab7b9c2d54a0d880/ruff-0.16.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:26e9ca5c9bc3971f20d3cf18a957f52ffd6a5f6564ff15c4912a144dcac22494", size = 11148137, upload-time = "2026-07-30T19:36:47.936Z" }, + { url = "https://files.pythonhosted.org/packages/2d/69/3609a09fa1cb46cc28b762363e440a354204e5dff01bd0c8d7437874d6b9/ruff-0.16.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:67e1e1e3fa4f0c82f0e36d4cd61e661f6e7a6196cb1aa92fe0828fa7b8f257cd", size = 11559211, upload-time = "2026-07-30T19:36:50.448Z" }, + { url = "https://files.pythonhosted.org/packages/fc/8a/fb22af2fd78a736e241fabf67e30ce1799a64244026377a49e133af90762/ruff-0.16.1-py3-none-win32.whl", hash = "sha256:d31765e131295b8445caf301e3e8a85b34d1b9b211b4109b7ba457888b051806", size = 10838258, upload-time = "2026-07-30T19:36:53.298Z" }, + { url = "https://files.pythonhosted.org/packages/d4/35/e57fd9fb5d423961df087a00b12d42c0a830288dc2f3b45ecca299158b4f/ruff-0.16.1-py3-none-win_amd64.whl", hash = "sha256:09b05e8b90c2cb06ad63464350e7a45e8e44a2dfe52072ebfba6666ca8d3f596", size = 11961111, upload-time = "2026-07-30T19:36:56.107Z" }, + { url = "https://files.pythonhosted.org/packages/cb/46/240ea004bf6dc4feb40e9832f2205a476a47dd5b8a3f8211a5fc5f95e20e/ruff-0.16.1-py3-none-win_arm64.whl", hash = "sha256:dbaadaac38c70239f056d306b7476f246b0bf000fa6b3876402acbf5b227eaf8", size = 11309414, upload-time = "2026-07-30T19:36:58.79Z" }, +] From f8678b7172b606277600e285127a7d5ea73dff32 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Aug 2026 16:53:27 +0000 Subject: [PATCH 05/19] Update example-app to use absolute path for example-lib dependency and add hatchling configuration --- apps/example-app/pyproject.toml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/example-app/pyproject.toml b/apps/example-app/pyproject.toml index 9f466c4..b87c899 100644 --- a/apps/example-app/pyproject.toml +++ b/apps/example-app/pyproject.toml @@ -13,7 +13,7 @@ authors = [ ] dependencies = [ "click>=8.4", - "example-lib @ file://../../libs/example-lib" + "example-lib @ file:///home/runner/work/python-project-blueprint/python-project-blueprint/libs/example-lib" ] [project.optional-dependencies] @@ -35,6 +35,9 @@ select = ["E", "F", "I", "UP", "B", "SIM"] [tool.ruff.format] quote-style = "double" +[tool.hatch.metadata] +allow-direct-references = true + [tool.hatch.build.targets.wheel] packages = ["app"] From ba39e5739dc7bf4821d5631e09c8344eaceaab04 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Aug 2026 16:58:59 +0000 Subject: [PATCH 06/19] Fix trailing newline in publish.yml --- .github/workflows/publish.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index f2c103c..1728fba 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -43,5 +43,4 @@ jobs: # (Settings → Secrets and variables → Actions → New repository secret) # 2. Create an account on https://pypi.org and generate an API token # 3. Uncomment the "Publish to PyPI" step above - # 4. Remove the "Verify build artifacts" step if desired - + # 4. Remove the "Verify build artifacts" step if desired \ No newline at end of file From 16deb7c10e4f9c238de2fa30ca24af792eac5df4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Aug 2026 17:01:21 +0000 Subject: [PATCH 07/19] Fix CI failures by ensuring dev dependencies are always installed --- .github/actions/setup-python-uv/action.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/actions/setup-python-uv/action.yml b/.github/actions/setup-python-uv/action.yml index 80c7276..1229888 100644 --- a/.github/actions/setup-python-uv/action.yml +++ b/.github/actions/setup-python-uv/action.yml @@ -35,7 +35,6 @@ runs: venv-${{ runner.os }}-${{ inputs.python-version }}- - name: Install dependencies - if: steps.cache-venv.outputs.cache-hit != 'true' shell: bash working-directory: ${{ inputs.working-directory }} run: uv sync --frozen From 648b08f037f78d97d2c99f91571d3b9fcf40b017 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Aug 2026 17:16:29 +0000 Subject: [PATCH 08/19] Fix trailing newline in publish.yml --- .github/workflows/publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 1728fba..3db460b 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -43,4 +43,4 @@ jobs: # (Settings → Secrets and variables → Actions → New repository secret) # 2. Create an account on https://pypi.org and generate an API token # 3. Uncomment the "Publish to PyPI" step above - # 4. Remove the "Verify build artifacts" step if desired \ No newline at end of file + # 4. Remove the "Verify build artifacts" step if desired From c710da6873ec203ff11e89ef9601a9f36f8e500d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Aug 2026 17:17:35 +0000 Subject: [PATCH 09/19] Migrate to uv dependency-groups format and regenerate lock files Replace project.optional-dependencies with dependency-groups (dev) to ensure uv sync installs dev dependencies by default. This fixes CI failures where ruff was not available when running 'uv run ruff'. --- apps/example-app/pyproject.toml | 2 +- apps/example-app/uv.lock | 20 ++++++++++++-------- libs/example-lib/pyproject.toml | 2 +- libs/example-lib/uv.lock | 11 ++++++----- 4 files changed, 20 insertions(+), 15 deletions(-) diff --git a/apps/example-app/pyproject.toml b/apps/example-app/pyproject.toml index b87c899..8ac1f6d 100644 --- a/apps/example-app/pyproject.toml +++ b/apps/example-app/pyproject.toml @@ -16,7 +16,7 @@ dependencies = [ "example-lib @ file:///home/runner/work/python-project-blueprint/python-project-blueprint/libs/example-lib" ] -[project.optional-dependencies] +[dependency-groups] dev = [ "pytest>=9.1", "ruff>=0.16", diff --git a/apps/example-app/uv.lock b/apps/example-app/uv.lock index 0974fc4..6305633 100644 --- a/apps/example-app/uv.lock +++ b/apps/example-app/uv.lock @@ -32,7 +32,7 @@ dependencies = [ { name = "example-lib" }, ] -[package.optional-dependencies] +[package.dev-dependencies] dev = [ { name = "pytest" }, { name = "ruff" }, @@ -42,10 +42,13 @@ dev = [ requires-dist = [ { name = "click", specifier = ">=8.4" }, { name = "example-lib", directory = "/home/runner/work/python-project-blueprint/python-project-blueprint/libs/example-lib" }, - { name = "pytest", marker = "extra == 'dev'", specifier = ">=9.1" }, - { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.16" }, ] -provides-extras = ["dev"] + +[package.metadata.requires-dev] +dev = [ + { name = "pytest", specifier = ">=9.1" }, + { name = "ruff", specifier = ">=0.16" }, +] [[package]] name = "example-lib" @@ -53,11 +56,12 @@ version = "0.1.0" source = { directory = "/home/runner/work/python-project-blueprint/python-project-blueprint/libs/example-lib" } [package.metadata] -requires-dist = [ - { name = "pytest", marker = "extra == 'dev'", specifier = ">=9.0" }, - { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.15,<0.17" }, + +[package.metadata.requires-dev] +dev = [ + { name = "pytest", specifier = ">=9.0" }, + { name = "ruff", specifier = ">=0.15,<0.17" }, ] -provides-extras = ["dev"] [[package]] name = "iniconfig" diff --git a/libs/example-lib/pyproject.toml b/libs/example-lib/pyproject.toml index ef1cdb5..faead76 100644 --- a/libs/example-lib/pyproject.toml +++ b/libs/example-lib/pyproject.toml @@ -13,7 +13,7 @@ authors = [ ] dependencies = [] -[project.optional-dependencies] +[dependency-groups] dev = [ "pytest>=9.0", "ruff>=0.15,<0.17", diff --git a/libs/example-lib/uv.lock b/libs/example-lib/uv.lock index 724eef8..c869c3c 100644 --- a/libs/example-lib/uv.lock +++ b/libs/example-lib/uv.lock @@ -16,18 +16,19 @@ name = "example-lib" version = "0.1.0" source = { editable = "." } -[package.optional-dependencies] +[package.dev-dependencies] dev = [ { name = "pytest" }, { name = "ruff" }, ] [package.metadata] -requires-dist = [ - { name = "pytest", marker = "extra == 'dev'", specifier = ">=9.0" }, - { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.15,<0.17" }, + +[package.metadata.requires-dev] +dev = [ + { name = "pytest", specifier = ">=9.0" }, + { name = "ruff", specifier = ">=0.15,<0.17" }, ] -provides-extras = ["dev"] [[package]] name = "iniconfig" From 5e68f7d57dbd6b65e02ce24d3f8b19d3af110638 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 1 Aug 2026 17:17:50 +0000 Subject: [PATCH 10/19] Remove old poetry.lock files - migrated to uv --- apps/example-app/poetry.lock | 153 ----------------------------------- libs/example-lib/poetry.lock | 124 ---------------------------- 2 files changed, 277 deletions(-) delete mode 100644 apps/example-app/poetry.lock delete mode 100644 libs/example-lib/poetry.lock diff --git a/apps/example-app/poetry.lock b/apps/example-app/poetry.lock deleted file mode 100644 index 10838b3..0000000 --- a/apps/example-app/poetry.lock +++ /dev/null @@ -1,153 +0,0 @@ -# This file is automatically @generated by Poetry 2.4.1 and should not be changed by hand. - -[[package]] -name = "click" -version = "8.4.2" -description = "Composable command line interface toolkit" -optional = false -python-versions = ">=3.10" -groups = ["main"] -files = [ - {file = "click-8.4.2-py3-none-any.whl", hash = "sha256:e6f9f66136c816745b9d65817da91d61d957fb16e02e4dcd0552553c5a197b76"}, - {file = "click-8.4.2.tar.gz", hash = "sha256:9a6cea6e60b17ebe0a44c5cc636d94f09bd66142c1cd7d8b4cd731c4917a15f6"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[[package]] -name = "colorama" -version = "0.4.6" -description = "Cross-platform colored terminal text." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["main", "dev"] -files = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] -markers = {main = "platform_system == \"Windows\"", dev = "sys_platform == \"win32\""} - -[[package]] -name = "example-lib" -version = "0.1.0" -description = "Example shared library demonstrating the monorepo library pattern" -optional = false -python-versions = "^3.12" -groups = ["main"] -files = [] -develop = true - -[package.source] -type = "directory" -url = "../../libs/example-lib" - -[[package]] -name = "iniconfig" -version = "2.3.0" -description = "brain-dead simple config-ini parsing" -optional = false -python-versions = ">=3.10" -groups = ["dev"] -files = [ - {file = "iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12"}, - {file = "iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"}, -] - -[[package]] -name = "packaging" -version = "26.0" -description = "Core utilities for Python packages" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529"}, - {file = "packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4"}, -] - -[[package]] -name = "pluggy" -version = "1.6.0" -description = "plugin and hook calling mechanisms for python" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, - {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, -] - -[package.extras] -dev = ["pre-commit", "tox"] -testing = ["coverage", "pytest", "pytest-benchmark"] - -[[package]] -name = "pygments" -version = "2.20.0" -description = "Pygments is a syntax highlighting package written in Python." -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176"}, - {file = "pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f"}, -] - -[package.extras] -windows-terminal = ["colorama (>=0.4.6)"] - -[[package]] -name = "pytest" -version = "9.1.1" -description = "pytest: simple powerful testing with Python" -optional = false -python-versions = ">=3.10" -groups = ["dev"] -files = [ - {file = "pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c"}, - {file = "pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313"}, -] - -[package.dependencies] -colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""} -iniconfig = ">=1.0.1" -packaging = ">=22" -pluggy = ">=1.5,<2" -pygments = ">=2.7.2" - -[package.extras] -dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] - -[[package]] -name = "ruff" -version = "0.16.0" -description = "An extremely fast Python linter and code formatter, written in Rust." -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "ruff-0.16.0-py3-none-linux_armv6l.whl", hash = "sha256:e5115729eb08c585e5121978ba5d5b60caeae394ce21b9fb5e6cd33a1c6c9b1e"}, - {file = "ruff-0.16.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:3c954b1d580bfa035b41654f7858cc7e71d5fc3ac5b723dd62bd9133830ed522"}, - {file = "ruff-0.16.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e01c21d10eb1b29f47b7454e1f4056db9a3f0260c646aa88457c610291db9f81"}, - {file = "ruff-0.16.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e364e5ed22ed8dc05082fd78e35308618260907ac2d3c1d637b2e682415b6c9"}, - {file = "ruff-0.16.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d327b8fc113a1d4421a04f3839d3752057c8dd1ee320223a6f3f52d04ada462a"}, - {file = "ruff-0.16.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9b50c55e263103586b3dcf5f73d479eb8cb5fdb6098fec59a62891dab653717"}, - {file = "ruff-0.16.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0ff4a79ce3ec0172f3241943835de1c4cb4e2dcd07f0f8c2d02603dbbbee4b17"}, - {file = "ruff-0.16.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e95c448fca1fb2a18372a9440926c5a6ee789639bb975c72e7ae6d0b04218ab4"}, - {file = "ruff-0.16.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f11a8d11010301d0a398a2fdef67691feca7294da6aef55e2150e8fa2cd520b"}, - {file = "ruff-0.16.0-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:48044c678e9cb8698246c99b14aaccfa6601dea7379eb48a6f8f73f7a6d86cd0"}, - {file = "ruff-0.16.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:7aa0959bad8eb8bef50340154fc9b58678dae31fa4293afa38b44b6e552c0213"}, - {file = "ruff-0.16.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:28ea2b7df8ebf7f9da6b7d47b230ab48f387c0a29be3b474c4d0740e197bb9af"}, - {file = "ruff-0.16.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:33a3dfac8c35f81498dea9181bccc2f4c4bc8f1521a1dd9406e77643e0f0fb09"}, - {file = "ruff-0.16.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a5237a0bda500d30d81b8e07a6973a5cbc772864cbf746ae2f4e8a2e01c9f4ed"}, - {file = "ruff-0.16.0-py3-none-win32.whl", hash = "sha256:7fab76fa065c873f41ff744347c6e77bcc3dfec4bcc754dc26b63d23c0f7f5fb"}, - {file = "ruff-0.16.0-py3-none-win_amd64.whl", hash = "sha256:429c117f022bf481fabd9d551e7a3952b24c65e6ef44337ea09d90bebef14472"}, - {file = "ruff-0.16.0-py3-none-win_arm64.whl", hash = "sha256:14296fedcd2705c77ab8235439278bbb38f285cf7da5528b00b3e330c3d4872d"}, - {file = "ruff-0.16.0.tar.gz", hash = "sha256:e460aafd5495ec89efaa6ced2e4a9a581116451e1c88b9d37ef497e0f8e93982"}, -] - -[metadata] -lock-version = "2.1" -python-versions = "^3.12" -content-hash = "5976ce4d62a0ab2fb468bf9f48888878764c3f33a04cb662d691b4955553cbfb" diff --git a/libs/example-lib/poetry.lock b/libs/example-lib/poetry.lock deleted file mode 100644 index 4f6fa16..0000000 --- a/libs/example-lib/poetry.lock +++ /dev/null @@ -1,124 +0,0 @@ -# This file is automatically @generated by Poetry 2.4.1 and should not be changed by hand. - -[[package]] -name = "colorama" -version = "0.4.6" -description = "Cross-platform colored terminal text." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["dev"] -markers = "sys_platform == \"win32\"" -files = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] - -[[package]] -name = "iniconfig" -version = "2.3.0" -description = "brain-dead simple config-ini parsing" -optional = false -python-versions = ">=3.10" -groups = ["dev"] -files = [ - {file = "iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12"}, - {file = "iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"}, -] - -[[package]] -name = "packaging" -version = "26.0" -description = "Core utilities for Python packages" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529"}, - {file = "packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4"}, -] - -[[package]] -name = "pluggy" -version = "1.6.0" -description = "plugin and hook calling mechanisms for python" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, - {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, -] - -[package.extras] -dev = ["pre-commit", "tox"] -testing = ["coverage", "pytest", "pytest-benchmark"] - -[[package]] -name = "pygments" -version = "2.20.0" -description = "Pygments is a syntax highlighting package written in Python." -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176"}, - {file = "pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f"}, -] - -[package.extras] -windows-terminal = ["colorama (>=0.4.6)"] - -[[package]] -name = "pytest" -version = "9.1.1" -description = "pytest: simple powerful testing with Python" -optional = false -python-versions = ">=3.10" -groups = ["dev"] -files = [ - {file = "pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c"}, - {file = "pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313"}, -] - -[package.dependencies] -colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""} -iniconfig = ">=1.0.1" -packaging = ">=22" -pluggy = ">=1.5,<2" -pygments = ">=2.7.2" - -[package.extras] -dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] - -[[package]] -name = "ruff" -version = "0.16.0" -description = "An extremely fast Python linter and code formatter, written in Rust." -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "ruff-0.16.0-py3-none-linux_armv6l.whl", hash = "sha256:e5115729eb08c585e5121978ba5d5b60caeae394ce21b9fb5e6cd33a1c6c9b1e"}, - {file = "ruff-0.16.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:3c954b1d580bfa035b41654f7858cc7e71d5fc3ac5b723dd62bd9133830ed522"}, - {file = "ruff-0.16.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e01c21d10eb1b29f47b7454e1f4056db9a3f0260c646aa88457c610291db9f81"}, - {file = "ruff-0.16.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e364e5ed22ed8dc05082fd78e35308618260907ac2d3c1d637b2e682415b6c9"}, - {file = "ruff-0.16.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d327b8fc113a1d4421a04f3839d3752057c8dd1ee320223a6f3f52d04ada462a"}, - {file = "ruff-0.16.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9b50c55e263103586b3dcf5f73d479eb8cb5fdb6098fec59a62891dab653717"}, - {file = "ruff-0.16.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0ff4a79ce3ec0172f3241943835de1c4cb4e2dcd07f0f8c2d02603dbbbee4b17"}, - {file = "ruff-0.16.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e95c448fca1fb2a18372a9440926c5a6ee789639bb975c72e7ae6d0b04218ab4"}, - {file = "ruff-0.16.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f11a8d11010301d0a398a2fdef67691feca7294da6aef55e2150e8fa2cd520b"}, - {file = "ruff-0.16.0-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:48044c678e9cb8698246c99b14aaccfa6601dea7379eb48a6f8f73f7a6d86cd0"}, - {file = "ruff-0.16.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:7aa0959bad8eb8bef50340154fc9b58678dae31fa4293afa38b44b6e552c0213"}, - {file = "ruff-0.16.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:28ea2b7df8ebf7f9da6b7d47b230ab48f387c0a29be3b474c4d0740e197bb9af"}, - {file = "ruff-0.16.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:33a3dfac8c35f81498dea9181bccc2f4c4bc8f1521a1dd9406e77643e0f0fb09"}, - {file = "ruff-0.16.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a5237a0bda500d30d81b8e07a6973a5cbc772864cbf746ae2f4e8a2e01c9f4ed"}, - {file = "ruff-0.16.0-py3-none-win32.whl", hash = "sha256:7fab76fa065c873f41ff744347c6e77bcc3dfec4bcc754dc26b63d23c0f7f5fb"}, - {file = "ruff-0.16.0-py3-none-win_amd64.whl", hash = "sha256:429c117f022bf481fabd9d551e7a3952b24c65e6ef44337ea09d90bebef14472"}, - {file = "ruff-0.16.0-py3-none-win_arm64.whl", hash = "sha256:14296fedcd2705c77ab8235439278bbb38f285cf7da5528b00b3e330c3d4872d"}, - {file = "ruff-0.16.0.tar.gz", hash = "sha256:e460aafd5495ec89efaa6ced2e4a9a581116451e1c88b9d37ef497e0f8e93982"}, -] - -[metadata] -lock-version = "2.1" -python-versions = "^3.12" -content-hash = "fb00fd9c967e0e71e49c09c1e2f0109018455adfae5334a8a845210c8383da9e" From 8e58edad2f6de2cf30e07962ad566fa3d6a64109 Mon Sep 17 00:00:00 2001 From: Eric Fischer Date: Sat, 1 Aug 2026 13:26:08 -0400 Subject: [PATCH 11/19] Update pyproject.toml fix double-path --- apps/example-app/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/example-app/pyproject.toml b/apps/example-app/pyproject.toml index 8ac1f6d..d37ecbe 100644 --- a/apps/example-app/pyproject.toml +++ b/apps/example-app/pyproject.toml @@ -13,7 +13,7 @@ authors = [ ] dependencies = [ "click>=8.4", - "example-lib @ file:///home/runner/work/python-project-blueprint/python-project-blueprint/libs/example-lib" + "example-lib @ file:///home/runner/work/python-project-blueprint/libs/example-lib" ] [dependency-groups] From a133d817aea3882107af10e93a46b279f0d7b1fa Mon Sep 17 00:00:00 2001 From: Eric Fischer Date: Sat, 1 Aug 2026 13:30:37 -0400 Subject: [PATCH 12/19] Update pyproject.toml try again --- apps/example-app/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/example-app/pyproject.toml b/apps/example-app/pyproject.toml index d37ecbe..0e9f22b 100644 --- a/apps/example-app/pyproject.toml +++ b/apps/example-app/pyproject.toml @@ -13,7 +13,7 @@ authors = [ ] dependencies = [ "click>=8.4", - "example-lib @ file:///home/runner/work/python-project-blueprint/libs/example-lib" + "example-lib @ file://../../libs/example-lib" ] [dependency-groups] From d27e703aa509a7c00c41a7c769fc549f2187701b Mon Sep 17 00:00:00 2001 From: Eric Fischer Date: Sat, 1 Aug 2026 13:43:17 -0400 Subject: [PATCH 13/19] fixes (#59) * Update ADR-015-use_uv.md * Update ADR-009-shared_library_versioning.md * Create pyproject.toml * Update pyproject.toml --- apps/example-app/pyproject.toml | 2 +- meta/adr/ADR-009-shared_library_versioning.md | 11 ++++++----- meta/adr/ADR-015-use_uv.md | 6 ++++++ pyproject.toml | 2 ++ 4 files changed, 15 insertions(+), 6 deletions(-) create mode 100644 pyproject.toml diff --git a/apps/example-app/pyproject.toml b/apps/example-app/pyproject.toml index 0e9f22b..860bb12 100644 --- a/apps/example-app/pyproject.toml +++ b/apps/example-app/pyproject.toml @@ -13,7 +13,7 @@ authors = [ ] dependencies = [ "click>=8.4", - "example-lib @ file://../../libs/example-lib" + "example-lib" ] [dependency-groups] diff --git a/meta/adr/ADR-009-shared_library_versioning.md b/meta/adr/ADR-009-shared_library_versioning.md index fc242eb..abbe41c 100644 --- a/meta/adr/ADR-009-shared_library_versioning.md +++ b/meta/adr/ADR-009-shared_library_versioning.md @@ -41,21 +41,22 @@ all shared libraries in `libs/`. * While at `0.x.y`, minor version bumps may include breaking changes (per SemVer spec). * Version `1.0.0` signals a stable public API. -* Within the monorepo, applications reference libraries as **path - dependencies** — the version number serves as documentation of the - API contract, not as a resolution constraint. +* Within the monorepo, applications reference libraries as workspace + dependencies — uv automatically resolves these local connections, + and the version number serves as documentation of the API contract, + not as a resolution constraint. * If a library is published externally, the version becomes a hard contract with downstream consumers. * Version bumps should be accompanied by a changelog entry in the library's `README.md` or `CHANGELOG.md`. -### Path Dependency Example +### Workspace Dependency Example ```toml # In apps/my-app/pyproject.toml [project] dependencies = [ - "my-lib @ file://../../libs/my-lib" + "my-lib" # Resolved automatically via uv workspace ] ``` diff --git a/meta/adr/ADR-015-use_uv.md b/meta/adr/ADR-015-use_uv.md index 4ea324a..2c34509 100644 --- a/meta/adr/ADR-015-use_uv.md +++ b/meta/adr/ADR-015-use_uv.md @@ -43,6 +43,10 @@ Each application in `apps/` and each library in `libs/` will have its own - **Extreme speed** — dependency resolution and installation are 10-100x faster than Poetry due to uv's Rust implementation. +To seamlessly link these components, we will utilize uv workspaces. A root +`pyproject.toml` will define the workspace, allowing applications to depend +on local libraries by name without brittle relative file paths. + ### Key Conventions - Applications **commit** their `uv.lock` files for reproducible @@ -52,6 +56,8 @@ Each application in `apps/` and each library in `libs/` will have its own - Use `uv sync` to install dependencies into the project's `.venv`. - Use `uv run ` to execute commands within the virtual environment (e.g., `uv run pytest`, `uv run ruff check .`). +- Internal dependencies between apps/ and libs/ must rely on the uv workspace + resolution rather than hardcoded file:// URIs. ## Considered Options diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..c974b93 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,2 @@ +[tool.uv.workspace] +members = ["apps/*", "libs/*"] From d0b14800c02d6888f54ba6729cca6f0e816f2d7e Mon Sep 17 00:00:00 2001 From: Eric Fischer Date: Sat, 1 Aug 2026 13:45:47 -0400 Subject: [PATCH 14/19] Update ADR-015-use_uv.md --- meta/adr/ADR-015-use_uv.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meta/adr/ADR-015-use_uv.md b/meta/adr/ADR-015-use_uv.md index 2c34509..dcb9760 100644 --- a/meta/adr/ADR-015-use_uv.md +++ b/meta/adr/ADR-015-use_uv.md @@ -43,7 +43,7 @@ Each application in `apps/` and each library in `libs/` will have its own - **Extreme speed** — dependency resolution and installation are 10-100x faster than Poetry due to uv's Rust implementation. -To seamlessly link these components, we will utilize uv workspaces. A root +To seamlessly link these components, we will utilize uv workspaces. A root `pyproject.toml` will define the workspace, allowing applications to depend on local libraries by name without brittle relative file paths. From f7f086c6fd3a7ed62615a7d7c29ffb2102b76a8f Mon Sep 17 00:00:00 2001 From: Eric Fischer Date: Sat, 1 Aug 2026 13:52:24 -0400 Subject: [PATCH 15/19] Update ci.yml --- .github/workflows/ci.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 87e70b4..2709df3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -111,8 +111,9 @@ jobs: - name: Set up Python and uv uses: ./.github/actions/setup-python-uv - with: - working-directory: ${{ matrix.project }} + # We removed the 'working-directory' input here! + # This forces the action to run `uv sync --frozen` at the repo root + # so it can locate the single workspace uv.lock file. - name: Ruff format check working-directory: ${{ matrix.project }} From 1262d47527b44305de79d906ac90b50db7083a4f Mon Sep 17 00:00:00 2001 From: Eric Fischer Date: Sat, 1 Aug 2026 13:57:54 -0400 Subject: [PATCH 16/19] fix lockfile --- apps/example-app/pyproject.toml | 3 + libs/example-lib/uv.lock | 108 ---------------------------- apps/example-app/uv.lock => uv.lock | 18 ++++- 3 files changed, 18 insertions(+), 111 deletions(-) delete mode 100644 libs/example-lib/uv.lock rename apps/example-app/uv.lock => uv.lock (97%) diff --git a/apps/example-app/pyproject.toml b/apps/example-app/pyproject.toml index 860bb12..c3d183f 100644 --- a/apps/example-app/pyproject.toml +++ b/apps/example-app/pyproject.toml @@ -43,3 +43,6 @@ packages = ["app"] [tool.pytest.ini_options] testpaths = ["tests"] + +[tool.uv.sources] +example-lib = { workspace = true } diff --git a/libs/example-lib/uv.lock b/libs/example-lib/uv.lock deleted file mode 100644 index c869c3c..0000000 --- a/libs/example-lib/uv.lock +++ /dev/null @@ -1,108 +0,0 @@ -version = 1 -revision = 3 -requires-python = ">=3.12" - -[[package]] -name = "colorama" -version = "0.4.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, -] - -[[package]] -name = "example-lib" -version = "0.1.0" -source = { editable = "." } - -[package.dev-dependencies] -dev = [ - { name = "pytest" }, - { name = "ruff" }, -] - -[package.metadata] - -[package.metadata.requires-dev] -dev = [ - { name = "pytest", specifier = ">=9.0" }, - { name = "ruff", specifier = ">=0.15,<0.17" }, -] - -[[package]] -name = "iniconfig" -version = "2.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, -] - -[[package]] -name = "packaging" -version = "26.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, -] - -[[package]] -name = "pluggy" -version = "1.6.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, -] - -[[package]] -name = "pygments" -version = "2.20.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, -] - -[[package]] -name = "pytest" -version = "9.1.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "iniconfig" }, - { name = "packaging" }, - { name = "pluggy" }, - { name = "pygments" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" }, -] - -[[package]] -name = "ruff" -version = "0.16.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/70/25/7113f6d5498888c5fb7db34081cba7d5971c4cb1bfb26819966eee68f003/ruff-0.16.1.tar.gz", hash = "sha256:fedad7c801dabd3fb9741d76aca39246e6ddd9ca446a015875207bf19f1e6bc7", size = 4877500, upload-time = "2026-07-30T19:37:01.379Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1b/bd/694da69368e0973de65df2ddc73ab18d43c469d5963d9b150911de6bc513/ruff-0.16.1-py3-none-linux_armv6l.whl", hash = "sha256:58edb313b88f0c5460a26adf5f39a37a3be789494a15e3e411e35fa78b89f9a0", size = 10839126, upload-time = "2026-07-30T19:36:13.697Z" }, - { url = "https://files.pythonhosted.org/packages/3f/f0/b626e5d5bd0dd9576263658ef12885e2288afd1029a48e26ffed65ec1ac1/ruff-0.16.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:fde5a99e2f97479af66edd6622c6d5a2a7592c77cf4153d9e4428f5eeb55b60c", size = 11070253, upload-time = "2026-07-30T19:36:17.14Z" }, - { url = "https://files.pythonhosted.org/packages/83/63/f40acfb6b35b88623e71684942b552c3edd96035f5d98f313815f7b277de/ruff-0.16.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e0d4c20532fca4f7fa609369161d968dd28f65d83dabbd61d8e9c7edbf7001f6", size = 10561425, upload-time = "2026-07-30T19:36:20.04Z" }, - { url = "https://files.pythonhosted.org/packages/aa/dd/14ec0e9c2b4d315547dd38765004b4863e354e1b52cb308272215d9f6f6d/ruff-0.16.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30affbcedf59ad5703d9c91f82266e02b47739f797e1a7b6e158e5526a6dae38", size = 10948879, upload-time = "2026-07-30T19:36:22.476Z" }, - { url = "https://files.pythonhosted.org/packages/33/e9/9d870cbae575030fdef595f04b4b97573c525b5497cce4f4498cf2f85446/ruff-0.16.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:24e9c631573cbca9d20f1283f8f479b2afa4a8503504822bd71a293889f16743", size = 10643691, upload-time = "2026-07-30T19:36:24.914Z" }, - { url = "https://files.pythonhosted.org/packages/c4/09/12743d544e2173f53ecd27217c65f90d2bc0f8424a66a60339e56bbc0457/ruff-0.16.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b41bdd48fb420987a9b5212e4957c26ad4abce401fa9ea9d4d85843727945f4f", size = 11435354, upload-time = "2026-07-30T19:36:28.447Z" }, - { url = "https://files.pythonhosted.org/packages/7f/89/a1652b2daee52083c9554a6333b678a8b01d0400f976827bb87857f9449a/ruff-0.16.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b0d1e1393b7648079e13669de1c1f4fde06d4583e84d8fd5c1551e0a77a2aa75", size = 12259033, upload-time = "2026-07-30T19:36:31.326Z" }, - { url = "https://files.pythonhosted.org/packages/16/96/ecdcb8c54ee7b123b487f807eb014e6e019155a0b81dfb669acd52f28ce3/ruff-0.16.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:07bf434b1c95f4e093be4532068ef4fcf00924eb2ade8796075980902d6fd54a", size = 11667981, upload-time = "2026-07-30T19:36:34.394Z" }, - { url = "https://files.pythonhosted.org/packages/cd/90/c52e12e0d862e9572f2a33aa227409143520abe53111e9a6babbac7b4af8/ruff-0.16.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39897739f112253ee4fdd2e8aa9a4f9ded99fb2be367d5f31dfa4ded6025584c", size = 11468183, upload-time = "2026-07-30T19:36:37.339Z" }, - { url = "https://files.pythonhosted.org/packages/2c/6b/4ffb7ad1d83eb16cf8cbb3c8815d3f11c88460fd162d4b372a2059be1c2a/ruff-0.16.1-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:82ae3c0c0d74daf17b968a10b7b3bb3ef297ab7de0c1f749646b25e690ccb150", size = 11470071, upload-time = "2026-07-30T19:36:39.91Z" }, - { url = "https://files.pythonhosted.org/packages/9c/72/32ae7db4c0b5e32ab611787caa19d1546800676d79f7483b7100a3561bf4/ruff-0.16.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:4d5f2ed10f8242d83fc08d521301089364e3375375705356f20c0e31606ef3ef", size = 10919503, upload-time = "2026-07-30T19:36:42.65Z" }, - { url = "https://files.pythonhosted.org/packages/f7/ca/3d901ba6ad6fc38da39c3448fc6c59ac945679293a17c3ceb6d6c1cba13e/ruff-0.16.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a4665b309891f83f3e3c25447935f1213e9abbd4b5640af7a1f2def9f8d413c1", size = 10649861, upload-time = "2026-07-30T19:36:45.18Z" }, - { url = "https://files.pythonhosted.org/packages/92/79/894ef1ced26552d5f8c9cf6d85b0687840e1128c55aeab7b9c2d54a0d880/ruff-0.16.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:26e9ca5c9bc3971f20d3cf18a957f52ffd6a5f6564ff15c4912a144dcac22494", size = 11148137, upload-time = "2026-07-30T19:36:47.936Z" }, - { url = "https://files.pythonhosted.org/packages/2d/69/3609a09fa1cb46cc28b762363e440a354204e5dff01bd0c8d7437874d6b9/ruff-0.16.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:67e1e1e3fa4f0c82f0e36d4cd61e661f6e7a6196cb1aa92fe0828fa7b8f257cd", size = 11559211, upload-time = "2026-07-30T19:36:50.448Z" }, - { url = "https://files.pythonhosted.org/packages/fc/8a/fb22af2fd78a736e241fabf67e30ce1799a64244026377a49e133af90762/ruff-0.16.1-py3-none-win32.whl", hash = "sha256:d31765e131295b8445caf301e3e8a85b34d1b9b211b4109b7ba457888b051806", size = 10838258, upload-time = "2026-07-30T19:36:53.298Z" }, - { url = "https://files.pythonhosted.org/packages/d4/35/e57fd9fb5d423961df087a00b12d42c0a830288dc2f3b45ecca299158b4f/ruff-0.16.1-py3-none-win_amd64.whl", hash = "sha256:09b05e8b90c2cb06ad63464350e7a45e8e44a2dfe52072ebfba6666ca8d3f596", size = 11961111, upload-time = "2026-07-30T19:36:56.107Z" }, - { url = "https://files.pythonhosted.org/packages/cb/46/240ea004bf6dc4feb40e9832f2205a476a47dd5b8a3f8211a5fc5f95e20e/ruff-0.16.1-py3-none-win_arm64.whl", hash = "sha256:dbaadaac38c70239f056d306b7476f246b0bf000fa6b3876402acbf5b227eaf8", size = 11309414, upload-time = "2026-07-30T19:36:58.79Z" }, -] diff --git a/apps/example-app/uv.lock b/uv.lock similarity index 97% rename from apps/example-app/uv.lock rename to uv.lock index 6305633..865fb62 100644 --- a/apps/example-app/uv.lock +++ b/uv.lock @@ -2,6 +2,12 @@ version = 1 revision = 3 requires-python = ">=3.12" +[manifest] +members = [ + "example-app", + "example-lib", +] + [[package]] name = "click" version = "8.4.2" @@ -26,7 +32,7 @@ wheels = [ [[package]] name = "example-app" version = "0.1.0" -source = { editable = "." } +source = { editable = "apps/example-app" } dependencies = [ { name = "click" }, { name = "example-lib" }, @@ -41,7 +47,7 @@ dev = [ [package.metadata] requires-dist = [ { name = "click", specifier = ">=8.4" }, - { name = "example-lib", directory = "/home/runner/work/python-project-blueprint/python-project-blueprint/libs/example-lib" }, + { name = "example-lib", editable = "libs/example-lib" }, ] [package.metadata.requires-dev] @@ -53,7 +59,13 @@ dev = [ [[package]] name = "example-lib" version = "0.1.0" -source = { directory = "/home/runner/work/python-project-blueprint/python-project-blueprint/libs/example-lib" } +source = { editable = "libs/example-lib" } + +[package.dev-dependencies] +dev = [ + { name = "pytest" }, + { name = "ruff" }, +] [package.metadata] From e3b4ab1d3d0358fe5fbf6d3123e575646f907fe4 Mon Sep 17 00:00:00 2001 From: Eric Fischer Date: Sat, 1 Aug 2026 14:00:28 -0400 Subject: [PATCH 17/19] Update Dockerfile --- apps/example-app/Dockerfile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/example-app/Dockerfile b/apps/example-app/Dockerfile index fb3862f..7c95067 100644 --- a/apps/example-app/Dockerfile +++ b/apps/example-app/Dockerfile @@ -19,8 +19,7 @@ WORKDIR /build COPY libs/example-lib ./libs/example-lib # Copy application manifests before source code for Docker layer cache efficiency -COPY apps/example-app/pyproject.toml apps/example-app/uv.lock \ - ./apps/example-app/ +COPY apps/example-app/pyproject.toml ./apps/example-app/ # Use uv to generate requirements from the lock file for reproducible builds. # The local path dependency (example-lib) is excluded and handled separately. From 8000e1918073f2c64e7dd9500168d785ef0fc685 Mon Sep 17 00:00:00 2001 From: Eric Fischer Date: Sat, 1 Aug 2026 14:02:03 -0400 Subject: [PATCH 18/19] Update Dockerfile --- apps/example-app/Dockerfile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/example-app/Dockerfile b/apps/example-app/Dockerfile index 7c95067..f2bdeb1 100644 --- a/apps/example-app/Dockerfile +++ b/apps/example-app/Dockerfile @@ -23,8 +23,7 @@ COPY apps/example-app/pyproject.toml ./apps/example-app/ # Use uv to generate requirements from the lock file for reproducible builds. # The local path dependency (example-lib) is excluded and handled separately. -RUN cd apps/example-app \ - && uv export \ +RUN uv export \ --frozen \ --no-dev \ --no-hashes \ From c5199630f5e36cebd418b14f9d9734ca0cfc1d1e Mon Sep 17 00:00:00 2001 From: Eric Fischer Date: Sat, 1 Aug 2026 14:08:11 -0400 Subject: [PATCH 19/19] Update Dockerfile --- apps/example-app/Dockerfile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/example-app/Dockerfile b/apps/example-app/Dockerfile index f2bdeb1..a295018 100644 --- a/apps/example-app/Dockerfile +++ b/apps/example-app/Dockerfile @@ -15,6 +15,8 @@ RUN pip install --no-cache-dir uv WORKDIR /build +COPY pyproject.toml uv.lock ./ + # Copy the shared library (local path dependency) COPY libs/example-lib ./libs/example-lib @@ -22,8 +24,8 @@ COPY libs/example-lib ./libs/example-lib COPY apps/example-app/pyproject.toml ./apps/example-app/ # Use uv to generate requirements from the lock file for reproducible builds. -# The local path dependency (example-lib) is excluded and handled separately. RUN uv export \ + --package example-app \ --frozen \ --no-dev \ --no-hashes \