Skip to content

Commit 982289e

Browse files
derens99claude
andcommitted
feat: initial project setup with uv-dynamic-versioning
Python template with hatchling build backend, uv-dynamic-versioning for git-tag-based versioning, semantic-release for CI tag creation, ruff linting/formatting, ty type checking, and pytest. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
0 parents  commit 982289e

14 files changed

Lines changed: 1912 additions & 0 deletions

File tree

.github/workflows/ci.yml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main, dev, hotfix, "feat/**"]
6+
pull_request:
7+
branches: [main, dev, hotfix, "feat/**"]
8+
9+
jobs:
10+
ci:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v4
14+
with:
15+
fetch-depth: 0
16+
17+
- name: Install uv
18+
uses: astral-sh/setup-uv@v4
19+
with:
20+
version: "latest"
21+
22+
- name: Install dependencies
23+
run: uv sync --dev
24+
25+
- name: Run tests
26+
run: uv run pytest tests
27+
28+
- name: Type check
29+
run: uv run ty check src
30+
31+
- name: Ruff format check
32+
run: uv run ruff format src tests --check
33+
34+
- name: Ruff check
35+
run: uv run ruff check src tests

.github/workflows/publish.yml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
name: Publish
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
tag:
7+
description: "Tag to publish (e.g. v1.0.0)"
8+
required: true
9+
type: string
10+
repository_url:
11+
description: "PyPI repository URL (blank for PyPI)"
12+
required: false
13+
default: ""
14+
type: string
15+
16+
jobs:
17+
publish:
18+
runs-on: ubuntu-latest
19+
environment: pypi
20+
steps:
21+
- name: Checkout
22+
uses: actions/checkout@v4
23+
with:
24+
ref: ${{ inputs.tag }}
25+
26+
- name: Install uv
27+
uses: astral-sh/setup-uv@v4
28+
with:
29+
version: "latest"
30+
31+
- name: Install dependencies
32+
run: uv sync
33+
34+
- name: Build
35+
run: uv run hatch build
36+
37+
- name: Publish to PyPI
38+
uses: pypa/gh-action-pypi-publish@release/v1
39+
with:
40+
skip-existing: true
41+
repository-url: ${{ inputs.repository_url || 'https://pypi.org/legacy/' }}
42+
env:
43+
PYPI_API_TOKEN: ${{ secrets.PYPI_API_TOKEN }}

.github/workflows/release.yml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
branches: [main, dev, "feat/**"]
6+
workflow_dispatch:
7+
8+
jobs:
9+
release:
10+
runs-on: ubuntu-latest
11+
permissions:
12+
contents: write
13+
steps:
14+
- name: Checkout
15+
uses: actions/checkout@v4
16+
with:
17+
fetch-depth: 0
18+
19+
- name: Install uv
20+
uses: astral-sh/setup-uv@v4
21+
with:
22+
version: "latest"
23+
24+
- name: Install dependencies
25+
run: uv sync --dev
26+
27+
- name: Configure Git
28+
run: |
29+
git config user.name "github-actions[bot]"
30+
git config user.email "github-actions[bot]@users.noreply.github.com"
31+
32+
- name: Run semantic-release
33+
env:
34+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
35+
run: |
36+
BRANCH="${{ github.ref_name }}"
37+
if [[ "$BRANCH" == feat/* ]]; then
38+
# Avoid tag collisions between feat branches (e.g. v1.2.0-alpha.1+feat-my-feature)
39+
uv run semantic-release version --push --vcs-release --build-metadata "${BRANCH//\//-}"
40+
else
41+
uv run semantic-release version --push --vcs-release
42+
fi

.gitignore

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# Byte-compiled / optimized / DLL files
2+
__pycache__/
3+
*.py[cod]
4+
*$py.class
5+
6+
# C extensions
7+
*.so
8+
9+
# Distribution / packaging
10+
.Python
11+
build/
12+
develop-eggs/
13+
dist/
14+
downloads/
15+
eggs/
16+
.eggs/
17+
lib/
18+
lib64/
19+
parts/
20+
sdist/
21+
var/
22+
wheels/
23+
share/python-wheels/
24+
*.egg-info/
25+
.installed.cfg
26+
*.egg
27+
MANIFEST
28+
29+
# PyInstaller
30+
*.manifest
31+
*.spec
32+
33+
# Installer logs
34+
pip-log.txt
35+
pip-delete-this-directory.txt
36+
37+
# Unit test / coverage
38+
htmlcov/
39+
.tox/
40+
.nox/
41+
.coverage
42+
.coverage.*
43+
.cache
44+
nosetests.xml
45+
coverage.xml
46+
*.cover
47+
*.py,cover
48+
.hypothesis/
49+
.pytest_cache/
50+
pytest_cache/
51+
52+
# Type checkers
53+
.mypy_cache/
54+
.dmypy.json
55+
dmypy.json
56+
.pyre/
57+
.pytype/
58+
59+
# Ruff
60+
.ruff_cache/
61+
62+
# Jupyter
63+
.ipynb_checkpoints/
64+
65+
# Virtual environments
66+
.venv/
67+
venv/
68+
ENV/
69+
env/
70+
.env
71+
.env.local
72+
.env.*.local
73+
74+
# uv
75+
.uv/
76+
77+
# IDE / editor
78+
.idea/
79+
.vscode/
80+
*.swp
81+
*.swo
82+
*~
83+
84+
# OS
85+
.DS_Store
86+
Thumbs.db

.python-version

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

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2025
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

Makefile

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
.PHONY: install test typecheck format-check format lint build ci ci-fix release-dry-run
2+
3+
# One-time setup: install Python (if needed) and dependencies
4+
install:
5+
uv sync --dev
6+
7+
# Run tests
8+
test:
9+
uv run pytest tests
10+
11+
# Type check
12+
typecheck:
13+
uv run ty check src
14+
15+
# Check formatting (no write)
16+
format-check:
17+
uv run ruff format src tests --check
18+
19+
# Apply formatting
20+
format:
21+
uv run ruff format src tests
22+
23+
# Lint (with auto-fix)
24+
lint:
25+
uv run ruff check src tests
26+
27+
lint-fix:
28+
uv run ruff check src tests --fix --unsafe-fixes
29+
30+
# Build package
31+
build:
32+
uv build
33+
34+
# Full CI pipeline (same order as .github/workflows/ci.yml)
35+
ci: install test typecheck format-check lint build
36+
37+
# Apply format and lint fixes (no --check)
38+
ci-fix: format lint-fix
39+
40+
# Show next version from conventional commits (no tag/commit)
41+
release-dry-run:
42+
uv run semantic-release --noop version

README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# python-template
2+
3+
Add your description here.
4+
5+
## Project layout
6+
7+
- **`src/python_template/`** — your library/package code (importable as `python_template`)
8+
- **`main.py`** — CLI entry point; run with `uv run python main.py`
9+
- **`tests/`** — pytest tests
10+
11+
## Development
12+
13+
- **Install:** `uv sync`
14+
- **Tests:** `uv run pytest tests`
15+
- **Lint/format:** `uv run ruff check src tests` / `uv run ruff format src tests`
16+
- **Type check:** `uv run ty check src`
17+
18+
## Versioning & releases (conventional commits + semver)
19+
20+
This project uses [Conventional Commits](https://www.conventionalcommits.org/), [python-semantic-release](https://python-semantic-release.readthedocs.io/) for tag/release creation, and [uv-dynamic-versioning](https://github.com/ninoseki/uv-dynamic-versioning) to derive the package version from git tags at build time.
21+
22+
- **Commit format:** `type(scope): description` — e.g. `feat: add X`, `fix: correct Y`, `BREAKING CHANGE:` for major bumps.
23+
- **Version** is derived from git tags by `uv-dynamic-versioning` at build time — no version is stored in `pyproject.toml`. Types: `feat` → minor, `fix`/`perf` → patch, breaking change → major.
24+
- **Releases run on push** (and can be triggered manually):
25+
- **main** → full release (e.g. `v1.0.0`, `v1.1.0`)
26+
- **dev** → release candidate (e.g. `v1.0.0-rc.1`, `v1.1.0-rc.2`)
27+
- **feat/** → alpha (e.g. `v1.2.0-alpha.1`; build metadata includes branch name so tags don't clash)
28+
- **Publish (PyPI):** After a release from **main**, run the **Publish** workflow with the tag and set `PYPI_API_TOKEN`.
29+
- **Local dry run:** `uv run semantic-release --noop version` to see the next version without changing anything.
30+
- **Build:** `uv build` (wheels and sdist in `dist/`)

main.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
"""CLI entry point. Run with: uv run python main.py"""
2+
3+
from python_template import hello
4+
5+
6+
def main() -> None:
7+
print(hello())
8+
9+
10+
if __name__ == "__main__":
11+
main()

0 commit comments

Comments
 (0)