Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions cookiecutter.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,21 @@
"version": "0.1.0",
"description": "TODO",
"python_version": "3.12",
"include_fastapi": true,
"include_metaflow": true,
"include_package": true,
"coverage_threshold": 80,
"__prompts__": {
"user": "GitHub User",
"name": "Project Name",
"repository": "GitHub Repository",
"package": "Python Package",
"license": "Project License",
Copy link

Copilot AI Dec 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The 'package' prompt was removed from prompts but the 'package' field still exists in the main configuration (line 6). If the package field should remain configurable, restore its prompt; otherwise, document that it defaults to the repository name.

Copilot uses AI. Check for mistakes.
"version": "Project Version",
"description": "Project Description",
"python_version": "Python Version"
"python_version": "Python Version",
"include_fastapi": "Include FastAPI component? (y/n)",
"include_metaflow": "Include Metaflow component? (y/n)",
"include_package": "Include Python package component? (y/n)",
"coverage_threshold": "Minimum code coverage percentage (cov-fail-under)"
}
}
30 changes: 30 additions & 0 deletions hooks/post_gen_project.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import shutil
import os

def is_false(val):
return str(val).strip().lower() in ("false", "n", "no", "0")

# Remove FastAPI component if not included
if is_false('{{cookiecutter.include_fastapi}}'):
shutil.rmtree('src/fastapi_app', ignore_errors=True)

# Remove Metaflow component if not included
if is_false('{{cookiecutter.include_metaflow}}'):
shutil.rmtree('src/metaflow_app', ignore_errors=True)

# Remove Python package component if not included
if is_false('{{cookiecutter.include_package}}'):
shutil.rmtree("src/{{cookiecutter.package}}", ignore_errors=True)
# Remove publish workflow if present
workflow_path = os.path.join('.github', 'workflows', 'publish.yml')
if os.path.exists(workflow_path):
os.remove(workflow_path)
# Remove publish badge from README if present
readme_path = os.path.join('.', 'README.md')
if os.path.exists(readme_path):
with open(readme_path, 'r') as f:
lines = f.readlines()
with open(readme_path, 'w') as f:
for line in lines:
if 'publish.yml' not in line:
f.write(line)
20 changes: 14 additions & 6 deletions tests/test_cookiecutter.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ def test_project_generation(cookies: Cookies) -> None:
"version": "1.0.0",
"description": "A test project.",
"python_version": "3.13",
"include_fastapi": "y",
"include_metaflow": "y",
"include_package": "y",
"coverage_threshold": "1"
}
repository = context['name'].lower().replace(' ', '-')
package = repository.replace('-', '_')
Expand All @@ -44,14 +48,18 @@ def test_project_generation(cookies: Cookies) -> None:
assert result.project_path.is_dir()
assert result.project_path.name == repository
assert result.context == {
"user": context['user'],
"name": context['name'],
"user": context["user"],
"name": context["name"],
"package": package,
"repository": repository,
"license": context['license'],
"version": context['version'],
"description": context['description'],
"python_version": context['python_version'],
"license": context["license"],
"version": context["version"],
"description": context["description"],
"python_version": context["python_version"],
"include_fastapi": context["include_fastapi"],
"include_metaflow": context["include_metaflow"],
"include_package": context["include_package"],
"coverage_threshold": context["coverage_threshold"]
}
# - commands
shell = Subprocess(cwd=result.project_path)
Expand Down
29 changes: 20 additions & 9 deletions {{cookiecutter.repository}}/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
# https://docs.docker.com/engine/reference/builder/
# https://docs.astral.sh/uv/guides/integration/docker/#intermediate-layers

