Skip to content
This repository was archived by the owner on Sep 24, 2025. It is now read-only.

Commit 0f28455

Browse files
committed
feat: init
1 parent 339b05f commit 0f28455

12 files changed

Lines changed: 994 additions & 1 deletion

File tree

.github/workflows/ci.yaml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
name: Continuous Integration
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
quality-check:
11+
name: Lint, Format, Type Check, Test
12+
runs-on: ubuntu-latest
13+
container:
14+
image: ghcr.io/astral-sh/uv:python3.12-bookworm-slim
15+
16+
steps:
17+
- uses: actions/checkout@v4
18+
- name: Install dependencies
19+
run: uv sync --locked
20+
- name: Lint (ruff)
21+
run: uv run ruff check . --fix
22+
- name: Format (ruff format)
23+
run: uv run ruff format .
24+
- name: Type check (pyright)
25+
run: uv run pyright
26+
- name: Run unit tests
27+
run: uv run pytest

.pre-commit-config.yaml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# See https://pre-commit.com for more hooks
2+
repos:
3+
- repo: https://github.com/pre-commit/pre-commit-hooks
4+
rev: v5.0.0
5+
hooks:
6+
- id: check-merge-conflict
7+
- id: debug-statements
8+
- id: trailing-whitespace
9+
- id: check-added-large-files
10+
- id: check-ast
11+
- id: check-case-conflict
12+
- id: check-json
13+
- id: check-toml
14+
- id: check-yaml
15+
- id: end-of-file-fixer
16+
17+
- repo: https://github.com/astral-sh/ruff-pre-commit
18+
rev: v0.13.1
19+
hooks:
20+
- id: ruff
21+
args:
22+
- --fix
23+
- id: ruff-format
24+
25+
- repo: https://github.com/astral-sh/uv-pre-commit
26+
rev: 0.8.20
27+
hooks:
28+
- id: uv-lock
29+
30+
- repo: https://github.com/RobertCraigie/pyright-python
31+
rev: v1.1.405
32+
hooks:
33+
- id: pyright
34+
35+
- repo: https://github.com/commitizen-tools/commitizen
36+
rev: v4.9.1
37+
hooks:
38+
- id: commitizen
39+
stages: [commit-msg]
40+
- id: commitizen-branch
41+
stages: [pre-push]
42+
# pre-commit install --hook-type commit-msg --hook-type pre-push

.python-version

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
3.12

Dockerfile

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim
2+
3+
# Use 'code' to avoid confusion with the 'app' directory
4+
WORKDIR /code
5+
6+
# Compile bytecode and avoid symlinks
7+
ENV UV_COMPILE_BYTECODE=1
8+
ENV UV_LINK_MODE=copy
9+
10+
# ---------- layer 1: heavy dependencies ----------
11+
# Cache only invalidates when these two files change
12+
COPY pyproject.toml uv.lock ./
13+
14+
RUN --mount=type=cache,target=/root/.cache/uv \
15+
uv sync --locked --no-dev
16+
17+
# ---------- layer 2: application code ----------
18+
COPY . /code
19+
20+
# Put the venv at the beginning of PATH
21+
ENV PATH="/code/.venv/bin:$PATH"
22+
23+
# Do not use uv as the entrypoint
24+
ENTRYPOINT []
25+
26+
CMD ["uv", "run", "main.py"]

README.md

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,63 @@
11
# uv-python-workflow
2-
A modern Python workflow powered by uv — includes CI/CD, Docker, pre-commit, and Pyright type checking.
2+
3+
A modern Python workflow powered by [uv](https://github.com/astral-sh/uv).
4+
This template integrates **CI/CD, Docker, pre-commit hooks, and Pyright type checking** for a reproducible and maintainable development setup.
5+
6+
## ✨ Features
7+
8+
- 🚀 **uv** for fast, reliable dependency management
9+
-**pre-commit** hooks (lint, format, type-check) (uv add --dev pre-commit)
10+
- 🔎 **Pyright** for static type checking (uv add --dev "pyright[nodejs]")
11+
- 🐳 **Dockerfile** for reproducible environments
12+
- 🔄 **CI/CD (GitHub Actions)** with linting, formatting, type checking, and tests
13+
14+
## 📦 Getting Started
15+
16+
```bash
17+
# Install dependencies
18+
uv sync
19+
20+
# Run app
21+
uv run main.py
22+
```
23+
24+
## 🧹 Pre-commit Hooks
25+
26+
Configured with [pre-commit](https://pre-commit.com/):
27+
28+
```bash
29+
pre-commit install --install-hooks --hook-type pre-commit --hook-type commit-msg --hook-type pre-push
30+
pre-commit run --all-files # Run all hooks on all files (only needed once after installation)
31+
```
32+
33+
## 🐳 Docker
34+
35+
```bash
36+
docker build -t uv-python-workflow .
37+
docker run --rm uv-python-workflow
38+
```
39+
40+
## 🔄 CI/CD
41+
42+
GitHub Actions workflow (`.github/workflows/ci.yml`) runs:
43+
44+
- Dependency install with `uv`
45+
- Linting & formatting with `ruff`
46+
- Type checking with `pyright`
47+
- Tests with `pytest`
48+
49+
## 🧭 Commitizen (Conventional Commits)
50+
51+
```powershell
52+
# Install (dev)
53+
uv add --dev commitizen
54+
55+
# Stage changes
56+
git add .
57+
58+
# Interactive commit
59+
cz commit or cz c
60+
61+
# Semantic version bump + tag (optional)
62+
cz bump --yes
63+
```

main.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
"""
2+
Entry point demonstrating usage of the calculator module.
3+
4+
Runs a simple example that prints the result of adding two numbers using the
5+
``src.calculator`` module.
6+
"""
7+
8+
from src.calculator import add
9+
10+
11+
def main() -> None:
12+
"""Run the example and print a simple addition result."""
13+
14+
result = add(1, 2)
15+
print(f"Example: 1 + 2 = {result}")
16+
17+
18+
if __name__ == "__main__":
19+
main()

pyproject.toml

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
[project]
2+
dependencies = []
3+
description = "Add your description here"
4+
name = "uv-python-workflow"
5+
readme = "README.md"
6+
requires-python = ">=3.12"
7+
version = "0.1.0"
8+
9+
[dependency-groups]
10+
dev = [
11+
"commitizen>=4.9.1",
12+
"pre-commit>=4.3.0",
13+
"pyright[nodejs]>=1.1.405",
14+
"pytest>=8.4.2",
15+
"pytest-cov>=7.0.0",
16+
"pytest-sugar>=1.1.1",
17+
]
18+
19+
[tool.commitizen]
20+
major_version_zero = true
21+
name = "cz_conventional_commits"
22+
tag_format = "v$version"
23+
update_changelog_on_bump = true
24+
version_provider = "pyproject"
25+
version_scheme = "pep440"
26+
27+
[tool.ruff] # https://docs.astral.sh/ruff/settings/#top-level
28+
exclude = [".venv"]
29+
line-length = 100
30+
31+
[tool.ruff.lint] # https://docs.astral.sh/ruff/settings/#lint
32+
extend-select = [
33+
"FAST001", # fast-api-redundant-response-model
34+
"FAST002", # fast-api-non-annotated-dependency
35+
"D100", # missing-module-docstring
36+
"D101", # undocumented-public-class
37+
"D102", # missing-class-docstring
38+
"D103", # undocumented-public-function
39+
"D213", # multi-line-summary-second-line
40+
"D400", # missing-trailing-period
41+
"ERA001", # commented-out-code
42+
]
43+
select = [
44+
"B", # flake8-bugbear
45+
"E", # pycodestyle
46+
"F", # pyflakes
47+
"I", # isort
48+
"S", # bandit
49+
"W", # pycodestyle
50+
"UP", # pyupgrade
51+
"C4", # flake8-comprehensions
52+
"ANN", # flake8-annotations
53+
"TRY", # tryceratops
54+
"PERF", # perflint
55+
"ASYNC", # flake8-async
56+
]
57+
58+
# Globally ignore noisy rules (example)
59+
ignore = [
60+
"E402", # module level import not at top of file
61+
"TRY003", # Avoid specifying long messages outside the exception class
62+
"TRY301", # Abstract `raise` to an inner function
63+
]
64+
65+
[tool.ruff.lint.per-file-ignores]
66+
"tests/*" = [
67+
"S101", # Allow use of assert statements in tests
68+
"S105", # hardcoded-password-string
69+
"S106", # hardcoded-password-func-arg
70+
]
71+
72+
[tool.ruff.format] # https://docs.astral.sh/ruff/settings/#format
73+
quote-style = "double" # Quote style, double quotes are default
74+
75+
[tool.coverage.run]
76+
branch = true
77+
omit = [
78+
"tests/*",
79+
".venv/*",
80+
]
81+
source = ["src"]
82+
83+
[tool.coverage.report]
84+
exclude_also = [
85+
"def __repr__",
86+
"if self\\.debug",
87+
"raise AssertionError",
88+
"raise NotImplementedError",
89+
"if 0:",
90+
"if __name__ == .__main__.:",
91+
"if TYPE_CHECKING:",
92+
"class .*\\bProtocol\\):",
93+
"@(abc\\.)?abstractmethod",
94+
]
95+
ignore_errors = true
96+
show_missing = true
97+
# skip_covered = true
98+
99+
[tool.coverage.xml]
100+
output = "coverage.xml"
101+
102+
[tool.coverage.html] # pytest --cov=src --cov-report=html
103+
directory = "coverage_html_report"
104+
show_contexts = true
105+
title = "Coverage Report"
106+
107+
[tool.pytest.ini_options]
108+
addopts = "-ra --strict-config --strict-markers --cov=src --cov-report=xml --cov-report=term-missing"
109+
filterwarnings = ["error"]
110+
markers = [
111+
"asyncio: marks tests as asyncio tests",
112+
]
113+
python_files = ["test_*.py"]
114+
testpaths = ["tests"]
115+
116+
[tool.pyright]
117+
exclude = [
118+
".venv",
119+
]
120+
typeCheckingMode = "basic"
121+
venv = ".venv"
122+
venvPath = "."

src/__init__.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
"""
2+
Public API for the calculator example package.
3+
4+
This module re-exports arithmetic functions for convenient imports:
5+
``from src import add, subtract, multiply, divide``.
6+
"""
7+
8+
from .calculator import add, divide, multiply, subtract
9+
10+
__all__ = ["add", "subtract", "multiply", "divide"]

src/calculator.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Enable postponed evaluation of type annotations (PEP 563)
2+
"""
3+
Calculator module providing basic arithmetic functions.
4+
5+
This module exposes four operations: addition, subtraction, multiplication, and
6+
division. All functions accept integers or floats and return the corresponding
7+
numeric result. Division by zero raises ``ZeroDivisionError``.
8+
"""
9+
10+
from __future__ import annotations
11+
12+
Number = int | float
13+
14+
15+
def add(a: Number, b: Number) -> Number:
16+
"""Return the sum of ``a`` and ``b``."""
17+
18+
return a + b
19+
20+
21+
def subtract(a: Number, b: Number) -> Number:
22+
"""Return the difference of ``a`` minus ``b``."""
23+
24+
return a - b
25+
26+
27+
def multiply(a: Number, b: Number) -> Number:
28+
"""Return the product of ``a`` and ``b``."""
29+
30+
return a * b
31+
32+
33+
def divide(a: Number, b: Number) -> Number:
34+
"""
35+
Return the quotient of ``a`` divided by ``b``.
36+
37+
Raises
38+
------
39+
ZeroDivisionError
40+
If ``b`` equals 0.
41+
"""
42+
43+
if b == 0:
44+
raise ZeroDivisionError("division by zero")
45+
return a / b

tests/conftest.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
"""
2+
Pytest configuration to ensure `src.*` imports work during tests.
3+
4+
This adds the project root directory to ``sys.path`` so that the ``src``
5+
directory is importable as a top-level package in tests.
6+
"""
7+
8+
import sys
9+
from pathlib import Path
10+
11+
PROJECT_ROOT = Path(__file__).resolve().parents[1]
12+
if str(PROJECT_ROOT) not in sys.path:
13+
sys.path.insert(0, str(PROJECT_ROOT))

0 commit comments

Comments
 (0)