Contributions of docs, tests, or code are welcome.
- For a new assertion or matcher, open an issue first. The API grows from demand, and a working implementation on its own is not enough to land one
- Fork the repo
- Clone your fork (
git clone <your_fork_url>) - Create a branch (
git checkout -b my_branch) - Install dependencies with the one sync command below, not a bare
uv sync - Make your changes
- Run the verification pipeline and fix any issues
- Commit using Conventional Commits
- Push your branch (
git push origin my_branch) - Open a Pull Request
Read more about how pulls work on GitHub's About pull requests page.
- Python 3.10+
- uv as the package manager
Read ARCHITECTURE.md. Five files under assertpy2/_engine/ are generated, the public
surface is pinned by a recorded snapshot, and neither is discoverable from the files themselves.
Run all checks before submitting a PR. Every step must pass.
A bare uv sync leaves out the optional libraries several tests import, and those tests then fail
rather than skip. Install exactly this set:
uv sync --extra json --extra data --extra inline --group integrations --group docs-examplesuv run ruff check .
uv run ruff format --check .
uv run ty check assertpy2/ tests/test_typing.py
uv run pytest --cov=assertpy2 --cov-fail-under=100 --ignore=tests/test_docs_examples.py tests
uv run pytest tests/test_docs_examples.py tests/test_typing_claims.py
uv run --group docs mkdocs build --strictCI requires 100% code coverage.
Run the suite on Python 3.10 as well. This is not optional, and the block above does not cover it.
A union carries __name__ from 3.14 onwards and not before. logging.LoggerAdapter became
subscriptable in 3.11. Neither difference is visible to a type checker told to target 3.10, and both
shipped as defects that every other gate passed.
Use a throwaway environment. uv run --python 3.10 inside the project rebuilds .venv for that
version, and on Windows it can fail partway and leave you without pytest:
uv venv /tmp/py310 --python 3.10
export VIRTUAL_ENV=/tmp/py310
uv pip install -e ".[json,data,inline]"
uv pip install pytest pytest-cov hypothesis ruff
uv pip install attrs pydantic requests httpx flask
/tmp/py310/bin/python -m pytest tests --ignore=tests/test_docs_examples.py -qruff belongs in that list: the tests that compare a generated file against its generator shell out
to it, and without it they fail for a reason that has nothing to do with your change.
Run these after the main gate, not before. uv run --group typecheck adds the checkers to the same
.venv and leaves them there, and a coverage run with them installed collects a different set:
uv run --group typecheck mypy --strict --follow-imports=silent tests/test_typing.py
PYRIGHT_PYTHON_FORCE_VERSION=1.1.414 uv run --group typecheck pyright --pythonversion 3.14 tests/test_typing.py
uv run --group typecheck pyrefly check tests/test_typing.py
uv run --group typecheck pytest tests/test_pyright_baseline.pyPYRIGHT_PYTHON_FORCE_VERSION picks the engine rather than the launcher. The pyright distribution on
PyPI is released separately from the npm engine it launches, and the two have drifted apart before: PyPI
stayed at 1.1.411 from June to September while npm shipped two builds. The baseline is recorded against
one engine, and tests/typing_harness.py pins the same build, so only this direct invocation needs the
variable.
--pythonversion 3.14 is not decoration. Pyright reports against the interpreter it finds unless
told otherwise, and the count moves with it: 174 diagnostics for this package on 3.10 against 169 on
3.14.
Without it a contributor on the supported floor meets a red baseline that says nothing about their change. The baseline test passes the same target itself, so it gives one answer everywhere.
ty checkis scoped on purpose. Run over the whole tree it reports hundreds of diagnostics fromtests/, which is full of deliberate negative typing cases: calls that must not type-check.--all-extrasis not a shortcut for the sync line. Installingallureorbehavemakes 100% coverage unreachable by construction, because theexcept ImportErrorbranch guarding them stops being executed. CI never installs them for the coverage job either.tests/test_docs_examples.pyis run separately because it executes the snippets indocs/, which needs thedocs-examplesgroup and a different collection.
Two gates record what the library currently decides rather than what it should. Both fail on a change and neither is a failure to paper over: read the diff, decide whether the change is meant to ship, then re-record it in the same commit so the diff carries the decision into review.
ASSERTPY2_UPDATE_API=1 uv run pytest tests/test_api_compatibility.py # the public surface
ASSERTPY2_UPDATE_VERDICTS=1 uv run pytest tests/test_verdict_corpus.py # what each input decidesThe second one's diff is the draft of the release's Behaviour changes section, so read it before writing that section rather than after.
Use Conventional Commits: feat:, fix:, refactor:, test:, docs:, chore:, etc.
Write tests for every new feature or bug fix. Use assertpy2 assertions in tests.
Guide code blocks are type-checked in CI, and every page that can run in the suite is executed, with what
each block prints compared with the comment beside it, so an example you add has to run. A block that
cannot (pseudo-context, a deliberate failure, a rejected counter-example) is marked with an HTML comment
above the fence. tests/test_docs_examples.py lists the markers and what each one exempts, and it and
tests/test_docs_typing.py each name the pages they leave out, with the reason.
Setup a page assumes (a domain class, a repository, an HTTP response) goes in
tests/docs_fixtures.py, not into an extra block on the page.