FROM ghcr.io/astral-sh/uv:python{{cookiecutter.python_version}}-bookworm
COPY dist/*.whl .
RUN uv pip install --system *.whl
CMD ["{{cookiecutter.repository}}", "--help"]
# Install uv
FROM python:{{cookiecutter.python_version}}-slim
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/

COPY ./src /app
ENV PYTHONPATH="${PYTHONPATH}:/app"
# Change the working directory to the `app` directory
WORKDIR /app

CMD ["{{cookiecutter.repository}}", "--help"]
# Example command
# CMD ["uvicorn", "example_app.main:app", "--host", "0.0.0.0"]
# Install dependencies
RUN --mount=type=cache,target=/root/.cache/uv \
--mount=type=bind,source=uv.lock,target=uv.lock \
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
uv sync --locked --no-install-project

# Copy the project into the image
ADD . /app

# Sync the project
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --locked

# Default command (overridden by docker-compose)
CMD ["python", "--version"]
25 changes: 4 additions & 21 deletions {{cookiecutter.repository}}/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,20 @@

{{cookiecutter.description}}.

(This README is generated from a cookiecutter template. Delete this comment and modify your README!)
This README is generated from a cookiecutter template. Delete this comment and modify your README!

# Installation

Initialize your project with the provided `just` command.
```bash
```shell
# Install dependencies and pre-commit hooks
uv run just install
```
# Usage

(The source comes with an example python package and an example FastAPI app. Delete this comment and add details for your application.)

Test the example package
```bash
uv run {{cookiecutter.repository}}
```

Test the example API with Docker:
```bash
uv add fastapi uvicorn
uv run just package
The provided template apps can be executed with the existing Docker templates. **Note:** Dependencies are not yet parametrized and need to be added using `uv add`

```shell
# Invoke docker compose
uv run just docker-compose

Expand All @@ -45,16 +36,8 @@ docker build --platform linux/amd64 -t {{cookiecutter.repository}}-image -f Dock
docker run -it --platform linux/amd64 --name {{cookiecutter.repository}}-ctr -p 8000:8000 {{cookiecutter.repository}}-image
```

Test the API using the local environment:
```bash
cd src
uv run uvicorn example_app.main:app --reload
```

## Development Features

(This section was copied into the created project's README so tool info is available to users.)

* **Streamlined Project Structure:** A well-defined directory layout for source code, tests, documentation, tasks, and Docker configurations.
Uv Integration: Effortless dependency management and packaging with [uv](https://docs.astral.sh/uv/).
* **Automated Testing and Checks:** Pre-configured workflows using [Pytest](https://docs.pytest.org/), [Ruff](https://docs.astral.sh/ruff/), [Mypy](https://mypy.readthedocs.io/), [Bandit](https://bandit.readthedocs.io/), and [Coverage](https://coverage.readthedocs.io/) to ensure code quality, style, security, and type safety.
Expand Down
24 changes: 20 additions & 4 deletions {{cookiecutter.repository}}/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,26 @@
# https://docs.docker.com/compose/compose-file/

services:
fastapi_app: # Name your service
build: . # Build the image from the current directory (.)
{% if cookiecutter.include_fastapi %}
fastapi_app:
build: .
command: ["uvicorn", "example_app.main:app", "--host", "0.0.0.0", "--reload"]
ports:
- "8000:8000" # Map container port 8000 to host port 8000
- "8000:8000"
volumes:
- ./src:/app/src # Mount your source code directory
- ./src:/app/src
{% endif %}
{% if cookiecutter.include_metaflow %}
metaflow_app:
build: .
command: ["python", "metaflow_app/spin_prototype.py"]
volumes:
- ./src:/app/src
{% endif %}
{% if cookiecutter.include_package %}
python_package:
build: .
command: ["python", "-m", "{{cookiecutter.package}}"]
volumes:
- ./src:/app/src
{% endif %}
8 changes: 7 additions & 1 deletion {{cookiecutter.repository}}/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,12 @@ check = [
"ruff>=0.9.9",
]
commit = ["commitizen>=4.4.1", "pre-commit>=4.1.0"]
dev = ["rust-just>=1.39.0"]
dev =[
"loguru>=0.7.3",
"rust-just>=1.39.0",
"locust>=2.31.8,<3.0.0",
"pyinstrument>=4.7.3,<5.0.0",
]
doc = ["pdoc>=15.0.1"]

# TOOLS
Expand Down Expand Up @@ -84,6 +89,7 @@ fix = true
indent-width = 4
line-length = 100
target-version = "py313"
src = ["src"]

[tool.ruff.lint]
extend-select = ["I"]
Expand Down
Empty file.
Empty file.
Loading