type-assert
pytest plugin that checks a value's static type and its runtime type in one assertion.
A type checker only ever sees the annotations. A runtime checker only ever sees the
values. Either can be right while the other is wrong, and overloaded signatures are
where they drift apart. type-assert pins both halves at once, from one line:
assert_types(json.loads('[1]'), Any)
assert_types(sorted({'b', 'a'}), list[str])
Each line becomes two tests. One runs the expression and checks the value it produced. The other checks what a type checker inferred for the same line. The line only passes if the two agree.
Warning — The API of this package is unstable and likely to change between minor versions (for example
0.1.0to0.2.0). Pin the exact version you depend on, for exampletype-assert==0.1.0.
Installation
pip install type-assert[mypy] # or [pyright], [pyrefly], or [all]
The checker itself is an extra, because it should be whichever one your project already uses.
Usage
Put a directory of case files somewhere in your test tree and point the plugin at it:
[tool.pytest.ini_options]
type_assert_cases = 'tests/typing/cases'
A case file is an ordinary Python module. Every top-level assert_types call is a case;
everything else — imports, helpers, constants — is setup shared by the cases in that
file:
from __future__ import annotations
import json
from typing import Any
from type_assert import assert_types
def payload() -> str:
"""Return a document to parse."""
return '{"a": 1}'
assert_types(json.loads(payload()), Any)
assert_types(sorted({'b', 'a'}), list[str])
assert_types(''.join([]), str)
Running pytest collects each case file as a test file of its own:
tests/typing/cases/basics.py::setup
tests/typing/cases/basics.py::sorted({'b', 'a'}) -> list[str] [runtime]
tests/typing/cases/basics.py::sorted({'b', 'a'}) -> list[str] [static]
How assert_types does both
To a type checker, assert_types is
typing_extensions.assert_type,
aliased under TYPE_CHECKING. Checkers resolve an aliased import back to its original
definition, so the special case still applies: the inferred type must match the second
argument exactly, and a supertype is a failure rather than a pass.
At runtime that name is bound to a real checker instead, backed by
pycroscope, which walks containers exhaustively —
it catches a None at any position in a list[int], not only the first element.
Writing the type once covers both halves, and there is no way for them to drift apart.
Choosing a checker
[tool.pytest.ini_options]
type_assert_checkers = 'mypy' # the default
type_assert_checkers = 'pyright'
type_assert_checkers = 'mypy pyright pyrefly' # each with its own test
Naming more than one gives every case a static test per checker, so a case has to hold under all of them:
cases/basics.py::sorted({'b', 'a'}) -> list[str] [runtime]
cases/basics.py::sorted({'b', 'a'}) -> list[str] [static: mypy]
cases/basics.py::sorted({'b', 'a'}) -> list[str] [static: pyright]
The runtime test is not repeated, since the value does not depend on who checked it. Bear in mind that two checkers do not always infer the same type for the same expression, so a case that satisfies one may need rewording to satisfy both.
ty is deliberately not supported yet: it is pre-1.0 and its output format is still
moving. Adding a backend is a single module — see type_assert/_checkers/.
Skipping a case at runtime
A case that cannot run everywhere — it crashes on a platform, or needs something that is
not always installed — is named in a SKIP_RUNTIME mapping in its own file:
SKIP_RUNTIME = {
'expression exactly as written': 'why running it fails here',
}
Only the runtime half is skipped; the checker still checks the case. The mapping is read
after the file's setup has run, so making an entry conditional is ordinary Python. An
entry naming an expression that no case makes fails the file's setup test, so a skip
cannot quietly outlive the case it was written for.
License
MIT
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file type_assert-0.1.0.tar.gz.
File metadata
- Download URL: type_assert-0.1.0.tar.gz
- Upload date:
- Size: 28.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
06af54dc9fd743694b0692bbe037d0a03c4b7bf7c570e7d42cf60b8e5751c348
|
|
| MD5 |
00d6c978e691aa77eef02e70d841870e
|
|
| BLAKE2b-256 |
cc07607ba9759d6c31cdd8327a77b79992ab92c2d4ea84e2c389d5338829c5ac
|
Provenance
The following attestation bundles were made for type_assert-0.1.0.tar.gz:
Publisher:
ci.yml on user27182/type-assert
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
type_assert-0.1.0.tar.gz -
Subject digest:
06af54dc9fd743694b0692bbe037d0a03c4b7bf7c570e7d42cf60b8e5751c348 - Sigstore transparency entry: 2717418091
- Sigstore integration time:
-
Permalink:
user27182/type-assert@868a2293d84b44102b054ea5a2fb75032a3ae317 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/user27182
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@868a2293d84b44102b054ea5a2fb75032a3ae317 -
Trigger Event:
push
-
Statement type:
File details
Details for the file type_assert-0.1.0-py3-none-any.whl.
File metadata
- Download URL: type_assert-0.1.0-py3-none-any.whl
- Upload date:
- Size: 19.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fd4be62a553978608af081c6066826f1a660872c1e899f75916506a5b2740fc2
|
|
| MD5 |
0843b156fb35e5579424cc57de09483e
|
|
| BLAKE2b-256 |
0d0631341be51688ba3e405ff7e374d0bab7b6e38f29dc0dfb2200ae98c9e1d0
|
Provenance
The following attestation bundles were made for type_assert-0.1.0-py3-none-any.whl:
Publisher:
ci.yml on user27182/type-assert
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
type_assert-0.1.0-py3-none-any.whl -
Subject digest:
fd4be62a553978608af081c6066826f1a660872c1e899f75916506a5b2740fc2 - Sigstore transparency entry: 2717418742
- Sigstore integration time:
-
Permalink:
user27182/type-assert@868a2293d84b44102b054ea5a2fb75032a3ae317 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/user27182
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@868a2293d84b44102b054ea5a2fb75032a3ae317 -
Trigger Event:
push
-
Statement type: