diff --git a/.github/workflows/template-ci.yml b/.github/workflows/template-ci.yml
new file mode 100644
index 0000000..ea03dbf
--- /dev/null
+++ b/.github/workflows/template-ci.yml
@@ -0,0 +1,29 @@
+# Template repository CI: bake the Copier template and run the generated
+# project's full quality gate (Ruff, Pyrefly, pytest at 100% coverage).
+# The generated project ships its own workflow at template/.github/workflows/build.yml.
+
+name: Template CI
+
+on:
+ push:
+ branches: [ main ]
+ pull_request:
+ branches: [ '**' ]
+
+permissions:
+ contents: read
+
+jobs:
+ bake:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+ - name: Install uv
+ uses: astral-sh/setup-uv@v5
+ with:
+ enable-cache: true
+ - name: Install authoring dependencies
+ run: uv sync
+ - name: Bake and test the template
+ run: uv run pytest -v
diff --git a/.gitignore b/.gitignore
index 36eb4c5..3ab4b09 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,164 +1,37 @@
-# Byte-compiled / optimized / DLL files
+# Template-authoring project (Cosmic FastAPI Copier template).
+# The generated project ships its own .gitignore under template/.
+
+# Python
__pycache__/
*.py[cod]
-*$py.class
-
-# C extensions
-*.so
-
-# Distribution / packaging
-.Python
-build/
-develop-eggs/
-dist/
-downloads/
-eggs/
-.eggs/
-lib/
-lib64/
-parts/
-sdist/
-var/
-wheels/
-share/python-wheels/
*.egg-info/
-.installed.cfg
-*.egg
-MANIFEST
+.Python
-# PyInstaller
-# Usually these files are written by a python script from a template
-# before PyInstaller builds the exe, so as to inject date/other infos into it.
-*.manifest
-*.spec
+# Virtual environments
+.venv/
+venv/
-# Installer logs
-pip-log.txt
-pip-delete-this-directory.txt
+# uv
+# (uv.lock IS tracked for the authoring project)
-# Unit test / coverage reports
-htmlcov/
-.tox/
-.nox/
+# Test & coverage artifacts
+.pytest_cache/
.coverage
.coverage.*
-.cache
-nosetests.xml
coverage.xml
-*.cover
-*.py,cover
-.hypothesis/
-.pytest_cache/
-cover/
+htmlcov/
reports/
-# Translations
-*.mo
-*.pot
-
-# Django stuff:
-*.log
-local_settings.py
-db.sqlite3
-db.sqlite3-journal
-
-# SQLite databases (default DATABASE_URL writes ./cosmic-fastapi.db)
-*.db
-
-# Flask stuff:
-instance/
-.webassets-cache
-
-# Scrapy stuff:
-.scrapy
-
-# Sphinx documentation
-docs/_build/
-
-# PyBuilder
-.pybuilder/
-target/
-
-# Jupyter Notebook
-.ipynb_checkpoints
-
-# IPython
-profile_default/
-ipython_config.py
-
-# pyenv
-# For a library or package, you might want to ignore these files since the code is
-# intended to run in multiple environments; otherwise, check them in:
-# .python-version
-
-# pipenv
-# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
-# However, in case of collaboration, if having platform-specific dependencies or dependencies
-# having no cross-platform support, pipenv may install dependencies that don't work, or not
-# install all needed dependencies.
-#Pipfile.lock
-
-# poetry
-# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
-# This is especially recommended for binary packages to ensure reproducibility, and is more
-# commonly ignored for libraries.
-# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
-#poetry.lock
-
-# pdm
-# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
-#pdm.lock
-# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
-# in version control.
-# https://pdm.fming.dev/#use-with-ide
-.pdm.toml
-
-# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
-__pypackages__/
-
-# Celery stuff
-celerybeat-schedule
-celerybeat.pid
-
-# SageMath parsed files
-*.sage.py
-
-# Environments
-.env
-.venv
-env/
-venv/
-ENV/
-env.bak/
-venv.bak/
-
-# Spyder project settings
-.spyderproject
-.spyproject
-
-# Rope project settings
-.ropeproject
-
-# mkdocs documentation
-/site
-
-# mypy
+# Tooling caches
+.ruff_cache/
.mypy_cache/
-.dmypy.json
-dmypy.json
-
-# Pyre type checker
-.pyre/
+.pyrefly_cache/
-# pytype static type analyzer
-.pytype/
-
-# Cython debug symbols
-cython_debug/
+# Baked output / scratch
+baked/
+*.db
-# PyCharm
-# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
-# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
-# and can be added to the global gitignore or merged into this file. For a more nuclear
-# option (not recommended) you can uncomment the following to ignore the entire idea folder.
+# IDE / OS
.idea/
+.vscode/
+.DS_Store
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 23ac166..31e780f 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,22 +1,12 @@
+# Pre-commit hooks for the template-authoring repository.
+# The generated project ships its own (richer) config under template/.
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
- rev: "v5.0.0"
+ rev: v5.0.0
hooks:
- - id: check-case-conflict
- id: check-merge-conflict
- - id: check-toml
- - id: check-yaml
- - id: check-json
+ - id: check-case-conflict
- id: end-of-file-fixer
+ exclude: '\.jinja$|/docs/.*\.png$'
- id: trailing-whitespace
- - repo: https://github.com/astral-sh/ruff-pre-commit
- # Ruff version.
- rev: v0.12.1
- hooks:
- # Run the linter.
- - id: ruff-check
- types_or: [ python, pyi ]
- args: [ --fix ]
- # Run the formatter.
- - id: ruff-format
- types_or: [ python, pyi ]
+ exclude: '\.jinja$'
diff --git a/.python-version b/.python-version
deleted file mode 100644
index 24ee5b1..0000000
--- a/.python-version
+++ /dev/null
@@ -1 +0,0 @@
-3.13
diff --git a/README.md b/README.md
index 4a16324..e3f31d2 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
-
+
@@ -8,410 +8,99 @@
---
-# Cosmic FastAPI
+# Cosmic FastAPI Template
-**Cosmic FastAPI** is a template for creating a [FastAPI](https://fastapi.tiangolo.com/) project following
-the [Cosmic Python](https://www.cosmicpython.com/) guidelines.
+**Cosmic FastAPI** is a [Copier](https://copier.readthedocs.io/) template that scaffolds a
+[FastAPI](https://fastapi.tiangolo.com/) service following the [Cosmic Python](https://www.cosmicpython.com/)
+guidelines: a domain-first architecture with Pydantic 2, SQLAlchemy 2, Alembic, uv, Ruff, Pyrefly, and a
+pytest suite at 100% coverage.
-## Content
+This repository serves two audiences, kept physically separated (see
+[ADR 0015](template/docs/adr/0015-copier-template-engine.md)):
-
+- **Author-facing** (this `README`, `copier.yml`, the bake-and-test suite) lives at the repository root and is
+ never rendered into a generated project.
+- **Generated-project-facing** lives under [`template/`](template/) and becomes a new project on `copier copy`.
-* [Cosmic FastAPI](#cosmic-fastapi)
- * [Content](#content)
- * [About](#about)
- * [Features](#features)
- * [Architecture Decision Records](#architecture-decision-records)
- * [Project Structure](#project-structure)
- * [Environment Variables](#environment-variables)
- * [Recommended Directory Structure](#recommended-directory-structure)
- * [Domain Driven Design](#domain-driven-design)
- * [Models](#models)
- * [Entities](#entities)
- * [Value Objects](#value-objects)
- * [Aggregates:](#aggregates)
- * [Schemas](#schemas)
- * [Event Driven Architecture](#event-driven-architecture)
- * [Commands](#commands)
- * [Events](#events)
- * [Clean Architecture](#clean-architecture)
- * [Continuous Integration](#continuous-integration)
- * [Development Environment](#development-environment)
- * [Installing UV](#installing-uv)
- * [Building the Development Environment](#building-the-development-environment)
- * [Running Local](#running-local)
- * [Running Tests](#running-tests)
- * [Recommended Readings](#recommended-readings)
- * [Licence](#licence)
- * [Acknowledgements](#acknowledgements)
+## Usage
-
-
-## About
-
-### Features
-
-This template includes `FastAPI` using the fastest `Pydantic V2` model validation. Designed for Event-Driven
-Architecture (EDA) and Domain-Driven Design (DDD). Providing a clean and simple structure to start a new project. With
-the addition of `pre-commit` hooks to ensure code quality. And ready to be used in a `CI/CD` GitHub Workflows pipeline.
-
-### Architecture Decision Records
-
-The [Architecture Decision Records](docs/adr/README.md) define the modern Cosmic Python standard used by this template.
-They are the first reference for people and coding agents extending the project. The records preserve the book's
-domain-first philosophy while adopting FastAPI, Pydantic 2, SQLAlchemy 2, Alembic, uv, Ruff, Pyrefly, and pytest.
-Repository-level [agent instructions](AGENTS.md) turn those decisions into an implementation workflow.
-The [Cosmic Python coverage matrix](docs/cosmic-python-coverage.md) distinguishes included patterns from conditional
-extensions such as optimistic locking, broker adapters, and the transactional outbox.
-
-### Project Structure
-
-#### Environment Variables
-
-Variables prefixed with `FASTAPI_` are used to configure the API UI.
-
-| Name | Description | Default Value |
-|------------------------------|---------------------------------------------|--------------------------------------------------|
-| FASTAPI_DEBUG | Debug Mode | False |
-| FASTAPI_PROJECT_NAME | Swagger Title | Cosmic FastAPI |
-| FASTAPI_PROJECT_DESCRIPTION | Swagger Description | This is a FastAPI template demo. |
-| FASTAPI_PROJECT_LICENSE | License info (JSON object, see below) | `{"name": "MIT", "url": "..."}` |
-| FASTAPI_PROJECT_CONTACT | Contact details (JSON object, see below) | `{"name": "Tomas Sanchez", "url": "...", ...}` |
-| FASTAPI_VERSION | Application Version | from package metadata (`pyproject.toml`) |
-| FASTAPI_DOCS_URL | Swagger Endpoint | /docs |
-| FASTAPI_BACKEND_CORS_ORIGINS | Allowed CORS origins (JSON list) | `["http://localhost:3000", "http://localhost:8000"]` |
-
-`FASTAPI_PROJECT_LICENSE` and `FASTAPI_PROJECT_CONTACT` parse into `LicenseInfo` and `ContactInfo` models, so they must
-be provided as JSON objects:
-
-```bash
-FASTAPI_PROJECT_LICENSE='{"name": "MIT", "url": "https://mit-license.org/"}'
-FASTAPI_PROJECT_CONTACT='{"name": "Tomas Sanchez", "url": "https://tomsanchez.com.ar", "email": "info@tomsanchez.com.ar"}'
-```
-
-`FASTAPI_BACKEND_CORS_ORIGINS` is a JSON list of allowed origins. The CORS middleware disables credentials whenever the
-list contains the `"*"` wildcard, since reflecting any origin together with credentials is an unsafe posture:
+Generate a new project with [Copier](https://copier.readthedocs.io/) and `uv`:
```bash
-FASTAPI_BACKEND_CORS_ORIGINS='["https://app.example.com", "https://admin.example.com"]'
+uvx copier copy gh:tomasanchez/cosmic-fastapi my-service
```
-`FASTAPI_VERSION` defaults to the installed package version (single source of truth in `pyproject.toml`).
-
-Variables prefixed with `UVICORN_` are used to configure the server.
-
-| Name | Description | Default Value |
-|-------------------|-----------------------|---------------|
-| UVICORN_HOST | Server Host | '127.0.0.1' |
-| UVICORN_PORT | Server Port | 8000 |
-| UVICORN_LOG_LEVEL | Log Level | 'info' |
-| UVICORN_RELOAD | Enable/Disable Reload | False |
-
-Variables prefixed with `DATABASE_` configure relational persistence.
-
-| Name | Description | Default Value |
-|-----------------------------|------------------------------------------|----------------------------------------|
-| DATABASE_URL | SQLAlchemy database URL | sqlite+pysqlite:///./cosmic-fastapi.db |
-| DATABASE_AUTO_CREATE_SCHEMA | Create tables at startup for local demos | False |
-
-Use Alembic migrations for normal schema management. `DATABASE_AUTO_CREATE_SCHEMA`
-exists for isolated tests and local demonstrations only.
-
-### Recommended Directory Structure
-
-As the application grows, keep the dependency direction visible in the directory structure. The domain remains plain
-Python. Framework validation belongs at the entrypoint boundary, and persistence belongs in adapters.
+Copier asks a few questions and renders the answers into the destination directory. A concrete run:
```text
-.
-|-- migrations
-|-- src/template
-| |-- adapters # (3)
-| | |-- models
-| | |-- queries.py
-| | |-- repository.py
-| | `-- unit_of_work.py
-| |-- domain # (1)
-| | |-- commands
-| | |-- events
-| | `-- models
-| |-- entrypoint # (4)
-| | |-- monitor.py
-| | |-- schemas.py
-| | `-- users.py
-| |-- service_layer # (2)
-| | |-- handlers.py
-| | |-- messagebus.py
-| | |-- queries.py
-| | |-- read_models.py
-| | |-- repository.py
-| | `-- unit_of_work.py
-| |-- settings
-| `-- bootstrap.py
-`-- tests
- |-- unit
- |-- integration
- `-- e2e
+project_name ........... Demo Service
+project_slug ........... demo-service
+package_name ........... demo_service
+project_description .... A demo service.
+author_name ............ Ada Lovelace
+author_email ........... ada@example.com
+author_url ............. https://example.com
+github_owner ........... demo-org
+license ................ MIT
+python_version ......... 3.13
+database_url ........... sqlite+pysqlite:///./demo-service.db
+include_user_example ... Yes
```
-- **(1)**. Domain, from Domain Driven Architecture.
- - **Commands**, from Command Query Responsibility Segregation (CQRS). Commands are the messages that change
- the state of the system.
- - **Events** describe facts that happened in the domain. Domain events do not imply Event Sourcing.
- - **Models**, represent the domain entities, business objects of interest.
-- **(2)**. The service layer coordinates use cases with handlers, a message bus, repository ports, and a unit of work.
- What is the difference between a domain service and a service layer?
- - Application service (our service layer) its job is to handle requests from the outside world and to orchestrate an
- operation.
- - Domain Service. This is the name for a piece of logic that belongs in the domain model but doesn't sit naturally
- inside a stateful
- entity or value object. For example, if you were building a shopping cart application, you might choose to build
- taxation rules as a domain service.
-- **(3)**. Adapters, it comes from "ports and adapters" terminology. This will fill up with any other abstractions
- around
- external I/O. Strictly speaking, you would call these secondary adapters or driven adapters, or sometimes
- inward-facing adapters.
-- **(4)**. Entrypoints are the places we drive our application from. FastAPI routes and Pydantic request or response
- schemas live here. In ports and adapters terminology, these are primary or driving adapters.
-
-The root `bootstrap.py` module is the composition root. It wires concrete adapters to application ports without leaking
-framework concerns into the domain.
-
-### Domain Driven Design
-
-`Commands`, `Events`, and `Models` are the building blocks of our **Domain**. Aggregates and behavior-rich domain objects
-remain plain Python. Commands and events may use frozen Pydantic models as immutable schema contracts. See
-[ADR 0011](docs/adr/0011-pydantic-message-schemas-with-plain-domain-aggregates.md). Python fields use `snake_case`;
-JSON message payloads use `camelCase` as documented in
-[ADR 0012](docs/adr/0012-camel-case-json-message-contracts.md).
-
-#### Models
-
-First, we define our domain models. These are the objects that represent the business concepts we’re working with.
-They should be as simple as possible and contain only the attributes that are essential to the business, using the
-business jargon. The idea is that, if you were to show these models to a non-technical person, but someone who
-understands the business process, they would be able to understand what the application does. Models encapsulate the
-behaviour, state, and business rules that govern the application. Models can encompass entities, aggregates, and
-sometimes even Value Objects.
-
-##### Entities
-
-Entities are objects that have distinct identities that run throughout their lifecycle. In other words, an entity is
-defined not just by its attributes but also by a unique identifier that differentiates it from other entities of the
-same type. Entities are mutable and can have their attributes modified while maintaining the same identity. They are
-often used to represent real-world objects or concepts that have an ongoing existence.
-
-For example, in an e-commerce system, a "Product" can be an entity. Each product has a unique identifier, and its
-attributes (such as name, description, price) can change without changing its identity.
-
-##### Value Objects
-
-A Value Object is a concept from Domain-Driven Design (DDD). It's an object that represents a descriptive aspect of the
-domain with no conceptual identity. In other words, a `Value Object` is defined solely by its attributes, and two Value
-Objects with the same attributes are considered equal. They are immutable and can be thought of as "flyweight" objects
-that are shared whenever their values are the same.
-
-Value Objects can be part of a `Model`. In fact, they often enhance the expressiveness and maintainability of Models.
-Value Objects help to define attributes with semantic meaning and encapsulate their validation and behaviour. In some
-cases, a Model might consist of one or more entities and Value Objects that work together to represent and manage the
-business logic and data.
-
-##### Aggregates:
-
-Aggregates are clusters of related `entities` and `value objects` that are treated as a single unit. The aggregate is
-the boundary within which changes are managed and consistency is maintained. One entity within the aggregate is
-designated as the "aggregate root."
-All interactions with the aggregate are done through this root entity. This helps ensure that the integrity and
-consistency of the data is maintained within the aggregate.
-
-For example, in the case of an e-commerce system, a "Shopping Cart" could be an aggregate. The shopping cart would be
-composed of multiple line items (entities representing products in the cart) and possibly other related information. All
-changes to the items in the cart would be managed through the shopping cart aggregate root.
-
-Entities can be part of an aggregate, and an aggregate often includes one or more entities and possibly value objects.
-Aggregates define the transactional boundaries and consistency rules within the domain. They encapsulate business rules
-and enforce invariants to ensure that the data remains in a valid and consistent state.
-
-#### Schemas
-
-Schemas define validated data contracts. Boundary schemas describe the input and output data of an API or external
-adapter. Immutable command and event schemas describe application messages.
-
-In this project we use `Pydantic` for entrypoint schemas and immutable message schemas. Boundary objects are translated
-when their external contract differs from the application message. See the
-[Pydantic documentation](https://docs.pydantic.dev/) for runtime validation.
-
-#### Event Driven Architecture
-
-`Commands` and `Events` are the building blocks of our **Event Driven Architecture**. They are immutable Pydantic
-models because they carry data contracts and have no behaviour.
-
-Both commands and events are often used in software architectures to promote separation of concerns, modularity, and
-extensibility. By encapsulating actions or occurrences into discrete objects, it becomes easier to reason about the
-system and make changes without impacting other parts of the codebase.
-
-In an API, boundary schemas validate client requests and are translated into commands. A handler executes the command
-inside a unit of work. Events raised by aggregates are dispatched to interested handlers after the command completes.
-HTTP responses remain boundary schemas rather than domain events.
-
-Commands may enter through HTTP or message-queue adapters. Domain events may remain in-process or be translated into
-versioned integration events for Kafka, RabbitMQ, SQS, or another broker. Integration events and HTTP responses can
-describe the same business fact without sharing one lifecycle or envelope.
-
-Write-side commands load aggregate roots through repositories and a unit of work. Read-only queries use purpose-built
-read models and reader adapters, as described in [ADR 0014](docs/adr/0014-cqrs-read-models-are-purpose-built.md).
-
-##### Commands
-
-Commands represent actions or requests to be performed by a system. They typically encapsulate a specific intent or
-operation that needs to be executed. In software development, commands are often used in conjunction with a command
-pattern or a similar architectural pattern to decouple the sender of the command from its execution.
-
-For example, in a web application, a command might be used to represent a user's request to update their profile
-information. The command object would contain the necessary data to carry out the update operation, and it would be
-executed by an appropriate handler or service.
-
-##### Events
-
-Events represent notifications or signals that something has happened within a system. They convey information about a
-specific occurrence and are often used for communication between different components or modules of an application.
-Events are typically used in event-driven architectures or publish-subscribe patterns.
-
-When an event occurs, it can be published to an event bus or a similar mechanism. Other components that have subscribed
-to that event can receive and react to it accordingly. This allows for loose coupling and enables different parts of the
-system to respond to events without direct dependencies on each other.
-
-For example, in an e-commerce application, an event might be triggered when a new order is placed. Subscribed
-components, such as inventory management or shipping modules, can then react to this event by updating their respective
-states or initiating further actions.
-
-### Clean Architecture
-
-The main idea behind Clean Architecture is to create a separation of concerns in software systems, allowing for easier
-maintenance, testing, and scalability. The core principle of Clean Architecture is the dependency rule, which states
-that dependencies should always point inward towards the core of the application and not outward towards external
-frameworks or tools. This helps keep the core of the application independent and flexible.
-
-Clean Architecture typically consists of several layers, each with a specific responsibility:
-
-* Entities: These are the core business objects and rules.
-* Use Cases (Interacts): These encapsulate the business logic and orchestrate interactions between entities.
-* Interfaces (Gateways): These define the interfaces that allow the use cases to interact with external data sources or
- systems.
-* Frameworks and Drivers: These are the outermost layers that deal with the infrastructure, such as databases, web
- frameworks, UI, etc.
-
-In our project, we can easily observe the different layers according to our directory structure. It's an architecture
-that "screams": by its naming conventions, we can easily understand what is the responsibility of each module.
-
-## Continuous Integration
-
-This project uses `make` as an adaptation layer.
-
-Run `make help` to see all available commands.
-
-## Development Environment
-
-### Installing UV
-
-This package uses `uv` for dependency management.
-
-Install `uv` in the system `site_packages`. DO NOT INSTALL IT in a virtual environment itself.
-
-To install `uv`, run:
+After copy, a post-copy task runs `uv lock`. Finish setup with:
```bash
-pip install uv
+cd demo-service
+uv sync
+make migrate
+uv run python -m demo_service.main
```
-### Building the Development Environment
-
-1. Clone the repository
-
- ```bash
- git clone "git@github.com:tomasanchez/cosmic-fastapi.git"
- ```
-2. Install dependencies
-
- ```bash
- cd cosmic-fastapi && uv sync --dev
- ```
-
-3. Activate pre-commit hooks (Optional)
-
- Using [pre-commit](https://pre-commit.com/) to run some checks before committing is highly recommended.
-
- To activate the pre-commit hooks, run:
-
- ```bash
- pre-commit install
- ```
+### Updating a generated project
- To run the checks manually, run:
-
- ```bash
- uv run pre-commit run --all-files
- ```
-
-## Running Local
-
-1. Apply database migrations:
-
- ```bash
- make migrate
- ```
-
-2. Run:
-
- ```bash
- uv run python -m template.main
- ```
-3. Go to http://localhost:8000/docs to see the API documentation.
-
-## Running Tests
-
-You can run the tests with:
+Because the answers are recorded in `.copier-answers.yml`, a generated project can pull standard improvements
+(new ADRs, dependency bumps, CI fixes) over time:
```bash
-uv run pytest
+cd demo-service
+copier update
```
-or with the `make` command:
+## Variables
+
+| Variable | Description | Default |
+|------------------------|-------------------------------------------------------------------|-----------------------------------------------|
+| `project_name` | Human-readable project name (Swagger title) | `My Service` |
+| `project_slug` | Kebab-case slug for repo, container, and database names | derived from `project_name` |
+| `package_name` | Importable Python package name (snake_case) | derived from `project_slug` |
+| `project_description` | One-line project description | _(empty)_ |
+| `author_name` | Author or organization name | `Your Name` |
+| `author_email` | Author contact email | `you@example.com` |
+| `author_url` | Author or project URL | `https://example.com` |
+| `github_owner` | GitHub owner/org used in clone URLs and image tags | `your-org` |
+| `license` | License (`MIT`, `Apache-2.0`, `BSD-3-Clause`, `Proprietary`) | `MIT` |
+| `python_version` | Minimum supported Python version (`3.13` or `3.12`) | `3.13` |
+| `database_url` | Default SQLAlchemy database URL | `sqlite+pysqlite:///./{project_slug}.db` |
+| `include_user_example` | Include the example User domain slice | `true` |
+
+## Developing the template
+
+The template repository cannot import its own package in place once the package path contains Jinja
+(`src/{{ package_name }}/`). The template is validated by **baking**: rendering it to a temporary directory
+and running the generated project's full suite.
```bash
-make test
-```
-
-To generate a coverage report add `--cov src`.
-
-```bash
-uv run pytest --cov src
-```
-
-Or with the `make` command:
-
-```bash
-make cover
+uv sync
+uv run pytest
```
-## Recommended Readings
-
-- [FastAPI official Documentation](https://fastapi.tiangolo.com/)
-- [Pydantic official Documentation](https://pydantic-docs.helpmanual.io/)
-- [UV official Documentation](https://docs.astral.sh/uv/)
-- [Cosmic Python](https://cosmicpython.com/)
-
-## Licence
-
-This project is licensed under the terms of the MIT licence unless otherwise specified. See [`LICENSE`](LICENSE) for
-more details or visit https://mit-license.org/.
+The bake-test ([`tests/test_bake.py`](tests/test_bake.py)) snapshots the tracked working tree, renders it with
+a sample answer set, then runs `uv sync`, `ruff check`, `pyrefly check`, and `pytest --cov src` inside the
+baked project, asserting 100% coverage. The same bake runs in CI via
+[`.github/workflows/template-ci.yml`](.github/workflows/template-ci.yml).
## Acknowledgements
-This project was designed and developed
-by [Tomás Sánchez](https://tomsanchez.com.ar/about/) <[info@tomsanchez.com.ar](mailto:info@tomsanchez.com.ar)>.
-
-Deeply inspired by [FastAPI-MVC](https://github.com/fastapi-mvc/fastapi-mvc)
-following [Cosmic Python](https://www.cosmicpython.com/) guidelines for project structure.
-
-If you find this project useful, please consider supporting its development by sponsoring it.
+Designed and developed by [Tomás Sánchez](https://tomsanchez.com.ar/about/)
+<[info@tomsanchez.com.ar](mailto:info@tomsanchez.com.ar)>, following
+[Cosmic Python](https://www.cosmicpython.com/) guidelines for project structure.
diff --git a/copier.yml b/copier.yml
new file mode 100644
index 0000000..8660bf3
--- /dev/null
+++ b/copier.yml
@@ -0,0 +1,102 @@
+_subdirectory: template
+_templates_suffix: .jinja
+
+_exclude:
+ - copier.yml
+ - "~*"
+ - "*.pyc"
+ - "__pycache__"
+ - ".git"
+ - ".venv"
+ - ".ruff_cache"
+ - ".pytest_cache"
+ - ".mypy_cache"
+
+_message_after_copy: |
+ Your project "{{ project_name }}" is ready.
+
+ Next steps:
+ 1. cd {{ _copier_conf.dst_path }}
+ 2. uv sync
+ 3. make migrate
+ 4. uv run python -m {{ package_name }}.main
+
+project_name:
+ type: str
+ help: Human-readable project name (used as the Swagger title)
+ default: My Service
+
+project_slug:
+ type: str
+ help: Kebab-case slug used for the repository, container, and database names
+ default: "{{ project_name | lower | replace(' ', '-') | replace('_', '-') }}"
+ validator: >-
+ {% if ' ' in project_slug or project_slug != project_slug | lower %}
+ project_slug must be lowercase with no spaces (use hyphens)
+ {% endif %}
+
+package_name:
+ type: str
+ help: Importable Python package name (snake_case)
+ default: "{{ project_slug | replace('-', '_') }}"
+ validator: >-
+ {% if ' ' in package_name or '-' in package_name or package_name != package_name | lower %}
+ package_name must be a valid lowercase Python identifier (snake_case)
+ {% endif %}
+
+project_description:
+ type: str
+ help: One-line project description
+ default: ""
+
+author_name:
+ type: str
+ help: Author or organization name
+ default: Your Name
+
+author_email:
+ type: str
+ help: Author contact email
+ default: you@example.com
+ validator: "{% if '@' not in author_email %}author_email must be a valid email address{% endif %}"
+
+author_url:
+ type: str
+ help: Author or project URL
+ default: https://example.com
+
+github_owner:
+ type: str
+ help: GitHub owner or organization (used in clone URLs and image tags)
+ default: your-org
+
+license:
+ type: str
+ help: Project license
+ choices:
+ - MIT
+ - Apache-2.0
+ - BSD-3-Clause
+ - Proprietary
+ default: MIT
+
+python_version:
+ type: str
+ help: Minimum supported Python version
+ choices:
+ - "3.13"
+ - "3.12"
+ default: "3.13"
+
+database_url:
+ type: str
+ help: Default SQLAlchemy database URL
+ default: "sqlite+pysqlite:///./{{ project_slug }}.db"
+
+include_user_example:
+ type: bool
+ help: Include the example User domain slice (commands, events, model, adapters, routes, tests)
+ default: true
+
+_tasks:
+ - "uv lock"
diff --git a/pyproject.toml b/pyproject.toml
index 07c647f..6054fe6 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,102 +1,18 @@
[project]
-name = "template"
-version = "1.0.0"
-description = ""
-authors = [{ name = "Tomas Sanchez", email = "info@tomsanchez.com.ar" }]
+name = "cosmic-fastapi-template"
+version = "0.1.0"
+description = "Copier template that scaffolds a Cosmic Python FastAPI service."
readme = "README.md"
requires-python = ">=3.13"
-dependencies = [
- "fastapi>=0.115.0",
- "uvicorn>=0.34.0",
- "pydantic-settings>=2.9.0",
- "pydantic[email]>=2.11.0",
- "sqlalchemy>=2.0.41",
- "alembic>=1.16.2",
-]
+dependencies = []
[dependency-groups]
dev = [
+ "copier>=9.4.0",
"pytest>=8.4.0",
- "coverage[toml]>=7.9.0",
"pre-commit>=4.2.0",
- "httpx>=0.28.0",
- "pytest-cov>=6.2.0",
- "ruff>=0.12,<0.13",
- "pyrefly>=1.0.0",
-]
-
-[build-system]
-requires = ["hatchling"]
-build-backend = "hatchling.build"
-
-[tool.ruff]
-line-length = 120
-
-[tool.ruff.lint]
-select = [
- # flake8-2020
- "YTT",
- # flake8-bandit
- "S",
- # flake8-bugbear
- "B",
- # flake8-builtins
- "A",
- # flake8-comprehensions
- "C4",
- # flake8-debugger
- "T10",
- # flake8-simplify
- "SIM",
- # isort
- "I",
- # mccabe
- "C90",
- # pycodestyle
- "E", "W",
- # pyflakes
- "F",
- # pygrep-hooks
- "PGH",
- # pyupgrade
- "UP",
- # ruff
- "RUF",
- # tryceratops
- "TRY",
-]
-# Disable rules that are too noisy for this scaffold.
-ignore = [
- # LineTooLong
- "E501",
- # DoNotAssignLambda
- "E731",
]
-[tool.ruff.lint.per-file-ignores]
-"tests/*" = ["S101"]
-
-[tool.ruff.lint.isort]
-known-first-party = ["template"]
-
-[tool.coverage.run]
-branch = true
-omit = ['tests/*', 'src/template/asgi.py', 'src/template/main.py', 'src/**/__init__.py']
-
-[tool.coverage.report]
-show_missing = true
-ignore_errors = true
-exclude_lines = ["pragma: no cover", "raise NotImplementedError", "if TYPE_CHECKING:", "if __name__ == .__main__.:"]
-
-
[tool.pytest.ini_options]
-pythonpath = ["src"]
testpaths = ["tests"]
python_files = "test_*.py"
-junit_family = "xunit1"
-log_cli = false
-log_level = "DEBUG"
-
-[tool.pyrefly]
-project-includes = ["src", "scripts", "tests", "migrations"]
-search-path = [".", "src"]
diff --git a/.dockerignore b/template/.dockerignore
similarity index 100%
rename from .dockerignore
rename to template/.dockerignore
diff --git a/.github/workflows/build.yml b/template/.github/workflows/build.yml.jinja
similarity index 88%
rename from .github/workflows/build.yml
rename to template/.github/workflows/build.yml.jinja
index 7e8acbe..3f693b6 100644
--- a/.github/workflows/build.yml
+++ b/template/.github/workflows/build.yml.jinja
@@ -34,4 +34,4 @@ jobs:
- name: Test with coverage gate
run: make cover
- name: Build image
- run: docker build . --file Dockerfile --tag template:${{ github.sha }}
+ run: docker build . --file Dockerfile --tag {{ project_slug }}:{% raw %}${{ github.sha }}{% endraw %}
diff --git a/template/.gitignore b/template/.gitignore
new file mode 100644
index 0000000..36eb4c5
--- /dev/null
+++ b/template/.gitignore
@@ -0,0 +1,164 @@
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+cover/
+reports/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# SQLite databases (default DATABASE_URL writes ./cosmic-fastapi.db)
+*.db
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+.pybuilder/
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+# For a library or package, you might want to ignore these files since the code is
+# intended to run in multiple environments; otherwise, check them in:
+# .python-version
+
+# pipenv
+# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+# However, in case of collaboration, if having platform-specific dependencies or dependencies
+# having no cross-platform support, pipenv may install dependencies that don't work, or not
+# install all needed dependencies.
+#Pipfile.lock
+
+# poetry
+# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
+# This is especially recommended for binary packages to ensure reproducibility, and is more
+# commonly ignored for libraries.
+# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
+#poetry.lock
+
+# pdm
+# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
+#pdm.lock
+# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
+# in version control.
+# https://pdm.fming.dev/#use-with-ide
+.pdm.toml
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# pytype static type analyzer
+.pytype/
+
+# Cython debug symbols
+cython_debug/
+
+# PyCharm
+# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
+# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
+# and can be added to the global gitignore or merged into this file. For a more nuclear
+# option (not recommended) you can uncomment the following to ignore the entire idea folder.
+.idea/
diff --git a/template/.pre-commit-config.yaml b/template/.pre-commit-config.yaml
new file mode 100644
index 0000000..23ac166
--- /dev/null
+++ b/template/.pre-commit-config.yaml
@@ -0,0 +1,22 @@
+repos:
+ - repo: https://github.com/pre-commit/pre-commit-hooks
+ rev: "v5.0.0"
+ hooks:
+ - id: check-case-conflict
+ - id: check-merge-conflict
+ - id: check-toml
+ - id: check-yaml
+ - id: check-json
+ - id: end-of-file-fixer
+ - id: trailing-whitespace
+ - repo: https://github.com/astral-sh/ruff-pre-commit
+ # Ruff version.
+ rev: v0.12.1
+ hooks:
+ # Run the linter.
+ - id: ruff-check
+ types_or: [ python, pyi ]
+ args: [ --fix ]
+ # Run the formatter.
+ - id: ruff-format
+ types_or: [ python, pyi ]
diff --git a/template/.python-version.jinja b/template/.python-version.jinja
new file mode 100644
index 0000000..7773b4e
--- /dev/null
+++ b/template/.python-version.jinja
@@ -0,0 +1 @@
+{{ python_version }}
diff --git a/AGENTS.md b/template/AGENTS.md
similarity index 100%
rename from AGENTS.md
rename to template/AGENTS.md
diff --git a/Dockerfile b/template/Dockerfile.jinja
similarity index 87%
rename from Dockerfile
rename to template/Dockerfile.jinja
index d3fd192..0c94c9d 100644
--- a/Dockerfile
+++ b/template/Dockerfile.jinja
@@ -1,11 +1,11 @@
-FROM python:3.13-slim AS builder
+FROM python:{{ python_version }}-slim AS builder
ARG APP_DIR=/app
ENV PYTHONUNBUFFERED=1 \
PYTHONHASHSEED=random \
PYTHONFAULTHANDLER=1 \
- UV_PYTHON=python3.13 \
+ UV_PYTHON=python{{ python_version }} \
UV_COMPILE_BYTECODE=1 \
UV_LINK_MODE=copy
@@ -28,7 +28,7 @@ RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --frozen --no-dev
-FROM python:3.13-slim AS runtime
+FROM python:{{ python_version }}-slim AS runtime
ARG APP_DIR=/app
@@ -54,4 +54,4 @@ USER app
EXPOSE 8000
# Run the application using the Python from the prebuilt venv.
-CMD ["python", "-m", "template.main"]
+CMD ["python", "-m", "{{ package_name }}.main"]
diff --git a/LICENSE b/template/LICENSE.jinja
similarity index 94%
rename from LICENSE
rename to template/LICENSE.jinja
index e83fc58..feeaf44 100644
--- a/LICENSE
+++ b/template/LICENSE.jinja
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2023 Tomas Sanchez
+Copyright (c) 2026 {{ author_name }} <{{ author_email }}>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/Makefile b/template/Makefile.jinja
similarity index 95%
rename from Makefile
rename to template/Makefile.jinja
index c4b3f6e..a5262e3 100644
--- a/Makefile
+++ b/template/Makefile.jinja
@@ -43,7 +43,7 @@ migrate: ## Applies relational database migrations
.PHONY: cover
cover: ## Executes tests cases with coverage reports
- uv run pytest --cov src/template --cov-fail-under=100 --junitxml reports/xunit.xml \
+ uv run pytest --cov src/{{ package_name }} --cov-fail-under=100 --junitxml reports/xunit.xml \
--cov-report xml:reports/coverage.xml --cov-report term-missing
.PHONY: format
diff --git a/template/README.md.jinja b/template/README.md.jinja
new file mode 100644
index 0000000..8dc3d6f
--- /dev/null
+++ b/template/README.md.jinja
@@ -0,0 +1,418 @@
+
+
+
+
+
+Kickoff your app like a kangaroo through the stars: clean, fast, and unapologetically structured.
+
+
+---
+
+# {{ project_name }}
+
+**{{ project_name }}** is a [FastAPI](https://fastapi.tiangolo.com/) project generated from the
+[Cosmic FastAPI](https://github.com/{{ github_owner }}/cosmic-fastapi) template, following
+the [Cosmic Python](https://www.cosmicpython.com/) guidelines.
+
+## Content
+
+
+
+* [Cosmic FastAPI](#cosmic-fastapi)
+ * [Content](#content)
+ * [About](#about)
+ * [Features](#features)
+ * [Architecture Decision Records](#architecture-decision-records)
+ * [Project Structure](#project-structure)
+ * [Environment Variables](#environment-variables)
+ * [Recommended Directory Structure](#recommended-directory-structure)
+ * [Domain Driven Design](#domain-driven-design)
+ * [Models](#models)
+ * [Entities](#entities)
+ * [Value Objects](#value-objects)
+ * [Aggregates:](#aggregates)
+ * [Schemas](#schemas)
+ * [Event Driven Architecture](#event-driven-architecture)
+ * [Commands](#commands)
+ * [Events](#events)
+ * [Clean Architecture](#clean-architecture)
+ * [Continuous Integration](#continuous-integration)
+ * [Development Environment](#development-environment)
+ * [Installing UV](#installing-uv)
+ * [Building the Development Environment](#building-the-development-environment)
+ * [Running Local](#running-local)
+ * [Running Tests](#running-tests)
+ * [Recommended Readings](#recommended-readings)
+ * [Licence](#licence)
+ * [Acknowledgements](#acknowledgements)
+
+
+
+## About
+
+### Features
+
+This template includes `FastAPI` using the fastest `Pydantic V2` model validation. Designed for Event-Driven
+Architecture (EDA) and Domain-Driven Design (DDD). Providing a clean and simple structure to start a new project. With
+the addition of `pre-commit` hooks to ensure code quality. And ready to be used in a `CI/CD` GitHub Workflows pipeline.
+
+### Architecture Decision Records
+
+The [Architecture Decision Records](docs/adr/README.md) define the modern Cosmic Python standard used by this template.
+They are the first reference for people and coding agents extending the project. The records preserve the book's
+domain-first philosophy while adopting FastAPI, Pydantic 2, SQLAlchemy 2, Alembic, uv, Ruff, Pyrefly, and pytest.
+Repository-level [agent instructions](AGENTS.md) turn those decisions into an implementation workflow.
+The [Cosmic Python coverage matrix](docs/cosmic-python-coverage.md) distinguishes included patterns from conditional
+extensions such as optimistic locking, broker adapters, and the transactional outbox.
+
+### Project Structure
+
+#### Environment Variables
+
+Variables prefixed with `FASTAPI_` are used to configure the API UI.
+
+| Name | Description | Default Value |
+|------------------------------|---------------------------------------------|--------------------------------------------------|
+| FASTAPI_DEBUG | Debug Mode | False |
+| FASTAPI_PROJECT_NAME | Swagger Title | {{ project_name }} |
+| FASTAPI_PROJECT_DESCRIPTION | Swagger Description | {{ project_description }} |
+| FASTAPI_PROJECT_LICENSE | License info (JSON object, see below) | `{"name": "MIT", "url": "..."}` |
+| FASTAPI_PROJECT_CONTACT | Contact details (JSON object, see below) | `{"name": "{{ author_name }}", "url": "...", ...}` |
+| FASTAPI_VERSION | Application Version | from package metadata (`pyproject.toml`) |
+| FASTAPI_DOCS_URL | Swagger Endpoint | /docs |
+| FASTAPI_BACKEND_CORS_ORIGINS | Allowed CORS origins (JSON list) | `["http://localhost:3000", "http://localhost:8000"]` |
+
+`FASTAPI_PROJECT_LICENSE` and `FASTAPI_PROJECT_CONTACT` parse into `LicenseInfo` and `ContactInfo` models, so they must
+be provided as JSON objects:
+
+```bash
+FASTAPI_PROJECT_LICENSE='{"name": "MIT", "url": "https://mit-license.org/"}'
+FASTAPI_PROJECT_CONTACT='{"name": "{{ author_name }}", "url": "{{ author_url }}", "email": "{{ author_email }}"}'
+```
+
+`FASTAPI_BACKEND_CORS_ORIGINS` is a JSON list of allowed origins. The CORS middleware disables credentials whenever the
+list contains the `"*"` wildcard, since reflecting any origin together with credentials is an unsafe posture:
+
+```bash
+FASTAPI_BACKEND_CORS_ORIGINS='["https://app.example.com", "https://admin.example.com"]'
+```
+
+`FASTAPI_VERSION` defaults to the installed package version (single source of truth in `pyproject.toml`).
+
+Variables prefixed with `UVICORN_` are used to configure the server.
+
+| Name | Description | Default Value |
+|-------------------|-----------------------|---------------|
+| UVICORN_HOST | Server Host | '127.0.0.1' |
+| UVICORN_PORT | Server Port | 8000 |
+| UVICORN_LOG_LEVEL | Log Level | 'info' |
+| UVICORN_RELOAD | Enable/Disable Reload | False |
+
+Variables prefixed with `DATABASE_` configure relational persistence.
+
+| Name | Description | Default Value |
+|-----------------------------|------------------------------------------|----------------------------------------|
+| DATABASE_URL | SQLAlchemy database URL | {{ database_url }} |
+| DATABASE_AUTO_CREATE_SCHEMA | Create tables at startup for local demos | False |
+
+Use Alembic migrations for normal schema management. `DATABASE_AUTO_CREATE_SCHEMA`
+exists for isolated tests and local demonstrations only.
+
+### Recommended Directory Structure
+
+As the application grows, keep the dependency direction visible in the directory structure. The domain remains plain
+Python. Framework validation belongs at the entrypoint boundary, and persistence belongs in adapters.
+
+```text
+.
+|-- migrations
+|-- src/{{ package_name }}
+| |-- adapters # (3)
+| | |-- models
+| | |-- queries.py
+| | |-- repository.py
+| | `-- unit_of_work.py
+| |-- domain # (1)
+| | |-- commands
+| | |-- events
+| | `-- models
+| |-- entrypoint # (4)
+| | |-- monitor.py
+| | |-- schemas.py
+| | `-- users.py
+| |-- service_layer # (2)
+| | |-- handlers.py
+| | |-- messagebus.py
+| | |-- queries.py
+| | |-- read_models.py
+| | |-- repository.py
+| | `-- unit_of_work.py
+| |-- settings
+| `-- bootstrap.py
+`-- tests
+ |-- unit
+ |-- integration
+ `-- e2e
+```
+
+- **(1)**. Domain, from Domain Driven Architecture.
+ - **Commands**, from Command Query Responsibility Segregation (CQRS). Commands are the messages that change
+ the state of the system.
+ - **Events** describe facts that happened in the domain. Domain events do not imply Event Sourcing.
+ - **Models**, represent the domain entities, business objects of interest.
+- **(2)**. The service layer coordinates use cases with handlers, a message bus, repository ports, and a unit of work.
+ What is the difference between a domain service and a service layer?
+ - Application service (our service layer) its job is to handle requests from the outside world and to orchestrate an
+ operation.
+ - Domain Service. This is the name for a piece of logic that belongs in the domain model but doesn't sit naturally
+ inside a stateful
+ entity or value object. For example, if you were building a shopping cart application, you might choose to build
+ taxation rules as a domain service.
+- **(3)**. Adapters, it comes from "ports and adapters" terminology. This will fill up with any other abstractions
+ around
+ external I/O. Strictly speaking, you would call these secondary adapters or driven adapters, or sometimes
+ inward-facing adapters.
+- **(4)**. Entrypoints are the places we drive our application from. FastAPI routes and Pydantic request or response
+ schemas live here. In ports and adapters terminology, these are primary or driving adapters.
+
+The root `bootstrap.py` module is the composition root. It wires concrete adapters to application ports without leaking
+framework concerns into the domain.
+
+### Domain Driven Design
+
+`Commands`, `Events`, and `Models` are the building blocks of our **Domain**. Aggregates and behavior-rich domain objects
+remain plain Python. Commands and events may use frozen Pydantic models as immutable schema contracts. See
+[ADR 0011](docs/adr/0011-pydantic-message-schemas-with-plain-domain-aggregates.md). Python fields use `snake_case`;
+JSON message payloads use `camelCase` as documented in
+[ADR 0012](docs/adr/0012-camel-case-json-message-contracts.md).
+
+#### Models
+
+First, we define our domain models. These are the objects that represent the business concepts we’re working with.
+They should be as simple as possible and contain only the attributes that are essential to the business, using the
+business jargon. The idea is that, if you were to show these models to a non-technical person, but someone who
+understands the business process, they would be able to understand what the application does. Models encapsulate the
+behaviour, state, and business rules that govern the application. Models can encompass entities, aggregates, and
+sometimes even Value Objects.
+
+##### Entities
+
+Entities are objects that have distinct identities that run throughout their lifecycle. In other words, an entity is
+defined not just by its attributes but also by a unique identifier that differentiates it from other entities of the
+same type. Entities are mutable and can have their attributes modified while maintaining the same identity. They are
+often used to represent real-world objects or concepts that have an ongoing existence.
+
+For example, in an e-commerce system, a "Product" can be an entity. Each product has a unique identifier, and its
+attributes (such as name, description, price) can change without changing its identity.
+
+##### Value Objects
+
+A Value Object is a concept from Domain-Driven Design (DDD). It's an object that represents a descriptive aspect of the
+domain with no conceptual identity. In other words, a `Value Object` is defined solely by its attributes, and two Value
+Objects with the same attributes are considered equal. They are immutable and can be thought of as "flyweight" objects
+that are shared whenever their values are the same.
+
+Value Objects can be part of a `Model`. In fact, they often enhance the expressiveness and maintainability of Models.
+Value Objects help to define attributes with semantic meaning and encapsulate their validation and behaviour. In some
+cases, a Model might consist of one or more entities and Value Objects that work together to represent and manage the
+business logic and data.
+
+##### Aggregates:
+
+Aggregates are clusters of related `entities` and `value objects` that are treated as a single unit. The aggregate is
+the boundary within which changes are managed and consistency is maintained. One entity within the aggregate is
+designated as the "aggregate root."
+All interactions with the aggregate are done through this root entity. This helps ensure that the integrity and
+consistency of the data is maintained within the aggregate.
+
+For example, in the case of an e-commerce system, a "Shopping Cart" could be an aggregate. The shopping cart would be
+composed of multiple line items (entities representing products in the cart) and possibly other related information. All
+changes to the items in the cart would be managed through the shopping cart aggregate root.
+
+Entities can be part of an aggregate, and an aggregate often includes one or more entities and possibly value objects.
+Aggregates define the transactional boundaries and consistency rules within the domain. They encapsulate business rules
+and enforce invariants to ensure that the data remains in a valid and consistent state.
+
+#### Schemas
+
+Schemas define validated data contracts. Boundary schemas describe the input and output data of an API or external
+adapter. Immutable command and event schemas describe application messages.
+
+In this project we use `Pydantic` for entrypoint schemas and immutable message schemas. Boundary objects are translated
+when their external contract differs from the application message. See the
+[Pydantic documentation](https://docs.pydantic.dev/) for runtime validation.
+
+#### Event Driven Architecture
+
+`Commands` and `Events` are the building blocks of our **Event Driven Architecture**. They are immutable Pydantic
+models because they carry data contracts and have no behaviour.
+
+Both commands and events are often used in software architectures to promote separation of concerns, modularity, and
+extensibility. By encapsulating actions or occurrences into discrete objects, it becomes easier to reason about the
+system and make changes without impacting other parts of the codebase.
+
+In an API, boundary schemas validate client requests and are translated into commands. A handler executes the command
+inside a unit of work. Events raised by aggregates are dispatched to interested handlers after the command completes.
+HTTP responses remain boundary schemas rather than domain events.
+
+Commands may enter through HTTP or message-queue adapters. Domain events may remain in-process or be translated into
+versioned integration events for Kafka, RabbitMQ, SQS, or another broker. Integration events and HTTP responses can
+describe the same business fact without sharing one lifecycle or envelope.
+
+Write-side commands load aggregate roots through repositories and a unit of work. Read-only queries use purpose-built
+read models and reader adapters, as described in [ADR 0014](docs/adr/0014-cqrs-read-models-are-purpose-built.md).
+
+##### Commands
+
+Commands represent actions or requests to be performed by a system. They typically encapsulate a specific intent or
+operation that needs to be executed. In software development, commands are often used in conjunction with a command
+pattern or a similar architectural pattern to decouple the sender of the command from its execution.
+
+For example, in a web application, a command might be used to represent a user's request to update their profile
+information. The command object would contain the necessary data to carry out the update operation, and it would be
+executed by an appropriate handler or service.
+
+##### Events
+
+Events represent notifications or signals that something has happened within a system. They convey information about a
+specific occurrence and are often used for communication between different components or modules of an application.
+Events are typically used in event-driven architectures or publish-subscribe patterns.
+
+When an event occurs, it can be published to an event bus or a similar mechanism. Other components that have subscribed
+to that event can receive and react to it accordingly. This allows for loose coupling and enables different parts of the
+system to respond to events without direct dependencies on each other.
+
+For example, in an e-commerce application, an event might be triggered when a new order is placed. Subscribed
+components, such as inventory management or shipping modules, can then react to this event by updating their respective
+states or initiating further actions.
+
+### Clean Architecture
+
+The main idea behind Clean Architecture is to create a separation of concerns in software systems, allowing for easier
+maintenance, testing, and scalability. The core principle of Clean Architecture is the dependency rule, which states
+that dependencies should always point inward towards the core of the application and not outward towards external
+frameworks or tools. This helps keep the core of the application independent and flexible.
+
+Clean Architecture typically consists of several layers, each with a specific responsibility:
+
+* Entities: These are the core business objects and rules.
+* Use Cases (Interacts): These encapsulate the business logic and orchestrate interactions between entities.
+* Interfaces (Gateways): These define the interfaces that allow the use cases to interact with external data sources or
+ systems.
+* Frameworks and Drivers: These are the outermost layers that deal with the infrastructure, such as databases, web
+ frameworks, UI, etc.
+
+In our project, we can easily observe the different layers according to our directory structure. It's an architecture
+that "screams": by its naming conventions, we can easily understand what is the responsibility of each module.
+
+## Continuous Integration
+
+This project uses `make` as an adaptation layer.
+
+Run `make help` to see all available commands.
+
+## Development Environment
+
+### Installing UV
+
+This package uses `uv` for dependency management.
+
+Install `uv` in the system `site_packages`. DO NOT INSTALL IT in a virtual environment itself.
+
+To install `uv`, run:
+
+```bash
+pip install uv
+```
+
+### Building the Development Environment
+
+1. Clone the repository
+
+ ```bash
+ git clone "git@github.com:{{ github_owner }}/{{ project_slug }}.git"
+ ```
+2. Install dependencies
+
+ ```bash
+ cd {{ project_slug }} && uv sync --dev
+ ```
+
+3. Activate pre-commit hooks (Optional)
+
+ Using [pre-commit](https://pre-commit.com/) to run some checks before committing is highly recommended.
+
+ To activate the pre-commit hooks, run:
+
+ ```bash
+ pre-commit install
+ ```
+
+ To run the checks manually, run:
+
+ ```bash
+ uv run pre-commit run --all-files
+ ```
+
+## Running Local
+
+1. Apply database migrations:
+
+ ```bash
+ make migrate
+ ```
+
+2. Run:
+
+ ```bash
+ uv run python -m {{ package_name }}.main
+ ```
+3. Go to http://localhost:8000/docs to see the API documentation.
+
+## Running Tests
+
+You can run the tests with:
+
+```bash
+uv run pytest
+```
+
+or with the `make` command:
+
+```bash
+make test
+```
+
+To generate a coverage report add `--cov src`.
+
+```bash
+uv run pytest --cov src
+```
+
+Or with the `make` command:
+
+```bash
+make cover
+```
+
+## Recommended Readings
+
+- [FastAPI official Documentation](https://fastapi.tiangolo.com/)
+- [Pydantic official Documentation](https://pydantic-docs.helpmanual.io/)
+- [UV official Documentation](https://docs.astral.sh/uv/)
+- [Cosmic Python](https://cosmicpython.com/)
+
+## Licence
+
+This project is licensed under the terms of the MIT licence unless otherwise specified. See [`LICENSE`](LICENSE) for
+more details or visit https://mit-license.org/.
+
+## Acknowledgements
+
+This project was generated by [{{ author_name }}]({{ author_url }}) <[{{ author_email }}](mailto:{{ author_email }})>
+from the [Cosmic FastAPI](https://github.com/{{ github_owner }}/cosmic-fastapi) template.
+
+Deeply inspired by [FastAPI-MVC](https://github.com/fastapi-mvc/fastapi-mvc)
+following [Cosmic Python](https://www.cosmicpython.com/) guidelines for project structure.
+
+If you find this project useful, please consider supporting its development by sponsoring it.
diff --git a/alembic.ini b/template/alembic.ini
similarity index 100%
rename from alembic.ini
rename to template/alembic.ini
diff --git a/docker-compose.yaml b/template/docker-compose.yaml.jinja
similarity index 90%
rename from docker-compose.yaml
rename to template/docker-compose.yaml.jinja
index 76b41f9..cdfd91c 100644
--- a/docker-compose.yaml
+++ b/template/docker-compose.yaml.jinja
@@ -3,7 +3,7 @@ services:
# Build the image from the Dockerfile in the current directory.
build: .
# Name the container for easier identification.
- container_name: cosmic-fastapi-app
+ container_name: {{ project_slug }}-app
# Map port 8000 on the host to port 8000 inside the container so the app is
# reachable at http://localhost:8000.
ports:
@@ -18,7 +18,7 @@ services:
# Enable Uvicorn auto-reload for local development.
- UVICORN_RELOAD=true
# Store the SQLite database on the persistent named volume.
- - DATABASE_URL=sqlite+pysqlite:///./data/cosmic-fastapi.db
+ - DATABASE_URL=sqlite+pysqlite:///./data/{{ project_slug }}.db
# Create the schema at startup so the demo works on first run.
# Production deployments must use Alembic migrations (make migrate)
# instead of auto-creating the schema.
diff --git a/docs/adr/0001-modern-cosmic-python-standard.md b/template/docs/adr/0001-modern-cosmic-python-standard.md
similarity index 100%
rename from docs/adr/0001-modern-cosmic-python-standard.md
rename to template/docs/adr/0001-modern-cosmic-python-standard.md
diff --git a/docs/adr/0002-domain-models-are-framework-independent.md b/template/docs/adr/0002-domain-models-are-framework-independent.md
similarity index 100%
rename from docs/adr/0002-domain-models-are-framework-independent.md
rename to template/docs/adr/0002-domain-models-are-framework-independent.md
diff --git a/docs/adr/0003-fastapi-and-pydantic-live-at-boundaries.md b/template/docs/adr/0003-fastapi-and-pydantic-live-at-boundaries.md
similarity index 100%
rename from docs/adr/0003-fastapi-and-pydantic-live-at-boundaries.md
rename to template/docs/adr/0003-fastapi-and-pydantic-live-at-boundaries.md
diff --git a/docs/adr/0004-sqlalchemy-2-persistence-behind-repositories.md b/template/docs/adr/0004-sqlalchemy-2-persistence-behind-repositories.md
similarity index 100%
rename from docs/adr/0004-sqlalchemy-2-persistence-behind-repositories.md
rename to template/docs/adr/0004-sqlalchemy-2-persistence-behind-repositories.md
diff --git a/docs/adr/0005-explicit-composition-and-message-dispatch.md b/template/docs/adr/0005-explicit-composition-and-message-dispatch.md
similarity index 100%
rename from docs/adr/0005-explicit-composition-and-message-dispatch.md
rename to template/docs/adr/0005-explicit-composition-and-message-dispatch.md
diff --git a/docs/adr/0006-async-is-an-explicit-end-to-end-choice.md b/template/docs/adr/0006-async-is-an-explicit-end-to-end-choice.md
similarity index 100%
rename from docs/adr/0006-async-is-an-explicit-end-to-end-choice.md
rename to template/docs/adr/0006-async-is-an-explicit-end-to-end-choice.md
diff --git a/docs/adr/0007-tooling-and-test-pyramid.md b/template/docs/adr/0007-tooling-and-test-pyramid.md
similarity index 100%
rename from docs/adr/0007-tooling-and-test-pyramid.md
rename to template/docs/adr/0007-tooling-and-test-pyramid.md
diff --git a/docs/adr/0008-static-typing-with-pyrefly.md b/template/docs/adr/0008-static-typing-with-pyrefly.md
similarity index 100%
rename from docs/adr/0008-static-typing-with-pyrefly.md
rename to template/docs/adr/0008-static-typing-with-pyrefly.md
diff --git a/docs/adr/0009-conventional-commits.md b/template/docs/adr/0009-conventional-commits.md
similarity index 100%
rename from docs/adr/0009-conventional-commits.md
rename to template/docs/adr/0009-conventional-commits.md
diff --git a/docs/adr/0010-adr-lifecycle-and-decision-pruning.md b/template/docs/adr/0010-adr-lifecycle-and-decision-pruning.md
similarity index 100%
rename from docs/adr/0010-adr-lifecycle-and-decision-pruning.md
rename to template/docs/adr/0010-adr-lifecycle-and-decision-pruning.md
diff --git a/docs/adr/0011-pydantic-message-schemas-with-plain-domain-aggregates.md b/template/docs/adr/0011-pydantic-message-schemas-with-plain-domain-aggregates.md
similarity index 100%
rename from docs/adr/0011-pydantic-message-schemas-with-plain-domain-aggregates.md
rename to template/docs/adr/0011-pydantic-message-schemas-with-plain-domain-aggregates.md
diff --git a/docs/adr/0012-camel-case-json-message-contracts.md b/template/docs/adr/0012-camel-case-json-message-contracts.md
similarity index 100%
rename from docs/adr/0012-camel-case-json-message-contracts.md
rename to template/docs/adr/0012-camel-case-json-message-contracts.md
diff --git a/docs/adr/0013-aggregates-define-consistency-boundaries.md b/template/docs/adr/0013-aggregates-define-consistency-boundaries.md
similarity index 100%
rename from docs/adr/0013-aggregates-define-consistency-boundaries.md
rename to template/docs/adr/0013-aggregates-define-consistency-boundaries.md
diff --git a/docs/adr/0014-cqrs-read-models-are-purpose-built.md b/template/docs/adr/0014-cqrs-read-models-are-purpose-built.md
similarity index 100%
rename from docs/adr/0014-cqrs-read-models-are-purpose-built.md
rename to template/docs/adr/0014-cqrs-read-models-are-purpose-built.md
diff --git a/template/docs/adr/0015-copier-template-engine.md b/template/docs/adr/0015-copier-template-engine.md
new file mode 100644
index 0000000..4d18452
--- /dev/null
+++ b/template/docs/adr/0015-copier-template-engine.md
@@ -0,0 +1,110 @@
+# ADR 0015: Copier as the Template Engine
+
+- Status: Accepted
+- Date: 2026-06-12
+
+## Context
+
+Cosmic FastAPI began as a *clone-and-rename* template: the Python package is
+literally named `template`, and project identity (name, author, URLs, database
+defaults) is hardcoded across `pyproject.toml`, settings, docs, Docker, and CI.
+Adopting it means cloning the repository and hand-editing every occurrence,
+which is error-prone and leaves the new project with no link back to the
+standard it was generated from.
+
+The value of this repository is not a one-time scaffold; it is an **evolving
+standard**. The ADR set, [AGENTS.md](../../AGENTS.md), the Cosmic Python
+coverage matrix, and the tooling baseline keep changing. A generated project
+should be able to pull those improvements over time, not fork them at birth.
+
+Two mature Python scaffolding engines exist: cookiecutter and Copier. We need a
+durable decision about which one this template targets, and how the repository
+is laid out to serve both template authors and generated projects.
+
+## Decision
+
+Use **Copier**.
+
+- **Updates are first-class.** Copier records answers in a generated
+ `.copier-answers.yml` and supports `copier update`, so a project created from
+ this template can re-pull standard improvements (new ADRs, dependency bumps,
+ CI fixes). This is the deciding factor: it matches the "reusable, evolving
+ standard for people and agents" intent. Cookiecutter has no native update
+ story (it requires bolting on `cruft`).
+- **Conditional content is declarative.** Copier renders Jinja in both file
+ contents and paths and excludes files with `_exclude` and templated
+ filenames, which fits optional features (for example
+ `include_user_example`) better than cookiecutter's post-generation hook
+ deletions.
+- **Questions are typed and validated.** Copier validates answers (slug regex,
+ email, choices) from `copier.yml` without extra hook code.
+
+### Repository layout
+
+The template repository serves two audiences. We keep them physically
+separated:
+
+- **Author-facing, at the repository root** (never rendered into a generated
+ project): `copier.yml`, the template `README` / `CONTRIBUTING`, this
+ `docs/adr/` standard, the coverage matrix, and the template's own
+ bake-and-test suite.
+- **Generated-project-facing, under a `template/` subdirectory** referenced by
+ Copier's `_subdirectory: template`: everything that becomes a new project —
+ `pyproject.toml`, `src/{{ package_name }}/`, `migrations/`, `Makefile`,
+ `Dockerfile`, `docker-compose.yaml`, CI, and a starter copy of `AGENTS.md`
+ and the ADRs.
+
+Generation copies the rendered `template/` tree into the destination directory
+the user names (`copier copy gh:tomasanchez/cosmic-fastapi my-service`); we do
+not nest the output inside a redundant `{{ project_slug }}/` folder.
+
+### Parameters
+
+The `template` package is renamed to `{{ package_name }}`. Identity is driven by
+Copier questions: `project_name`, `project_slug`, `package_name`,
+`project_description`, `author_name`, `author_email`, `author_url`,
+`github_owner`, `license`, `python_version`, `database_url`, and the
+`include_user_example` feature flag. `uv.lock` is never templated; a post-copy
+task regenerates it with `uv lock`.
+
+### Testing
+
+The template repository cannot import its own package in place once the package
+path contains Jinja. The template is validated by **baking**: render it to a
+temporary directory and run the generated project's full suite (Ruff, Pyrefly,
+pytest at 100% coverage). This bake-and-test replaces in-place testing for the
+template repository and runs across the feature-flag matrix in CI.
+
+## Consequences
+
+Adopting and updating a project becomes a supported workflow rather than a
+manual rename. Optional features can be added later as Jinja-guarded flags (the
+Kafka, MCP, and integration-event scaffolds removed during hardening can return
+this way once they carry real content). The ADRs ship into generated projects
+as starter records while this repository remains the authoritative standard.
+
+The costs: contributors must run a bake to test changes (slower than a direct
+`pytest`), and template authoring requires care where Jinja delimiters meet
+literal `${{ }}` in GitHub Actions or `{}` in f-strings — those regions are
+wrapped in `{% raw %}` blocks. Copier becomes a development dependency of the
+template repository.
+
+## Agent Guidance
+
+- Edit generated-project files under `template/`. Do not add project identity as
+ a hardcoded string; use the Copier variable (`{{ package_name }}`,
+ `{{ project_name }}`, etc.).
+- Keep author-facing files (this ADR set, the template README/CONTRIBUTING, the
+ bake-and-test suite) at the repository root, outside `_subdirectory`.
+- When adding an optional feature, add a boolean question to `copier.yml`, guard
+ the files with `_exclude` or templated names, and cover both states in the
+ bake matrix.
+- Never template `uv.lock`; regenerate it in a post-copy task.
+- Wrap GitHub Actions `${{ ... }}` and other literal brace blocks in
+ `{% raw %}{% endraw %}` inside `.jinja` files.
+
+## References
+
+- [Copier documentation](https://copier.readthedocs.io/)
+- [Copier: updating a project](https://copier.readthedocs.io/en/stable/updating/)
+- [Epic #24: convert cosmic-fastapi into a Copier template](https://github.com/tomasanchez/cosmic-fastapi/issues/24)
diff --git a/docs/adr/0016-aggregate-persistence-write-back.md b/template/docs/adr/0016-aggregate-persistence-write-back.md
similarity index 100%
rename from docs/adr/0016-aggregate-persistence-write-back.md
rename to template/docs/adr/0016-aggregate-persistence-write-back.md
diff --git a/docs/adr/README.md b/template/docs/adr/README.md
similarity index 97%
rename from docs/adr/README.md
rename to template/docs/adr/README.md
index 03ac73e..13a7ac4 100644
--- a/docs/adr/README.md
+++ b/template/docs/adr/README.md
@@ -33,6 +33,7 @@ treated as migration work, not as a precedent to repeat.
| [0012](0012-camel-case-json-message-contracts.md) | Camel-Case JSON Message Contracts | Accepted |
| [0013](0013-aggregates-define-consistency-boundaries.md) | Aggregates Define Consistency Boundaries | Accepted |
| [0014](0014-cqrs-read-models-are-purpose-built.md) | CQRS Read Models Are Purpose Built | Accepted |
+| [0015](0015-copier-template-engine.md) | Copier as the Template Engine | Accepted |
| [0016](0016-aggregate-persistence-write-back.md) | Aggregate Persistence Write-Back on Commit | Accepted |
## Agent Checklist
diff --git a/docs/adr/template.md b/template/docs/adr/template.md
similarity index 100%
rename from docs/adr/template.md
rename to template/docs/adr/template.md
diff --git a/docs/cosmic-python-coverage.md b/template/docs/cosmic-python-coverage.md
similarity index 100%
rename from docs/cosmic-python-coverage.md
rename to template/docs/cosmic-python-coverage.md
diff --git a/docs/cosmic_fastapi.png b/template/docs/cosmic_fastapi.png
similarity index 100%
rename from docs/cosmic_fastapi.png
rename to template/docs/cosmic_fastapi.png
diff --git a/migrations/README b/template/migrations/README
similarity index 100%
rename from migrations/README
rename to template/migrations/README
diff --git a/migrations/env.py b/template/migrations/env.py.jinja
similarity index 87%
rename from migrations/env.py
rename to template/migrations/env.py.jinja
index 1240032..252ba2e 100644
--- a/migrations/env.py
+++ b/template/migrations/env.py.jinja
@@ -5,9 +5,9 @@
from alembic import context
from sqlalchemy import engine_from_config, pool
-from template.adapters.models.base import Base
-from template.adapters.models.user import UserRecord # noqa: F401
-from template.settings.database_settings import DatabaseSettings
+from {{ package_name }}.adapters.models.base import Base
+from {{ package_name }}.adapters.models.user import UserRecord # noqa: F401
+from {{ package_name }}.settings.database_settings import DatabaseSettings
config = context.config
if config.config_file_name is not None:
diff --git a/migrations/script.py.mako b/template/migrations/script.py.mako
similarity index 100%
rename from migrations/script.py.mako
rename to template/migrations/script.py.mako
diff --git a/migrations/versions/20260531_0001_create_users.py b/template/migrations/versions/20260531_0001_create_users.py
similarity index 100%
rename from migrations/versions/20260531_0001_create_users.py
rename to template/migrations/versions/20260531_0001_create_users.py
diff --git a/template/pyproject.toml.jinja b/template/pyproject.toml.jinja
new file mode 100644
index 0000000..7990161
--- /dev/null
+++ b/template/pyproject.toml.jinja
@@ -0,0 +1,103 @@
+[project]
+name = "{{ project_slug }}"
+version = "0.1.0"
+description = "{{ project_description }}"
+authors = [{ name = "{{ author_name }}", email = "{{ author_email }}" }]
+readme = "README.md"
+requires-python = ">={{ python_version }}"
+license = "{{ license }}"
+dependencies = [
+ "fastapi>=0.115.0",
+ "uvicorn>=0.34.0",
+ "pydantic-settings>=2.9.0",
+ "pydantic[email]>=2.11.0",
+ "sqlalchemy>=2.0.41",
+ "alembic>=1.16.2",
+]
+
+[dependency-groups]
+dev = [
+ "pytest>=8.4.0",
+ "coverage[toml]>=7.9.0",
+ "pre-commit>=4.2.0",
+ "httpx>=0.28.0",
+ "pytest-cov>=6.2.0",
+ "ruff>=0.12,<0.13",
+ "pyrefly>=1.0.0",
+]
+
+[build-system]
+requires = ["hatchling"]
+build-backend = "hatchling.build"
+
+[tool.ruff]
+line-length = 120
+
+[tool.ruff.lint]
+select = [
+ # flake8-2020
+ "YTT",
+ # flake8-bandit
+ "S",
+ # flake8-bugbear
+ "B",
+ # flake8-builtins
+ "A",
+ # flake8-comprehensions
+ "C4",
+ # flake8-debugger
+ "T10",
+ # flake8-simplify
+ "SIM",
+ # isort
+ "I",
+ # mccabe
+ "C90",
+ # pycodestyle
+ "E", "W",
+ # pyflakes
+ "F",
+ # pygrep-hooks
+ "PGH",
+ # pyupgrade
+ "UP",
+ # ruff
+ "RUF",
+ # tryceratops
+ "TRY",
+]
+# Disable rules that are too noisy for this scaffold.
+ignore = [
+ # LineTooLong
+ "E501",
+ # DoNotAssignLambda
+ "E731",
+]
+
+[tool.ruff.lint.per-file-ignores]
+"tests/*" = ["S101"]
+
+[tool.ruff.lint.isort]
+known-first-party = ["{{ package_name }}"]
+
+[tool.coverage.run]
+branch = true
+omit = ['tests/*', 'src/{{ package_name }}/asgi.py', 'src/{{ package_name }}/main.py', 'src/**/__init__.py']
+
+[tool.coverage.report]
+show_missing = true
+ignore_errors = true
+exclude_lines = ["pragma: no cover", "raise NotImplementedError", "if TYPE_CHECKING:", "if __name__ == .__main__.:"]
+
+
+[tool.pytest.ini_options]
+pythonpath = ["src"]
+testpaths = ["tests"]
+python_files = "test_*.py"
+junit_family = "xunit1"
+log_cli = false
+log_level = "DEBUG"
+
+[tool.pyrefly]
+project-includes = ["src", "scripts", "tests", "migrations"]
+search-path = [".", "src"]
diff --git a/scripts/__init__.py b/template/scripts/__init__.py
similarity index 100%
rename from scripts/__init__.py
rename to template/scripts/__init__.py
diff --git a/scripts/prune_decisions.py b/template/scripts/prune_decisions.py
similarity index 100%
rename from scripts/prune_decisions.py
rename to template/scripts/prune_decisions.py
diff --git a/src/__init__.py b/template/src/__init__.py
similarity index 100%
rename from src/__init__.py
rename to template/src/__init__.py
diff --git a/src/template/__init__.py b/template/src/{{ package_name }}/__init__.py
similarity index 100%
rename from src/template/__init__.py
rename to template/src/{{ package_name }}/__init__.py
diff --git a/src/template/adapters/__init__.py b/template/src/{{ package_name }}/adapters/__init__.py
similarity index 100%
rename from src/template/adapters/__init__.py
rename to template/src/{{ package_name }}/adapters/__init__.py
diff --git a/src/template/adapters/models/__init__.py b/template/src/{{ package_name }}/adapters/models/__init__.py
similarity index 100%
rename from src/template/adapters/models/__init__.py
rename to template/src/{{ package_name }}/adapters/models/__init__.py
diff --git a/src/template/adapters/models/base.py b/template/src/{{ package_name }}/adapters/models/base.py
similarity index 100%
rename from src/template/adapters/models/base.py
rename to template/src/{{ package_name }}/adapters/models/base.py
diff --git a/src/template/adapters/models/user.py b/template/src/{{ package_name }}/adapters/models/user.py.jinja
similarity index 92%
rename from src/template/adapters/models/user.py
rename to template/src/{{ package_name }}/adapters/models/user.py.jinja
index 6941d4a..44c8db3 100644
--- a/src/template/adapters/models/user.py
+++ b/template/src/{{ package_name }}/adapters/models/user.py.jinja
@@ -6,7 +6,7 @@
from sqlalchemy import JSON, DateTime
from sqlalchemy.orm import Mapped, mapped_column
-from template.adapters.models.base import Base
+from {{ package_name }}.adapters.models.base import Base
class UserRecord(Base):
diff --git a/src/template/adapters/queries.py b/template/src/{{ package_name }}/adapters/queries.py.jinja
similarity index 83%
rename from src/template/adapters/queries.py
rename to template/src/{{ package_name }}/adapters/queries.py.jinja
index 9f83f5d..64e9346 100644
--- a/src/template/adapters/queries.py
+++ b/template/src/{{ package_name }}/adapters/queries.py.jinja
@@ -4,9 +4,9 @@
from sqlalchemy.orm import Session, sessionmaker
-from template.adapters.models.user import UserRecord
-from template.domain.models.user import UserSettings
-from template.service_layer.read_models import UserReadModel
+from {{ package_name }}.adapters.models.user import UserRecord
+from {{ package_name }}.domain.models.user import UserSettings
+from {{ package_name }}.service_layer.read_models import UserReadModel
class SqlAlchemyUserReader:
diff --git a/src/template/adapters/repository.py b/template/src/{{ package_name }}/adapters/repository.py.jinja
similarity index 96%
rename from src/template/adapters/repository.py
rename to template/src/{{ package_name }}/adapters/repository.py.jinja
index 05dd81d..71712d7 100644
--- a/src/template/adapters/repository.py
+++ b/template/src/{{ package_name }}/adapters/repository.py.jinja
@@ -8,8 +8,8 @@
from sqlalchemy import select
from sqlalchemy.orm import Session
-from template.adapters.models.user import UserRecord
-from template.domain.models.user import User, UserSettings
+from {{ package_name }}.adapters.models.user import UserRecord
+from {{ package_name }}.domain.models.user import User, UserSettings
class SqlAlchemyUserRepository:
diff --git a/src/template/adapters/unit_of_work.py b/template/src/{{ package_name }}/adapters/unit_of_work.py.jinja
similarity index 89%
rename from src/template/adapters/unit_of_work.py
rename to template/src/{{ package_name }}/adapters/unit_of_work.py.jinja
index b42f5cd..9b0f46f 100644
--- a/src/template/adapters/unit_of_work.py
+++ b/template/src/{{ package_name }}/adapters/unit_of_work.py.jinja
@@ -7,8 +7,8 @@
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import Session, sessionmaker
-from template.adapters.repository import SqlAlchemyUserRepository
-from template.service_layer.unit_of_work import AbstractUnitOfWork, IntegrityConflict
+from {{ package_name }}.adapters.repository import SqlAlchemyUserRepository
+from {{ package_name }}.service_layer.unit_of_work import AbstractUnitOfWork, IntegrityConflict
class SqlAlchemyUnitOfWork(AbstractUnitOfWork):
diff --git a/src/template/asgi.py b/template/src/{{ package_name }}/asgi.py.jinja
similarity index 92%
rename from src/template/asgi.py
rename to template/src/{{ package_name }}/asgi.py.jinja
index 28a1557..61937f4 100644
--- a/src/template/asgi.py
+++ b/template/src/{{ package_name }}/asgi.py.jinja
@@ -6,9 +6,9 @@
from fastapi import FastAPI
from starlette.middleware.cors import CORSMiddleware
-from template.bootstrap import ApplicationContainer, bootstrap
-from template.router import api_router_v1, root_router
-from template.settings.api_settings import ApplicationSettings
+from {{ package_name }}.bootstrap import ApplicationContainer, bootstrap
+from {{ package_name }}.router import api_router_v1, root_router
+from {{ package_name }}.settings.api_settings import ApplicationSettings
log = logging.getLogger(__name__)
diff --git a/src/template/bootstrap.py b/template/src/{{ package_name }}/bootstrap.py.jinja
similarity index 81%
rename from src/template/bootstrap.py
rename to template/src/{{ package_name }}/bootstrap.py.jinja
index 9a153d9..6b4a45f 100644
--- a/src/template/bootstrap.py
+++ b/template/src/{{ package_name }}/bootstrap.py.jinja
@@ -11,16 +11,16 @@
from sqlalchemy.orm import Session, sessionmaker
from sqlalchemy.pool import StaticPool
-from template.adapters.models.base import Base
-from template.adapters.queries import SqlAlchemyUserReader
-from template.adapters.unit_of_work import SqlAlchemyUnitOfWork
-from template.domain.commands.user import DeactivateUser, RegisterUser
-from template.domain.events.user import UserDeactivated, UserRegistered
-from template.service_layer import handlers
-from template.service_layer.messagebus import MessageBus
-from template.service_layer.queries import UserReader
-from template.service_layer.unit_of_work import AbstractUnitOfWork
-from template.settings.database_settings import DatabaseSettings
+from {{ package_name }}.adapters.models.base import Base
+from {{ package_name }}.adapters.queries import SqlAlchemyUserReader
+from {{ package_name }}.adapters.unit_of_work import SqlAlchemyUnitOfWork
+from {{ package_name }}.domain.commands.user import DeactivateUser, RegisterUser
+from {{ package_name }}.domain.events.user import UserDeactivated, UserRegistered
+from {{ package_name }}.service_layer import handlers
+from {{ package_name }}.service_layer.messagebus import MessageBus
+from {{ package_name }}.service_layer.queries import UserReader
+from {{ package_name }}.service_layer.unit_of_work import AbstractUnitOfWork
+from {{ package_name }}.settings.database_settings import DatabaseSettings
def _ignore_user_registered(event: UserRegistered) -> None:
diff --git a/src/template/domain/__init__.py b/template/src/{{ package_name }}/domain/__init__.py
similarity index 100%
rename from src/template/domain/__init__.py
rename to template/src/{{ package_name }}/domain/__init__.py
diff --git a/src/template/domain/commands/__init__.py b/template/src/{{ package_name }}/domain/commands/__init__.py
similarity index 100%
rename from src/template/domain/commands/__init__.py
rename to template/src/{{ package_name }}/domain/commands/__init__.py
diff --git a/src/template/domain/commands/user.py b/template/src/{{ package_name }}/domain/commands/user.py.jinja
similarity index 90%
rename from src/template/domain/commands/user.py
rename to template/src/{{ package_name }}/domain/commands/user.py.jinja
index a39647f..1343772 100644
--- a/src/template/domain/commands/user.py
+++ b/template/src/{{ package_name }}/domain/commands/user.py.jinja
@@ -5,8 +5,8 @@
from pydantic import EmailStr, Field
-from template.domain.messages import Command
-from template.domain.models.user import UserSettings
+from {{ package_name }}.domain.messages import Command
+from {{ package_name }}.domain.models.user import UserSettings
class RegisterUserSettings(Command):
diff --git a/src/template/domain/events/__init__.py b/template/src/{{ package_name }}/domain/events/__init__.py
similarity index 100%
rename from src/template/domain/events/__init__.py
rename to template/src/{{ package_name }}/domain/events/__init__.py
diff --git a/src/template/domain/events/user.py b/template/src/{{ package_name }}/domain/events/user.py.jinja
similarity index 84%
rename from src/template/domain/events/user.py
rename to template/src/{{ package_name }}/domain/events/user.py.jinja
index 278aff1..a6dfe1d 100644
--- a/src/template/domain/events/user.py
+++ b/template/src/{{ package_name }}/domain/events/user.py.jinja
@@ -2,7 +2,7 @@
from uuid import UUID
-from template.domain.messages import Event
+from {{ package_name }}.domain.messages import Event
class UserRegistered(Event):
diff --git a/src/template/domain/messages.py b/template/src/{{ package_name }}/domain/messages.py
similarity index 100%
rename from src/template/domain/messages.py
rename to template/src/{{ package_name }}/domain/messages.py
diff --git a/src/template/domain/models/__init__.py b/template/src/{{ package_name }}/domain/models/__init__.py
similarity index 100%
rename from src/template/domain/models/__init__.py
rename to template/src/{{ package_name }}/domain/models/__init__.py
diff --git a/src/template/domain/models/user.py b/template/src/{{ package_name }}/domain/models/user.py.jinja
similarity index 93%
rename from src/template/domain/models/user.py
rename to template/src/{{ package_name }}/domain/models/user.py.jinja
index 38760c3..0cca6ef 100644
--- a/src/template/domain/models/user.py
+++ b/template/src/{{ package_name }}/domain/models/user.py.jinja
@@ -7,8 +7,8 @@
from typing import Literal
from uuid import UUID, uuid4
-from template.domain.events.user import UserDeactivated, UserRegistered
-from template.domain.messages import Event
+from {{ package_name }}.domain.events.user import UserDeactivated, UserRegistered
+from {{ package_name }}.domain.messages import Event
@dataclass(frozen=True)
diff --git a/src/template/entrypoint/__init__.py b/template/src/{{ package_name }}/entrypoint/__init__.py
similarity index 100%
rename from src/template/entrypoint/__init__.py
rename to template/src/{{ package_name }}/entrypoint/__init__.py
diff --git a/src/template/entrypoint/dependencies.py b/template/src/{{ package_name }}/entrypoint/dependencies.py.jinja
similarity index 89%
rename from src/template/entrypoint/dependencies.py
rename to template/src/{{ package_name }}/entrypoint/dependencies.py.jinja
index 46a1d24..1cc8b9d 100644
--- a/src/template/entrypoint/dependencies.py
+++ b/template/src/{{ package_name }}/entrypoint/dependencies.py.jinja
@@ -9,7 +9,7 @@
from fastapi import Depends, Request
-from template.bootstrap import ApplicationContainer
+from {{ package_name }}.bootstrap import ApplicationContainer
def get_container(request: Request) -> ApplicationContainer:
diff --git a/src/template/entrypoint/monitor.py b/template/src/{{ package_name }}/entrypoint/monitor.py.jinja
similarity index 93%
rename from src/template/entrypoint/monitor.py
rename to template/src/{{ package_name }}/entrypoint/monitor.py.jinja
index 402f1ad..cbceca3 100644
--- a/src/template/entrypoint/monitor.py
+++ b/template/src/{{ package_name }}/entrypoint/monitor.py.jinja
@@ -10,8 +10,8 @@
from sqlalchemy.exc import SQLAlchemyError
from starlette.responses import JSONResponse, RedirectResponse
-from template.entrypoint.dependencies import Container
-from template.entrypoint.schemas import LivenessProbed, ReadinessProbed, ResponseModel
+from {{ package_name }}.entrypoint.dependencies import Container
+from {{ package_name }}.entrypoint.schemas import LivenessProbed, ReadinessProbed, ResponseModel
log = logging.getLogger(__name__)
diff --git a/src/template/entrypoint/schemas.py b/template/src/{{ package_name }}/entrypoint/schemas.py
similarity index 100%
rename from src/template/entrypoint/schemas.py
rename to template/src/{{ package_name }}/entrypoint/schemas.py
diff --git a/src/template/entrypoint/users.py b/template/src/{{ package_name }}/entrypoint/users.py.jinja
similarity index 86%
rename from src/template/entrypoint/users.py
rename to template/src/{{ package_name }}/entrypoint/users.py.jinja
index 1eb3b47..a41d53c 100644
--- a/src/template/entrypoint/users.py
+++ b/template/src/{{ package_name }}/entrypoint/users.py.jinja
@@ -6,12 +6,12 @@
from fastapi import APIRouter, HTTPException, status
from pydantic import EmailStr, Field
-from template.domain.commands.user import RegisterUser
-from template.entrypoint.dependencies import Container
-from template.entrypoint.schemas import CamelCaseModel, ResponseModel
-from template.service_layer.handlers import EmailAlreadyRegistered
-from template.service_layer.queries import get_user
-from template.service_layer.read_models import UserReadModel
+from {{ package_name }}.domain.commands.user import RegisterUser
+from {{ package_name }}.entrypoint.dependencies import Container
+from {{ package_name }}.entrypoint.schemas import CamelCaseModel, ResponseModel
+from {{ package_name }}.service_layer.handlers import EmailAlreadyRegistered
+from {{ package_name }}.service_layer.queries import get_user
+from {{ package_name }}.service_layer.read_models import UserReadModel
router = APIRouter(prefix="/users", tags=["Users"])
diff --git a/src/template/main.py b/template/src/{{ package_name }}/main.py.jinja
similarity index 66%
rename from src/template/main.py
rename to template/src/{{ package_name }}/main.py.jinja
index f7633fc..0250e08 100644
--- a/src/template/main.py
+++ b/template/src/{{ package_name }}/main.py.jinja
@@ -4,7 +4,7 @@
from fastapi import FastAPI
-from template.asgi import get_application
+from {{ package_name }}.asgi import get_application
app: FastAPI = get_application()
@@ -12,12 +12,12 @@
if __name__ == "__main__":
import uvicorn
- from template.settings.uvicorn_settings import UvicornSettings
+ from {{ package_name }}.settings.uvicorn_settings import UvicornSettings
settings = UvicornSettings()
uvicorn.run(
- "template.main:app",
+ "{{ package_name }}.main:app",
host=str(settings.HOST),
port=settings.PORT,
log_level=settings.LOG_LEVEL,
diff --git a/src/template/router.py b/template/src/{{ package_name }}/router.py.jinja
similarity index 88%
rename from src/template/router.py
rename to template/src/{{ package_name }}/router.py.jinja
index 3da4d63..3fc4b05 100644
--- a/src/template/router.py
+++ b/template/src/{{ package_name }}/router.py.jinja
@@ -8,7 +8,7 @@
from fastapi import APIRouter
-from template.entrypoint import monitor, users
+from {{ package_name }}.entrypoint import monitor, users
api_v1_prefix: str = "/api/v1"
diff --git a/src/template/service_layer/__init__.py b/template/src/{{ package_name }}/service_layer/__init__.py
similarity index 100%
rename from src/template/service_layer/__init__.py
rename to template/src/{{ package_name }}/service_layer/__init__.py
diff --git a/src/template/service_layer/handlers.py b/template/src/{{ package_name }}/service_layer/handlers.py.jinja
similarity index 89%
rename from src/template/service_layer/handlers.py
rename to template/src/{{ package_name }}/service_layer/handlers.py.jinja
index fba04c9..4c522d9 100644
--- a/src/template/service_layer/handlers.py
+++ b/template/src/{{ package_name }}/service_layer/handlers.py.jinja
@@ -3,10 +3,10 @@
from collections.abc import Callable
from uuid import UUID
-from template.domain.commands.user import DeactivateUser, RegisterUser
-from template.domain.events.user import UserDeactivated, UserRegistered
-from template.domain.models.user import User
-from template.service_layer.unit_of_work import AbstractUnitOfWork, IntegrityConflict
+from {{ package_name }}.domain.commands.user import DeactivateUser, RegisterUser
+from {{ package_name }}.domain.events.user import UserDeactivated, UserRegistered
+from {{ package_name }}.domain.models.user import User
+from {{ package_name }}.service_layer.unit_of_work import AbstractUnitOfWork, IntegrityConflict
class EmailAlreadyRegistered(ValueError):
diff --git a/src/template/service_layer/messagebus.py b/template/src/{{ package_name }}/service_layer/messagebus.py.jinja
similarity index 94%
rename from src/template/service_layer/messagebus.py
rename to template/src/{{ package_name }}/service_layer/messagebus.py.jinja
index 3724c8c..107dc8c 100644
--- a/src/template/service_layer/messagebus.py
+++ b/template/src/{{ package_name }}/service_layer/messagebus.py.jinja
@@ -6,8 +6,8 @@
from collections.abc import Callable
from typing import Any
-from template.domain.messages import Command, Event, Message
-from template.service_layer.unit_of_work import AbstractUnitOfWork
+from {{ package_name }}.domain.messages import Command, Event, Message
+from {{ package_name }}.service_layer.unit_of_work import AbstractUnitOfWork
log = logging.getLogger(__name__)
diff --git a/src/template/service_layer/queries.py b/template/src/{{ package_name }}/service_layer/queries.py.jinja
similarity index 86%
rename from src/template/service_layer/queries.py
rename to template/src/{{ package_name }}/service_layer/queries.py.jinja
index 98ab86e..6dc93ba 100644
--- a/src/template/service_layer/queries.py
+++ b/template/src/{{ package_name }}/service_layer/queries.py.jinja
@@ -3,7 +3,7 @@
from typing import Protocol
from uuid import UUID
-from template.service_layer.read_models import UserReadModel
+from {{ package_name }}.service_layer.read_models import UserReadModel
class UserReader(Protocol):
diff --git a/src/template/service_layer/read_models.py b/template/src/{{ package_name }}/service_layer/read_models.py.jinja
similarity index 82%
rename from src/template/service_layer/read_models.py
rename to template/src/{{ package_name }}/service_layer/read_models.py.jinja
index f3cb749..7f34c5f 100644
--- a/src/template/service_layer/read_models.py
+++ b/template/src/{{ package_name }}/service_layer/read_models.py.jinja
@@ -3,7 +3,7 @@
from dataclasses import dataclass
from uuid import UUID
-from template.domain.models.user import UserSettings
+from {{ package_name }}.domain.models.user import UserSettings
@dataclass(frozen=True)
diff --git a/src/template/service_layer/repository.py b/template/src/{{ package_name }}/service_layer/repository.py.jinja
similarity index 92%
rename from src/template/service_layer/repository.py
rename to template/src/{{ package_name }}/service_layer/repository.py.jinja
index 221b658..82d72b8 100644
--- a/src/template/service_layer/repository.py
+++ b/template/src/{{ package_name }}/service_layer/repository.py.jinja
@@ -3,7 +3,7 @@
from typing import Protocol
from uuid import UUID
-from template.domain.models.user import User
+from {{ package_name }}.domain.models.user import User
class UserRepository(Protocol):
diff --git a/src/template/service_layer/unit_of_work.py b/template/src/{{ package_name }}/service_layer/unit_of_work.py.jinja
similarity index 92%
rename from src/template/service_layer/unit_of_work.py
rename to template/src/{{ package_name }}/service_layer/unit_of_work.py.jinja
index f912bfd..2f54f3c 100644
--- a/src/template/service_layer/unit_of_work.py
+++ b/template/src/{{ package_name }}/service_layer/unit_of_work.py.jinja
@@ -6,8 +6,8 @@
from collections.abc import Iterator
from types import TracebackType
-from template.domain.messages import Event
-from template.service_layer.repository import UserRepository
+from {{ package_name }}.domain.messages import Event
+from {{ package_name }}.service_layer.repository import UserRepository
class IntegrityConflict(RuntimeError):
diff --git a/src/template/settings/__init__.py b/template/src/{{ package_name }}/settings/__init__.py
similarity index 100%
rename from src/template/settings/__init__.py
rename to template/src/{{ package_name }}/settings/__init__.py
diff --git a/src/template/settings/api_settings.py b/template/src/{{ package_name }}/settings/api_settings.py.jinja
similarity index 87%
rename from src/template/settings/api_settings.py
rename to template/src/{{ package_name }}/settings/api_settings.py.jinja
index 154b0fb..ac0d8d8 100644
--- a/src/template/settings/api_settings.py
+++ b/template/src/{{ package_name }}/settings/api_settings.py.jinja
@@ -5,7 +5,7 @@
from pydantic import BaseModel, EmailStr, HttpUrl
from pydantic_settings import BaseSettings, SettingsConfigDict
-from template.version import __version__
+from {{ package_name }}.version import __version__
class LicenseInfo(BaseModel):
@@ -68,11 +68,11 @@ class ApplicationSettings(BaseSettings):
"""
DEBUG: bool = False
- PROJECT_NAME: str = "Cosmic FastAPI"
- PROJECT_DESCRIPTION: str = "This is a FastAPI template demo."
- PROJECT_LICENSE: LicenseInfo | None = LicenseInfo(name="MIT", url="https://mit-license.org/")
+ PROJECT_NAME: str = "{{ project_name }}"
+ PROJECT_DESCRIPTION: str = "{{ project_description }}"
+ PROJECT_LICENSE: LicenseInfo | None = LicenseInfo(name="{{ license }}", url="https://mit-license.org/")
PROJECT_CONTACT: ContactInfo | None = ContactInfo(
- name="Tomas Sanchez", url="https://tomsanchez.com.ar", email="info@tomsanchez.com.ar"
+ name="{{ author_name }}", url="{{ author_url }}", email="{{ author_email }}"
)
VERSION: str = __version__
DOCS_URL: str = "/docs"
diff --git a/src/template/settings/database_settings.py b/template/src/{{ package_name }}/settings/database_settings.py.jinja
similarity index 84%
rename from src/template/settings/database_settings.py
rename to template/src/{{ package_name }}/settings/database_settings.py.jinja
index ead218a..a15c107 100644
--- a/src/template/settings/database_settings.py
+++ b/template/src/{{ package_name }}/settings/database_settings.py.jinja
@@ -6,7 +6,7 @@
class DatabaseSettings(BaseSettings):
"""Configure relational persistence."""
- URL: str = "sqlite+pysqlite:///./cosmic-fastapi.db"
+ URL: str = "{{ database_url }}"
AUTO_CREATE_SCHEMA: bool = False
model_config = SettingsConfigDict(case_sensitive=True, env_prefix="DATABASE_")
diff --git a/src/template/settings/uvicorn_settings.py b/template/src/{{ package_name }}/settings/uvicorn_settings.py
similarity index 100%
rename from src/template/settings/uvicorn_settings.py
rename to template/src/{{ package_name }}/settings/uvicorn_settings.py
diff --git a/src/template/version.py b/template/src/{{ package_name }}/version.py.jinja
similarity index 85%
rename from src/template/version.py
rename to template/src/{{ package_name }}/version.py.jinja
index 3fac7c6..b768fbd 100644
--- a/src/template/version.py
+++ b/template/src/{{ package_name }}/version.py.jinja
@@ -5,7 +5,7 @@
from importlib.metadata import PackageNotFoundError, version
try:
- __version__: str = version("template")
+ __version__: str = version("{{ package_name }}")
except PackageNotFoundError: # pragma: no cover
# Fallback when the package metadata is unavailable (e.g. running from a
# source tree without an installed distribution).
diff --git a/tests/__init__.py b/template/tests/__init__.py
similarity index 100%
rename from tests/__init__.py
rename to template/tests/__init__.py
diff --git a/tests/conftest.py b/template/tests/conftest.py.jinja
similarity index 82%
rename from tests/conftest.py
rename to template/tests/conftest.py.jinja
index 6ea25dc..4b3bf91 100644
--- a/tests/conftest.py
+++ b/template/tests/conftest.py.jinja
@@ -7,9 +7,9 @@
import pytest
from starlette.testclient import TestClient
-from template.asgi import get_application
-from template.bootstrap import bootstrap
-from template.settings.database_settings import DatabaseSettings
+from {{ package_name }}.asgi import get_application
+from {{ package_name }}.bootstrap import bootstrap
+from {{ package_name }}.settings.database_settings import DatabaseSettings
@pytest.fixture(name="test_client")
diff --git a/tests/e2e/__init__.py b/template/tests/e2e/__init__.py
similarity index 100%
rename from tests/e2e/__init__.py
rename to template/tests/e2e/__init__.py
diff --git a/tests/e2e/entrypoint/__init__.py b/template/tests/e2e/entrypoint/__init__.py
similarity index 100%
rename from tests/e2e/entrypoint/__init__.py
rename to template/tests/e2e/entrypoint/__init__.py
diff --git a/tests/e2e/entrypoint/test_monitor.py b/template/tests/e2e/entrypoint/test_monitor.py.jinja
similarity index 92%
rename from tests/e2e/entrypoint/test_monitor.py
rename to template/tests/e2e/entrypoint/test_monitor.py.jinja
index 6f02535..c867a2e 100644
--- a/tests/e2e/entrypoint/test_monitor.py
+++ b/template/tests/e2e/entrypoint/test_monitor.py.jinja
@@ -7,10 +7,10 @@
from sqlalchemy.exc import SQLAlchemyError
from starlette.testclient import TestClient
-from template.asgi import get_application
-from template.bootstrap import bootstrap
-from template.entrypoint.schemas import LivenessProbed, ReadinessProbed, ResponseModel
-from template.settings.database_settings import DatabaseSettings
+from {{ package_name }}.asgi import get_application
+from {{ package_name }}.bootstrap import bootstrap
+from {{ package_name }}.entrypoint.schemas import LivenessProbed, ReadinessProbed, ResponseModel
+from {{ package_name }}.settings.database_settings import DatabaseSettings
class TestMonitorEntryPoint:
diff --git a/tests/e2e/entrypoint/test_users.py b/template/tests/e2e/entrypoint/test_users.py.jinja
similarity index 91%
rename from tests/e2e/entrypoint/test_users.py
rename to template/tests/e2e/entrypoint/test_users.py.jinja
index 3ea74ef..cea1c0f 100644
--- a/tests/e2e/entrypoint/test_users.py
+++ b/template/tests/e2e/entrypoint/test_users.py.jinja
@@ -6,10 +6,10 @@
from fastapi import status
from starlette.testclient import TestClient
-from template.asgi import get_application
-from template.bootstrap import bootstrap
-from template.domain.events.user import UserRegistered
-from template.settings.database_settings import DatabaseSettings
+from {{ package_name }}.asgi import get_application
+from {{ package_name }}.bootstrap import bootstrap
+from {{ package_name }}.domain.events.user import UserRegistered
+from {{ package_name }}.settings.database_settings import DatabaseSettings
@pytest.fixture(name="user_client")
diff --git a/tests/integration/__init__.py b/template/tests/integration/__init__.py
similarity index 100%
rename from tests/integration/__init__.py
rename to template/tests/integration/__init__.py
diff --git a/tests/integration/test_migrations.py b/template/tests/integration/test_migrations.py
similarity index 100%
rename from tests/integration/test_migrations.py
rename to template/tests/integration/test_migrations.py
diff --git a/tests/integration/test_persistence.py b/template/tests/integration/test_persistence.py.jinja
similarity index 94%
rename from tests/integration/test_persistence.py
rename to template/tests/integration/test_persistence.py.jinja
index 26d2298..036c04b 100644
--- a/tests/integration/test_persistence.py
+++ b/template/tests/integration/test_persistence.py.jinja
@@ -9,13 +9,13 @@
from sqlalchemy.orm import Session, sessionmaker
from sqlalchemy.pool import StaticPool
-from template.adapters.models.base import Base
-from template.adapters.models.user import UserRecord
-from template.adapters.queries import SqlAlchemyUserReader
-from template.adapters.repository import SqlAlchemyUserRepository
-from template.adapters.unit_of_work import SqlAlchemyUnitOfWork
-from template.domain.models.user import User
-from template.service_layer.unit_of_work import IntegrityConflict
+from {{ package_name }}.adapters.models.base import Base
+from {{ package_name }}.adapters.models.user import UserRecord
+from {{ package_name }}.adapters.queries import SqlAlchemyUserReader
+from {{ package_name }}.adapters.repository import SqlAlchemyUserRepository
+from {{ package_name }}.adapters.unit_of_work import SqlAlchemyUnitOfWork
+from {{ package_name }}.domain.models.user import User
+from {{ package_name }}.service_layer.unit_of_work import IntegrityConflict
@pytest.fixture(name="session_factory")
diff --git a/tests/unit/__init__.py b/template/tests/unit/__init__.py
similarity index 100%
rename from tests/unit/__init__.py
rename to template/tests/unit/__init__.py
diff --git a/tests/unit/domain/__init__.py b/template/tests/unit/domain/__init__.py
similarity index 100%
rename from tests/unit/domain/__init__.py
rename to template/tests/unit/domain/__init__.py
diff --git a/tests/unit/domain/models/__init__.py b/template/tests/unit/domain/models/__init__.py
similarity index 100%
rename from tests/unit/domain/models/__init__.py
rename to template/tests/unit/domain/models/__init__.py
diff --git a/tests/unit/domain/models/test_user.py b/template/tests/unit/domain/models/test_user.py.jinja
similarity index 86%
rename from tests/unit/domain/models/test_user.py
rename to template/tests/unit/domain/models/test_user.py.jinja
index 31b97aa..3f24188 100644
--- a/tests/unit/domain/models/test_user.py
+++ b/template/tests/unit/domain/models/test_user.py.jinja
@@ -2,8 +2,8 @@
from uuid import uuid4
-from template.domain.events.user import UserRegistered
-from template.domain.models.user import User
+from {{ package_name }}.domain.events.user import UserRegistered
+from {{ package_name }}.domain.models.user import User
class TestUser:
diff --git a/tests/unit/domain/test_messages.py b/template/tests/unit/domain/test_messages.py.jinja
similarity index 95%
rename from tests/unit/domain/test_messages.py
rename to template/tests/unit/domain/test_messages.py.jinja
index cabbcd5..77dafc7 100644
--- a/tests/unit/domain/test_messages.py
+++ b/template/tests/unit/domain/test_messages.py.jinja
@@ -6,8 +6,8 @@
import pytest
from pydantic import ValidationError
-from template.domain.commands.user import RegisterUser
-from template.domain.events.user import UserRegistered
+from {{ package_name }}.domain.commands.user import RegisterUser
+from {{ package_name }}.domain.events.user import UserRegistered
class TestMessages:
diff --git a/tests/unit/entrypoint/__init__.py b/template/tests/unit/entrypoint/__init__.py
similarity index 100%
rename from tests/unit/entrypoint/__init__.py
rename to template/tests/unit/entrypoint/__init__.py
diff --git a/tests/unit/entrypoint/test_dependencies.py b/template/tests/unit/entrypoint/test_dependencies.py.jinja
similarity index 91%
rename from tests/unit/entrypoint/test_dependencies.py
rename to template/tests/unit/entrypoint/test_dependencies.py.jinja
index c0a9854..75e688b 100644
--- a/tests/unit/entrypoint/test_dependencies.py
+++ b/template/tests/unit/entrypoint/test_dependencies.py.jinja
@@ -2,7 +2,7 @@
from types import SimpleNamespace
-from template.entrypoint.dependencies import get_container
+from {{ package_name }}.entrypoint.dependencies import get_container
class TestGetContainer:
diff --git a/tests/unit/entrypoint/test_schemas.py b/template/tests/unit/entrypoint/test_schemas.py.jinja
similarity index 96%
rename from tests/unit/entrypoint/test_schemas.py
rename to template/tests/unit/entrypoint/test_schemas.py.jinja
index c4ea037..3d33f6f 100644
--- a/tests/unit/entrypoint/test_schemas.py
+++ b/template/tests/unit/entrypoint/test_schemas.py.jinja
@@ -1,6 +1,6 @@
"""Test suite for boundary schemas."""
-from template.entrypoint.schemas import CamelCaseModel, ResponseModel
+from {{ package_name }}.entrypoint.schemas import CamelCaseModel, ResponseModel
class TestSchemas:
diff --git a/tests/unit/scripts/__init__.py b/template/tests/unit/scripts/__init__.py
similarity index 100%
rename from tests/unit/scripts/__init__.py
rename to template/tests/unit/scripts/__init__.py
diff --git a/tests/unit/scripts/test_prune_decisions.py b/template/tests/unit/scripts/test_prune_decisions.py
similarity index 100%
rename from tests/unit/scripts/test_prune_decisions.py
rename to template/tests/unit/scripts/test_prune_decisions.py
diff --git a/tests/unit/service_layer/__init__.py b/template/tests/unit/service_layer/__init__.py
similarity index 100%
rename from tests/unit/service_layer/__init__.py
rename to template/tests/unit/service_layer/__init__.py
diff --git a/tests/unit/service_layer/test_handlers.py b/template/tests/unit/service_layer/test_handlers.py.jinja
similarity index 94%
rename from tests/unit/service_layer/test_handlers.py
rename to template/tests/unit/service_layer/test_handlers.py.jinja
index a9a1f0f..5e9bb5b 100644
--- a/tests/unit/service_layer/test_handlers.py
+++ b/template/tests/unit/service_layer/test_handlers.py.jinja
@@ -4,10 +4,10 @@
import pytest
-from template.domain.commands.user import DeactivateUser, RegisterUser
-from template.domain.events.user import UserDeactivated, UserRegistered
-from template.domain.models.user import User
-from template.service_layer.handlers import (
+from {{ package_name }}.domain.commands.user import DeactivateUser, RegisterUser
+from {{ package_name }}.domain.events.user import UserDeactivated, UserRegistered
+from {{ package_name }}.domain.models.user import User
+from {{ package_name }}.service_layer.handlers import (
EmailAlreadyRegistered,
UserNotFound,
deactivate_user,
@@ -15,7 +15,7 @@
publish_user_registered,
register_user,
)
-from template.service_layer.unit_of_work import AbstractUnitOfWork, IntegrityConflict
+from {{ package_name }}.service_layer.unit_of_work import AbstractUnitOfWork, IntegrityConflict
class FakeUserRepository:
diff --git a/tests/unit/service_layer/test_messagebus.py b/template/tests/unit/service_layer/test_messagebus.py.jinja
similarity index 87%
rename from tests/unit/service_layer/test_messagebus.py
rename to template/tests/unit/service_layer/test_messagebus.py.jinja
index b3e7ca3..43d025b 100644
--- a/tests/unit/service_layer/test_messagebus.py
+++ b/template/tests/unit/service_layer/test_messagebus.py.jinja
@@ -6,12 +6,12 @@
import pytest
-from template.domain.commands.user import DeactivateUser, RegisterUser
-from template.domain.events.user import UserRegistered
-from template.domain.messages import Message
-from template.service_layer.handlers import publish_user_registered, register_user
-from template.service_layer.messagebus import MessageBus, UnhandledCommand
-from template.service_layer.unit_of_work import AbstractUnitOfWork
+from {{ package_name }}.domain.commands.user import DeactivateUser, RegisterUser
+from {{ package_name }}.domain.events.user import UserRegistered
+from {{ package_name }}.domain.messages import Message
+from {{ package_name }}.service_layer.handlers import publish_user_registered, register_user
+from {{ package_name }}.service_layer.messagebus import MessageBus, UnhandledCommand
+from {{ package_name }}.service_layer.unit_of_work import AbstractUnitOfWork
from tests.unit.service_layer.test_handlers import FakeUnitOfWork
@@ -59,7 +59,7 @@ def fail_to_publish(event: UserRegistered) -> None:
)
# WHEN
- with patch("template.service_layer.messagebus.log.exception") as log_exception:
+ with patch("{{ package_name }}.service_layer.messagebus.log.exception") as log_exception:
user_id = bus.handle(RegisterUser(name="Ada Lovelace", email="ada@example.com"))
# THEN
diff --git a/tests/unit/settings/__init__.py b/template/tests/unit/settings/__init__.py
similarity index 100%
rename from tests/unit/settings/__init__.py
rename to template/tests/unit/settings/__init__.py
diff --git a/tests/unit/settings/test_api_settings.py b/template/tests/unit/settings/test_api_settings.py.jinja
similarity index 81%
rename from tests/unit/settings/test_api_settings.py
rename to template/tests/unit/settings/test_api_settings.py.jinja
index cf50629..26b7cf5 100644
--- a/tests/unit/settings/test_api_settings.py
+++ b/template/tests/unit/settings/test_api_settings.py.jinja
@@ -4,8 +4,8 @@
import pytest
-from template.settings.api_settings import ApplicationSettings, ContactInfo, LicenseInfo
-from template.version import __version__
+from {{ package_name }}.settings.api_settings import ApplicationSettings, ContactInfo, LicenseInfo
+from {{ package_name }}.version import __version__
class TestAPISettings:
@@ -42,11 +42,11 @@ def test_api_default_values(self):
settings = ApplicationSettings()
assert settings.DEBUG is False
- assert settings.PROJECT_NAME == "Cosmic FastAPI"
- assert settings.PROJECT_DESCRIPTION
+ assert settings.PROJECT_NAME == "{{ project_name }}"
+ assert settings.PROJECT_DESCRIPTION == "{{ project_description }}"
assert isinstance(settings.PROJECT_LICENSE, LicenseInfo)
assert isinstance(settings.PROJECT_CONTACT, ContactInfo)
- assert settings.PROJECT_CONTACT.name == "Tomas Sanchez"
+ assert settings.PROJECT_CONTACT.name == "{{ author_name }}"
assert __version__ == settings.VERSION
def test_cors_origins_default_to_safe_allow_list(self):
diff --git a/tests/unit/settings/test_uvicorn_settings.py b/template/tests/unit/settings/test_uvicorn_settings.py.jinja
similarity index 83%
rename from tests/unit/settings/test_uvicorn_settings.py
rename to template/tests/unit/settings/test_uvicorn_settings.py.jinja
index e3c44d9..173e17b 100644
--- a/tests/unit/settings/test_uvicorn_settings.py
+++ b/template/tests/unit/settings/test_uvicorn_settings.py.jinja
@@ -2,7 +2,7 @@
Uvicorn settings tests.
"""
-from template.settings.uvicorn_settings import UvicornSettings
+from {{ package_name }}.settings.uvicorn_settings import UvicornSettings
class TestUvicornSettings:
diff --git a/tests/unit/test_asgi.py b/template/tests/unit/test_asgi.py.jinja
similarity index 92%
rename from tests/unit/test_asgi.py
rename to template/tests/unit/test_asgi.py.jinja
index ce23920..9399c40 100644
--- a/tests/unit/test_asgi.py
+++ b/template/tests/unit/test_asgi.py.jinja
@@ -5,9 +5,9 @@
from fastapi.testclient import TestClient
from starlette.middleware.cors import CORSMiddleware
-from template.asgi import get_application
-from template.bootstrap import bootstrap
-from template.settings.database_settings import DatabaseSettings
+from {{ package_name }}.asgi import get_application
+from {{ package_name }}.bootstrap import bootstrap
+from {{ package_name }}.settings.database_settings import DatabaseSettings
class TestASGI:
diff --git a/tests/unit/test_bootstrap.py b/template/tests/unit/test_bootstrap.py.jinja
similarity index 90%
rename from tests/unit/test_bootstrap.py
rename to template/tests/unit/test_bootstrap.py.jinja
index 738d64c..6e4b5f6 100644
--- a/tests/unit/test_bootstrap.py
+++ b/template/tests/unit/test_bootstrap.py.jinja
@@ -1,7 +1,7 @@
"""Test suite for application composition."""
-from template.bootstrap import bootstrap
-from template.settings.database_settings import DatabaseSettings
+from {{ package_name }}.bootstrap import bootstrap
+from {{ package_name }}.settings.database_settings import DatabaseSettings
class TestBootstrap:
diff --git a/tests/test_bake.py b/tests/test_bake.py
new file mode 100644
index 0000000..a99cbf0
--- /dev/null
+++ b/tests/test_bake.py
@@ -0,0 +1,126 @@
+"""Bake-and-test suite for the Cosmic FastAPI Copier template.
+
+The template repository cannot import its own package in place once the package
+path contains Jinja. Instead we *bake* the template: render it to a temporary
+directory with a sample answer set and run the generated project's full quality
+gate (Ruff, Pyrefly, pytest at 100% coverage).
+"""
+
+from __future__ import annotations
+
+import shutil
+import subprocess
+import sys
+from pathlib import Path
+
+import copier
+import pytest
+
+TEMPLATE_ROOT = Path(__file__).resolve().parent.parent
+
+SAMPLE_ANSWERS: dict[str, object] = {
+ "project_name": "Demo Service",
+ "project_slug": "demo-service",
+ "package_name": "demo_service",
+ "project_description": "A demo service generated from the template.",
+ "author_name": "Ada Lovelace",
+ "author_email": "ada@example.com",
+ "author_url": "https://example.com",
+ "github_owner": "demo-org",
+ "license": "MIT",
+ "python_version": "3.13",
+ "database_url": "sqlite+pysqlite:///./demo-service.db",
+ "include_user_example": True,
+}
+
+
+def _run(args: list[str], cwd: Path) -> subprocess.CompletedProcess[str]:
+ """Run a command inside the baked project and capture its output."""
+ return subprocess.run(
+ args,
+ cwd=cwd,
+ capture_output=True,
+ text=True,
+ check=False,
+ )
+
+
+def _snapshot_working_tree(dst: Path) -> Path:
+ """Copy the git-tracked working tree into a git-free source directory.
+
+ Copier renders a committed ref when the source is a git repository, which
+ would ignore uncommitted template changes. Baking from a snapshot of the
+ tracked working tree validates the *current* template instead.
+ """
+ tracked = subprocess.run(
+ ["git", "ls-files", "-z"],
+ cwd=TEMPLATE_ROOT,
+ capture_output=True,
+ text=True,
+ check=True,
+ ).stdout.split("\0")
+ for rel in filter(None, tracked):
+ source = TEMPLATE_ROOT / rel
+ target = dst / rel
+ target.parent.mkdir(parents=True, exist_ok=True)
+ shutil.copy2(source, target)
+ return dst
+
+
+@pytest.fixture(scope="module")
+def baked_project(tmp_path_factory: pytest.TempPathFactory) -> Path:
+ """GIVEN the template, render it once with the sample answers.
+
+ `unsafe=True` is required so Copier executes the `_tasks` entry (`uv lock`).
+ """
+ src = _snapshot_working_tree(tmp_path_factory.mktemp("template-src"))
+ dst = tmp_path_factory.mktemp("baked")
+ copier.run_copy(
+ str(src),
+ str(dst),
+ data=SAMPLE_ANSWERS,
+ defaults=True,
+ unsafe=True,
+ quiet=True,
+ )
+ return dst
+
+
+def test_package_directory_is_rendered(baked_project: Path) -> None:
+ """WHEN the template is baked THEN the package dir uses the answer, not Jinja."""
+ assert (baked_project / "src" / "demo_service").is_dir()
+ assert not (baked_project / "src" / "{{ package_name }}").exists()
+ # No Jinja markers should survive in rendered files.
+ assert "{{" not in (baked_project / "pyproject.toml").read_text(encoding="utf-8")
+
+
+def test_no_template_package_references(baked_project: Path) -> None:
+ """WHEN baked THEN no stale `template.` package imports remain in src/tests."""
+ offenders: list[str] = []
+ for path in baked_project.rglob("*.py"):
+ text = path.read_text(encoding="utf-8")
+ if "from template" in text or "import template" in text:
+ offenders.append(str(path))
+ assert not offenders, f"stale template imports: {offenders}"
+
+
+def test_baked_project_passes_quality_gate(baked_project: Path) -> None:
+ """WHEN the baked project is synced THEN ruff, pyrefly and pytest all pass at 100%."""
+ sync = _run(["uv", "sync"], baked_project)
+ assert sync.returncode == 0, f"uv sync failed:\n{sync.stdout}\n{sync.stderr}"
+
+ ruff = _run(["uv", "run", "ruff", "check", "."], baked_project)
+ assert ruff.returncode == 0, f"ruff failed:\n{ruff.stdout}\n{ruff.stderr}"
+
+ pyrefly = _run(["uv", "run", "pyrefly", "check"], baked_project)
+ assert pyrefly.returncode == 0, f"pyrefly failed:\n{pyrefly.stdout}\n{pyrefly.stderr}"
+
+ tests = _run(
+ ["uv", "run", "pytest", "--cov", "src", "--cov-report=term-missing", "--cov-fail-under=100"],
+ baked_project,
+ )
+ assert tests.returncode == 0, f"pytest/coverage failed:\n{tests.stdout}\n{tests.stderr}"
+
+
+if __name__ == "__main__": # pragma: no cover
+ sys.exit(pytest.main([__file__, "-v"]))
diff --git a/uv.lock b/uv.lock
index ce56a2e..46ac69e 100644
--- a/uv.lock
+++ b/uv.lock
@@ -2,20 +2,6 @@ version = 1
revision = 2
requires-python = ">=3.13"
-[[package]]
-name = "alembic"
-version = "1.16.2"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "mako" },
- { name = "sqlalchemy" },
- { name = "typing-extensions" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/9c/35/116797ff14635e496bbda0c168987f5326a6555b09312e9b817e360d1f56/alembic-1.16.2.tar.gz", hash = "sha256:e53c38ff88dadb92eb22f8b150708367db731d58ad7e9d417c9168ab516cbed8", size = 1963563, upload-time = "2025-06-16T18:05:08.566Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/dd/e2/88e425adac5ad887a087c38d04fe2030010572a3e0e627f8a6e8c33eeda8/alembic-1.16.2-py3-none-any.whl", hash = "sha256:5f42e9bd0afdbd1d5e3ad856c01754530367debdebf21ed6894e34af52b3bb03", size = 242717, upload-time = "2025-06-16T18:05:10.27Z" },
-]
-
[[package]]
name = "annotated-types"
version = "0.7.0"
@@ -25,47 +11,13 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" },
]
-[[package]]
-name = "anyio"
-version = "4.9.0"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "idna" },
- { name = "sniffio" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949, upload-time = "2025-03-17T00:02:54.77Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916, upload-time = "2025-03-17T00:02:52.713Z" },
-]
-
-[[package]]
-name = "certifi"
-version = "2025.4.26"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/e8/9e/c05b3920a3b7d20d3d3310465f50348e5b3694f4f88c6daf736eef3024c4/certifi-2025.4.26.tar.gz", hash = "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6", size = 160705, upload-time = "2025-04-26T02:12:29.51Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/4a/7e/3db2bd1b1f9e95f7cddca6d6e75e2f2bd9f51b1246e546d88addca0106bd/certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3", size = 159618, upload-time = "2025-04-26T02:12:27.662Z" },
-]
-
[[package]]
name = "cfgv"
-version = "3.4.0"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114, upload-time = "2023-08-12T20:38:17.776Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249, upload-time = "2023-08-12T20:38:16.269Z" },
-]
-
-[[package]]
-name = "click"
-version = "8.2.1"
+version = "3.5.0"
source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "colorama", marker = "sys_platform == 'win32'" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342, upload-time = "2025-05-20T23:19:49.832Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/4e/b5/721b8799b04bf9afe054a3899c6cf4e880fcf8563cc71c15610242490a0c/cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132", size = 7334, upload-time = "2025-11-19T20:55:51.612Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215, upload-time = "2025-05-20T23:19:47.796Z" },
+ { url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445, upload-time = "2025-11-19T20:55:50.744Z" },
]
[[package]]
@@ -78,243 +30,218 @@ wheels = [
]
[[package]]
-name = "coverage"
-version = "7.9.0"
+name = "copier"
+version = "9.15.2"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/06/d1/7b18a2e0d2994e4e108dadf16580ec192e0a9c65f7456ccb82ced059f9bf/coverage-7.9.0.tar.gz", hash = "sha256:1a93b43de2233a7670a8bf2520fed8ebd5eea6a65b47417500a9d882b0533fa2", size = 813385, upload-time = "2025-06-11T23:23:34.004Z" }
+dependencies = [
+ { name = "colorama" },
+ { name = "dunamai" },
+ { name = "funcy" },
+ { name = "jinja2" },
+ { name = "jinja2-ansible-filters" },
+ { name = "packaging" },
+ { name = "pathspec" },
+ { name = "platformdirs" },
+ { name = "plumbum" },
+ { name = "pydantic" },
+ { name = "pygments" },
+ { name = "pyyaml" },
+ { name = "questionary" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/ae/8f/c0e30c11a9c0e0bd6158a1ba33190f3faf2e3659da1295f8817ccab9002b/copier-9.15.2.tar.gz", hash = "sha256:58d07c62a3426c4f5b8c482c8cda59646de53733388536427356a8625116050c", size = 640695, upload-time = "2026-06-12T08:54:12.425Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/0b/59/449eb05f795d0050007b57a4efee79b540fa6fcccad813a191351964a001/coverage-7.9.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:122c60e92ab66c9c88e17565f67a91b3b3be5617cb50f73cfd34a4c60ed4aab0", size = 212271, upload-time = "2025-06-11T23:22:38.305Z" },
- { url = "https://files.pythonhosted.org/packages/e0/3b/26852a4fb719a6007b0169c1b52116ed14b61267f0bf3ba1e23db516f352/coverage-7.9.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:813c11b367a6b3cf37212ec36b230f8d086c22b69dbf62877b40939fb2c79e74", size = 212538, upload-time = "2025-06-11T23:22:39.665Z" },
- { url = "https://files.pythonhosted.org/packages/f6/80/99f82896119f36984a5b9189e71c7310fc036613276560b5884b5ee890d7/coverage-7.9.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f05e0f5e87f23d43fefe49e86655c6209dd4f9f034786b983e6803cf4554183", size = 245705, upload-time = "2025-06-11T23:22:41.103Z" },
- { url = "https://files.pythonhosted.org/packages/a9/29/0b007deb096dd527c42e933129a8e4d5f9f1026f4953979c3a1e60e7ea9f/coverage-7.9.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62f465886fa4f86d5515da525aead97c5dff13a5cf997fc4c5097a1a59e063b2", size = 242918, upload-time = "2025-06-11T23:22:42.88Z" },
- { url = "https://files.pythonhosted.org/packages/6f/eb/273855b57c7fb387dd9787f250b8b333ba8c1c100877c21e32eb1b24ff29/coverage-7.9.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:549ea4ca901595bbe3270e1afdef98bf5d4d5791596efbdc90b00449a2bb1f91", size = 244902, upload-time = "2025-06-11T23:22:44.563Z" },
- { url = "https://files.pythonhosted.org/packages/20/57/4e411b47dbfd831538ecf9e5f407e42888b0c56aedbfe0ea7b102a787559/coverage-7.9.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8cae1d4450945c74a6a65a09864ed3eaa917055cf70aa65f83ac1b9b0d8d5f9a", size = 245069, upload-time = "2025-06-11T23:22:46.352Z" },
- { url = "https://files.pythonhosted.org/packages/91/75/b24cf5703fb325fc4b1899d89984dac117b99e757b9fadd525cad7ecc020/coverage-7.9.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d7b263910234c0d5ec913ec79ca921152fe874b805a7bcaf67118ef71708e5d2", size = 243040, upload-time = "2025-06-11T23:22:48.147Z" },
- { url = "https://files.pythonhosted.org/packages/c7/e1/9495751d5315c3d76ee2c7b5dbc1935ab891d45ad585e1910a333dbdef43/coverage-7.9.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7d7b7425215963da8f5968096a20c5b5c9af4a86a950fcc25dcc2177ab33e9e5", size = 244424, upload-time = "2025-06-11T23:22:49.574Z" },
- { url = "https://files.pythonhosted.org/packages/94/2a/ee504188a586da2379939f37fdc69047d9c46d35c34d1196f2605974a17d/coverage-7.9.0-cp313-cp313-win32.whl", hash = "sha256:e7dcfa92867b0c53d2e22e985c66af946dc09e8bb13c556709e396e90a0adf5c", size = 214677, upload-time = "2025-06-11T23:22:51.394Z" },
- { url = "https://files.pythonhosted.org/packages/80/2b/5eab6518643c7560fe180ba5e0f35a0be3d4fc0a88aa6601120407b1fd03/coverage-7.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:aa34ca040785a2b768da489df0c036364d47a6c1c00bdd8f662b98fd3277d3d4", size = 215482, upload-time = "2025-06-11T23:22:53.151Z" },
- { url = "https://files.pythonhosted.org/packages/fd/7f/9c9c8b736c4f40d7247bea8339afac40d8f6465491440608b3d73c10ffce/coverage-7.9.0-cp313-cp313-win_arm64.whl", hash = "sha256:9c5dcb5cd3c52d84c5f52045e1c87c16bf189c2fbfa57cc0d811a3b4059939df", size = 213852, upload-time = "2025-06-11T23:22:54.568Z" },
- { url = "https://files.pythonhosted.org/packages/e5/83/056464aec8b360dee6f4d7a517dc5ae5a9f462ff895ff536588b42f95b2d/coverage-7.9.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:b52d2fdc1940f90c4572bd48211475a7b102f75a7f9a5e6cfc6e3da7dc380c44", size = 212994, upload-time = "2025-06-11T23:22:56.173Z" },
- { url = "https://files.pythonhosted.org/packages/a3/87/f0291ecaa6baaaedbd428cf8b7e1d16b5dc010718fe7739cce955149ef83/coverage-7.9.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4cc555a3e6ceb8841df01a4634374f5f9635e661f5c307da00bce19819e8bcdf", size = 213212, upload-time = "2025-06-11T23:22:58.051Z" },
- { url = "https://files.pythonhosted.org/packages/16/a0/9eb39541774a5beb662dc4ae98fee23afb947414b6aa1443b53d2ad3ea05/coverage-7.9.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:244f613617876b7cd32a097788d49c952a8f1698afb25275b2a825a4e895854e", size = 256453, upload-time = "2025-06-11T23:22:59.485Z" },
- { url = "https://files.pythonhosted.org/packages/93/33/d0e99f4c809334dfed20f17234080a9003a713ddb80e33ad22697a8aa8e5/coverage-7.9.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5c335d77539e66bc6f83e8f1ef207d038129d9b9acd9dc9f0ca42fa9eedf564a", size = 252674, upload-time = "2025-06-11T23:23:00.984Z" },
- { url = "https://files.pythonhosted.org/packages/0b/3a/d2a64e7ee5eb783e44e6ca404f8fc2a45afef052ed6593afb4ce9663dae6/coverage-7.9.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b335c7077c8da7bb8173d4f9ebd90ff1a97af6a6bec4fc4e6db4856ae80b31e", size = 254830, upload-time = "2025-06-11T23:23:02.445Z" },
- { url = "https://files.pythonhosted.org/packages/e2/6a/9de640f8e2b097d155532d1bc16eb9c5186fccc7c4b8148fe1dd2520875a/coverage-7.9.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:01cbc2c36895b7ab906514042c92b3fc9dd0526bf1c3251cb6aefd9c71ae6dda", size = 256060, upload-time = "2025-06-11T23:23:03.89Z" },
- { url = "https://files.pythonhosted.org/packages/07/72/928fa3583b9783fc32e3dfafb6cc0cf73bdd73d1dc41e3a973f203c6aeff/coverage-7.9.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:1ac62880a9dff0726a193ce77a1bcdd4e8491009cb3a0510d31381e8b2c46d7a", size = 254174, upload-time = "2025-06-11T23:23:05.366Z" },
- { url = "https://files.pythonhosted.org/packages/ad/01/2fd0785f8768693b748e36b442352bc26edf3391246eedcc80d480d06da1/coverage-7.9.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:95314eb306cf54af3d1147e27ba008cf78eed6f1309a1310772f4f05b12c9c65", size = 255011, upload-time = "2025-06-11T23:23:07.212Z" },
- { url = "https://files.pythonhosted.org/packages/b7/49/1d0120cfa24e001e0d38795388914183c48cd86fc8640ca3b01337831917/coverage-7.9.0-cp313-cp313t-win32.whl", hash = "sha256:c5cbf3ddfb68de8dc8ce33caa9321df27297a032aeaf2e99b278f183fb4ebc37", size = 215349, upload-time = "2025-06-11T23:23:09.037Z" },
- { url = "https://files.pythonhosted.org/packages/9f/48/7625c09621a206fff0b51fcbcf5d6c1162ab10a5ffa546fc132f01c9132b/coverage-7.9.0-cp313-cp313t-win_amd64.whl", hash = "sha256:e3ec9e1525eb7a0f89d31083539b398d921415d884e9f55400002a1e9fe0cf63", size = 216516, upload-time = "2025-06-11T23:23:11.083Z" },
- { url = "https://files.pythonhosted.org/packages/bb/50/048b55c34985c3aafcecb32cced3abc4291969bfd967dbcaed95cfc26b2a/coverage-7.9.0-cp313-cp313t-win_arm64.whl", hash = "sha256:a02efe6769f74245ce476e89db3d4e110db07b4c0c3d3f81728e2464bbbbcb8e", size = 214308, upload-time = "2025-06-11T23:23:12.522Z" },
- { url = "https://files.pythonhosted.org/packages/70/0d/534c1e35cb7688b5c40de93fcca07e3ddc0287659ff85cd376b1dd3f770f/coverage-7.9.0-py3-none-any.whl", hash = "sha256:79ea9a26b27c963cdf541e1eb9ac05311b012bc367d0e31816f1833b06c81c02", size = 203917, upload-time = "2025-06-11T23:23:32.413Z" },
+ { url = "https://files.pythonhosted.org/packages/72/7d/733c088265e01e653c70e89e58345a3dd346acc34af26987314b84b9ffaa/copier-9.15.2-py3-none-any.whl", hash = "sha256:50a39142d6493f2261762309374a7a7462dbab654c5d22fd9aa17b6a69b84a95", size = 63389, upload-time = "2026-06-12T08:54:10.991Z" },
]
[[package]]
-name = "distlib"
-version = "0.3.9"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/0d/dd/1bec4c5ddb504ca60fc29472f3d27e8d4da1257a854e1d96742f15c1d02d/distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403", size = 613923, upload-time = "2024-10-09T18:35:47.551Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/91/a1/cf2472db20f7ce4a6be1253a81cfdf85ad9c7885ffbed7047fb72c24cf87/distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87", size = 468973, upload-time = "2024-10-09T18:35:44.272Z" },
+name = "cosmic-fastapi-template"
+version = "0.1.0"
+source = { virtual = "." }
+
+[package.dev-dependencies]
+dev = [
+ { name = "copier" },
+ { name = "pre-commit" },
+ { name = "pytest" },
]
-[[package]]
-name = "dnspython"
-version = "2.7.0"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/b5/4a/263763cb2ba3816dd94b08ad3a33d5fdae34ecb856678773cc40a3605829/dnspython-2.7.0.tar.gz", hash = "sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1", size = 345197, upload-time = "2024-10-05T20:14:59.362Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/68/1b/e0a87d256e40e8c888847551b20a017a6b98139178505dc7ffb96f04e954/dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86", size = 313632, upload-time = "2024-10-05T20:14:57.687Z" },
+[package.metadata]
+
+[package.metadata.requires-dev]
+dev = [
+ { name = "copier", specifier = ">=9.4.0" },
+ { name = "pre-commit", specifier = ">=4.2.0" },
+ { name = "pytest", specifier = ">=8.4.0" },
]
[[package]]
-name = "email-validator"
-version = "2.2.0"
+name = "distlib"
+version = "0.4.3"
source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "dnspython" },
- { name = "idna" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/48/ce/13508a1ec3f8bb981ae4ca79ea40384becc868bfae97fd1c942bb3a001b1/email_validator-2.2.0.tar.gz", hash = "sha256:cb690f344c617a714f22e66ae771445a1ceb46821152df8e165c5f9a364582b7", size = 48967, upload-time = "2024-06-20T11:30:30.034Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/c9/02/bd72be9134d25ed783ecbbc38a539ffaefbf90c78418c7fb7229600dbac7/distlib-0.4.3.tar.gz", hash = "sha256:f152097224a0ae24be5a0f6bae1b9359af82133bce63f98a95f86cae1aede9ed", size = 615141, upload-time = "2026-06-12T08:04:52.847Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/d7/ee/bf0adb559ad3c786f12bcbc9296b3f5675f529199bef03e2df281fa1fadb/email_validator-2.2.0-py3-none-any.whl", hash = "sha256:561977c2d73ce3611850a06fa56b414621e0c8faa9d66f2611407d87465da631", size = 33521, upload-time = "2024-06-20T11:30:28.248Z" },
+ { url = "https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl", hash = "sha256:4b0ce306c966eb73bc3a7b6abad017c556dadd92c44701562cd528ac7fde4d5b", size = 470628, upload-time = "2026-06-12T08:04:50.506Z" },
]
[[package]]
-name = "fastapi"
-version = "0.115.12"
+name = "dunamai"
+version = "1.26.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pydantic" },
- { name = "starlette" },
- { name = "typing-extensions" },
+ { name = "packaging" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/f4/55/ae499352d82338331ca1e28c7f4a63bfd09479b16395dce38cf50a39e2c2/fastapi-0.115.12.tar.gz", hash = "sha256:1e2c2a2646905f9e83d32f04a3f86aff4a286669c6c950ca95b5fd68c2602681", size = 295236, upload-time = "2025-03-23T22:55:43.822Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/9f/67/d5611975faaa5e4a920f4b19e4caccd5df0facb925687850f1e45f5876f2/dunamai-1.26.1.tar.gz", hash = "sha256:3b46007bd65b00b4824ead0a1aee365fd22d0ec2b9c219497d4fd48f52860c8b", size = 45567, upload-time = "2026-04-04T14:07:11.17Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/50/b3/b51f09c2ba432a576fe63758bddc81f78f0c6309d9e5c10d194313bf021e/fastapi-0.115.12-py3-none-any.whl", hash = "sha256:e94613d6c05e27be7ffebdd6ea5f388112e5e430c8f7d6494a9d1d88d43e814d", size = 95164, upload-time = "2025-03-23T22:55:42.101Z" },
+ { url = "https://files.pythonhosted.org/packages/fa/bc/8b8ec5a4bfc5b9cf3ce27a118339e994f88410be5677c96493e0ea28e76d/dunamai-1.26.1-py3-none-any.whl", hash = "sha256:2727d939c5b4257cb01ea404372803b477f5176e5a347c43beaf89cd5072e853", size = 27332, upload-time = "2026-04-04T14:07:10.079Z" },
]
[[package]]
name = "filelock"
-version = "3.18.0"
+version = "3.29.3"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/0a/10/c23352565a6544bdc5353e0b15fc1c563352101f30e24bf500207a54df9a/filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2", size = 18075, upload-time = "2025-03-14T07:11:40.47Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/91/f5/3557bf28e0f1943e4849154c821533706e6dea010f96fb6aa0b6949037d1/filelock-3.29.3.tar.gz", hash = "sha256:7fc1b3f39cf172fd8203812043c57b8a65aef9969f38b6704f628b881f761a84", size = 61956, upload-time = "2026-06-10T17:37:11.832Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/4d/36/2a115987e2d8c300a974597416d9de88f2444426de9571f4b59b2cca3acc/filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de", size = 16215, upload-time = "2025-03-14T07:11:39.145Z" },
+ { url = "https://files.pythonhosted.org/packages/81/8f/b61d427c4f49a8bdadc93f4e7e74df8a6df6f77ee6e26bf0df53d3925363/filelock-3.29.3-py3-none-any.whl", hash = "sha256:e58333029cc9b925f39aad59b1d8f0a1ad836af4e60d7217f4a4dba87461261d", size = 42324, upload-time = "2026-06-10T17:37:10.37Z" },
]
[[package]]
-name = "greenlet"
-version = "3.2.3"
+name = "funcy"
+version = "2.0"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/c9/92/bb85bd6e80148a4d2e0c59f7c0c2891029f8fd510183afc7d8d2feeed9b6/greenlet-3.2.3.tar.gz", hash = "sha256:8b0dd8ae4c0d6f5e54ee55ba935eeb3d735a9b58a8a1e5b5cbab64e01a39f365", size = 185752, upload-time = "2025-06-05T16:16:09.955Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/70/b8/c6081521ff70afdff55cd9512b2220bbf4fa88804dae51d1b57b4b58ef32/funcy-2.0.tar.gz", hash = "sha256:3963315d59d41c6f30c04bc910e10ab50a3ac4a225868bfa96feed133df075cb", size = 537931, upload-time = "2023-03-28T06:22:46.764Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/b1/cf/f5c0b23309070ae93de75c90d29300751a5aacefc0a3ed1b1d8edb28f08b/greenlet-3.2.3-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:500b8689aa9dd1ab26872a34084503aeddefcb438e2e7317b89b11eaea1901ad", size = 270732, upload-time = "2025-06-05T16:10:08.26Z" },
- { url = "https://files.pythonhosted.org/packages/48/ae/91a957ba60482d3fecf9be49bc3948f341d706b52ddb9d83a70d42abd498/greenlet-3.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a07d3472c2a93117af3b0136f246b2833fdc0b542d4a9799ae5f41c28323faef", size = 639033, upload-time = "2025-06-05T16:38:53.983Z" },
- { url = "https://files.pythonhosted.org/packages/6f/df/20ffa66dd5a7a7beffa6451bdb7400d66251374ab40b99981478c69a67a8/greenlet-3.2.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:8704b3768d2f51150626962f4b9a9e4a17d2e37c8a8d9867bbd9fa4eb938d3b3", size = 652999, upload-time = "2025-06-05T16:41:37.89Z" },
- { url = "https://files.pythonhosted.org/packages/51/b4/ebb2c8cb41e521f1d72bf0465f2f9a2fd803f674a88db228887e6847077e/greenlet-3.2.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:5035d77a27b7c62db6cf41cf786cfe2242644a7a337a0e155c80960598baab95", size = 647368, upload-time = "2025-06-05T16:48:21.467Z" },
- { url = "https://files.pythonhosted.org/packages/8e/6a/1e1b5aa10dced4ae876a322155705257748108b7fd2e4fae3f2a091fe81a/greenlet-3.2.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2d8aa5423cd4a396792f6d4580f88bdc6efcb9205891c9d40d20f6e670992efb", size = 650037, upload-time = "2025-06-05T16:13:06.402Z" },
- { url = "https://files.pythonhosted.org/packages/26/f2/ad51331a157c7015c675702e2d5230c243695c788f8f75feba1af32b3617/greenlet-3.2.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2c724620a101f8170065d7dded3f962a2aea7a7dae133a009cada42847e04a7b", size = 608402, upload-time = "2025-06-05T16:12:51.91Z" },
- { url = "https://files.pythonhosted.org/packages/26/bc/862bd2083e6b3aff23300900a956f4ea9a4059de337f5c8734346b9b34fc/greenlet-3.2.3-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:873abe55f134c48e1f2a6f53f7d1419192a3d1a4e873bace00499a4e45ea6af0", size = 1119577, upload-time = "2025-06-05T16:36:49.787Z" },
- { url = "https://files.pythonhosted.org/packages/86/94/1fc0cc068cfde885170e01de40a619b00eaa8f2916bf3541744730ffb4c3/greenlet-3.2.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:024571bbce5f2c1cfff08bf3fbaa43bbc7444f580ae13b0099e95d0e6e67ed36", size = 1147121, upload-time = "2025-06-05T16:12:42.527Z" },
- { url = "https://files.pythonhosted.org/packages/27/1a/199f9587e8cb08a0658f9c30f3799244307614148ffe8b1e3aa22f324dea/greenlet-3.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:5195fb1e75e592dd04ce79881c8a22becdfa3e6f500e7feb059b1e6fdd54d3e3", size = 297603, upload-time = "2025-06-05T16:20:12.651Z" },
- { url = "https://files.pythonhosted.org/packages/d8/ca/accd7aa5280eb92b70ed9e8f7fd79dc50a2c21d8c73b9a0856f5b564e222/greenlet-3.2.3-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:3d04332dddb10b4a211b68111dabaee2e1a073663d117dc10247b5b1642bac86", size = 271479, upload-time = "2025-06-05T16:10:47.525Z" },
- { url = "https://files.pythonhosted.org/packages/55/71/01ed9895d9eb49223280ecc98a557585edfa56b3d0e965b9fa9f7f06b6d9/greenlet-3.2.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8186162dffde068a465deab08fc72c767196895c39db26ab1c17c0b77a6d8b97", size = 683952, upload-time = "2025-06-05T16:38:55.125Z" },
- { url = "https://files.pythonhosted.org/packages/ea/61/638c4bdf460c3c678a0a1ef4c200f347dff80719597e53b5edb2fb27ab54/greenlet-3.2.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f4bfbaa6096b1b7a200024784217defedf46a07c2eee1a498e94a1b5f8ec5728", size = 696917, upload-time = "2025-06-05T16:41:38.959Z" },
- { url = "https://files.pythonhosted.org/packages/22/cc/0bd1a7eb759d1f3e3cc2d1bc0f0b487ad3cc9f34d74da4b80f226fde4ec3/greenlet-3.2.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:ed6cfa9200484d234d8394c70f5492f144b20d4533f69262d530a1a082f6ee9a", size = 692443, upload-time = "2025-06-05T16:48:23.113Z" },
- { url = "https://files.pythonhosted.org/packages/67/10/b2a4b63d3f08362662e89c103f7fe28894a51ae0bc890fabf37d1d780e52/greenlet-3.2.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:02b0df6f63cd15012bed5401b47829cfd2e97052dc89da3cfaf2c779124eb892", size = 692995, upload-time = "2025-06-05T16:13:07.972Z" },
- { url = "https://files.pythonhosted.org/packages/5a/c6/ad82f148a4e3ce9564056453a71529732baf5448ad53fc323e37efe34f66/greenlet-3.2.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:86c2d68e87107c1792e2e8d5399acec2487a4e993ab76c792408e59394d52141", size = 655320, upload-time = "2025-06-05T16:12:53.453Z" },
- { url = "https://files.pythonhosted.org/packages/5c/4f/aab73ecaa6b3086a4c89863d94cf26fa84cbff63f52ce9bc4342b3087a06/greenlet-3.2.3-cp314-cp314-win_amd64.whl", hash = "sha256:8c47aae8fbbfcf82cc13327ae802ba13c9c36753b67e760023fd116bc124a62a", size = 301236, upload-time = "2025-06-05T16:15:20.111Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/08/c2409cb01d5368dcfedcbaffa7d044cc8957d57a9d0855244a5eb4709d30/funcy-2.0-py2.py3-none-any.whl", hash = "sha256:53df23c8bb1651b12f095df764bfb057935d49537a56de211b098f4c79614bb0", size = 30891, upload-time = "2023-03-28T06:22:42.576Z" },
]
[[package]]
-name = "h11"
-version = "0.16.0"
+name = "identify"
+version = "2.6.19"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/52/63/51723b5f116cc04b061cb6f5a561790abf249d25931d515cd375e063e0f4/identify-2.6.19.tar.gz", hash = "sha256:6be5020c38fcb07da56c53733538a3081ea5aa70d36a156f83044bfbf9173842", size = 99567, upload-time = "2026-04-17T18:39:50.265Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
+ { url = "https://files.pythonhosted.org/packages/94/84/d9273cd09688070a6523c4aee4663a8538721b2b755c4962aafae0011e72/identify-2.6.19-py2.py3-none-any.whl", hash = "sha256:20e6a87f786f768c092a721ad107fc9df0eb89347be9396cadf3f4abbd1fb78a", size = 99397, upload-time = "2026-04-17T18:39:49.221Z" },
]
[[package]]
-name = "httpcore"
-version = "1.0.9"
+name = "iniconfig"
+version = "2.3.0"
source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "certifi" },
- { name = "h11" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" }
+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/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
+ { 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 = "httpx"
-version = "0.28.1"
+name = "jinja2"
+version = "3.1.6"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "anyio" },
- { name = "certifi" },
- { name = "httpcore" },
- { name = "idna" },
+ { name = "markupsafe" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
+ { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" },
]
[[package]]
-name = "identify"
-version = "2.6.12"
+name = "jinja2-ansible-filters"
+version = "1.3.2"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/a2/88/d193a27416618628a5eea64e3223acd800b40749a96ffb322a9b55a49ed1/identify-2.6.12.tar.gz", hash = "sha256:d8de45749f1efb108badef65ee8386f0f7bb19a7f26185f74de6367bffbaf0e6", size = 99254, upload-time = "2025-05-23T20:37:53.3Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/7a/cd/18f8da995b658420625f7ef13f037be53ae04ec5ad33f9b718240dcfd48c/identify-2.6.12-py2.py3-none-any.whl", hash = "sha256:ad9672d5a72e0d2ff7c5c8809b62dfa60458626352fb0eb7b55e69bdc45334a2", size = 99145, upload-time = "2025-05-23T20:37:51.495Z" },
+dependencies = [
+ { name = "jinja2" },
+ { name = "pyyaml" },
]
-
-[[package]]
-name = "idna"
-version = "3.10"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/1a/27/fa186af4b246eb869ffca8ffa42d92b05abaec08c99329e74d88b2c46ec7/jinja2-ansible-filters-1.3.2.tar.gz", hash = "sha256:07c10cf44d7073f4f01102ca12d9a2dc31b41d47e4c61ed92ef6a6d2669b356b", size = 16945, upload-time = "2022-06-30T14:08:50.775Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" },
+ { url = "https://files.pythonhosted.org/packages/72/b9/313e8f2f2e9517ae050a692ae7b3e4b3f17cc5e6dfea0db51fe14e586580/jinja2_ansible_filters-1.3.2-py3-none-any.whl", hash = "sha256:e1082f5564917649c76fed239117820610516ec10f87735d0338688800a55b34", size = 18975, upload-time = "2022-06-30T14:08:49.571Z" },
]
[[package]]
-name = "iniconfig"
-version = "2.1.0"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" },
+name = "markupsafe"
+version = "3.0.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" },
+ { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" },
+ { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" },
+ { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" },
+ { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" },
+ { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" },
+ { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" },
+ { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" },
+ { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" },
+ { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" },
+ { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" },
+ { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" },
+ { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" },
+ { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" },
+ { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" },
+ { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" },
+ { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" },
+ { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" },
+ { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" },
+ { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" },
+ { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" },
+ { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" },
+ { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" },
+ { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" },
+ { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" },
]
[[package]]
-name = "mako"
-version = "1.3.10"
+name = "nodeenv"
+version = "1.10.0"
source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "markupsafe" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/9e/38/bd5b78a920a64d708fe6bc8e0a2c075e1389d53bef8413725c63ba041535/mako-1.3.10.tar.gz", hash = "sha256:99579a6f39583fa7e5630a28c3c1f440e4e97a414b80372649c0ce338da2ea28", size = 392474, upload-time = "2025-04-10T12:44:31.16Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/87/fb/99f81ac72ae23375f22b7afdb7642aba97c00a713c217124420147681a2f/mako-1.3.10-py3-none-any.whl", hash = "sha256:baef24a52fc4fc514a0887ac600f9f1cff3d82c61d4d700a1fa84d597b88db59", size = 78509, upload-time = "2025-04-10T12:50:53.297Z" },
-]
-
-[[package]]
-name = "markupsafe"
-version = "3.0.2"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload-time = "2024-10-18T15:21:24.577Z" },
- { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload-time = "2024-10-18T15:21:25.382Z" },
- { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload-time = "2024-10-18T15:21:26.199Z" },
- { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085, upload-time = "2024-10-18T15:21:27.029Z" },
- { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978, upload-time = "2024-10-18T15:21:27.846Z" },
- { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208, upload-time = "2024-10-18T15:21:28.744Z" },
- { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357, upload-time = "2024-10-18T15:21:29.545Z" },
- { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344, upload-time = "2024-10-18T15:21:30.366Z" },
- { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101, upload-time = "2024-10-18T15:21:31.207Z" },
- { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603, upload-time = "2024-10-18T15:21:32.032Z" },
- { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510, upload-time = "2024-10-18T15:21:33.625Z" },
- { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486, upload-time = "2024-10-18T15:21:34.611Z" },
- { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480, upload-time = "2024-10-18T15:21:35.398Z" },
- { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914, upload-time = "2024-10-18T15:21:36.231Z" },
- { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796, upload-time = "2024-10-18T15:21:37.073Z" },
- { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473, upload-time = "2024-10-18T15:21:37.932Z" },
- { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114, upload-time = "2024-10-18T15:21:39.799Z" },
- { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload-time = "2024-10-18T15:21:40.813Z" },
- { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload-time = "2024-10-18T15:21:41.814Z" },
- { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" },
+ { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" },
]
[[package]]
-name = "nodeenv"
-version = "1.9.1"
+name = "packaging"
+version = "26.2"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437, upload-time = "2024-06-04T18:44:11.171Z" }
+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/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" },
+ { 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 = "packaging"
-version = "25.0"
+name = "pathspec"
+version = "1.1.1"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/5a/82/42f767fc1c1143d6fd36efb827202a2d997a375e160a71eb2888a925aac1/pathspec-1.1.1.tar.gz", hash = "sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a", size = 135180, upload-time = "2026-04-27T01:46:08.907Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" },
+ { url = "https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189", size = 57328, upload-time = "2026-04-27T01:46:07.06Z" },
]
[[package]]
name = "platformdirs"
-version = "4.3.8"
+version = "4.10.0"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362, upload-time = "2025-05-07T22:47:42.121Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/d7/47/e4501f49c178ae1d9f4a75073fda4204f52647993f075a9db4d14930e0c5/platformdirs-4.10.0.tar.gz", hash = "sha256:31e761a6a0ca04faf7353ea759bdba55652be214725111e5aac52dfa29d4bef7", size = 31224, upload-time = "2026-05-28T03:32:53.587Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567, upload-time = "2025-05-07T22:47:40.376Z" },
+ { url = "https://files.pythonhosted.org/packages/81/e6/cd9575ac904136b3cbf7aa7ee819ef86eedb7274e46f230e94ea4342e729/platformdirs-4.10.0-py3-none-any.whl", hash = "sha256:fb516cdb12eb0d857d0cd85a7c57cea4d060bee4578d6cf5a14dfdf8cbf8784a", size = 22743, upload-time = "2026-05-28T03:32:52.175Z" },
]
[[package]]
@@ -326,9 +253,18 @@ 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 = "plumbum"
+version = "2.0.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/0c/6a/1d1b143420fcdfc8902f2db6b7d1d2325211461c5f2a43c849de7afad688/plumbum-2.0.1.tar.gz", hash = "sha256:61623f856dcb09eb20dcd5aa708dfb3cd04b6f4ab10224d39303b163bb1c4c61", size = 377668, upload-time = "2026-06-08T14:44:06.17Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/59/2d/d741fbbbcba7eb6f9f1a829373c558114d59cb882b95f941aa0dc060861f/plumbum-2.0.1-py3-none-any.whl", hash = "sha256:27a454980f91689aae8f18242a36daaf2636219171cf0e6a849744aa1d6fff85", size = 164460, upload-time = "2026-06-08T14:44:04.75Z" },
+]
+
[[package]]
name = "pre-commit"
-version = "4.2.0"
+version = "4.6.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "cfgv" },
@@ -337,102 +273,106 @@ dependencies = [
{ name = "pyyaml" },
{ name = "virtualenv" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/08/39/679ca9b26c7bb2999ff122d50faa301e49af82ca9c066ec061cfbc0c6784/pre_commit-4.2.0.tar.gz", hash = "sha256:601283b9757afd87d40c4c4a9b2b5de9637a8ea02eaff7adc2d0fb4e04841146", size = 193424, upload-time = "2025-03-18T21:35:20.987Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/8e/22/2de9408ac81acbb8a7d05d4cc064a152ccf33b3d480ebe0cd292153db239/pre_commit-4.6.0.tar.gz", hash = "sha256:718d2208cef53fdc38206e40524a6d4d9576d103eb16f0fec11c875e7716e9d9", size = 198525, upload-time = "2026-04-21T20:31:41.613Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/88/74/a88bf1b1efeae488a0c0b7bdf71429c313722d1fc0f377537fbe554e6180/pre_commit-4.2.0-py2.py3-none-any.whl", hash = "sha256:a009ca7205f1eb497d10b845e52c838a98b6cdd2102a6c8e4540e94ee75c58bd", size = 220707, upload-time = "2025-03-18T21:35:19.343Z" },
+ { url = "https://files.pythonhosted.org/packages/80/6e/4b28b62ecb6aae56769c34a8ff1d661473ec1e9519e2d5f8b2c150086b26/pre_commit-4.6.0-py2.py3-none-any.whl", hash = "sha256:e2cf246f7299edcabcf15f9b0571fdce06058527f0a06535068a86d38089f29b", size = 226472, upload-time = "2026-04-21T20:31:40.092Z" },
]
[[package]]
-name = "pydantic"
-version = "2.11.5"
+name = "prompt-toolkit"
+version = "3.0.52"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "annotated-types" },
- { name = "pydantic-core" },
- { name = "typing-extensions" },
- { name = "typing-inspection" },
+ { name = "wcwidth" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/f0/86/8ce9040065e8f924d642c58e4a344e33163a07f6b57f836d0d734e0ad3fb/pydantic-2.11.5.tar.gz", hash = "sha256:7f853db3d0ce78ce8bbb148c401c2cdd6431b3473c0cdff2755c7690952a7b7a", size = 787102, upload-time = "2025-05-22T21:18:08.761Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198, upload-time = "2025-08-27T15:24:02.057Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/b5/69/831ed22b38ff9b4b64b66569f0e5b7b97cf3638346eb95a2147fdb49ad5f/pydantic-2.11.5-py3-none-any.whl", hash = "sha256:f9c26ba06f9747749ca1e5c94d6a85cb84254577553c8785576fd38fa64dc0f7", size = 444229, upload-time = "2025-05-22T21:18:06.329Z" },
-]
-
-[package.optional-dependencies]
-email = [
- { name = "email-validator" },
+ { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" },
]
[[package]]
-name = "pydantic-core"
-version = "2.33.2"
+name = "pydantic"
+version = "2.13.4"
source = { registry = "https://pypi.org/simple" }
dependencies = [
+ { name = "annotated-types" },
+ { name = "pydantic-core" },
{ name = "typing-extensions" },
+ { name = "typing-inspection" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195, upload-time = "2025-04-23T18:33:52.104Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/18/a5/b60d21ac674192f8ab0ba4e9fd860690f9b4a6e51ca5df118733b487d8d6/pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6", size = 844775, upload-time = "2026-05-06T13:43:05.343Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688, upload-time = "2025-04-23T18:31:53.175Z" },
- { url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808, upload-time = "2025-04-23T18:31:54.79Z" },
- { url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580, upload-time = "2025-04-23T18:31:57.393Z" },
- { url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859, upload-time = "2025-04-23T18:31:59.065Z" },
- { url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810, upload-time = "2025-04-23T18:32:00.78Z" },
- { url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498, upload-time = "2025-04-23T18:32:02.418Z" },
- { url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611, upload-time = "2025-04-23T18:32:04.152Z" },
- { url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924, upload-time = "2025-04-23T18:32:06.129Z" },
- { url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196, upload-time = "2025-04-23T18:32:08.178Z" },
- { url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389, upload-time = "2025-04-23T18:32:10.242Z" },
- { url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223, upload-time = "2025-04-23T18:32:12.382Z" },
- { url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473, upload-time = "2025-04-23T18:32:14.034Z" },
- { url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269, upload-time = "2025-04-23T18:32:15.783Z" },
- { url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921, upload-time = "2025-04-23T18:32:18.473Z" },
- { url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162, upload-time = "2025-04-23T18:32:20.188Z" },
- { url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560, upload-time = "2025-04-23T18:32:22.354Z" },
- { url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777, upload-time = "2025-04-23T18:32:25.088Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba", size = 472262, upload-time = "2026-05-06T13:43:02.641Z" },
]
[[package]]
-name = "pydantic-settings"
-version = "2.9.1"
+name = "pydantic-core"
+version = "2.46.4"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "pydantic" },
- { name = "python-dotenv" },
- { name = "typing-inspection" },
+ { name = "typing-extensions" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/67/1d/42628a2c33e93f8e9acbde0d5d735fa0850f3e6a2f8cb1eb6c40b9a732ac/pydantic_settings-2.9.1.tar.gz", hash = "sha256:c509bf79d27563add44e8446233359004ed85066cd096d8b510f715e6ef5d268", size = 163234, upload-time = "2025-04-18T16:44:48.265Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/b6/5f/d6d641b490fd3ec2c4c13b4244d68deea3a1b970a97be64f34fb5504ff72/pydantic_settings-2.9.1-py3-none-any.whl", hash = "sha256:59b4f431b1defb26fe620c71a7d3968a710d719f5f4cdbbdb7926edeb770f6ef", size = 44356, upload-time = "2025-04-18T16:44:46.617Z" },
+sdist = { url = "https://files.pythonhosted.org/packages/9d/56/921726b776ace8d8f5db44c4ef961006580d91dc52b803c489fafd1aa249/pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1", size = 471464, upload-time = "2026-05-06T13:37:06.98Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/51/a2/5d30b469c5267a17b39dec53208222f76a8d351dfac4af661888c5aee77d/pydantic_core-2.46.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008", size = 2106306, upload-time = "2026-05-06T13:37:48.029Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/81/4fa520eaffa8bd7d1525e644cd6d39e7d60b1592bc5b516693c7340b50f1/pydantic_core-2.46.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4", size = 1951906, upload-time = "2026-05-06T13:37:17.012Z" },
+ { url = "https://files.pythonhosted.org/packages/03/d5/fd02da45b659668b05923b17ba3a0100a0a3d5541e3bd8fcc4ecb711309e/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76", size = 1976802, upload-time = "2026-05-06T13:37:35.113Z" },
+ { url = "https://files.pythonhosted.org/packages/21/f2/95727e1368be3d3ed485eaab7adbd7dda408f33f7a36e8b48e0144002b91/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e739fee756ba1010f8bcccb534252e85a35fe45ae92c295a06059ce58b74ccd3", size = 2052446, upload-time = "2026-05-06T13:37:12.313Z" },
+ { url = "https://files.pythonhosted.org/packages/9c/86/5d99feea3f77c7234b8718075b23db11532773c1a0dbd9b9490215dc2eeb/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d56801be94b86a9da183e5f3766e6310752b99ff647e38b09a9500d88e46e76", size = 2232757, upload-time = "2026-05-06T13:39:01.149Z" },
+ { url = "https://files.pythonhosted.org/packages/d2/3a/508ac615935ef7588cf6d9e9b91309fdc2da751af865e02a9098de88258c/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2412e734dcb48da14d4e4006b82b46b74f2518b8a26ee7e58c6844a6cd6d03c4", size = 2309275, upload-time = "2026-05-06T13:37:41.406Z" },
+ { url = "https://files.pythonhosted.org/packages/07/f8/41db9de19d7987d6b04715a02b3b40aea467000275d9d758ffaa31af7d50/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9551187363ffc0de2a00b2e47c25aeaeb1020b69b668762966df15fc5659dd5a", size = 2094467, upload-time = "2026-05-06T13:39:18.847Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/e2/f35033184cb11d0052daf4416e8e10a502ea2ac006fc4f459aee872727d1/pydantic_core-2.46.4-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0186750b482eefa11d7f435892b09c5c606193ef3375bcf94aa00ae6bfb66262", size = 2134417, upload-time = "2026-05-06T13:40:17.944Z" },
+ { url = "https://files.pythonhosted.org/packages/7e/7b/6ceeb1cc90e193862f444ebe373d8fdf613f0a82572dde03fb10734c6c71/pydantic_core-2.46.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5855698a4856556d86e8e6cd8434bc3ac0314ee8e12089ae0e143f64c6256e4e", size = 2179782, upload-time = "2026-05-06T13:40:32.618Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/f2/c8d7773ede6af08036423a00ae0ceffce266c3c52a096c435d68c896083f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cbaf13819775b7f769bf4a1f066cb6df7a28d4480081a589828ef190226881cd", size = 2188782, upload-time = "2026-05-06T13:36:51.018Z" },
+ { url = "https://files.pythonhosted.org/packages/59/31/0c864784e31f09f05cdd87606f08923b9c9e7f6e51dd27f20f62f975ce9f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:633147d34cf4550417f12e2b1a0383973bdf5cdfde212cb09e9a581cf10820be", size = 2328334, upload-time = "2026-05-06T13:40:37.764Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/eb/4f6c8a41efa30baa755590f4141abf3a8c370fab610915733e74134a7270/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:82cf5301172168103724d49a1444d3378cb20cdee30b116a1bd6031236298a5d", size = 2372986, upload-time = "2026-05-06T13:39:34.152Z" },
+ { url = "https://files.pythonhosted.org/packages/5b/24/b375a480d53113860c299764bfe9f349a3dc9108b3adc0d7f0d786492ebf/pydantic_core-2.46.4-cp313-cp313-win32.whl", hash = "sha256:9fa8ae11da9e2b3126c6426f147e0fba88d96d65921799bb30c6abd1cb2c97fb", size = 1973693, upload-time = "2026-05-06T13:37:55.072Z" },
+ { url = "https://files.pythonhosted.org/packages/7e/e8/cff247591966f2d22ec8c003cd7587e27b7ba7b81ab2fb888e3ab75dc285/pydantic_core-2.46.4-cp313-cp313-win_amd64.whl", hash = "sha256:6b3ace8194b0e5204818c92802dcdca7fc6d88aabbb799d7c795540d9cd6d292", size = 2071819, upload-time = "2026-05-06T13:38:49.139Z" },
+ { url = "https://files.pythonhosted.org/packages/c6/1a/f4aee670d5670e9e148e0c82c7db98d780be566c6e6a97ee8035528ca0b3/pydantic_core-2.46.4-cp313-cp313-win_arm64.whl", hash = "sha256:184c081504d17f1c1066e430e117142b2c77d9448a97f7b65c6ac9fd9aee238d", size = 2027411, upload-time = "2026-05-06T13:40:45.796Z" },
+ { url = "https://files.pythonhosted.org/packages/8d/74/228a26ddad29c6672b805d9fd78e8d251cd04004fa7eed0e622096cd0250/pydantic_core-2.46.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:428e04521a40150c85216fc8b85e8d39fece235a9cf5e383761238c7fa9b96fb", size = 2102079, upload-time = "2026-05-06T13:38:41.019Z" },
+ { url = "https://files.pythonhosted.org/packages/ad/1f/8970b150a4b4365623ae00fc88603491f763c627311ae8031e3111356d6e/pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23ace664830ee0bfe014a0c7bc248b1f7f25ed7ad103852c317624a1083af462", size = 1952179, upload-time = "2026-05-06T13:36:59.812Z" },
+ { url = "https://files.pythonhosted.org/packages/95/30/5211a831ae054928054b2f79731661087a2bc5c01e825c672b3a4a8f1b3e/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce5c1d2a8b27468f433ca974829c44060b8097eedc39933e3c206a90ee49c4a9", size = 1978926, upload-time = "2026-05-06T13:37:39.933Z" },
+ { url = "https://files.pythonhosted.org/packages/57/e9/689668733b1eb67adeef047db3c2e8788fcf65a7fd9c9e2b46b7744fe245/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7283d57845ecf5a163403eb0702dfc220cc4fbdd18919cb5ccea4f95ee1cdab4", size = 2046785, upload-time = "2026-05-06T13:38:01.995Z" },
+ { url = "https://files.pythonhosted.org/packages/60/d9/6715260422ff50a2109878fd24d948a6c3446bb2664f34ee78cd972b3acd/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8daafc69c93ee8a0204506a3b6b30f586ef54028f52aeeeb5c4cfc5184fd5914", size = 2228733, upload-time = "2026-05-06T13:40:50.371Z" },
+ { url = "https://files.pythonhosted.org/packages/18/ae/fdb2f64316afca925640f8e70bb1a564b0ec2721c1389e25b8eb4bf9a299/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2213145bcc2ba85884d0ac63d222fece9209678f77b9b4d76f054c561adb28", size = 2307534, upload-time = "2026-05-06T13:37:21.531Z" },
+ { url = "https://files.pythonhosted.org/packages/89/1d/8eff589b45bb8190a9d12c49cfad0f176a5cbd1534908a6b5125e2886239/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b", size = 2099732, upload-time = "2026-05-06T13:39:31.942Z" },
+ { url = "https://files.pythonhosted.org/packages/06/d5/ee5a3366637fee41dee51a1fc91562dcf12ddbc68fda34e6b253da2324bb/pydantic_core-2.46.4-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:c1b3f518abeca3aa13c712fd202306e145abf59a18b094a6bafb2d2bbf59192c", size = 2129627, upload-time = "2026-05-06T13:37:25.033Z" },
+ { url = "https://files.pythonhosted.org/packages/94/33/2414be571d2c6a6c4d08be21f9292b6d3fdb08949a97b6dfe985017821db/pydantic_core-2.46.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a7dd0b3ee80d90150e3495a3a13ac34dbcbfd4f012996a6a1d8900e91b5c0fb", size = 2179141, upload-time = "2026-05-06T13:37:14.046Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/79/7daa95be995be0eecc4cf75064cb33f9bbbfe3fe0158caf2f0d4a996a5c7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:3fb702cd90b0446a3a1c5e470bfa0dd23c0233b676a9099ddcc964fa6ca13898", size = 2184325, upload-time = "2026-05-06T13:36:53.615Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/cb/d0a382f5c0de8a222dc61c65348e0ce831b1f68e0a018450d31c2cace3a5/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b8458003118a712e66286df6a707db01c52c0f52f7db8e4a38f0da1d3b94fc4e", size = 2323990, upload-time = "2026-05-06T13:40:29.971Z" },
+ { url = "https://files.pythonhosted.org/packages/05/db/d9ba624cc4a5aced1598e88c04fdbd8310c8a69b9d38b9a3d39ce3a61ed7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:372429a130e469c9cd698925ce5fc50940b7a1336b0d82038e63d5bbc4edc519", size = 2369978, upload-time = "2026-05-06T13:37:23.027Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/20/d15df15ba918c423461905802bfd2981c3af0bfa0e40d05e13edbfa48bc3/pydantic_core-2.46.4-cp314-cp314-win32.whl", hash = "sha256:85bb3611ff1802f3ee7fdd7dbff26b56f343fb432d57a4728fdd49b6ef35e2f4", size = 1966354, upload-time = "2026-05-06T13:38:03.499Z" },
+ { url = "https://files.pythonhosted.org/packages/fc/b6/6b8de4c0a7d7ab3004c439c80c5c1e0a3e8d78bbae19379b01960383d9e5/pydantic_core-2.46.4-cp314-cp314-win_amd64.whl", hash = "sha256:811ff8e9c313ab425368bcbb36e5c4ebd7108c2bbf4e4089cfbb0b01eff63fac", size = 2072238, upload-time = "2026-05-06T13:39:40.807Z" },
+ { url = "https://files.pythonhosted.org/packages/32/36/51eb763beec1f4cf59b1db243a7dcc39cbb41230f050a09b9d69faaf0a48/pydantic_core-2.46.4-cp314-cp314-win_arm64.whl", hash = "sha256:bfec22eab3c8cc2ceec0248aec886624116dc079afa027ecc8ad4a7e62010f8a", size = 2018251, upload-time = "2026-05-06T13:37:26.72Z" },
+ { url = "https://files.pythonhosted.org/packages/e8/91/855af51d625b23aa987116a19e231d2aaef9c4a415273ddc189b79a45fee/pydantic_core-2.46.4-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:af8244b2bef6aaad6d92cda81372de7f8c8d36c9f0c3ea36e827c60e7d9467a0", size = 2099593, upload-time = "2026-05-06T13:39:47.682Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/1b/8784a54c65edb5f49f0a14d6977cf1b209bba85a4c77445b255c2de58ab3/pydantic_core-2.46.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a4330cdbc57162e4b3aa303f588ba752257694c9c9be3e7ebb11b4aca659b5d", size = 1935226, upload-time = "2026-05-06T13:40:40.428Z" },
+ { url = "https://files.pythonhosted.org/packages/e8/e7/1955d28d1afc56dd4b3ad7cc0cf39df1b9852964cf16e5d13912756d6d6b/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c61fc04a3d840155ff08e475a04809278972fe6aef51e2720554e96367e34b", size = 1974605, upload-time = "2026-05-06T13:37:32.029Z" },
+ { url = "https://files.pythonhosted.org/packages/93/e2/3fedbf0ba7a22850e6e9fd78117f1c0f10f950182344d8a6c535d468fdd8/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c50f2528cf200c5eed56faf3f4e22fcd5f38c157a8b78576e6ba3168ec35f000", size = 2030777, upload-time = "2026-05-06T13:38:55.239Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/61/46be275fcaaba0b4f5b9669dd852267ce1ff616592dccf7a7845588df091/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cbe8b01f948de4286c74cdd6c667aceb38f5c1e26f0693b3983d9d74887c65e", size = 2236641, upload-time = "2026-05-06T13:37:08.096Z" },
+ { url = "https://files.pythonhosted.org/packages/60/db/12e93e46a8bac9988be3c016860f83293daea8c716c029c9ace279036f2f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:617d7e2ca7dcb8c5cf6bcb8c59b8832c94b36196bbf1cbd1bfb56ed341905edd", size = 2286404, upload-time = "2026-05-06T13:40:20.221Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/4a/4d8b19008f38d31c53b8219cfedc2e3d5de5fe99d90076b7e767de29274f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7027560ee92211647d0d34e3f7cd6f50da56399d26a9c8ad0da286d3869a53f3", size = 2109219, upload-time = "2026-05-06T13:38:12.153Z" },
+ { url = "https://files.pythonhosted.org/packages/88/70/3cbc40978fefb7bb09c6708d40d4ad1a5d70fd7213c3d17f971de868ec1f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:f99626688942fb746e545232e7726926f3be91b5975f8b55327665fafda991c7", size = 2110594, upload-time = "2026-05-06T13:40:02.971Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/20/b8d36736216e29491125531685b2f9e61aa5b4b2599893f8268551da3338/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3e9034a63de20e15e8ade85358bc6efc614008cab72898b4b4952bea0509ff", size = 2159542, upload-time = "2026-05-06T13:39:27.506Z" },
+ { url = "https://files.pythonhosted.org/packages/1d/a2/367df868eb584dacf6bf82a389272406d7178e301c4ac82545ab98bc2dd9/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:97e7cf2be5c77b7d1a9713a05605d49460d02c6078d38d8bef3cbe323c548424", size = 2168146, upload-time = "2026-05-06T13:38:31.93Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/b8/4460f77f7e201893f649a29ab355dddd3beee8a97bcb1a320db414f9a06e/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:3bf92c5d0e00fefaab325a4d27828fe6b6e2a21848686b5b60d2d9eeb09d76c6", size = 2306309, upload-time = "2026-05-06T13:37:44.717Z" },
+ { url = "https://files.pythonhosted.org/packages/64/c4/be2639293acd87dc8ddbcec41a73cee9b2ebf996fe6d892a1a74e88ad3f7/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:3ecbc122d18468d06ca279dc26a8c2e2d5acb10943bb35e36ae92096dc3b5565", size = 2369736, upload-time = "2026-05-06T13:37:05.645Z" },
+ { url = "https://files.pythonhosted.org/packages/30/a6/9f9f380dbb301f67023bf8f707aaa75daadf84f7152d95c410fd7e81d994/pydantic_core-2.46.4-cp314-cp314t-win32.whl", hash = "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02", size = 1955575, upload-time = "2026-05-06T13:38:51.116Z" },
+ { url = "https://files.pythonhosted.org/packages/40/1f/f1eb9eb350e795d1af8586289746f5c5677d16043040d63710e22abc43c9/pydantic_core-2.46.4-cp314-cp314t-win_amd64.whl", hash = "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5", size = 2051624, upload-time = "2026-05-06T13:38:21.672Z" },
+ { url = "https://files.pythonhosted.org/packages/f6/d2/42dd53d0a85c27606f316d3aa5d2869c4e8470a5ed6dec30e4a1abe19192/pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596", size = 2017325, upload-time = "2026-05-06T13:40:52.723Z" },
]
[[package]]
name = "pygments"
-version = "2.19.1"
+version = "2.20.0"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581, upload-time = "2025-01-06T17:26:30.443Z" }
+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/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293, upload-time = "2025-01-06T17:26:25.553Z" },
-]
-
-[[package]]
-name = "pyrefly"
-version = "1.0.0"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/9f/3a/9045b0097ac58979c7c30a4fa0e673db942d4adbc7b6d439bd54ae58c441/pyrefly-1.0.0.tar.gz", hash = "sha256:5c2b810ffcebd84be71de5df1223651edee951653a66935c6f091e957c452455", size = 5677995, upload-time = "2026-05-12T20:12:46.812Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/f4/c6/90788819bac9c61dd7bacba53b79f3c12d47ccbe5e51b3d6d89f2387e1d2/pyrefly-1.0.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e355a0908555348ed4b9585ef25c76ff566673e345c866c325f1633f44d890b6", size = 13122950, upload-time = "2026-05-12T20:12:20.711Z" },
- { url = "https://files.pythonhosted.org/packages/82/91/a3cf2a1e87d336eaa804a1e6fc93266faf6dc2a97eecdbc7eae289628022/pyrefly-1.0.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a7038efc3a40f8294edee339895633cf22db268c0d434cdbcbefc34f78a9ecc3", size = 12599494, upload-time = "2026-05-12T20:12:23.495Z" },
- { url = "https://files.pythonhosted.org/packages/cd/ab/74d1e11e737e99b1c003ecc5d7d2e846c4ea1f328966bfdbbd0ac63fad0a/pyrefly-1.0.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da331ca515ed1c08791da2b5f664cf9c1294c48fd802133262e7d5d51e0f4416", size = 12995507, upload-time = "2026-05-12T20:12:25.951Z" },
- { url = "https://files.pythonhosted.org/packages/7c/ac/2df0899f8464c97e5d995f994c97c5cb5b0f58610432aa90d26d924e1db5/pyrefly-1.0.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c74219d8f3e63cdaa5501a0b21d1c9d37011820f9606728d0ed06f09ae86a878", size = 13947693, upload-time = "2026-05-12T20:12:29.188Z" },
- { url = "https://files.pythonhosted.org/packages/6b/3e/b247c24321e36f04b7d51f9ccf3df93e5009e4b29939524b36ec2e17dc2a/pyrefly-1.0.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c0d05543b1bb6ee6d64149eb5d6b2fb15aa72d3962d6a97abca0afaca8b0c131", size = 13925803, upload-time = "2026-05-12T20:12:31.904Z" },
- { url = "https://files.pythonhosted.org/packages/61/16/cfa2d61a4aa1e1f7bca48bb37acd01c6a09db4864b16a54f9587092765ff/pyrefly-1.0.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1382d5b1fcdb49a4de9f34d112d2bddf290a78ff93ee8149492ad5f1077ddffc", size = 13470398, upload-time = "2026-05-12T20:12:35.302Z" },
- { url = "https://files.pythonhosted.org/packages/cb/2b/6372c7dddb326223e24a46b17efd0d4bd7b4fe22c821e523157577eed2d2/pyrefly-1.0.0-py3-none-win32.whl", hash = "sha256:aa8b5d0e47080e3202a2547b39f7a5a61d2c781c712b3b67884f745ca2c759d2", size = 12222643, upload-time = "2026-05-12T20:12:38.618Z" },
- { url = "https://files.pythonhosted.org/packages/be/ad/1d23be700b6b2ddaeb362360c7145917a8edbbf7240ae428d40541772fce/pyrefly-1.0.0-py3-none-win_amd64.whl", hash = "sha256:c8abcb0f2082e83c890375128f9cff4aa4d3f210b85eea7b3046c1ae764e77f5", size = 13146369, upload-time = "2026-05-12T20:12:41.423Z" },
- { url = "https://files.pythonhosted.org/packages/8c/38/16589134f3012fd097a10dcc85771555f1a5fb76e04b682597180743af30/pyrefly-1.0.0-py3-none-win_arm64.whl", hash = "sha256:d150fa9e40e8392832be81c3bcfc0497c146674ce4d0f8e04e1ec29e775ffb8c", size = 12538326, upload-time = "2026-05-12T20:12:43.996Z" },
+ { 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 = "8.4.0"
+version = "9.0.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
@@ -441,208 +381,113 @@ dependencies = [
{ name = "pluggy" },
{ name = "pygments" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/fb/aa/405082ce2749be5398045152251ac69c0f3578c7077efc53431303af97ce/pytest-8.4.0.tar.gz", hash = "sha256:14d920b48472ea0dbf68e45b96cd1ffda4705f33307dcc86c676c1b5104838a6", size = 1515232, upload-time = "2025-06-02T17:36:30.03Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/2f/de/afa024cbe022b1b318a3d224125aa24939e99b4ff6f22e0ba639a2eaee47/pytest-8.4.0-py3-none-any.whl", hash = "sha256:f40f825768ad76c0977cbacdf1fd37c6f7a468e460ea6a0636078f8972d4517e", size = 363797, upload-time = "2025-06-02T17:36:27.859Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" },
]
[[package]]
-name = "pytest-cov"
-version = "6.2.1"
+name = "python-discovery"
+version = "1.4.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "coverage" },
- { name = "pluggy" },
- { name = "pytest" },
+ { name = "filelock" },
+ { name = "platformdirs" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/18/99/668cade231f434aaa59bbfbf49469068d2ddd945000621d3d165d2e7dd7b/pytest_cov-6.2.1.tar.gz", hash = "sha256:25cc6cc0a5358204b8108ecedc51a9b57b34cc6b8c967cc2c01a4e00d8a67da2", size = 69432, upload-time = "2025-06-12T10:47:47.684Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/0b/1a/cbbaf13b730abb0a16b964d984e19f2fe520c21a4dc664051359a3f5a9e7/python_discovery-1.4.2.tar.gz", hash = "sha256:8f3746c4b4968d22afbb97d36e1a0e5b66e6c0f297290f2e95f05b9b8bf18690", size = 70277, upload-time = "2026-06-11T16:10:42.383Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/bc/16/4ea354101abb1287856baa4af2732be351c7bee728065aed451b678153fd/pytest_cov-6.2.1-py3-none-any.whl", hash = "sha256:f5bc4c23f42f1cdd23c70b1dab1bbaef4fc505ba950d53e0081d0730dd7e86d5", size = 24644, upload-time = "2025-06-12T10:47:45.932Z" },
-]
-
-[[package]]
-name = "python-dotenv"
-version = "1.1.0"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/88/2c/7bb1416c5620485aa793f2de31d3df393d3686aa8a8506d11e10e13c5baf/python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5", size = 39920, upload-time = "2025-03-25T10:14:56.835Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/1e/18/98a99ad95133c6a6e2005fe89faedf294a748bd5dc803008059409ac9b1e/python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d", size = 20256, upload-time = "2025-03-25T10:14:55.034Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/82/a70006589557f267f15bd384c0642ad49f0d97b690c3a05b166b9dcbad3b/python_discovery-1.4.2-py3-none-any.whl", hash = "sha256:475803f53b7b2ed6e490e27373f9d8340f7d2eebf9acdaf645d7d714c97bb500", size = 33886, upload-time = "2026-06-11T16:10:41.192Z" },
]
[[package]]
name = "pyyaml"
-version = "6.0.2"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309, upload-time = "2024-08-06T20:32:43.4Z" },
- { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679, upload-time = "2024-08-06T20:32:44.801Z" },
- { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428, upload-time = "2024-08-06T20:32:46.432Z" },
- { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361, upload-time = "2024-08-06T20:32:51.188Z" },
- { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523, upload-time = "2024-08-06T20:32:53.019Z" },
- { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660, upload-time = "2024-08-06T20:32:54.708Z" },
- { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597, upload-time = "2024-08-06T20:32:56.985Z" },
- { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload-time = "2024-08-06T20:33:03.001Z" },
- { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" },
-]
-
-[[package]]
-name = "ruff"
-version = "0.12.12"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/a8/f0/e0965dd709b8cabe6356811c0ee8c096806bb57d20b5019eb4e48a117410/ruff-0.12.12.tar.gz", hash = "sha256:b86cd3415dbe31b3b46a71c598f4c4b2f550346d1ccf6326b347cc0c8fd063d6", size = 5359915, upload-time = "2025-09-04T16:50:18.273Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/09/79/8d3d687224d88367b51c7974cec1040c4b015772bfbeffac95face14c04a/ruff-0.12.12-py3-none-linux_armv6l.whl", hash = "sha256:de1c4b916d98ab289818e55ce481e2cacfaad7710b01d1f990c497edf217dafc", size = 12116602, upload-time = "2025-09-04T16:49:18.892Z" },
- { url = "https://files.pythonhosted.org/packages/c3/c3/6e599657fe192462f94861a09aae935b869aea8a1da07f47d6eae471397c/ruff-0.12.12-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:7acd6045e87fac75a0b0cdedacf9ab3e1ad9d929d149785903cff9bb69ad9727", size = 12868393, upload-time = "2025-09-04T16:49:23.043Z" },
- { url = "https://files.pythonhosted.org/packages/e8/d2/9e3e40d399abc95336b1843f52fc0daaceb672d0e3c9290a28ff1a96f79d/ruff-0.12.12-py3-none-macosx_11_0_arm64.whl", hash = "sha256:abf4073688d7d6da16611f2f126be86523a8ec4343d15d276c614bda8ec44edb", size = 12036967, upload-time = "2025-09-04T16:49:26.04Z" },
- { url = "https://files.pythonhosted.org/packages/e9/03/6816b2ed08836be272e87107d905f0908be5b4a40c14bfc91043e76631b8/ruff-0.12.12-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:968e77094b1d7a576992ac078557d1439df678a34c6fe02fd979f973af167577", size = 12276038, upload-time = "2025-09-04T16:49:29.056Z" },
- { url = "https://files.pythonhosted.org/packages/9f/d5/707b92a61310edf358a389477eabd8af68f375c0ef858194be97ca5b6069/ruff-0.12.12-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42a67d16e5b1ffc6d21c5f67851e0e769517fb57a8ebad1d0781b30888aa704e", size = 11901110, upload-time = "2025-09-04T16:49:32.07Z" },
- { url = "https://files.pythonhosted.org/packages/9d/3d/f8b1038f4b9822e26ec3d5b49cf2bc313e3c1564cceb4c1a42820bf74853/ruff-0.12.12-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b216ec0a0674e4b1214dcc998a5088e54eaf39417327b19ffefba1c4a1e4971e", size = 13668352, upload-time = "2025-09-04T16:49:35.148Z" },
- { url = "https://files.pythonhosted.org/packages/98/0e/91421368ae6c4f3765dd41a150f760c5f725516028a6be30e58255e3c668/ruff-0.12.12-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:59f909c0fdd8f1dcdbfed0b9569b8bf428cf144bec87d9de298dcd4723f5bee8", size = 14638365, upload-time = "2025-09-04T16:49:38.892Z" },
- { url = "https://files.pythonhosted.org/packages/74/5d/88f3f06a142f58ecc8ecb0c2fe0b82343e2a2b04dcd098809f717cf74b6c/ruff-0.12.12-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9ac93d87047e765336f0c18eacad51dad0c1c33c9df7484c40f98e1d773876f5", size = 14060812, upload-time = "2025-09-04T16:49:42.732Z" },
- { url = "https://files.pythonhosted.org/packages/13/fc/8962e7ddd2e81863d5c92400820f650b86f97ff919c59836fbc4c1a6d84c/ruff-0.12.12-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:01543c137fd3650d322922e8b14cc133b8ea734617c4891c5a9fccf4bfc9aa92", size = 13050208, upload-time = "2025-09-04T16:49:46.434Z" },
- { url = "https://files.pythonhosted.org/packages/53/06/8deb52d48a9a624fd37390555d9589e719eac568c020b27e96eed671f25f/ruff-0.12.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2afc2fa864197634e549d87fb1e7b6feb01df0a80fd510d6489e1ce8c0b1cc45", size = 13311444, upload-time = "2025-09-04T16:49:49.931Z" },
- { url = "https://files.pythonhosted.org/packages/2a/81/de5a29af7eb8f341f8140867ffb93f82e4fde7256dadee79016ac87c2716/ruff-0.12.12-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:0c0945246f5ad776cb8925e36af2438e66188d2b57d9cf2eed2c382c58b371e5", size = 13279474, upload-time = "2025-09-04T16:49:53.465Z" },
- { url = "https://files.pythonhosted.org/packages/7f/14/d9577fdeaf791737ada1b4f5c6b59c21c3326f3f683229096cccd7674e0c/ruff-0.12.12-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a0fbafe8c58e37aae28b84a80ba1817f2ea552e9450156018a478bf1fa80f4e4", size = 12070204, upload-time = "2025-09-04T16:49:56.882Z" },
- { url = "https://files.pythonhosted.org/packages/77/04/a910078284b47fad54506dc0af13839c418ff704e341c176f64e1127e461/ruff-0.12.12-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b9c456fb2fc8e1282affa932c9e40f5ec31ec9cbb66751a316bd131273b57c23", size = 11880347, upload-time = "2025-09-04T16:49:59.729Z" },
- { url = "https://files.pythonhosted.org/packages/df/58/30185fcb0e89f05e7ea82e5817b47798f7fa7179863f9d9ba6fd4fe1b098/ruff-0.12.12-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5f12856123b0ad0147d90b3961f5c90e7427f9acd4b40050705499c98983f489", size = 12891844, upload-time = "2025-09-04T16:50:02.591Z" },
- { url = "https://files.pythonhosted.org/packages/21/9c/28a8dacce4855e6703dcb8cdf6c1705d0b23dd01d60150786cd55aa93b16/ruff-0.12.12-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:26a1b5a2bf7dd2c47e3b46d077cd9c0fc3b93e6c6cc9ed750bd312ae9dc302ee", size = 13360687, upload-time = "2025-09-04T16:50:05.8Z" },
- { url = "https://files.pythonhosted.org/packages/c8/fa/05b6428a008e60f79546c943e54068316f32ec8ab5c4f73e4563934fbdc7/ruff-0.12.12-py3-none-win32.whl", hash = "sha256:173be2bfc142af07a01e3a759aba6f7791aa47acf3604f610b1c36db888df7b1", size = 12052870, upload-time = "2025-09-04T16:50:09.121Z" },
- { url = "https://files.pythonhosted.org/packages/85/60/d1e335417804df452589271818749d061b22772b87efda88354cf35cdb7a/ruff-0.12.12-py3-none-win_amd64.whl", hash = "sha256:e99620bf01884e5f38611934c09dd194eb665b0109104acae3ba6102b600fd0d", size = 13178016, upload-time = "2025-09-04T16:50:12.559Z" },
- { url = "https://files.pythonhosted.org/packages/28/7e/61c42657f6e4614a4258f1c3b0c5b93adc4d1f8575f5229d1906b483099b/ruff-0.12.12-py3-none-win_arm64.whl", hash = "sha256:2a8199cab4ce4d72d158319b63370abf60991495fb733db96cd923a34c52d093", size = 12256762, upload-time = "2025-09-04T16:50:15.737Z" },
-]
-
-[[package]]
-name = "sniffio"
-version = "1.3.1"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" },
-]
-
-[[package]]
-name = "sqlalchemy"
-version = "2.0.41"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "greenlet", marker = "(python_full_version < '3.14' and platform_machine == 'AMD64') or (python_full_version < '3.14' and platform_machine == 'WIN32') or (python_full_version < '3.14' and platform_machine == 'aarch64') or (python_full_version < '3.14' and platform_machine == 'amd64') or (python_full_version < '3.14' and platform_machine == 'ppc64le') or (python_full_version < '3.14' and platform_machine == 'win32') or (python_full_version < '3.14' and platform_machine == 'x86_64')" },
- { name = "typing-extensions" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/63/66/45b165c595ec89aa7dcc2c1cd222ab269bc753f1fc7a1e68f8481bd957bf/sqlalchemy-2.0.41.tar.gz", hash = "sha256:edba70118c4be3c2b1f90754d308d0b79c6fe2c0fdc52d8ddf603916f83f4db9", size = 9689424, upload-time = "2025-05-14T17:10:32.339Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/d3/ad/2e1c6d4f235a97eeef52d0200d8ddda16f6c4dd70ae5ad88c46963440480/sqlalchemy-2.0.41-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4eeb195cdedaf17aab6b247894ff2734dcead6c08f748e617bfe05bd5a218443", size = 2115491, upload-time = "2025-05-14T17:55:31.177Z" },
- { url = "https://files.pythonhosted.org/packages/cf/8d/be490e5db8400dacc89056f78a52d44b04fbf75e8439569d5b879623a53b/sqlalchemy-2.0.41-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d4ae769b9c1c7757e4ccce94b0641bc203bbdf43ba7a2413ab2523d8d047d8dc", size = 2102827, upload-time = "2025-05-14T17:55:34.921Z" },
- { url = "https://files.pythonhosted.org/packages/a0/72/c97ad430f0b0e78efaf2791342e13ffeafcbb3c06242f01a3bb8fe44f65d/sqlalchemy-2.0.41-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a62448526dd9ed3e3beedc93df9bb6b55a436ed1474db31a2af13b313a70a7e1", size = 3225224, upload-time = "2025-05-14T17:50:41.418Z" },
- { url = "https://files.pythonhosted.org/packages/5e/51/5ba9ea3246ea068630acf35a6ba0d181e99f1af1afd17e159eac7e8bc2b8/sqlalchemy-2.0.41-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc56c9788617b8964ad02e8fcfeed4001c1f8ba91a9e1f31483c0dffb207002a", size = 3230045, upload-time = "2025-05-14T17:51:54.722Z" },
- { url = "https://files.pythonhosted.org/packages/78/2f/8c14443b2acea700c62f9b4a8bad9e49fc1b65cfb260edead71fd38e9f19/sqlalchemy-2.0.41-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c153265408d18de4cc5ded1941dcd8315894572cddd3c58df5d5b5705b3fa28d", size = 3159357, upload-time = "2025-05-14T17:50:43.483Z" },
- { url = "https://files.pythonhosted.org/packages/fc/b2/43eacbf6ccc5276d76cea18cb7c3d73e294d6fb21f9ff8b4eef9b42bbfd5/sqlalchemy-2.0.41-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f67766965996e63bb46cfbf2ce5355fc32d9dd3b8ad7e536a920ff9ee422e23", size = 3197511, upload-time = "2025-05-14T17:51:57.308Z" },
- { url = "https://files.pythonhosted.org/packages/fa/2e/677c17c5d6a004c3c45334ab1dbe7b7deb834430b282b8a0f75ae220c8eb/sqlalchemy-2.0.41-cp313-cp313-win32.whl", hash = "sha256:bfc9064f6658a3d1cadeaa0ba07570b83ce6801a1314985bf98ec9b95d74e15f", size = 2082420, upload-time = "2025-05-14T17:55:52.69Z" },
- { url = "https://files.pythonhosted.org/packages/e9/61/e8c1b9b6307c57157d328dd8b8348ddc4c47ffdf1279365a13b2b98b8049/sqlalchemy-2.0.41-cp313-cp313-win_amd64.whl", hash = "sha256:82ca366a844eb551daff9d2e6e7a9e5e76d2612c8564f58db6c19a726869c1df", size = 2108329, upload-time = "2025-05-14T17:55:54.495Z" },
- { url = "https://files.pythonhosted.org/packages/1c/fc/9ba22f01b5cdacc8f5ed0d22304718d2c758fce3fd49a5372b886a86f37c/sqlalchemy-2.0.41-py3-none-any.whl", hash = "sha256:57df5dc6fdb5ed1a88a1ed2195fd31927e705cad62dedd86b46972752a80f576", size = 1911224, upload-time = "2025-05-14T17:39:42.154Z" },
-]
-
-[[package]]
-name = "starlette"
-version = "0.46.2"
+version = "6.0.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" },
+ { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" },
+ { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" },
+ { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" },
+ { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" },
+ { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" },
+ { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" },
+ { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" },
+ { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" },
+ { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" },
+ { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" },
+ { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" },
+ { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" },
+ { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" },
+ { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" },
+]
+
+[[package]]
+name = "questionary"
+version = "2.1.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "anyio" },
+ { name = "prompt-toolkit" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/ce/20/08dfcd9c983f6a6f4a1000d934b9e6d626cff8d2eeb77a89a68eef20a2b7/starlette-0.46.2.tar.gz", hash = "sha256:7f7361f34eed179294600af672f565727419830b54b7b084efe44bb82d2fccd5", size = 2580846, upload-time = "2025-04-13T13:56:17.942Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/f6/45/eafb0bba0f9988f6a2520f9ca2df2c82ddfa8d67c95d6625452e97b204a5/questionary-2.1.1.tar.gz", hash = "sha256:3d7e980292bb0107abaa79c68dd3eee3c561b83a0f89ae482860b181c8bd412d", size = 25845, upload-time = "2025-08-28T19:00:20.851Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/8b/0c/9d30a4ebeb6db2b25a841afbb80f6ef9a854fc3b41be131d249a977b4959/starlette-0.46.2-py3-none-any.whl", hash = "sha256:595633ce89f8ffa71a015caed34a5b2dc1c0cdb3f0f1fbd1e69339cf2abeec35", size = 72037, upload-time = "2025-04-13T13:56:16.21Z" },
-]
-
-[[package]]
-name = "template"
-version = "1.0.0"
-source = { editable = "." }
-dependencies = [
- { name = "alembic" },
- { name = "fastapi" },
- { name = "pydantic", extra = ["email"] },
- { name = "pydantic-settings" },
- { name = "sqlalchemy" },
- { name = "uvicorn" },
-]
-
-[package.dev-dependencies]
-dev = [
- { name = "coverage" },
- { name = "httpx" },
- { name = "pre-commit" },
- { name = "pyrefly" },
- { name = "pytest" },
- { name = "pytest-cov" },
- { name = "ruff" },
-]
-
-[package.metadata]
-requires-dist = [
- { name = "alembic", specifier = ">=1.16.2" },
- { name = "fastapi", specifier = ">=0.115.0" },
- { name = "pydantic", extras = ["email"], specifier = ">=2.11.0" },
- { name = "pydantic-settings", specifier = ">=2.9.0" },
- { name = "sqlalchemy", specifier = ">=2.0.41" },
- { name = "uvicorn", specifier = ">=0.34.0" },
-]
-
-[package.metadata.requires-dev]
-dev = [
- { name = "coverage", extras = ["toml"], specifier = ">=7.9.0" },
- { name = "httpx", specifier = ">=0.28.0" },
- { name = "pre-commit", specifier = ">=4.2.0" },
- { name = "pyrefly", specifier = ">=1.0.0" },
- { name = "pytest", specifier = ">=8.4.0" },
- { name = "pytest-cov", specifier = ">=6.2.0" },
- { name = "ruff", specifier = ">=0.12,<0.13" },
+ { url = "https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl", hash = "sha256:a51af13f345f1cdea62347589fbb6df3b290306ab8930713bfae4d475a7d4a59", size = 36753, upload-time = "2025-08-28T19:00:19.56Z" },
]
[[package]]
name = "typing-extensions"
-version = "4.14.0"
+version = "4.15.0"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/d1/bc/51647cd02527e87d05cb083ccc402f93e441606ff1f01739a62c8ad09ba5/typing_extensions-4.14.0.tar.gz", hash = "sha256:8676b788e32f02ab42d9e7c61324048ae4c6d844a399eebace3d4979d75ceef4", size = 107423, upload-time = "2025-06-02T14:52:11.399Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/69/e0/552843e0d356fbb5256d21449fa957fa4eff3bbc135a74a691ee70c7c5da/typing_extensions-4.14.0-py3-none-any.whl", hash = "sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af", size = 43839, upload-time = "2025-06-02T14:52:10.026Z" },
+ { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
]
[[package]]
name = "typing-inspection"
-version = "0.4.1"
+version = "0.4.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726, upload-time = "2025-05-21T18:55:23.885Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552, upload-time = "2025-05-21T18:55:22.152Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" },
]
[[package]]
-name = "uvicorn"
-version = "0.34.3"
+name = "virtualenv"
+version = "21.4.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "click" },
- { name = "h11" },
+ { name = "distlib" },
+ { name = "filelock" },
+ { name = "platformdirs" },
+ { name = "python-discovery" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/de/ad/713be230bcda622eaa35c28f0d328c3675c371238470abdea52417f17a8e/uvicorn-0.34.3.tar.gz", hash = "sha256:35919a9a979d7a59334b6b10e05d77c1d0d574c50e0fc98b8b1a0f165708b55a", size = 76631, upload-time = "2025-06-01T07:48:17.531Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/4b/50/7564c805bb8966d9771caaba8a143fa5e57c848ce4e7fdf2d55a1feb2ead/virtualenv-21.4.3.tar.gz", hash = "sha256:938ff0fd3f4e0f0d3a025f67a3d2f25e3c3aabbcd5857ea6170619138d72d141", size = 7644454, upload-time = "2026-06-11T16:47:04.843Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/6d/0d/8adfeaa62945f90d19ddc461c55f4a50c258af7662d34b6a3d5d1f8646f6/uvicorn-0.34.3-py3-none-any.whl", hash = "sha256:16246631db62bdfbf069b0645177d6e8a77ba950cfedbfd093acef9444e4d885", size = 62431, upload-time = "2025-06-01T07:48:15.664Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/8d/84b0d07c6b5f685f85ddf6c87a59d3a8a895a3dfd89e759666fabe951b94/virtualenv-21.4.3-py3-none-any.whl", hash = "sha256:75f4127d4067397c64f38579ce918fec6bf9ca2cd4f48685e82952cc3c035840", size = 7625544, upload-time = "2026-06-11T16:47:01.78Z" },
]
[[package]]
-name = "virtualenv"
-version = "20.31.2"
+name = "wcwidth"
+version = "0.8.1"
source = { registry = "https://pypi.org/simple" }
-dependencies = [
- { name = "distlib" },
- { name = "filelock" },
- { name = "platformdirs" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/56/2c/444f465fb2c65f40c3a104fd0c495184c4f2336d65baf398e3c75d72ea94/virtualenv-20.31.2.tar.gz", hash = "sha256:e10c0a9d02835e592521be48b332b6caee6887f332c111aa79a09b9e79efc2af", size = 6076316, upload-time = "2025-05-08T17:58:23.811Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/49/b4/51fe890511f0f242d07cb1ebe6a5b6db417262b9d2568b460347c57d95cc/wcwidth-0.8.1.tar.gz", hash = "sha256:faf5b4a5366a72dc49cad48cdf21f52bdf63bdda995178e483ba247ff79089b9", size = 1466072, upload-time = "2026-06-08T05:57:23.146Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/f3/40/b1c265d4b2b62b58576588510fc4d1fe60a86319c8de99fd8e9fec617d2c/virtualenv-20.31.2-py3-none-any.whl", hash = "sha256:36efd0d9650ee985f0cad72065001e66d49a6f24eb44d98980f630686243cf11", size = 6057982, upload-time = "2025-05-08T17:58:21.15Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/6e/95b0e537de1f4d4301f76f944642c6da50d1511cc7b3d64dc418a66c7509/wcwidth-0.8.1-py3-none-any.whl", hash = "sha256:f453740b1e4a4f3291faa37944c555d71056c4da08d59809b307ef4feba695c8", size = 323092, upload-time = "2026-06-08T05:57:21.413Z" },
]