Skip to content

Commit b61a08c

Browse files
committed
0.1.0
0 parents  commit b61a08c

18 files changed

+7408
-0
lines changed

.gitignore

Whitespace-only changes.

README.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Copier Python Template
2+
3+
My personal Copier template for Python projects.
4+
5+
## Features
6+
7+
* **Modern Python tooling** with [uv](https://docs.astral.sh/uv/) for dependency management and environment setup
8+
* **Pre-configured ```pyproject.toml```**
9+
* **Automated workflows** - [just](https://github.com/casey/just) commands for testing, linting, and building
10+
* **Code quality tools**: [ruff](https://docs.astral.sh/ruff/) for linting and formatting, pre-commit hooks
11+
* **Testing setup** with [pytest](https://docs.pytest.org/en/stable/) and [Hatch](https://hatch.pypa.io/latest/) for cross-version testing
12+
* **GitHub Actions integration**: optional workflows for testing and PyPI publishing
13+
* **Licenses from** [choosealicense.com](https://choosealicense.com/)
14+
* **Basic ```README.md```** with badges, installation instructions, and project structure
15+
16+
## Requirements
17+
18+
* Python 3.8+
19+
* [Copier](https://copier.readthedocs.io/) (`pip install copier` or `uv tool install copier`)
20+
* Git
21+
22+
## Quick Setup
23+
24+
Create a new Python project:
25+
26+
```bash
27+
copier copy --trust "https://github.com/zigai/python-project-template.git" /path/to/your/project
28+
```
29+
30+
or
31+
32+
```bash
33+
copier copy --trust "gh:zigai/python-project-template" /path/to/your/project
34+
```
35+
36+
## Generated Project Structure
37+
38+
```
39+
your-project/
40+
├── your_package/
41+
│ └── __init__.py
42+
├── tests/
43+
│ └── test_your_package.py
44+
├── .github/workflows/
45+
│ └── publish.yml
46+
├── pyproject.toml
47+
├── README.md
48+
├── CONTRIBUTING.md
49+
├── Justfile
50+
├── LICENSE
51+
└── .gitignore
52+
```
53+
54+
## Similar Templates
55+
56+
* [copier-uv](https://github.com/pawamoy/copier-uv)
57+
* [python-copier-template](https://github.com/DiamondLightSource/python-copier-template)
58+
59+
## License
60+
61+
This template is released under the [MIT License](https://github.com/zigai/python-project-template/blob/master/LICENSE).

copier.yml

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
_templates_suffix: .jinja
2+
_envops:
3+
autoescape: false
4+
_jinja_extensions:
5+
- copier_templates_extensions.TemplateExtensionLoader
6+
- extensions.py:GitDefaultsExtension
7+
- extensions.py:PythonVersionExtension
8+
_subdirectory: "template"
9+
10+
_templates:
11+
- path: LICENSE.jinja
12+
when: "{{ license != 'None' }}"
13+
package_name:
14+
type: str
15+
help: Python package name (must be a valid identifier)
16+
placeholder: my_package
17+
18+
repo_name:
19+
type: str
20+
help: Repository name (typically package name with dashes)
21+
default: "{{ package_name | replace('_', '-') }}"
22+
23+
author_name:
24+
type: str
25+
help: Author name
26+
default: "{{ git_user_name }}"
27+
28+
author_email:
29+
type: str
30+
help: Author email address
31+
default: "{{ git_user_email }}"
32+
33+
description:
34+
type: str
35+
help: Project description
36+
placeholder: ""
37+
38+
repository_url:
39+
type: str
40+
help: GitHub repository URL
41+
default: "https://github.com/{{ github_username }}/{{ repo_name }}"
42+
43+
python_min_version:
44+
type: str
45+
help: Minimum supported Python version
46+
default: "3.10"
47+
choices:
48+
- "3.8"
49+
- "3.9"
50+
- "3.10"
51+
- "3.11"
52+
- "3.12"
53+
- "3.13"
54+
- "3.14"
55+
56+
python_max_version:
57+
type: str
58+
help: Maximum supported Python version
59+
default: "3.13"
60+
choices:
61+
- "3.8"
62+
- "3.9"
63+
- "3.10"
64+
- "3.11"
65+
- "3.12"
66+
- "3.13"
67+
- "3.14"
68+
69+
python_default_version:
70+
type: str
71+
help: Default Python version for development
72+
default: "3.12"
73+
choices:
74+
- "3.8"
75+
- "3.9"
76+
- "3.10"
77+
- "3.11"
78+
- "3.12"
79+
- "3.13"
80+
- "3.14"
81+
82+
copyright_license:
83+
type: str
84+
help: Your project's license
85+
default: None
86+
choices:
87+
No license: None
88+
MIT License: MIT
89+
Apache License 2.0: Apache-2.0
90+
GNU General Public License v3.0 only: GPL-3.0
91+
BSD 3-Clause "New" or "Revised" License: BSD-3-Clause
92+
The Unlicense: Unlicense
93+
zlib License: Zlib
94+
Academic Free License v3.0: AFL-3.0
95+
Artistic License 2.0: Artistic-2.0
96+
BSD 2-Clause "Simplified" License: BSD-2-Clause
97+
BSD 3-Clause Clear License: BSD-3-Clause-Clear
98+
Boost Software License 1.0: BSL-1.0
99+
Creative Commons Attribution 4.0 International: CC-BY-4.0
100+
Creative Commons Attribution Share Alike 4.0 International: CC-BY-SA-4.0
101+
Creative Commons Zero v1.0 Universal: CC0-1.0
102+
Do What The F*ck You Want To Public License: WTFPL
103+
Educational Community License v2.0: ECL-2.0
104+
Eclipse Public License 1.0: EPL-1.0
105+
Eclipse Public License 2.0: EPL-2.0
106+
European Union Public License 1.1: EUPL-1.1
107+
European Union Public License 1.2: EUPL-1.2
108+
GNU Affero General Public License v3.0: AGPL-3.0
109+
GNU General Public License v2.0 only: GPL-2.0
110+
GNU Lesser General Public License v2.1 only: LGPL-2.1
111+
GNU Lesser General Public License v3.0 only: LGPL-3.0
112+
ISC License: ISC
113+
LaTeX Project Public License v1.3c: LPPL-1.3c
114+
Mozilla Public License 2.0: MPL-2.0
115+
Microsoft Public License: MS-PL
116+
Microsoft Reciprocal License: MS-RL
117+
University of Illinois/NCSA Open Source License: NCSA
118+
SIL Open Font License 1.1: OFL-1.1
119+
Open Software License 3.0: OSL-3.0
120+
PostgreSQL License: PostgreSQL
121+
122+
readme_badges:
123+
type: bool
124+
help: Add badges to the README?
125+
default: true
126+
127+
publish_gh_action:
128+
type: bool
129+
help: Add a GitHub action for publishing to PyPi?
130+
default: true
131+
132+
_tasks:
133+
- '{% if copyright_license == ''None'' %}python -c "import os; os.remove(''LICENSE'') if os.path.exists(''LICENSE'') else None"{% endif %}'

extensions.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import re
2+
import subprocess
3+
4+
from jinja2.ext import Extension
5+
6+
7+
class GitDefaultsExtension(Extension):
8+
def __init__(self, environment):
9+
super().__init__(environment)
10+
environment.globals["git_user_name"] = self._get_git_config("user.name")
11+
environment.globals["git_user_email"] = self._get_git_config("user.email")
12+
environment.globals["github_username"] = self._get_github_username()
13+
14+
def _get_git_config(self, key: str) -> str:
15+
"""Get a git configuration value.
16+
17+
Args:
18+
key: The git config key to retrieve (e.g., 'user.name', 'user.email')
19+
20+
Returns:
21+
The configuration value or empty string if not found
22+
"""
23+
try:
24+
result = subprocess.run(
25+
["git", "config", "--get", key],
26+
capture_output=True,
27+
text=True,
28+
check=True,
29+
shell=True,
30+
)
31+
return result.stdout.strip()
32+
except (subprocess.CalledProcessError, FileNotFoundError):
33+
return ""
34+
35+
def _get_github_username(self) -> str:
36+
try:
37+
result = subprocess.run(
38+
["git", "remote", "-v"],
39+
capture_output=True,
40+
text=True,
41+
check=True,
42+
shell=True,
43+
)
44+
for line in result.stdout.splitlines():
45+
if "github.com" in line:
46+
# Extract username from URLs like:
47+
# [email protected]:username/repo.git
48+
# https://github.com/username/repo.git
49+
match = re.search(r"github\.com[:/]([^/]+)", line)
50+
if match:
51+
return match.group(1)
52+
53+
except (subprocess.CalledProcessError, FileNotFoundError):
54+
return ""
55+
return ""
56+
57+
58+
class PythonVersionExtension(Extension):
59+
def __init__(self, environment):
60+
super().__init__(environment)
61+
environment.globals["py_versions_range"] = self._py_versions_range
62+
environment.globals["py_classifiers"] = self._py_classifiers
63+
environment.globals["black_target_versions"] = self._black_target_versions
64+
65+
def _py_versions_range(self, min_version: str, max_version: str) -> list[str]:
66+
min_major, min_minor = map(int, min_version.split("."))
67+
_, max_minor = map(int, max_version.split("."))
68+
versions = []
69+
for minor in range(min_minor, max_minor + 1):
70+
versions.append(f"{min_major}.{minor}")
71+
return versions
72+
73+
def _py_classifiers(self, min_version: str, max_version: str) -> list[str]:
74+
versions = self._py_versions_range(min_version, max_version)
75+
return [f"Programming Language :: Python :: {version}" for version in versions]
76+
77+
def _black_target_versions(self, min_version: str, max_version: str) -> list[str]:
78+
versions = self._py_versions_range(min_version, max_version)
79+
return [f"py{version.replace('.', '')}" for version in versions]

template/.editorconfig

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
root = true
2+
3+
[*]
4+
indent_style = space
5+
indent_size = 4
6+
charset = utf-8
7+
trim_trailing_whitespace = true
8+
insert_final_newline = true
9+
10+
[{*.yaml, *.yml}]
11+
indent_size = 2
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
name: Lint
2+
on: [push, pull_request]
3+
jobs:
4+
lint:
5+
runs-on: ubuntu-latest
6+
steps:
7+
- uses: actions/checkout@v4
8+
- name: Install uv
9+
uses: astral-sh/setup-uv@v3
10+
- name: Set up Python
11+
run: uv python install {{ python_default_version }}
12+
- name: Install dependencies
13+
run: uv sync --extra dev
14+
- name: Run ruff
15+
run: uv run ruff check .
16+
- name: Run black
17+
run: uv run black --check .

template/.github/workflows/tests.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
name: Tests
2+
on: [push, pull_request]
3+
jobs:
4+
tests:
5+
runs-on: ubuntu-latest
6+
strategy:
7+
fail-fast: false
8+
matrix:
9+
python-version: ["3.10", "3.11", "3.12", "3.13"]
10+
steps:
11+
- uses: actions/checkout@v4
12+
- name: Install uv
13+
uses: astral-sh/setup-uv@v3
14+
- name: Set up Python ${{ matrix.python-version }}
15+
run: uv python install ${{ matrix.python-version }}
16+
- name: Install dependencies
17+
run: uv sync --extra test --python ${{ matrix.python-version }}
18+
- name: Run pytest
19+
run: uv run pytest -v
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
name: Upload Python Package
2+
on:
3+
release:
4+
types: [published]
5+
jobs:
6+
deploy:
7+
runs-on: ubuntu-latest
8+
steps:
9+
- uses: actions/checkout@v4
10+
- name: Install uv
11+
uses: astral-sh/setup-uv@v3
12+
- name: Set up Python
13+
run: uv python install {{ python_default_version }}
14+
- name: Build and publish
15+
env:
16+
TWINE_USERNAME: __token__
17+
{% raw %}
18+
TWINE_PASSWORD: ${{ secrets.TOKEN }}
19+
{% endraw %}
20+
run: |
21+
uv build
22+
uv run --with twine twine upload dist/*

0 commit comments

Comments
 (0)