Skip to content
Open
Show file tree
Hide file tree
Changes from 17 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
4 changes: 3 additions & 1 deletion hooks/pre_gen_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
namespace_import = "{{ cookiecutter.project_namespace_import }}"
if namespace_import and not re.match(NAMESPACE_REGEX, namespace_import):
print(f"ERROR: '{namespace_import}' is not a valid Python namespace import path!")
print(f" It must follow regex '{NAMESPACE_REGEX}', i.e. 'one_two' or 'one_two.three'")
print(
f" It must follow regex '{NAMESPACE_REGEX}', i.e. 'one_two' or 'one_two.three'"
)
sys.exit(1)


Expand Down
3 changes: 2 additions & 1 deletion {{cookiecutter.project_slug}}/.github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ jobs:
strategy:
matrix:
python:
- "3.9"
- "3.10"
- "3.11"
- "3.12"
- "3.13"
- "3.14"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
Expand Down
46 changes: 46 additions & 0 deletions {{cookiecutter.project_slug}}/.pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Pre-commit hooks for code quality
# See https://pre-commit.com for more information
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.14.8
hooks:
# Run the linter.
- id: ruff-check
args: [--fix]
# Run the formatter.
- id: ruff-format

- repo: local
hooks:
- id: pyright
name: pyright type check
entry: uv run pyright
language: system
types: [python]
pass_filenames: false
require_serial: true

{%- if cookiecutter.docstring_coverage %}
- id: interrogate
name: interrogate docstring coverage
entry: uv run interrogate -c pyproject.toml
language: system
types: [python]
pass_filenames: false
{%- endif %}

- id: pytest
name: pytest
entry: uv run pytest
language: system
types: [python]
pass_filenames: false
require_serial: true
# Only run on pre-commit, not on push
stages: [pre-commit]

# Configuration
ci:
autofix_prs: true
autoupdate_schedule: weekly
8 changes: 3 additions & 5 deletions {{cookiecutter.project_slug}}/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ all:

.PHONY: dev
dev: $(VENV)/pyvenv.cfg
uv run pre-commit install

{%- if cookiecutter.entry_point %}
.PHONY: run
Expand All @@ -59,11 +60,8 @@ $(VENV)/pyvenv.cfg: pyproject.toml
lint: $(VENV)/pyvenv.cfg
uv run ruff format --check && \
uv run ruff check && \
uv run mypy

{%- if cookiecutter.docstring_coverage %}
uv run interrogate -c pyproject.toml .
{%- endif %}
uv run pyright{% if cookiecutter.docstring_coverage %} && \
uv run interrogate -c pyproject.toml .{% endif %}

.PHONY: reformat
reformat:
Expand Down
76 changes: 47 additions & 29 deletions {{cookiecutter.project_slug}}/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ dynamic = ["version"]
description = "{{ cookiecutter.project_description }}"
readme = "README.md"
license-files = ["LICENSE"]

{%- if cookiecutter.license == "Apache 2.0" %}
license = "Apache-2.0"
{%- elif cookiecutter.license == "AGPL v3" %}
Expand All @@ -20,7 +19,7 @@ classifiers = [
"Programming Language :: Python :: 3",
]
dependencies = []
requires-python = ">=3.9"
requires-python = ">=3.10"

[tool.setuptools.dynamic]
version = { attr = "{{ cookiecutter.__project_import }}.__version__" }
Expand All @@ -35,16 +34,16 @@ test = ["pytest", "pytest-cov", "pretend", "coverage[toml]"]
lint = [
# NOTE: ruff is under active development, so we pin conservatively here
# and let Dependabot periodically perform this update.
"ruff ~= 0.6.2",
"mypy >= 1.0",
"ruff ~= 0.14.8",
"pyright >= 1.1",
"types-html5lib",
"types-requests",
"types-toml",
{%- if cookiecutter.docstring_coverage %}
"interrogate",
{%- endif %}
]
dev = ["{{ cookiecutter.project_slug }}[doc,test,lint]", "twine", "build"]
dev = ["{{ cookiecutter.project_slug }}[doc,test,lint]", "twine", "build", "pre-commit"]

{% if cookiecutter.entry_point -%}
[project.scripts]
Expand All @@ -61,46 +60,59 @@ Source = "https://github.com/{{ cookiecutter.github_username }}/{{ cookiecutter.
# don't attempt code coverage for the CLI entrypoints
omit = ["{{ cookiecutter.__project_src_path }}/_cli.py"]

[tool.mypy]
mypy_path = "src"
packages = "{{ cookiecutter.__project_import }}"
allow_redefinition = true
check_untyped_defs = true
disallow_incomplete_defs = true
disallow_untyped_defs = true
ignore_missing_imports = true
no_implicit_optional = true
show_error_codes = true
sqlite_cache = true
strict_equality = true
warn_no_return = true
warn_redundant_casts = true
warn_return_any = true
warn_unreachable = true
warn_unused_configs = true
warn_unused_ignores = true
[tool.pyright]
# Type checking configuration
include = ["src", "test"]
pythonVersion = "3.10"
typeCheckingMode = "strict"
useLibraryCodeForTypes = true
reportMissingTypeStubs = false

[tool.ruff]
line-length = 100
include = ["src/**/*.py", "test/**/*.py"]
target-version = "py310"

[tool.ruff.format]
line-ending = "lf"
quote-style = "double"

[tool.ruff.lint]
select = ["ALL"]
# D203 and D213 are incompatible with D211 and D212 respectively.
# COM812 and ISC001 can cause conflicts when using ruff as a formatter.
# See https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules.
ignore = ["D203", "D213", "COM812", "ISC001"]
ignore = [
"D203", # Incompatible with D211
"D213", # Incompatible with D212
"COM812", # Can conflict with formatter
"ISC001", # Can conflict with formatter
]

[tool.ruff.lint.mccabe]
# Maximum cyclomatic complexity
max-complexity = 8

[tool.ruff.lint.pydocstyle]
# Use Google-style docstrings
convention = "google"

[tool.ruff.lint.pylint]
# Maximum number of branches for function or method
max-branches = 12
# Maximum number of return statements in function or method
max-returns = 6
# Maximum number of positional arguments for function or method
max-positional-args = 5

[tool.ruff.lint.per-file-ignores]
{% if cookiecutter.entry_point -%}
"{{ cookiecutter.__project_src_path }}/_cli.py" = [
"T201", # allow `print` in cli module
"T201", # allow print in cli module
]
{%- endif %}
"test/**/*.py" = [
"D", # no docstrings in tests
"S101", # asserts are expected in tests
"PLR2004", # Allow magic values in tests
]
"**/conftest.py" = ["D"] # No docstrings in pytest config

{%- if cookiecutter.docstring_coverage %}
[tool.interrogate]
Expand All @@ -110,3 +122,9 @@ exclude = ["env", "test", "{{ cookiecutter.__project_src_path }}/_cli.py"]
ignore-semiprivate = true
fail-under = 100
{%- endif %}

[tool.pytest.ini_options]
testpaths = ["test"]
python_files = ["test_*.py"]
# Show test durations
addopts = "--durations=10"