From f5378266829bd06625ac6f46e861ace2f2f3f2ef Mon Sep 17 00:00:00 2001 From: Dan Guido Date: Tue, 29 Jul 2025 18:58:55 -0400 Subject: [PATCH 01/17] feat: enhance cookiecutter template with stricter standards MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Update Python requirement to 3.11+ to match CLAUDE.md standards - Replace mypy with Astral's experimental ty type checker - Add comprehensive ruff.toml with strict code quality constraints: - Cyclomatic complexity ≤ 8 - Max 5 positional args, 12 branches, 6 returns - Google-style docstrings enforced - Add data library option (polars/pandas/none) in cookiecutter.json - Create pre-commit hooks configuration for automated checks - Add CLAUDE.md template with project-specific instructions - Configure pytest to support tests beside code - Update Makefile with common commands (ty, fix, check) - Remove Python 3.9/3.10 from CI matrix These changes align the cookiecutter template with modern Python development practices and enforce stricter code quality standards. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- cookiecutter.json | 8 +- .../.github/workflows/tests.yml | 2 - .../.pre-commit-config.yaml | 45 +++++ {{cookiecutter.project_slug}}/CLAUDE.md | 155 ++++++++++++++++++ {{cookiecutter.project_slug}}/Makefile | 19 ++- {{cookiecutter.project_slug}}/pyproject.toml | 65 +++----- {{cookiecutter.project_slug}}/ruff.toml | 45 +++++ 7 files changed, 289 insertions(+), 50 deletions(-) create mode 100644 {{cookiecutter.project_slug}}/.pre-commit-config.yaml create mode 100644 {{cookiecutter.project_slug}}/CLAUDE.md create mode 100644 {{cookiecutter.project_slug}}/ruff.toml diff --git a/cookiecutter.json b/cookiecutter.json index 8d61f41..16b3f0e 100644 --- a/cookiecutter.json +++ b/cookiecutter.json @@ -23,6 +23,11 @@ "none" ], "docstring_coverage": true, + "data_library": [ + "polars", + "pandas", + "none" + ], "license": [ "Apache 2.0", "AGPL v3", @@ -32,7 +37,8 @@ ".github/workflows/docs.yml", ".github/workflows/lint.yml", ".github/workflows/tests.yml", - ".github/workflows/zizmor.yml" + ".github/workflows/zizmor.yml", + "ruff.toml" ], "__prompts__": { "project_name": "Human-readable project name (translated into module slug and import)", diff --git a/{{cookiecutter.project_slug}}/.github/workflows/tests.yml b/{{cookiecutter.project_slug}}/.github/workflows/tests.yml index 18423d9..5493ee0 100644 --- a/{{cookiecutter.project_slug}}/.github/workflows/tests.yml +++ b/{{cookiecutter.project_slug}}/.github/workflows/tests.yml @@ -13,8 +13,6 @@ jobs: strategy: matrix: python: - - "3.9" - - "3.10" - "3.11" - "3.12" runs-on: ubuntu-latest diff --git a/{{cookiecutter.project_slug}}/.pre-commit-config.yaml b/{{cookiecutter.project_slug}}/.pre-commit-config.yaml new file mode 100644 index 0000000..0a278f4 --- /dev/null +++ b/{{cookiecutter.project_slug}}/.pre-commit-config.yaml @@ -0,0 +1,45 @@ +# Pre-commit hooks for code quality +# See https://pre-commit.com for more information +repos: + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.6.2 + hooks: + # Run the formatter + - id: ruff-format + # Run the linter + - id: ruff + args: [--fix] + + - repo: local + hooks: + - id: ty + name: ty type check + entry: uv run ty check --strict + 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 -q + language: system + types: [python] + pass_filenames: false + require_serial: true + # Only run on commit, not on push + stages: [commit] + +# Configuration +ci: + autofix_prs: true + autoupdate_schedule: weekly \ No newline at end of file diff --git a/{{cookiecutter.project_slug}}/CLAUDE.md b/{{cookiecutter.project_slug}}/CLAUDE.md new file mode 100644 index 0000000..f86e455 --- /dev/null +++ b/{{cookiecutter.project_slug}}/CLAUDE.md @@ -0,0 +1,155 @@ +# {{ cookiecutter.project_name }} - Claude Instructions + +This document contains project-specific instructions for Claude when working on this codebase. + +## Project Overview + +{{ cookiecutter.project_description }} + +## Code Standards + +This project enforces strict code quality standards: + +### Code Complexity Limits +- **Max 50 lines per function** - Split larger functions +- **Cyclomatic complexity ≤ 8** - Simplify complex logic +- **Max 5 positional parameters** - Use keyword arguments or dataclasses +- **Max 12 branches per function** - Extract to helper functions +- **Max 6 return statements** - Consolidate exit points + +### Style Guidelines +- **Line length**: 100 characters max +- **Docstrings**: Google style on all public functions/classes +- **Type hints**: Required for all function signatures +- **Tests**: Must live beside code (`test_*.py` or `*_test.py`) + +## Quick Commands + +```bash +# Development setup +make dev + +# Run all checks +make check # Runs lint + tests + +# Code quality +make lint # ruff format --check + ruff check + ty check +make fix # Auto-fix formatting and lint issues +make ty # Run type checker with strict mode + +# Testing +make test # Run pytest with coverage + +# Development +{% if cookiecutter.entry_point -%} +make run ARGS="--help" # Run the CLI +{%- endif %} +make doc # Build documentation +``` + +## Project Structure + +``` +src/ +└── {{ cookiecutter.__project_import.replace('.', '/') }}/ + ├── __init__.py + {%- if cookiecutter.entry_point %} + ├── __main__.py # CLI entry point + ├── _cli.py # CLI implementation + {%- endif %} + └── py.typed # Type checking marker + +test/ +└── test_*.py # Traditional test location +``` + +Tests can also live beside source files as `test_*.py` or `*_test.py`. + +## Key Libraries + +{%- if cookiecutter.data_library == "polars" %} +- **Data processing**: Polars (preferred over pandas) + ```python + import polars as pl + df = pl.DataFrame({"col": [1, 2, 3]}) + ``` +{%- elif cookiecutter.data_library == "pandas" %} +- **Data processing**: Pandas + ```python + import pandas as pd + df = pd.DataFrame({"col": [1, 2, 3]}) + ``` +{%- endif %} + +## Common Patterns + +### Error Handling +```python +from typing import Result # If using result types + +def process_data(path: str) -> Result[Data, str]: + """Process data from file. + + Args: + path: Path to data file. + + Returns: + Result with Data on success, error message on failure. + """ + try: + # Implementation + return Ok(data) + except Exception as e: + return Err(f"Failed to process: {e}") +``` + +### Logging +```python +import logging + +logger = logging.getLogger(__name__) +``` + +{%- if cookiecutter.entry_point %} + +### CLI Arguments +Use the existing `_cli.py` structure: +```python +parser.add_argument( + "--verbose", "-v", + action="store_true", + help="Enable verbose output" +) +``` +{%- endif %} + +## Testing Guidelines + +- Aim for 100% test coverage (enforced by CI) +- Use `pytest.mark.parametrize` for multiple test cases +- Mock external dependencies +- Test both success and error paths + +## CI/CD + +GitHub Actions run on every push/PR: +1. **Linting**: ruff format/check + ty type checking +2. **Tests**: pytest with coverage +3. **Security**: zizmor workflow scanning +{%- if cookiecutter.documentation == "pdoc" %} +4. **Docs**: Auto-deploy to GitHub Pages +{%- endif %} + +## Important Notes + +1. **Never commit code that violates the quality standards** - refactor instead +2. **All public APIs need Google-style docstrings** +3. **Type hints are mandatory** - use `ty check --strict` +4. **Tests can live beside code** - prefer colocated tests for better maintainability + +## Project-Specific Instructions + + + +--- +*This file helps Claude understand project conventions. Update it as patterns emerge.* \ No newline at end of file diff --git a/{{cookiecutter.project_slug}}/Makefile b/{{cookiecutter.project_slug}}/Makefile index 4f99666..d89ead1 100644 --- a/{{cookiecutter.project_slug}}/Makefile +++ b/{{cookiecutter.project_slug}}/Makefile @@ -44,6 +44,7 @@ all: .PHONY: dev dev: $(VENV)/pyvenv.cfg + uv run pre-commit install {%- if cookiecutter.entry_point %} .PHONY: run @@ -59,20 +60,30 @@ $(VENV)/pyvenv.cfg: pyproject.toml lint: $(VENV)/pyvenv.cfg uv run ruff format --check && \ uv run ruff check && \ - uv run mypy + uv run ty check {%- if cookiecutter.docstring_coverage %} uv run interrogate -c pyproject.toml . {%- endif %} -.PHONY: reformat -reformat: +.PHONY: format +format: reformat + +.PHONY: check +check: lint test + +.PHONY: typecheck ty +typecheck ty: $(VENV)/pyvenv.cfg + uv run ty check --strict + +.PHONY: reformat fix +reformat fix: uv run ruff format && \ uv run ruff check --fix .PHONY: test tests test tests: $(VENV)/pyvenv.cfg - uv run pytest --cov=$(PY_IMPORT) $(T) $(TEST_ARGS) + uv run pytest -q --cov=$(PY_IMPORT) $(T) $(TEST_ARGS) uv run coverage report -m $(COV_ARGS) .PHONY: doc diff --git a/{{cookiecutter.project_slug}}/pyproject.toml b/{{cookiecutter.project_slug}}/pyproject.toml index b364bd1..32b9a53 100644 --- a/{{cookiecutter.project_slug}}/pyproject.toml +++ b/{{cookiecutter.project_slug}}/pyproject.toml @@ -19,8 +19,14 @@ authors = [ classifiers = [ "Programming Language :: Python :: 3", ] -dependencies = [] -requires-python = ">=3.9" +dependencies = [ + {%- if cookiecutter.data_library == "polars" %} + "polars >= 0.20.0", + {%- elif cookiecutter.data_library == "pandas" %} + "pandas >= 2.0.0", + {%- endif %} +] +requires-python = ">=3.11" [tool.setuptools.dynamic] version = { attr = "{{ cookiecutter.__project_import }}.__version__" } @@ -36,7 +42,7 @@ 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", + "ty >= 0.1.0", "types-html5lib", "types-requests", "types-toml", @@ -44,7 +50,7 @@ lint = [ "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] @@ -61,46 +67,12 @@ 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.ruff] -line-length = 100 -include = ["src/**/*.py", "test/**/*.py"] +[tool.ty] +# Astral's experimental type checker +search_path = ["src"] +strict = true -[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"] - -[tool.ruff.lint.per-file-ignores] -{% if cookiecutter.entry_point -%} -"{{ cookiecutter.__project_src_path }}/_cli.py" = [ - "T201", # allow `print` in cli module -] -{%- endif %} -"test/**/*.py" = [ - "D", # no docstrings in tests - "S101", # asserts are expected in tests -] +# Ruff configuration is in ruff.toml {%- if cookiecutter.docstring_coverage %} [tool.interrogate] @@ -110,3 +82,10 @@ exclude = ["env", "test", "{{ cookiecutter.__project_src_path }}/_cli.py"] ignore-semiprivate = true fail-under = 100 {%- endif %} + +[tool.pytest.ini_options] +# Support tests living beside code +testpaths = ["src", "test"] +python_files = ["test_*.py", "*_test.py"] +# Show test durations +addopts = "-q --durations=10" diff --git a/{{cookiecutter.project_slug}}/ruff.toml b/{{cookiecutter.project_slug}}/ruff.toml new file mode 100644 index 0000000..f08868b --- /dev/null +++ b/{{cookiecutter.project_slug}}/ruff.toml @@ -0,0 +1,45 @@ +# Ruff configuration aligned with CLAUDE.md standards +line-length = 100 +target-version = "py311" + +[lint] +select = ["ALL"] +ignore = [ + "D203", # Incompatible with D211 + "D213", # Incompatible with D212 + "COM812", # Can conflict with formatter + "ISC001", # Can conflict with formatter +] + +[lint.mccabe] +# Maximum cyclomatic complexity +max-complexity = 8 + +[lint.pydocstyle] +# Use Google-style docstrings +convention = "google" + +[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 + +[lint.per-file-ignores] +# CLI-specific ignores (will be removed by post-gen hook if not CLI project) +"src/**/_cli.py" = [ + "T201", # Allow print in CLI module +] +"test/**/*.py" = [ + "D", # No docstrings required in tests + "S101", # Allow assert in tests + "PLR2004", # Allow magic values in tests +] +"**/conftest.py" = ["D"] # No docstrings in pytest config + +[format] +# Consistent with line-length +line-ending = "lf" +quote-style = "double" \ No newline at end of file From c975cb8d5222051e155cd9f7b5486974def73266 Mon Sep 17 00:00:00 2001 From: Dan Guido Date: Tue, 29 Jul 2025 19:13:39 -0400 Subject: [PATCH 02/17] feat: add FastAPI as preferred web framework option MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add web_framework choice in cookiecutter.json (fastapi/none) - Include FastAPI and uvicorn dependencies when selected - Create basic FastAPI app template with health check endpoint - Add 'make serve' command to run development server - Update CLAUDE.md to express strong preference for FastAPI over Flask - Configure post-generation hook to remove _app.py if not using FastAPI This enforces the preference for modern async frameworks and explicitly discourages Flask usage in new projects. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- cookiecutter.json | 4 +++ hooks/post_gen_project.py | 2 ++ {{cookiecutter.project_slug}}/CLAUDE.md | 22 +++++++++++++ {{cookiecutter.project_slug}}/Makefile | 6 ++++ {{cookiecutter.project_slug}}/pyproject.toml | 4 +++ .../{{cookiecutter.__module_import}}/_app.py | 33 +++++++++++++++++++ 6 files changed, 71 insertions(+) create mode 100644 {{cookiecutter.project_slug}}/src/{{cookiecutter.__module_import}}/_app.py diff --git a/cookiecutter.json b/cookiecutter.json index 16b3f0e..836a75a 100644 --- a/cookiecutter.json +++ b/cookiecutter.json @@ -28,6 +28,10 @@ "pandas", "none" ], + "web_framework": [ + "fastapi", + "none" + ], "license": [ "Apache 2.0", "AGPL v3", diff --git a/hooks/post_gen_project.py b/hooks/post_gen_project.py index ff8ac06..9b27560 100644 --- a/hooks/post_gen_project.py +++ b/hooks/post_gen_project.py @@ -6,6 +6,8 @@ "{% if cookiecutter.entry_point == '' %} {{ cookiecutter.__project_src_path }}/__main__.py {% endif %}", # We delete the docs GH workflow if the project has no documentation "{% if cookiecutter.documentation == 'none' %} .github/workflows/docs.yml {% endif %}", + # We delete _app.py if we're not generating a FastAPI app + "{% if cookiecutter.web_framework != 'fastapi' %} {{ cookiecutter.__project_src_path }}/_app.py {% endif %}", ] for path in REMOVE_PATHS: diff --git a/{{cookiecutter.project_slug}}/CLAUDE.md b/{{cookiecutter.project_slug}}/CLAUDE.md index f86e455..3819bf8 100644 --- a/{{cookiecutter.project_slug}}/CLAUDE.md +++ b/{{cookiecutter.project_slug}}/CLAUDE.md @@ -44,6 +44,9 @@ make test # Run pytest with coverage {% if cookiecutter.entry_point -%} make run ARGS="--help" # Run the CLI {%- endif %} +{%- if cookiecutter.web_framework == "fastapi" %} +make serve # Run FastAPI dev server on http://localhost:8000 +{%- endif %} make doc # Build documentation ``` @@ -81,6 +84,25 @@ Tests can also live beside source files as `test_*.py` or `*_test.py`. ``` {%- endif %} +{%- if cookiecutter.web_framework == "fastapi" %} +- **Web framework**: FastAPI (never use Flask) + ```python + from fastapi import FastAPI + + app = FastAPI() + + @app.get("/") + async def root(): + return {"message": "Hello World"} + ``` +{%- endif %} + +## Framework Preferences + +- **Web**: Always use FastAPI, never Flask +- **Data**: Prefer Polars over Pandas for new code +- **Async**: Use native async/await, not threading + ## Common Patterns ### Error Handling diff --git a/{{cookiecutter.project_slug}}/Makefile b/{{cookiecutter.project_slug}}/Makefile index d89ead1..31a83ac 100644 --- a/{{cookiecutter.project_slug}}/Makefile +++ b/{{cookiecutter.project_slug}}/Makefile @@ -52,6 +52,12 @@ run: $(VENV)/pyvenv.cfg uv run {{ cookiecutter.entry_point }} $(ARGS) {%- endif %} +{%- if cookiecutter.web_framework == "fastapi" %} +.PHONY: serve +serve: $(VENV)/pyvenv.cfg + uv run uvicorn {{ cookiecutter.__project_import }}._app:app --reload --host 0.0.0.0 --port 8000 +{%- endif %} + $(VENV)/pyvenv.cfg: pyproject.toml uv venv $(VENV) uv pip install -e '.[$(INSTALL_EXTRA)]' diff --git a/{{cookiecutter.project_slug}}/pyproject.toml b/{{cookiecutter.project_slug}}/pyproject.toml index 32b9a53..ac6df4f 100644 --- a/{{cookiecutter.project_slug}}/pyproject.toml +++ b/{{cookiecutter.project_slug}}/pyproject.toml @@ -25,6 +25,10 @@ dependencies = [ {%- elif cookiecutter.data_library == "pandas" %} "pandas >= 2.0.0", {%- endif %} + {%- if cookiecutter.web_framework == "fastapi" %} + "fastapi >= 0.100.0", + "uvicorn[standard] >= 0.23.0", + {%- endif %} ] requires-python = ">=3.11" diff --git a/{{cookiecutter.project_slug}}/src/{{cookiecutter.__module_import}}/_app.py b/{{cookiecutter.project_slug}}/src/{{cookiecutter.__module_import}}/_app.py new file mode 100644 index 0000000..694c193 --- /dev/null +++ b/{{cookiecutter.project_slug}}/src/{{cookiecutter.__module_import}}/_app.py @@ -0,0 +1,33 @@ +"""FastAPI application setup.""" + +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware + +from {{ cookiecutter.__project_import }} import __version__ + +app = FastAPI( + title="{{ cookiecutter.project_name }}", + description="{{ cookiecutter.project_description }}", + version=__version__, +) + +# Configure CORS +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], # Configure appropriately for production + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + + +@app.get("/") +async def root() -> dict[str, str]: + """Root endpoint.""" + return {"message": "{{ cookiecutter.project_name }} API", "version": __version__} + + +@app.get("/health") +async def health() -> dict[str, str]: + """Health check endpoint.""" + return {"status": "ok"} \ No newline at end of file From 6fa0b6a897b332fbbae6df58c95639a3b31d472b Mon Sep 17 00:00:00 2001 From: Dan Guido Date: Tue, 29 Jul 2025 19:33:34 -0400 Subject: [PATCH 03/17] fix: correct ty version and remove unsupported --strict flag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix ty version to 0.0.1a16 (current alpha release) - Remove --strict flag which doesn't exist in ty - Update ty commands to explicitly check src directory - Remove strict=true from pyproject.toml ty config ty is still in early alpha, so we're using the latest available version with basic configuration. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- {{cookiecutter.project_slug}}/.pre-commit-config.yaml | 2 +- {{cookiecutter.project_slug}}/CLAUDE.md | 2 +- {{cookiecutter.project_slug}}/Makefile | 4 ++-- {{cookiecutter.project_slug}}/pyproject.toml | 3 +-- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/{{cookiecutter.project_slug}}/.pre-commit-config.yaml b/{{cookiecutter.project_slug}}/.pre-commit-config.yaml index 0a278f4..6bffec0 100644 --- a/{{cookiecutter.project_slug}}/.pre-commit-config.yaml +++ b/{{cookiecutter.project_slug}}/.pre-commit-config.yaml @@ -14,7 +14,7 @@ repos: hooks: - id: ty name: ty type check - entry: uv run ty check --strict + entry: uv run ty check src language: system types: [python] pass_filenames: false diff --git a/{{cookiecutter.project_slug}}/CLAUDE.md b/{{cookiecutter.project_slug}}/CLAUDE.md index 3819bf8..c46aae3 100644 --- a/{{cookiecutter.project_slug}}/CLAUDE.md +++ b/{{cookiecutter.project_slug}}/CLAUDE.md @@ -166,7 +166,7 @@ GitHub Actions run on every push/PR: 1. **Never commit code that violates the quality standards** - refactor instead 2. **All public APIs need Google-style docstrings** -3. **Type hints are mandatory** - use `ty check --strict` +3. **Type hints are mandatory** - use `ty check` 4. **Tests can live beside code** - prefer colocated tests for better maintainability ## Project-Specific Instructions diff --git a/{{cookiecutter.project_slug}}/Makefile b/{{cookiecutter.project_slug}}/Makefile index 31a83ac..efc2597 100644 --- a/{{cookiecutter.project_slug}}/Makefile +++ b/{{cookiecutter.project_slug}}/Makefile @@ -66,7 +66,7 @@ $(VENV)/pyvenv.cfg: pyproject.toml lint: $(VENV)/pyvenv.cfg uv run ruff format --check && \ uv run ruff check && \ - uv run ty check + uv run ty check src {%- if cookiecutter.docstring_coverage %} uv run interrogate -c pyproject.toml . @@ -80,7 +80,7 @@ check: lint test .PHONY: typecheck ty typecheck ty: $(VENV)/pyvenv.cfg - uv run ty check --strict + uv run ty check src .PHONY: reformat fix reformat fix: diff --git a/{{cookiecutter.project_slug}}/pyproject.toml b/{{cookiecutter.project_slug}}/pyproject.toml index ac6df4f..7d23105 100644 --- a/{{cookiecutter.project_slug}}/pyproject.toml +++ b/{{cookiecutter.project_slug}}/pyproject.toml @@ -46,7 +46,7 @@ lint = [ # NOTE: ruff is under active development, so we pin conservatively here # and let Dependabot periodically perform this update. "ruff ~= 0.6.2", - "ty >= 0.1.0", + "ty >= 0.0.1a16", "types-html5lib", "types-requests", "types-toml", @@ -74,7 +74,6 @@ omit = ["{{ cookiecutter.__project_src_path }}/_cli.py"] [tool.ty] # Astral's experimental type checker search_path = ["src"] -strict = true # Ruff configuration is in ruff.toml From 517395b4002084711855bcfd6edf90c6d8c5c19e Mon Sep 17 00:00:00 2001 From: Dan Guido Date: Tue, 29 Jul 2025 19:44:44 -0400 Subject: [PATCH 04/17] fix: apply ruff formatting to pre_gen_project.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Apply ruff formatting to fix CI failure. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- hooks/pre_gen_project.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/hooks/pre_gen_project.py b/hooks/pre_gen_project.py index 8c7a3ef..80a0ff1 100644 --- a/hooks/pre_gen_project.py +++ b/hooks/pre_gen_project.py @@ -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) From 08fb765a5ef453637c83dc17664690e3d32bf9b0 Mon Sep 17 00:00:00 2001 From: Dan Guido Date: Tue, 29 Jul 2025 19:49:56 -0400 Subject: [PATCH 05/17] refactor: remove data library option from cookiecutter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove the data_library choice (polars/pandas/none) as it's rarely needed for Trail of Bits projects. This simplifies the template and reduces unnecessary options during project creation. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- cookiecutter.json | 5 ----- {{cookiecutter.project_slug}}/CLAUDE.md | 15 --------------- {{cookiecutter.project_slug}}/pyproject.toml | 5 ----- 3 files changed, 25 deletions(-) diff --git a/cookiecutter.json b/cookiecutter.json index 836a75a..bd12c5b 100644 --- a/cookiecutter.json +++ b/cookiecutter.json @@ -23,11 +23,6 @@ "none" ], "docstring_coverage": true, - "data_library": [ - "polars", - "pandas", - "none" - ], "web_framework": [ "fastapi", "none" diff --git a/{{cookiecutter.project_slug}}/CLAUDE.md b/{{cookiecutter.project_slug}}/CLAUDE.md index c46aae3..fd67013 100644 --- a/{{cookiecutter.project_slug}}/CLAUDE.md +++ b/{{cookiecutter.project_slug}}/CLAUDE.md @@ -70,20 +70,6 @@ Tests can also live beside source files as `test_*.py` or `*_test.py`. ## Key Libraries -{%- if cookiecutter.data_library == "polars" %} -- **Data processing**: Polars (preferred over pandas) - ```python - import polars as pl - df = pl.DataFrame({"col": [1, 2, 3]}) - ``` -{%- elif cookiecutter.data_library == "pandas" %} -- **Data processing**: Pandas - ```python - import pandas as pd - df = pd.DataFrame({"col": [1, 2, 3]}) - ``` -{%- endif %} - {%- if cookiecutter.web_framework == "fastapi" %} - **Web framework**: FastAPI (never use Flask) ```python @@ -100,7 +86,6 @@ Tests can also live beside source files as `test_*.py` or `*_test.py`. ## Framework Preferences - **Web**: Always use FastAPI, never Flask -- **Data**: Prefer Polars over Pandas for new code - **Async**: Use native async/await, not threading ## Common Patterns diff --git a/{{cookiecutter.project_slug}}/pyproject.toml b/{{cookiecutter.project_slug}}/pyproject.toml index 7d23105..c592fdb 100644 --- a/{{cookiecutter.project_slug}}/pyproject.toml +++ b/{{cookiecutter.project_slug}}/pyproject.toml @@ -20,11 +20,6 @@ classifiers = [ "Programming Language :: Python :: 3", ] dependencies = [ - {%- if cookiecutter.data_library == "polars" %} - "polars >= 0.20.0", - {%- elif cookiecutter.data_library == "pandas" %} - "pandas >= 2.0.0", - {%- endif %} {%- if cookiecutter.web_framework == "fastapi" %} "fastapi >= 0.100.0", "uvicorn[standard] >= 0.23.0", From e80506107b5c0ae16d2d814119c917a8d9a285bc Mon Sep 17 00:00:00 2001 From: Dan Guido Date: Tue, 29 Jul 2025 20:04:32 -0400 Subject: [PATCH 06/17] refactor: simplify PR by removing FastAPI and consolidating config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove FastAPI web framework option to reduce complexity - Consolidate ruff configuration from ruff.toml into pyproject.toml - Fix Makefile redundancies: keep 'fix' and 'format' as aliases - Remove duplicated ty command from lint target - Clean up CLAUDE.md to remove FastAPI references This simplifies the PR to focus on core improvements: stricter code quality standards, ty type checker, and pre-commit hooks. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- cookiecutter.json | 7 +-- hooks/post_gen_project.py | 2 - {{cookiecutter.project_slug}}/CLAUDE.md | 21 +------- {{cookiecutter.project_slug}}/Makefile | 13 +---- {{cookiecutter.project_slug}}/pyproject.toml | 53 ++++++++++++++++--- {{cookiecutter.project_slug}}/ruff.toml | 45 ---------------- .../{{cookiecutter.__module_import}}/_app.py | 33 ------------ 7 files changed, 51 insertions(+), 123 deletions(-) delete mode 100644 {{cookiecutter.project_slug}}/ruff.toml delete mode 100644 {{cookiecutter.project_slug}}/src/{{cookiecutter.__module_import}}/_app.py diff --git a/cookiecutter.json b/cookiecutter.json index bd12c5b..8d61f41 100644 --- a/cookiecutter.json +++ b/cookiecutter.json @@ -23,10 +23,6 @@ "none" ], "docstring_coverage": true, - "web_framework": [ - "fastapi", - "none" - ], "license": [ "Apache 2.0", "AGPL v3", @@ -36,8 +32,7 @@ ".github/workflows/docs.yml", ".github/workflows/lint.yml", ".github/workflows/tests.yml", - ".github/workflows/zizmor.yml", - "ruff.toml" + ".github/workflows/zizmor.yml" ], "__prompts__": { "project_name": "Human-readable project name (translated into module slug and import)", diff --git a/hooks/post_gen_project.py b/hooks/post_gen_project.py index 9b27560..ff8ac06 100644 --- a/hooks/post_gen_project.py +++ b/hooks/post_gen_project.py @@ -6,8 +6,6 @@ "{% if cookiecutter.entry_point == '' %} {{ cookiecutter.__project_src_path }}/__main__.py {% endif %}", # We delete the docs GH workflow if the project has no documentation "{% if cookiecutter.documentation == 'none' %} .github/workflows/docs.yml {% endif %}", - # We delete _app.py if we're not generating a FastAPI app - "{% if cookiecutter.web_framework != 'fastapi' %} {{ cookiecutter.__project_src_path }}/_app.py {% endif %}", ] for path in REMOVE_PATHS: diff --git a/{{cookiecutter.project_slug}}/CLAUDE.md b/{{cookiecutter.project_slug}}/CLAUDE.md index fd67013..90f4adb 100644 --- a/{{cookiecutter.project_slug}}/CLAUDE.md +++ b/{{cookiecutter.project_slug}}/CLAUDE.md @@ -35,7 +35,7 @@ make check # Runs lint + tests # Code quality make lint # ruff format --check + ruff check + ty check make fix # Auto-fix formatting and lint issues -make ty # Run type checker with strict mode +make ty # Run type checker # Testing make test # Run pytest with coverage @@ -44,9 +44,6 @@ make test # Run pytest with coverage {% if cookiecutter.entry_point -%} make run ARGS="--help" # Run the CLI {%- endif %} -{%- if cookiecutter.web_framework == "fastapi" %} -make serve # Run FastAPI dev server on http://localhost:8000 -{%- endif %} make doc # Build documentation ``` @@ -68,24 +65,10 @@ test/ Tests can also live beside source files as `test_*.py` or `*_test.py`. -## Key Libraries - -{%- if cookiecutter.web_framework == "fastapi" %} -- **Web framework**: FastAPI (never use Flask) - ```python - from fastapi import FastAPI - - app = FastAPI() - - @app.get("/") - async def root(): - return {"message": "Hello World"} - ``` -{%- endif %} - ## Framework Preferences - **Web**: Always use FastAPI, never Flask +- **Data**: Prefer Polars over Pandas for new code - **Async**: Use native async/await, not threading ## Common Patterns diff --git a/{{cookiecutter.project_slug}}/Makefile b/{{cookiecutter.project_slug}}/Makefile index efc2597..7cede45 100644 --- a/{{cookiecutter.project_slug}}/Makefile +++ b/{{cookiecutter.project_slug}}/Makefile @@ -52,12 +52,6 @@ run: $(VENV)/pyvenv.cfg uv run {{ cookiecutter.entry_point }} $(ARGS) {%- endif %} -{%- if cookiecutter.web_framework == "fastapi" %} -.PHONY: serve -serve: $(VENV)/pyvenv.cfg - uv run uvicorn {{ cookiecutter.__project_import }}._app:app --reload --host 0.0.0.0 --port 8000 -{%- endif %} - $(VENV)/pyvenv.cfg: pyproject.toml uv venv $(VENV) uv pip install -e '.[$(INSTALL_EXTRA)]' @@ -72,9 +66,6 @@ lint: $(VENV)/pyvenv.cfg uv run interrogate -c pyproject.toml . {%- endif %} -.PHONY: format -format: reformat - .PHONY: check check: lint test @@ -82,8 +73,8 @@ check: lint test typecheck ty: $(VENV)/pyvenv.cfg uv run ty check src -.PHONY: reformat fix -reformat fix: +.PHONY: fix format +fix format: uv run ruff format && \ uv run ruff check --fix diff --git a/{{cookiecutter.project_slug}}/pyproject.toml b/{{cookiecutter.project_slug}}/pyproject.toml index c592fdb..f4e1ca7 100644 --- a/{{cookiecutter.project_slug}}/pyproject.toml +++ b/{{cookiecutter.project_slug}}/pyproject.toml @@ -19,12 +19,7 @@ authors = [ classifiers = [ "Programming Language :: Python :: 3", ] -dependencies = [ - {%- if cookiecutter.web_framework == "fastapi" %} - "fastapi >= 0.100.0", - "uvicorn[standard] >= 0.23.0", - {%- endif %} -] +dependencies = [] requires-python = ">=3.11" [tool.setuptools.dynamic] @@ -70,7 +65,51 @@ omit = ["{{ cookiecutter.__project_src_path }}/_cli.py"] # Astral's experimental type checker search_path = ["src"] -# Ruff configuration is in ruff.toml +[tool.ruff] +line-length = 100 +target-version = "py311" + +[tool.ruff.format] +line-ending = "lf" +quote-style = "double" + +[tool.ruff.lint] +select = ["ALL"] +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 +] +{%- 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] diff --git a/{{cookiecutter.project_slug}}/ruff.toml b/{{cookiecutter.project_slug}}/ruff.toml deleted file mode 100644 index f08868b..0000000 --- a/{{cookiecutter.project_slug}}/ruff.toml +++ /dev/null @@ -1,45 +0,0 @@ -# Ruff configuration aligned with CLAUDE.md standards -line-length = 100 -target-version = "py311" - -[lint] -select = ["ALL"] -ignore = [ - "D203", # Incompatible with D211 - "D213", # Incompatible with D212 - "COM812", # Can conflict with formatter - "ISC001", # Can conflict with formatter -] - -[lint.mccabe] -# Maximum cyclomatic complexity -max-complexity = 8 - -[lint.pydocstyle] -# Use Google-style docstrings -convention = "google" - -[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 - -[lint.per-file-ignores] -# CLI-specific ignores (will be removed by post-gen hook if not CLI project) -"src/**/_cli.py" = [ - "T201", # Allow print in CLI module -] -"test/**/*.py" = [ - "D", # No docstrings required in tests - "S101", # Allow assert in tests - "PLR2004", # Allow magic values in tests -] -"**/conftest.py" = ["D"] # No docstrings in pytest config - -[format] -# Consistent with line-length -line-ending = "lf" -quote-style = "double" \ No newline at end of file diff --git a/{{cookiecutter.project_slug}}/src/{{cookiecutter.__module_import}}/_app.py b/{{cookiecutter.project_slug}}/src/{{cookiecutter.__module_import}}/_app.py deleted file mode 100644 index 694c193..0000000 --- a/{{cookiecutter.project_slug}}/src/{{cookiecutter.__module_import}}/_app.py +++ /dev/null @@ -1,33 +0,0 @@ -"""FastAPI application setup.""" - -from fastapi import FastAPI -from fastapi.middleware.cors import CORSMiddleware - -from {{ cookiecutter.__project_import }} import __version__ - -app = FastAPI( - title="{{ cookiecutter.project_name }}", - description="{{ cookiecutter.project_description }}", - version=__version__, -) - -# Configure CORS -app.add_middleware( - CORSMiddleware, - allow_origins=["*"], # Configure appropriately for production - allow_credentials=True, - allow_methods=["*"], - allow_headers=["*"], -) - - -@app.get("/") -async def root() -> dict[str, str]: - """Root endpoint.""" - return {"message": "{{ cookiecutter.project_name }} API", "version": __version__} - - -@app.get("/health") -async def health() -> dict[str, str]: - """Health check endpoint.""" - return {"status": "ok"} \ No newline at end of file From 330cbb9bd27df997ee6cfc7bcd079513d5951795 Mon Sep 17 00:00:00 2001 From: Dan Guido Date: Tue, 29 Jul 2025 20:06:43 -0400 Subject: [PATCH 07/17] fix: restore 'reformat' target as alias to fix CI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The self-test CI workflow expects 'make reformat' to exist. Add it back as an alias alongside 'fix' and 'format'. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- {{cookiecutter.project_slug}}/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/{{cookiecutter.project_slug}}/Makefile b/{{cookiecutter.project_slug}}/Makefile index 7cede45..41ca53c 100644 --- a/{{cookiecutter.project_slug}}/Makefile +++ b/{{cookiecutter.project_slug}}/Makefile @@ -73,8 +73,8 @@ check: lint test typecheck ty: $(VENV)/pyvenv.cfg uv run ty check src -.PHONY: fix format -fix format: +.PHONY: fix format reformat +fix format reformat: uv run ruff format && \ uv run ruff check --fix From 2fbd39b7a7a01243fd870d427073c4e69ab84d5e Mon Sep 17 00:00:00 2001 From: Dan Guido Date: Tue, 29 Jul 2025 20:16:32 -0400 Subject: [PATCH 08/17] fix: correct license field syntax in pyproject.toml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace deprecated 'license-files' with proper license field syntax. Use 'license = {text = "SPDX-ID"}' for standard licenses and 'license = {file = "LICENSE"}' for proprietary licenses. This fixes the RUF200 parsing error in generated projects. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- {{cookiecutter.project_slug}}/pyproject.toml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/{{cookiecutter.project_slug}}/pyproject.toml b/{{cookiecutter.project_slug}}/pyproject.toml index f4e1ca7..b7426bc 100644 --- a/{{cookiecutter.project_slug}}/pyproject.toml +++ b/{{cookiecutter.project_slug}}/pyproject.toml @@ -3,14 +3,12 @@ name = "{{ cookiecutter.project_slug }}" dynamic = ["version"] description = "{{ cookiecutter.project_description }}" readme = "README.md" -license-files = ["LICENSE"] - {%- if cookiecutter.license == "Apache 2.0" %} -license = "Apache-2.0" +license = {text = "Apache-2.0"} {%- elif cookiecutter.license == "AGPL v3" %} -license = "AGPL-3.0-or-later" +license = {text = "AGPL-3.0-or-later"} {%- elif cookiecutter.license == "Proprietary" %} -license = "LicenseRef-Proprietary-License" +license = {file = "LICENSE"} {%- endif %} authors = [ From d405f816ff37a157b6f13dfd8eb5a28e85843e50 Mon Sep 17 00:00:00 2001 From: Dan Guido Date: Tue, 29 Jul 2025 20:34:58 -0400 Subject: [PATCH 09/17] fix: correct ty configuration field name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change 'search_path' to 'src' in [tool.ty] configuration. The ty type checker expects 'src' as the field name, not 'search_path'. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- {{cookiecutter.project_slug}}/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/{{cookiecutter.project_slug}}/pyproject.toml b/{{cookiecutter.project_slug}}/pyproject.toml index b7426bc..a15c466 100644 --- a/{{cookiecutter.project_slug}}/pyproject.toml +++ b/{{cookiecutter.project_slug}}/pyproject.toml @@ -61,7 +61,7 @@ omit = ["{{ cookiecutter.__project_src_path }}/_cli.py"] [tool.ty] # Astral's experimental type checker -search_path = ["src"] +src = ["src"] [tool.ruff] line-length = 100 From d4812985aede36cc858151223727184bc4d84efe Mon Sep 17 00:00:00 2001 From: Dan Guido Date: Tue, 29 Jul 2025 20:39:06 -0400 Subject: [PATCH 10/17] fix: remove ty configuration section to use defaults MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove the [tool.ty] section entirely as ty is in early alpha and the configuration format is not well documented. Let ty use its default configuration which should work for standard project layouts. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- {{cookiecutter.project_slug}}/pyproject.toml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/{{cookiecutter.project_slug}}/pyproject.toml b/{{cookiecutter.project_slug}}/pyproject.toml index a15c466..ab6ab1b 100644 --- a/{{cookiecutter.project_slug}}/pyproject.toml +++ b/{{cookiecutter.project_slug}}/pyproject.toml @@ -59,9 +59,7 @@ 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.ty] -# Astral's experimental type checker -src = ["src"] +# ty configuration - using defaults for now as ty is in early alpha [tool.ruff] line-length = 100 From 2902ab9d55572e93155bffcc3a06df500b4d3cb6 Mon Sep 17 00:00:00 2001 From: Dan Guido Date: Tue, 29 Jul 2025 20:53:44 -0400 Subject: [PATCH 11/17] test commit --- test-pyright/python-project/README.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 test-pyright/python-project/README.md diff --git a/test-pyright/python-project/README.md b/test-pyright/python-project/README.md new file mode 100644 index 0000000..bda5fad --- /dev/null +++ b/test-pyright/python-project/README.md @@ -0,0 +1,27 @@ +# Python Project + + +[![CI](https://github.com/trailofbits/python-project/actions/workflows/tests.yml/badge.svg)](https://github.com/trailofbits/python-project/actions/workflows/tests.yml) +[![PyPI version](https://badge.fury.io/py/python-project.svg)](https://pypi.org/project/python-project) +[![Packaging status](https://repology.org/badge/tiny-repos/python:python-project.svg)](https://repology.org/project/python:python-project/versions) + + + + +## License +``` +Copyright 2025 Trail of Bits + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. + +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +``` +# Test From 67244e8174ffccf11275534ca8b4340b19db5598 Mon Sep 17 00:00:00 2001 From: Dan Guido Date: Tue, 29 Jul 2025 20:56:24 -0400 Subject: [PATCH 12/17] refactor: replace ty with pyright and improve configurations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace experimental ty (0.0.1a16) with stable pyright (1.1+) - Configure pyright with standard type checking mode - Make pre-commit pytest less aggressive: only run fast tests (-k "not slow") - Remove -q flag from pytest for better debugging visibility - Clarify CLAUDE.md framework preferences as general guidelines - Add missing newline to pre-commit config - Update all references from ty to pyright in docs and commands This makes the type checking more reliable while maintaining the same strict code quality standards. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .../.pre-commit-config.yaml | 12 ++++++------ {{cookiecutter.project_slug}}/CLAUDE.md | 19 +++++++++++-------- {{cookiecutter.project_slug}}/Makefile | 10 +++++----- {{cookiecutter.project_slug}}/pyproject.toml | 11 ++++++++--- 4 files changed, 30 insertions(+), 22 deletions(-) diff --git a/{{cookiecutter.project_slug}}/.pre-commit-config.yaml b/{{cookiecutter.project_slug}}/.pre-commit-config.yaml index 6bffec0..01b2b4f 100644 --- a/{{cookiecutter.project_slug}}/.pre-commit-config.yaml +++ b/{{cookiecutter.project_slug}}/.pre-commit-config.yaml @@ -12,9 +12,9 @@ repos: - repo: local hooks: - - id: ty - name: ty type check - entry: uv run ty check src + - id: pyright + name: pyright type check + entry: uv run pyright language: system types: [python] pass_filenames: false @@ -30,8 +30,8 @@ repos: {%- endif %} - id: pytest - name: pytest - entry: uv run pytest -q + name: pytest (fast tests only) + entry: uv run pytest -x --tb=short -k "not slow" language: system types: [python] pass_filenames: false @@ -42,4 +42,4 @@ repos: # Configuration ci: autofix_prs: true - autoupdate_schedule: weekly \ No newline at end of file + autoupdate_schedule: weekly diff --git a/{{cookiecutter.project_slug}}/CLAUDE.md b/{{cookiecutter.project_slug}}/CLAUDE.md index 90f4adb..e57767c 100644 --- a/{{cookiecutter.project_slug}}/CLAUDE.md +++ b/{{cookiecutter.project_slug}}/CLAUDE.md @@ -33,9 +33,9 @@ make dev make check # Runs lint + tests # Code quality -make lint # ruff format --check + ruff check + ty check +make lint # ruff format --check + ruff check + pyright make fix # Auto-fix formatting and lint issues -make ty # Run type checker +make typecheck # Run pyright type checker # Testing make test # Run pytest with coverage @@ -65,11 +65,14 @@ test/ Tests can also live beside source files as `test_*.py` or `*_test.py`. -## Framework Preferences +## General Python Guidelines -- **Web**: Always use FastAPI, never Flask -- **Data**: Prefer Polars over Pandas for new code -- **Async**: Use native async/await, not threading +These are general preferences for Python development: + +- **Web frameworks**: Prefer FastAPI over Flask for new projects +- **Data processing**: Consider Polars for performance-critical data operations +- **Async programming**: Use native async/await instead of threading +- **Type checking**: Always use type hints and run pyright ## Common Patterns @@ -123,7 +126,7 @@ parser.add_argument( ## CI/CD GitHub Actions run on every push/PR: -1. **Linting**: ruff format/check + ty type checking +1. **Linting**: ruff format/check + pyright type checking 2. **Tests**: pytest with coverage 3. **Security**: zizmor workflow scanning {%- if cookiecutter.documentation == "pdoc" %} @@ -134,7 +137,7 @@ GitHub Actions run on every push/PR: 1. **Never commit code that violates the quality standards** - refactor instead 2. **All public APIs need Google-style docstrings** -3. **Type hints are mandatory** - use `ty check` +3. **Type hints are mandatory** - use `pyright` 4. **Tests can live beside code** - prefer colocated tests for better maintainability ## Project-Specific Instructions diff --git a/{{cookiecutter.project_slug}}/Makefile b/{{cookiecutter.project_slug}}/Makefile index 41ca53c..ad0ca7b 100644 --- a/{{cookiecutter.project_slug}}/Makefile +++ b/{{cookiecutter.project_slug}}/Makefile @@ -60,7 +60,7 @@ $(VENV)/pyvenv.cfg: pyproject.toml lint: $(VENV)/pyvenv.cfg uv run ruff format --check && \ uv run ruff check && \ - uv run ty check src + uv run pyright {%- if cookiecutter.docstring_coverage %} uv run interrogate -c pyproject.toml . @@ -69,9 +69,9 @@ lint: $(VENV)/pyvenv.cfg .PHONY: check check: lint test -.PHONY: typecheck ty -typecheck ty: $(VENV)/pyvenv.cfg - uv run ty check src +.PHONY: typecheck +typecheck: $(VENV)/pyvenv.cfg + uv run pyright .PHONY: fix format reformat fix format reformat: @@ -80,7 +80,7 @@ fix format reformat: .PHONY: test tests test tests: $(VENV)/pyvenv.cfg - uv run pytest -q --cov=$(PY_IMPORT) $(T) $(TEST_ARGS) + uv run pytest --cov=$(PY_IMPORT) $(T) $(TEST_ARGS) uv run coverage report -m $(COV_ARGS) .PHONY: doc diff --git a/{{cookiecutter.project_slug}}/pyproject.toml b/{{cookiecutter.project_slug}}/pyproject.toml index ab6ab1b..18a0a0a 100644 --- a/{{cookiecutter.project_slug}}/pyproject.toml +++ b/{{cookiecutter.project_slug}}/pyproject.toml @@ -34,7 +34,7 @@ lint = [ # NOTE: ruff is under active development, so we pin conservatively here # and let Dependabot periodically perform this update. "ruff ~= 0.6.2", - "ty >= 0.0.1a16", + "pyright >= 1.1", "types-html5lib", "types-requests", "types-toml", @@ -59,7 +59,12 @@ Source = "https://github.com/{{ cookiecutter.github_username }}/{{ cookiecutter. # don't attempt code coverage for the CLI entrypoints omit = ["{{ cookiecutter.__project_src_path }}/_cli.py"] -# ty configuration - using defaults for now as ty is in early alpha +[tool.pyright] +# Type checking configuration +pythonVersion = "3.11" +typeCheckingMode = "standard" +useLibraryCodeForTypes = true +reportMissingTypeStubs = false [tool.ruff] line-length = 100 @@ -121,4 +126,4 @@ fail-under = 100 testpaths = ["src", "test"] python_files = ["test_*.py", "*_test.py"] # Show test durations -addopts = "-q --durations=10" +addopts = "--durations=10" From a249f002da262fdfc636b927f802ed444550a4bb Mon Sep 17 00:00:00 2001 From: Dan Guido Date: Tue, 29 Jul 2025 21:04:54 -0400 Subject: [PATCH 13/17] test commit --- {test-pyright => test-final}/python-project/README.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {test-pyright => test-final}/python-project/README.md (100%) diff --git a/test-pyright/python-project/README.md b/test-final/python-project/README.md similarity index 100% rename from test-pyright/python-project/README.md rename to test-final/python-project/README.md From 0030cefbcf53d9d35d68c00cbda9625a198f81f4 Mon Sep 17 00:00:00 2001 From: Dan Guido Date: Tue, 29 Jul 2025 21:06:01 -0400 Subject: [PATCH 14/17] fix: address all code review issues MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove accidentally included test-pyright directory - Fix pre-commit deprecation: use stages: [pre-commit] instead of [commit] - Change pyright to strict mode for better type safety - Add pyright include paths: ["src", "test"] - Add pytest marker documentation for @pytest.mark.slow - Simplify Makefile: keep 'fix' as primary command, 'reformat' as alias All changes tested locally - lint, fix, reformat, and pre-commit hooks work correctly with strict pyright configuration. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- test-final/python-project/README.md | 27 ------------------- .../.pre-commit-config.yaml | 4 +-- {{cookiecutter.project_slug}}/CLAUDE.md | 19 +++++++++++++ {{cookiecutter.project_slug}}/Makefile | 8 ++++-- {{cookiecutter.project_slug}}/pyproject.toml | 3 ++- 5 files changed, 29 insertions(+), 32 deletions(-) delete mode 100644 test-final/python-project/README.md diff --git a/test-final/python-project/README.md b/test-final/python-project/README.md deleted file mode 100644 index bda5fad..0000000 --- a/test-final/python-project/README.md +++ /dev/null @@ -1,27 +0,0 @@ -# Python Project - - -[![CI](https://github.com/trailofbits/python-project/actions/workflows/tests.yml/badge.svg)](https://github.com/trailofbits/python-project/actions/workflows/tests.yml) -[![PyPI version](https://badge.fury.io/py/python-project.svg)](https://pypi.org/project/python-project) -[![Packaging status](https://repology.org/badge/tiny-repos/python:python-project.svg)](https://repology.org/project/python:python-project/versions) - - - - -## License -``` -Copyright 2025 Trail of Bits - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. - -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -``` -# Test diff --git a/{{cookiecutter.project_slug}}/.pre-commit-config.yaml b/{{cookiecutter.project_slug}}/.pre-commit-config.yaml index 01b2b4f..5980969 100644 --- a/{{cookiecutter.project_slug}}/.pre-commit-config.yaml +++ b/{{cookiecutter.project_slug}}/.pre-commit-config.yaml @@ -36,8 +36,8 @@ repos: types: [python] pass_filenames: false require_serial: true - # Only run on commit, not on push - stages: [commit] + # Only run on pre-commit, not on push + stages: [pre-commit] # Configuration ci: diff --git a/{{cookiecutter.project_slug}}/CLAUDE.md b/{{cookiecutter.project_slug}}/CLAUDE.md index e57767c..f52d165 100644 --- a/{{cookiecutter.project_slug}}/CLAUDE.md +++ b/{{cookiecutter.project_slug}}/CLAUDE.md @@ -123,6 +123,25 @@ parser.add_argument( - Mock external dependencies - Test both success and error paths +### Test Markers + +Mark slow tests to exclude them from pre-commit hooks: + +```python +import pytest + +@pytest.mark.slow +def test_integration_with_external_api(): + """This test won't run during pre-commit hooks.""" + ... + +def test_fast_unit_test(): + """This test will run during pre-commit hooks.""" + ... +``` + +The pre-commit hook runs `pytest -k "not slow"` to skip slow tests. + ## CI/CD GitHub Actions run on every push/PR: diff --git a/{{cookiecutter.project_slug}}/Makefile b/{{cookiecutter.project_slug}}/Makefile index ad0ca7b..06b4586 100644 --- a/{{cookiecutter.project_slug}}/Makefile +++ b/{{cookiecutter.project_slug}}/Makefile @@ -73,11 +73,15 @@ check: lint test typecheck: $(VENV)/pyvenv.cfg uv run pyright -.PHONY: fix format reformat -fix format reformat: +.PHONY: fix +fix: uv run ruff format && \ uv run ruff check --fix +# Alias for backwards compatibility +.PHONY: reformat +reformat: fix + .PHONY: test tests test tests: $(VENV)/pyvenv.cfg uv run pytest --cov=$(PY_IMPORT) $(T) $(TEST_ARGS) diff --git a/{{cookiecutter.project_slug}}/pyproject.toml b/{{cookiecutter.project_slug}}/pyproject.toml index 18a0a0a..b75c3e8 100644 --- a/{{cookiecutter.project_slug}}/pyproject.toml +++ b/{{cookiecutter.project_slug}}/pyproject.toml @@ -61,8 +61,9 @@ omit = ["{{ cookiecutter.__project_src_path }}/_cli.py"] [tool.pyright] # Type checking configuration +include = ["src", "test"] pythonVersion = "3.11" -typeCheckingMode = "standard" +typeCheckingMode = "strict" useLibraryCodeForTypes = true reportMissingTypeStubs = false From d7c9bbf6a3d690f4c202194941f088c83df3ef04 Mon Sep 17 00:00:00 2001 From: Alexis Date: Fri, 5 Dec 2025 11:57:04 +0100 Subject: [PATCH 15/17] Fix the PR --- .../.github/workflows/tests.yml | 3 + .../.pre-commit-config.yaml | 15 +- {{cookiecutter.project_slug}}/CLAUDE.md | 167 ------------------ {{cookiecutter.project_slug}}/Makefile | 7 +- {{cookiecutter.project_slug}}/pyproject.toml | 20 +-- 5 files changed, 23 insertions(+), 189 deletions(-) delete mode 100644 {{cookiecutter.project_slug}}/CLAUDE.md diff --git a/{{cookiecutter.project_slug}}/.github/workflows/tests.yml b/{{cookiecutter.project_slug}}/.github/workflows/tests.yml index f812a53..c39dec0 100644 --- a/{{cookiecutter.project_slug}}/.github/workflows/tests.yml +++ b/{{cookiecutter.project_slug}}/.github/workflows/tests.yml @@ -13,8 +13,11 @@ jobs: strategy: matrix: python: + - "3.10" - "3.11" - "3.12" + - "3.13" + - "3.14" runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 diff --git a/{{cookiecutter.project_slug}}/.pre-commit-config.yaml b/{{cookiecutter.project_slug}}/.pre-commit-config.yaml index 5980969..04b2646 100644 --- a/{{cookiecutter.project_slug}}/.pre-commit-config.yaml +++ b/{{cookiecutter.project_slug}}/.pre-commit-config.yaml @@ -2,13 +2,14 @@ # See https://pre-commit.com for more information repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.6.2 + # Ruff version. + rev: v0.14.8 hooks: - # Run the formatter - - id: ruff-format - # Run the linter - - id: ruff + # Run the linter. + - id: ruff-check args: [--fix] + # Run the formatter. + - id: ruff-format - repo: local hooks: @@ -30,8 +31,8 @@ repos: {%- endif %} - id: pytest - name: pytest (fast tests only) - entry: uv run pytest -x --tb=short -k "not slow" + name: pytest + entry: uv run pytest language: system types: [python] pass_filenames: false diff --git a/{{cookiecutter.project_slug}}/CLAUDE.md b/{{cookiecutter.project_slug}}/CLAUDE.md deleted file mode 100644 index f52d165..0000000 --- a/{{cookiecutter.project_slug}}/CLAUDE.md +++ /dev/null @@ -1,167 +0,0 @@ -# {{ cookiecutter.project_name }} - Claude Instructions - -This document contains project-specific instructions for Claude when working on this codebase. - -## Project Overview - -{{ cookiecutter.project_description }} - -## Code Standards - -This project enforces strict code quality standards: - -### Code Complexity Limits -- **Max 50 lines per function** - Split larger functions -- **Cyclomatic complexity ≤ 8** - Simplify complex logic -- **Max 5 positional parameters** - Use keyword arguments or dataclasses -- **Max 12 branches per function** - Extract to helper functions -- **Max 6 return statements** - Consolidate exit points - -### Style Guidelines -- **Line length**: 100 characters max -- **Docstrings**: Google style on all public functions/classes -- **Type hints**: Required for all function signatures -- **Tests**: Must live beside code (`test_*.py` or `*_test.py`) - -## Quick Commands - -```bash -# Development setup -make dev - -# Run all checks -make check # Runs lint + tests - -# Code quality -make lint # ruff format --check + ruff check + pyright -make fix # Auto-fix formatting and lint issues -make typecheck # Run pyright type checker - -# Testing -make test # Run pytest with coverage - -# Development -{% if cookiecutter.entry_point -%} -make run ARGS="--help" # Run the CLI -{%- endif %} -make doc # Build documentation -``` - -## Project Structure - -``` -src/ -└── {{ cookiecutter.__project_import.replace('.', '/') }}/ - ├── __init__.py - {%- if cookiecutter.entry_point %} - ├── __main__.py # CLI entry point - ├── _cli.py # CLI implementation - {%- endif %} - └── py.typed # Type checking marker - -test/ -└── test_*.py # Traditional test location -``` - -Tests can also live beside source files as `test_*.py` or `*_test.py`. - -## General Python Guidelines - -These are general preferences for Python development: - -- **Web frameworks**: Prefer FastAPI over Flask for new projects -- **Data processing**: Consider Polars for performance-critical data operations -- **Async programming**: Use native async/await instead of threading -- **Type checking**: Always use type hints and run pyright - -## Common Patterns - -### Error Handling -```python -from typing import Result # If using result types - -def process_data(path: str) -> Result[Data, str]: - """Process data from file. - - Args: - path: Path to data file. - - Returns: - Result with Data on success, error message on failure. - """ - try: - # Implementation - return Ok(data) - except Exception as e: - return Err(f"Failed to process: {e}") -``` - -### Logging -```python -import logging - -logger = logging.getLogger(__name__) -``` - -{%- if cookiecutter.entry_point %} - -### CLI Arguments -Use the existing `_cli.py` structure: -```python -parser.add_argument( - "--verbose", "-v", - action="store_true", - help="Enable verbose output" -) -``` -{%- endif %} - -## Testing Guidelines - -- Aim for 100% test coverage (enforced by CI) -- Use `pytest.mark.parametrize` for multiple test cases -- Mock external dependencies -- Test both success and error paths - -### Test Markers - -Mark slow tests to exclude them from pre-commit hooks: - -```python -import pytest - -@pytest.mark.slow -def test_integration_with_external_api(): - """This test won't run during pre-commit hooks.""" - ... - -def test_fast_unit_test(): - """This test will run during pre-commit hooks.""" - ... -``` - -The pre-commit hook runs `pytest -k "not slow"` to skip slow tests. - -## CI/CD - -GitHub Actions run on every push/PR: -1. **Linting**: ruff format/check + pyright type checking -2. **Tests**: pytest with coverage -3. **Security**: zizmor workflow scanning -{%- if cookiecutter.documentation == "pdoc" %} -4. **Docs**: Auto-deploy to GitHub Pages -{%- endif %} - -## Important Notes - -1. **Never commit code that violates the quality standards** - refactor instead -2. **All public APIs need Google-style docstrings** -3. **Type hints are mandatory** - use `pyright` -4. **Tests can live beside code** - prefer colocated tests for better maintainability - -## Project-Specific Instructions - - - ---- -*This file helps Claude understand project conventions. Update it as patterns emerge.* \ No newline at end of file diff --git a/{{cookiecutter.project_slug}}/Makefile b/{{cookiecutter.project_slug}}/Makefile index 06b4586..44fc66e 100644 --- a/{{cookiecutter.project_slug}}/Makefile +++ b/{{cookiecutter.project_slug}}/Makefile @@ -60,11 +60,8 @@ $(VENV)/pyvenv.cfg: pyproject.toml lint: $(VENV)/pyvenv.cfg uv run ruff format --check && \ uv run ruff check && \ - uv run pyright - - {%- 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: check check: lint test diff --git a/{{cookiecutter.project_slug}}/pyproject.toml b/{{cookiecutter.project_slug}}/pyproject.toml index b75c3e8..e8cc6bb 100644 --- a/{{cookiecutter.project_slug}}/pyproject.toml +++ b/{{cookiecutter.project_slug}}/pyproject.toml @@ -3,12 +3,13 @@ name = "{{ cookiecutter.project_slug }}" dynamic = ["version"] description = "{{ cookiecutter.project_description }}" readme = "README.md" +license-files = ["LICENSE"] {%- if cookiecutter.license == "Apache 2.0" %} -license = {text = "Apache-2.0"} +license = "Apache-2.0" {%- elif cookiecutter.license == "AGPL v3" %} -license = {text = "AGPL-3.0-or-later"} +license = "AGPL-3.0-or-later" {%- elif cookiecutter.license == "Proprietary" %} -license = {file = "LICENSE"} +license = "LicenseRef-Proprietary-License" {%- endif %} authors = [ @@ -18,7 +19,7 @@ classifiers = [ "Programming Language :: Python :: 3", ] dependencies = [] -requires-python = ">=3.11" +requires-python = ">=3.10" [tool.setuptools.dynamic] version = { attr = "{{ cookiecutter.__project_import }}.__version__" } @@ -33,7 +34,7 @@ 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", + "ruff ~= 0.14.8", "pyright >= 1.1", "types-html5lib", "types-requests", @@ -62,14 +63,14 @@ omit = ["{{ cookiecutter.__project_src_path }}/_cli.py"] [tool.pyright] # Type checking configuration include = ["src", "test"] -pythonVersion = "3.11" +pythonVersion = "3.10" typeCheckingMode = "strict" useLibraryCodeForTypes = true reportMissingTypeStubs = false [tool.ruff] line-length = 100 -target-version = "py311" +target-version = "py310" [tool.ruff.format] line-ending = "lf" @@ -123,8 +124,7 @@ fail-under = 100 {%- endif %} [tool.pytest.ini_options] -# Support tests living beside code -testpaths = ["src", "test"] -python_files = ["test_*.py", "*_test.py"] +testpaths = ["test"] +python_files = ["test_*.py"] # Show test durations addopts = "--durations=10" From 08e45c62d10ddfa266e4729dff74f74a09224e0b Mon Sep 17 00:00:00 2001 From: Alexis Date: Mon, 8 Dec 2025 15:14:07 +0100 Subject: [PATCH 16/17] Update Makefile --- {{cookiecutter.project_slug}}/Makefile | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/{{cookiecutter.project_slug}}/Makefile b/{{cookiecutter.project_slug}}/Makefile index 44fc66e..be0bd66 100644 --- a/{{cookiecutter.project_slug}}/Makefile +++ b/{{cookiecutter.project_slug}}/Makefile @@ -63,22 +63,11 @@ lint: $(VENV)/pyvenv.cfg uv run pyright{% if cookiecutter.docstring_coverage %} && \ uv run interrogate -c pyproject.toml .{% endif %} -.PHONY: check -check: lint test - -.PHONY: typecheck -typecheck: $(VENV)/pyvenv.cfg - uv run pyright - -.PHONY: fix -fix: +.PHONY: reformat +reformat: uv run ruff format && \ uv run ruff check --fix -# Alias for backwards compatibility -.PHONY: reformat -reformat: fix - .PHONY: test tests test tests: $(VENV)/pyvenv.cfg uv run pytest --cov=$(PY_IMPORT) $(T) $(TEST_ARGS) From 3726689f3824d403f1581391fe86c84a077a55d0 Mon Sep 17 00:00:00 2001 From: Alexis Date: Tue, 9 Dec 2025 11:03:11 +0100 Subject: [PATCH 17/17] Update the pre-commit --- .../.pre-commit-config.yaml | 34 ++++++------------- {{cookiecutter.project_slug}}/Makefile | 5 +++ {{cookiecutter.project_slug}}/pyproject.toml | 8 ++--- 3 files changed, 18 insertions(+), 29 deletions(-) diff --git a/{{cookiecutter.project_slug}}/.pre-commit-config.yaml b/{{cookiecutter.project_slug}}/.pre-commit-config.yaml index 04b2646..67afd28 100644 --- a/{{cookiecutter.project_slug}}/.pre-commit-config.yaml +++ b/{{cookiecutter.project_slug}}/.pre-commit-config.yaml @@ -1,46 +1,32 @@ # 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 + - id: format + name: format code + entry: make format 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 + - id: lint + name: lint code + entry: make lint language: system types: [python] pass_filenames: false - {%- endif %} + require_serial: true - - id: pytest - name: pytest - entry: uv run pytest + - id: test + name: run tests + entry: make test 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 diff --git a/{{cookiecutter.project_slug}}/Makefile b/{{cookiecutter.project_slug}}/Makefile index e029b32..6988f95 100644 --- a/{{cookiecutter.project_slug}}/Makefile +++ b/{{cookiecutter.project_slug}}/Makefile @@ -21,6 +21,11 @@ endif all: @echo "Run my targets individually!" +.PHONY: dev +dev: + uv sync --group dev + uv run pre-commit install + {%- if cookiecutter.entry_point %} .PHONY: run run: diff --git a/{{cookiecutter.project_slug}}/pyproject.toml b/{{cookiecutter.project_slug}}/pyproject.toml index 288d148..6bda506 100644 --- a/{{cookiecutter.project_slug}}/pyproject.toml +++ b/{{cookiecutter.project_slug}}/pyproject.toml @@ -36,10 +36,7 @@ lint = [ # NOTE: ruff is under active development, so we pin conservatively here # and let Dependabot periodically perform this update. "ruff ~= 0.14.0", - "pyright ~= 1.1.407", - "types-html5lib", - "types-requests", - "types-toml", + "pyright", {%- if cookiecutter.docstring_coverage %} "interrogate", {%- endif %} @@ -48,6 +45,7 @@ dev = [ {include-group = "doc"}, {include-group = "test"}, {include-group = "lint"}, + "pre-commit", ] {% if cookiecutter.entry_point -%} @@ -72,7 +70,7 @@ typeCheckingMode = "strict" reportUnusedImport = "warning" reportUnusedVariable = "warning" reportGeneralTypeIssues = "error" -reportMissingTypeStubs = false +reportMissingTypeStubs = true [tool.ruff] line-length = 100