Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
45 changes: 45 additions & 0 deletions .cursor/rules/commit-messages.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
Use the Conventional Commit Messages specification to generate commit messages

The commit message should be structured as follows:


```
<type>[optional scope]: <description>

[optional body]

[optional footer(s)]
```
--------------------------------

The commit contains the following structural elements, to communicate intent to the consumers of your library:

- fix: a commit of the type fix patches a bug in your codebase (this correlates with PATCH in Semantic Versioning).
- feat: a commit of the type feat introduces a new feature to the codebase (this correlates with MINOR in Semantic Versioning).
- BREAKING CHANGE: a commit that has a footer BREAKING CHANGE:, or appends a ! after the type/scope, introduces a breaking API change (correlating with MAJOR in Semantic Versioning). A BREAKING CHANGE can be part of commits of any type.
- types other than fix: and feat: are allowed, for example @commitlint/config-conventional (based on the Angular convention) recommends build:, chore:, ci:, docs:, style:, refactor:, perf:, test:, and others.
- footers other than BREAKING CHANGE: <description> may be provided and follow a convention similar to git trailer format.
- Additional types are not mandated by the Conventional Commits specification, and have no implicit effect in Semantic Versioning (unless they include a BREAKING CHANGE). A scope may be provided to a commit’s type, to provide additional contextual information and is contained within parenthesis, e.g., feat(parser): add ability to parse arrays.



### Specification Details

The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119.

Commits MUST be prefixed with a type, which consists of a noun, feat, fix, etc., followed by the OPTIONAL scope, OPTIONAL !, and REQUIRED terminal colon and space.
The type feat MUST be used when a commit adds a new feature to your application or library.
The type fix MUST be used when a commit represents a bug fix for your application.
A scope MAY be provided after a type. A scope MUST consist of a noun describing a section of the codebase surrounded by parenthesis, e.g., fix(parser):
A description MUST immediately follow the colon and space after the type/scope prefix. The description is a short summary of the code changes, e.g., fix: array parsing issue when multiple spaces were contained in string.
A longer commit body MAY be provided after the short description, providing additional contextual information about the code changes. The body MUST begin one blank line after the description.
A commit body is free-form and MAY consist of any number of newline separated paragraphs.
One or more footers MAY be provided one blank line after the body. Each footer MUST consist of a word token, followed by either a :<space> or <space># separator, followed by a string value (this is inspired by the git trailer convention).
A footer’s token MUST use - in place of whitespace characters, e.g., Acked-by (this helps differentiate the footer section from a multi-paragraph body). An exception is made for BREAKING CHANGE, which MAY also be used as a token.
A footer’s value MAY contain spaces and newlines, and parsing MUST terminate when the next valid footer token/separator pair is observed.
Breaking changes MUST be indicated in the type/scope prefix of a commit, or as an entry in the footer.
If included as a footer, a breaking change MUST consist of the uppercase text BREAKING CHANGE, followed by a colon, space, and description, e.g., BREAKING CHANGE: environment variables now take precedence over config files.
If included in the type/scope prefix, breaking changes MUST be indicated by a ! immediately before the :. If ! is used, BREAKING CHANGE: MAY be omitted from the footer section, and the commit description SHALL be used to describe the breaking change.
Types other than feat and fix MAY be used in your commit messages, e.g., docs: update ref docs.
The units of information that make up Conventional Commits MUST NOT be treated as case sensitive by implementors, with the exception of BREAKING CHANGE which MUST be uppercase.
BREAKING-CHANGE MUST be synonymous with BREAKING CHANGE, when used as a token in a footer.
79 changes: 79 additions & 0 deletions .cursor/rules/python.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Cursor Rules: Python (project-specific)

These rules guide Python contributions in this repository.

## Environment and tooling
- Use `uv` for dependency management and virtualenvs. Supported Python versions are 3.9–3.13.
- Use Nox sessions defined in `noxfile.py` to run tasks:
- `uv run nox -s fmt` to sort imports and format code (ruff is the single formatter)
- `uv run nox -s lint` to lint and check formatting (no autofix)
- `uv run nox -s type_check` to run mypy (strict)
- `uv run nox -s test` to run tests with coverage (100% threshold enforced)
- `uv run nox -s docs` to build docs
- `uv run nox -s licenses -- --format=json` for license reports when needed
- Pre-commit hooks must pass before pushing (`ruff`, `ruff-format`, `mypy`, and repo checks).
- Prefer running tools via `uv run` so executables resolve from the project environment.

## Project layout and imports
- Place source under `src/` with package root `src/fact/`.
- Place tests under `tests/` with pytest.
- Do not introduce relative imports; always use absolute imports.
- Follow import grouping and order enforced by ruff-isort:
- standard library, third-party, first-party; type-only imports last when possible.
- Avoid wildcard imports; export public APIs explicitly via `__all__` when applicable.

## Code style
- Line length is 99 characters.
- Ruff is the single source of truth for formatting and linting (see `pyproject.toml`).
- Run `uv run nox -s fmt` before committing. Do not use additional formatters.
- Prefer `pathlib.Path` over `os.path`, and f-strings over string formatting.
- Keep functions small and focused; avoid deep nesting; favor early returns.
- Eliminate trailing whitespace and ensure a newline at end of file.

