-
Notifications
You must be signed in to change notification settings - Fork 6
Enhance cookiecutter template with stricter code quality standards #84
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
f537826
c975cb8
f6dee7f
6fa0b6a
517395b
08fb765
e805061
330cbb9
2fbd39b
d405f81
d481298
2902ab9
67244e8
a249f00
0030cef
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,8 +13,6 @@ jobs: | |
| strategy: | ||
| matrix: | ||
| python: | ||
| - "3.9" | ||
| - "3.10" | ||
| - "3.11" | ||
| - "3.12" | ||
| runs-on: ubuntu-latest | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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: 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 (fast tests only) | ||
| entry: uv run pytest -x --tb=short -k "not slow" | ||
| 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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,167 @@ | ||
| # {{ 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`) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This looks to contradict the current structure. The current structure has the source files in I don't think I've seen Python projects with test files next to the source code files. I have seen it for C and C++ projects though, particularly projects from Google. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. After some research, some people are saying that for large projects, it's helpful to have unit tests next to the source code to easily test distributions of the project and also to more easily find tests for functionality in that source file. Others say that including the test files in the source directory will make distribution packages larger, which may or may not be a concern. I don't think I have strong opinions on this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've always found it better to have tests in a separate dir, but... whatever works. |
||
|
|
||
| ## 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 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is this thing real or ai-made up? I've never seen it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch. I also get an error when trying to import this under Python 3.13. The Google style guide has a section on error handling and exceptions https://google.github.io/styleguide/pyguide.html#24-exceptions |
||
|
|
||
| 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 | ||
| <!-- Add any project-specific Claude instructions here --> | ||
| --- | ||
| *This file helps Claude understand project conventions. Update it as patterns emerge.* | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For context, we were trying to keep track of active Python versions, as listed here https://devguide.python.org/versions/
Removing Python 3.9 would be fine, since it'll be EOL soon. However, unless we're using Python 3.11 features, I think we should still include Python 3.10. This will ensure compatibility with all supported Python versions by default, and then each project can make the explicit decision to remove compatibility with older versions.
We should add "3.13" while we're here.