## Typing and APIs
- Add precise type hints for all new functions, methods, and public APIs.
- Follow strict typing; mypy is configured with `strict = true`.
- Prefer clear names over abbreviations; avoid 1–2 character identifiers.
- Use early-return patterns and handle error cases first.
- Prefer PEP 604 unions (`str | None`), `typing.Literal`, `Final`, `TypedDict`, `Protocol`, `NewType`.
- Use `dataclasses.dataclass` for simple data containers (`frozen=True, slots=True` when sensible).
- Avoid `Any`. If unavoidable, minimize scope and justify with a focused `# type: ignore[code]`.
- Public APIs should have stable, well-typed signatures and clear error semantics.

## Tests
- Add or update tests for new behavior in `tests/`.
- Keep coverage at or above the configured threshold (100%).
- Use pytest naming: files `test_*.py`, tests `test_*`.
- Treat warnings as errors (configured); fix root causes or mark with precise filters.
- Use `pytest.mark.parametrize` for input matrices; prefer fixtures over ad-hoc setup.
- Avoid broad exception catching in tests; assert specific exceptions and messages.
- Use `tmp_path`/`monkeypatch` judiciously; avoid network or external side effects.

## Docs and docstrings
- Use Google-style docstrings for public functions/classes.
- Keep docs up to date when modifying public behavior; update MkDocs content if needed.
- Type hints are the source of truth for types; docstrings should describe behavior and errors.
- Build docs with `uv run nox -s docs`. Validate external links via `docs_check_urls` when needed.

## CLI specifics
- The CLI is implemented with Typer in `src/fact/cli.py`. Preserve the current CLI UX unless explicitly changing it.

- Do not add TODO comments; implement the behavior or open an issue.

## Error handling and security
- Raise specific exceptions (`ValueError`, `TypeError`, etc.); avoid bare `except`.
- Prefer `subprocess.run([...], check=True)` without `shell=True`. If shell is required, document why.
- Never use `eval`/`exec` on untrusted input. Keep secrets in environment variables, not in code.

## Commit messages
- Follow Conventional Commits as documented in `.cursor/rules/commit-messages.md`.

## Useful commands
- `uv run nox -s fmt` — sort imports and format
- `uv run nox -s lint` — lint and format check
- `uv run nox -s type_check` — mypy strict type checking
- `uv run nox -s test` — run tests with coverage enforcement
- `uv run nox -s docs` — build documentation

References: `pyproject.toml`, `noxfile.py`,
`https://github.com/PatrickJS/awesome-cursorrules/blob/main/rules-new/python.mdc`.
2 changes: 1 addition & 1 deletion .gitattributes
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto
* text=auto
47 changes: 47 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# See https://pre-commit.com for usage and config
# Run: pre-commit install
# Run all hooks on all files: pre-commit run --all-files

repos:
# Common sanity checks
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: check-merge-conflict
- id: check-added-large-files
- id: end-of-file-fixer
- id: trailing-whitespace
- id: debug-statements
- id: check-yaml
exclude: ^mkdocs\.yml$
- id: check-toml
- id: check-case-conflict
- id: check-symlinks
- id: detect-private-key

# Ruff: formatter and linter
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.5.7
hooks:
- id: ruff
args: ["--fix", "--exit-non-zero-on-fix"]
- id: ruff-format

# Mypy: static type checker (configured in pyproject.toml)
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.17.0
hooks:
- id: mypy
args:
- "--config-file=pyproject.toml"
- "src"
Copy link

Copilot AI Aug 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The mypy exclusion for 'src/fact/cli.py' should be documented with a comment explaining why this file is excluded from type checking.

Suggested change
- "src"
- "src"
# Exclude src/fact/cli.py from type checking due to dynamic CLI argument parsing that is difficult to type-check.

Copilot uses AI. Check for mistakes.
- "--exclude=^src/fact/cli\\.py$"
pass_filenames: false


- repo: https://github.com/compilerla/conventional-pre-commit
rev: v4.2.0
hooks:
- id: conventional-pre-commit
stages: [commit-msg]
args: []
41 changes: 26 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# python-blueprint
# python-blueprint fork

[![GitHub Actions][github-actions-badge]](https://github.com/johnthagen/python-blueprint/actions)
[![uv][uv-badge]](https://github.com/astral-sh/uv)
Expand All @@ -18,6 +18,17 @@ provides a simple implementation of the
[factorial algorithm](https://en.wikipedia.org/wiki/Factorial) (`fact.lib`) and a command line
interface (`fact.cli`).

# Additions

Relative to the original [repo](https://github.com/johnthagen/python-blueprint)

We have added
* Pre-commit hooks to enable a number formatting/linting/checking before
each commit.
* Add a checker for enforcing "Conventional Commits" in our history.
* Cursor rules for Python and enforcing Conventional Commits
inspired by [awesome-cursorrules](https://github.com/PatrickJS/awesome-cursorrules)

# Package Management

This package uses [uv](https://docs.astral.sh/uv/) to manage dependencies and
Expand Down Expand Up @@ -66,7 +77,7 @@ Packaging is configured by:

- [`pyproject.toml`](./pyproject.toml)

To package the project as both a
To package the project as both a
[source distribution](https://packaging.python.org/en/latest/flow/#the-source-distribution-sdist)
and a [wheel](https://packaging.python.org/en/latest/specifications/binary-distribution-format/):

Expand Down Expand Up @@ -254,11 +265,11 @@ run:

```shell
uv run nox -s docs_serve
```
```

and open <http://127.0.0.1:8000> in a browser.

Each time the `main` Git branch is updated, the
Each time the `main` Git branch is updated, the
[`.github/workflows/pages.yml`](.github/workflows/pages.yml) GitHub Action will
automatically build the user guide and publish it to [GitHub Pages](https://pages.github.com/).
This is configured in the `docs_github_pages` Nox session. This hosted user guide
Expand Down Expand Up @@ -313,7 +324,7 @@ distribution has been packaged and installed, thereby catching any errors in pac
installation scripts, which are common. Having the Python packages in the project root subverts
this isolation for two reasons:

1. Calling `python` in the project root (for example, `python -m pytest tests/`)
1. Calling `python` in the project root (for example, `python -m pytest tests/`)
[causes Python to add the current working directory](https://docs.pytest.org/en/latest/pythonpath.html#invoking-pytest-versus-python-m-pytest)
(the project root) to `sys.path`, which Python uses to find modules. Because the source
package `fact` is in the project root, it shadows the `fact` package installed in the Nox
Expand All @@ -332,7 +343,7 @@ prevent this, there are three possible solutions:
to `tests`.
3. Move the source packages to a dedicated `src` folder.

The dedicated `src` directory is the
The dedicated `src` directory is the
[recommended solution](https://docs.pytest.org/en/latest/pythonpath.html#test-modules-conftest-py-files-inside-packages)
by `pytest` when using Nox and the solution this blueprint promotes because it is the least brittle
even though it deviates from the traditional Python project structure. It results is a directory
Expand Down Expand Up @@ -368,10 +379,10 @@ using [pip-licenses](https://github.com/raimon49/pip-licenses):
```shell
$ uv run nox -s licenses
...
Name Version License
Pygments 2.19.1 BSD License
click 8.1.8 BSD License
markdown-it-py 3.0.0 MIT License
Name Version License
Pygments 2.19.1 BSD License
click 8.1.8 BSD License
markdown-it-py 3.0.0 MIT License
```

# Container
Expand Down Expand Up @@ -485,7 +496,7 @@ project:

## Ruff Integration

Integrate [Ruff](https://docs.astral.sh/ruff/editors/setup/#pycharm) linting and
Integrate [Ruff](https://docs.astral.sh/ruff/editors/setup/#pycharm) linting and
formatting into PyCharm.

### Linting and Formatting
Expand All @@ -500,15 +511,15 @@ Now, on <kbd>ctrl+s</kbd>, the current source file will be automatically formatt
sorted on save.

> [!TIP]
> These tools work best if you properly mark directories as excluded from the project that should
> be, such as `.nox`. See
> <https://www.jetbrains.com/help/pycharm/project-tool-window.html#content_pane_context_menu> on
> These tools work best if you properly mark directories as excluded from the project that should
> be, such as `.nox`. See
> <https://www.jetbrains.com/help/pycharm/project-tool-window.html#content_pane_context_menu> on
> how to Right-Click | Mark Directory as | Excluded.

## Nox Support

[PyCharm does not yet natively support Nox](https://youtrack.jetbrains.com/issue/PY-37302). The
recommended way to launch Nox from PyCharm is to create a **Python**
recommended way to launch Nox from PyCharm is to create a **Python**
[Run Configuration](https://www.jetbrains.com/help/pycharm/run-debug-configuration.html).

- Beside **Script Path**, press `▼` and select **Module name**: `nox`
Expand Down
4 changes: 2 additions & 2 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ status: new

!!! info

This user guide is purely an illustrative example that shows off several features of
This user guide is purely an illustrative example that shows off several features of
[Material for MkDocs](https://squidfunk.github.io/mkdocs-material/) and included Markdown
extensions[^1].

Expand Down Expand Up @@ -74,6 +74,6 @@ assert factorial(3) == 6 # (1)!
| 1 | 1 |
| 2 | 2 |
| 3 | 6 |
| 4 | 24 |
| 4 | 24 |

</div>
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ type_check = [
]
lint = [
"ruff",
"pre-commit",
]
docs = [
"mkdocs-material",
Expand Down Expand Up @@ -89,6 +90,8 @@ enable_error_code = ["deprecated", "exhaustive-match", "explicit-override"]
# If certain strict config options are too pedantic for a project,
# disable them selectively here by setting to false.

# mypy CLI flags are set in pre-commit to avoid noisy decorator warnings in CLI entrypoint.

[tool.ruff]
line-length = 99
src = ["src"]
Expand Down
Loading