type-assert 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. type_assert-0.1.0/.github/dependabot.yml +16 -0
  2. type_assert-0.1.0/.github/release.yml +9 -0
  3. type_assert-0.1.0/.github/workflows/ci.yml +112 -0
  4. type_assert-0.1.0/.gitignore +14 -0
  5. type_assert-0.1.0/.pre-commit-config.yaml +38 -0
  6. type_assert-0.1.0/LICENSE +21 -0
  7. type_assert-0.1.0/PKG-INFO +168 -0
  8. type_assert-0.1.0/README.md +128 -0
  9. type_assert-0.1.0/pyproject.toml +125 -0
  10. type_assert-0.1.0/setup.cfg +4 -0
  11. type_assert-0.1.0/tests/conftest.py +37 -0
  12. type_assert-0.1.0/tests/test_assertions.py +167 -0
  13. type_assert-0.1.0/tests/test_cases.py +217 -0
  14. type_assert-0.1.0/tests/test_checkers.py +284 -0
  15. type_assert-0.1.0/tests/test_plugin.py +310 -0
  16. type_assert-0.1.0/tools/check_without_checker.py +36 -0
  17. type_assert-0.1.0/type_assert/__init__.py +27 -0
  18. type_assert-0.1.0/type_assert/_assertions.py +55 -0
  19. type_assert-0.1.0/type_assert/_cases.py +182 -0
  20. type_assert-0.1.0/type_assert/_checkers/__init__.py +43 -0
  21. type_assert-0.1.0/type_assert/_checkers/_base.py +54 -0
  22. type_assert-0.1.0/type_assert/_checkers/_mypy.py +86 -0
  23. type_assert-0.1.0/type_assert/_checkers/_pyrefly.py +100 -0
  24. type_assert-0.1.0/type_assert/_checkers/_pyright.py +97 -0
  25. type_assert-0.1.0/type_assert/_version.py +24 -0
  26. type_assert-0.1.0/type_assert/plugin.py +227 -0
  27. type_assert-0.1.0/type_assert/py.typed +0 -0
  28. type_assert-0.1.0/type_assert.egg-info/PKG-INFO +168 -0
  29. type_assert-0.1.0/type_assert.egg-info/SOURCES.txt +33 -0
  30. type_assert-0.1.0/type_assert.egg-info/dependency_links.txt +1 -0
  31. type_assert-0.1.0/type_assert.egg-info/entry_points.txt +2 -0
  32. type_assert-0.1.0/type_assert.egg-info/requires.txt +15 -0
  33. type_assert-0.1.0/type_assert.egg-info/scm_file_list.json +28 -0
  34. type_assert-0.1.0/type_assert.egg-info/scm_version.json +8 -0
  35. type_assert-0.1.0/type_assert.egg-info/top_level.txt +1 -0
@@ -0,0 +1,16 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: pip
4
+ directory: /
5
+ schedule:
6
+ interval: weekly
7
+ labels: [maintenance, dependencies]
8
+ open-pull-requests-limit: 20
9
+ - package-ecosystem: github-actions
10
+ directory: /.github/workflows
11
+ schedule:
12
+ interval: weekly
13
+ labels: [maintenance, dependencies]
14
+ groups:
15
+ artifacts:
16
+ patterns: [actions/upload-artifact, actions/download-artifact]
@@ -0,0 +1,9 @@
1
+ changelog:
2
+ exclude:
3
+ authors:
4
+ - dependabot
5
+ - dependabot[bot]
6
+ - pre-commit-ci
7
+ - pre-commit-ci[bot]
8
+ - github-actions
9
+ - github-actions[bot]
@@ -0,0 +1,112 @@
1
+ name: CI
2
+
3
+ # `pull_request` is deliberately unfiltered: filtering it by base branch would
4
+ # skip every pull request in a stack that does not target main.
5
+ on:
6
+ push:
7
+ branches: [main]
8
+ tags: [v*]
9
+ pull_request:
10
+
11
+ concurrency:
12
+ group: ${{ github.workflow }}-${{ github.ref }}
13
+ cancel-in-progress: true
14
+
15
+ permissions:
16
+ contents: read
17
+
18
+ jobs:
19
+ pre-commit:
20
+ runs-on: ubuntu-latest
21
+ steps:
22
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
23
+ with:
24
+ persist-credentials: false
25
+ fetch-depth: 0
26
+ - uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
27
+ with:
28
+ enable-cache: false
29
+ - run: uv sync --group dev
30
+ - run: uv run pre-commit run --all-files --show-diff-on-failure
31
+ env:
32
+ # This hook exists to stop *local* commits straight to main; it
33
+ # would always fail here since CI checks out that branch/tag directly.
34
+ SKIP: no-commit-to-branch
35
+
36
+ test:
37
+ # Every supported Python, on every platform: the plugin shells out to a
38
+ # checker and parses its output, which is exactly where paths differ.
39
+ runs-on: ${{ matrix.os }}
40
+ strategy:
41
+ fail-fast: false
42
+ matrix:
43
+ os: [ubuntu-latest, windows-latest, macos-latest]
44
+ python-version: ['3.10', '3.11', '3.12', '3.13', '3.14']
45
+ steps:
46
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
47
+ with:
48
+ persist-credentials: false
49
+ fetch-depth: 0
50
+ - uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
51
+ with:
52
+ enable-cache: false
53
+ - run: uv sync --group dev --extra all --python ${{ matrix.python-version }}
54
+ # Coverage has to start before pytest, since the plugin is imported by
55
+ # entry point before any pytest-cov hook would run.
56
+ - run: uv run coverage run -m pytest
57
+ - run: uv run coverage combine
58
+ - run: uv run coverage report --fail-under=95
59
+ - run: uv run coverage xml
60
+ - uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
61
+ if: matrix.os == 'ubuntu-latest'
62
+ with:
63
+ files: ./coverage.xml
64
+ env:
65
+ CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
66
+
67
+ test-no-checker:
68
+ # Neither checker is a hard dependency. With neither installed the package
69
+ # must still import and the runtime half must still work; only the static
70
+ # half is unavailable, and it has to say so plainly.
71
+ runs-on: ubuntu-latest
72
+ steps:
73
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
74
+ with:
75
+ persist-credentials: false
76
+ fetch-depth: 0
77
+ - uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
78
+ with:
79
+ enable-cache: false
80
+ - run: uv sync --no-dev
81
+ - run: uv run --no-sync python tools/check_without_checker.py
82
+
83
+ build:
84
+ needs: [pre-commit, test]
85
+ runs-on: ubuntu-latest
86
+ steps:
87
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
88
+ with:
89
+ persist-credentials: false
90
+ fetch-depth: 0
91
+ - uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
92
+ with:
93
+ enable-cache: false
94
+ - run: uv build
95
+ - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
96
+ with:
97
+ name: dist
98
+ path: dist/
99
+
100
+ publish:
101
+ needs: build
102
+ if: startsWith(github.ref, 'refs/tags/v')
103
+ runs-on: ubuntu-latest
104
+ environment: release
105
+ permissions:
106
+ id-token: write
107
+ steps:
108
+ - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
109
+ with:
110
+ name: dist
111
+ path: dist/
112
+ - uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # release/v1
@@ -0,0 +1,14 @@
1
+ .DS_Store
2
+ .coverage
3
+ .coverage.*
4
+ .idea/
5
+ .mypy_cache/
6
+ .pytest_cache/
7
+ .ruff_cache/
8
+ .venv/
9
+ build/
10
+ dist/
11
+ *.egg-info/
12
+ __pycache__/
13
+ type_assert/_version.py
14
+ uv.lock
@@ -0,0 +1,38 @@
1
+ ci:
2
+ autoupdate_commit_msg: 'chore: update pre-commit hooks'
3
+ autofix_prs: true
4
+ autoupdate_schedule: quarterly
5
+
6
+ repos:
7
+ - repo: https://github.com/pre-commit/pre-commit-hooks
8
+ rev: v5.0.0
9
+ hooks:
10
+ - id: check-merge-conflict
11
+ - id: debug-statements
12
+ - id: no-commit-to-branch
13
+ args: [--branch, main]
14
+
15
+ - repo: https://github.com/astral-sh/ruff-pre-commit
16
+ rev: v0.16.0
17
+ hooks:
18
+ - id: ruff-check
19
+ args: [--fix, --show-fixes]
20
+ - id: ruff-format
21
+
22
+ - repo: https://github.com/zizmorcore/zizmor-pre-commit
23
+ rev: v1.11.0
24
+ hooks:
25
+ - id: zizmor
26
+
27
+ - repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks
28
+ rev: v2.15.0
29
+ hooks:
30
+ - id: pretty-format-yaml
31
+ args: [--autofix, --indent, '2']
32
+
33
+ - repo: https://github.com/ComPWA/taplo-pre-commit
34
+ rev: v0.9.3
35
+ hooks:
36
+ - id: taplo-format
37
+ # See options: https://taplo.tamasfe.dev/configuration/formatter-options.html
38
+ args: [--option, reorder_arrays=true, --option, reorder_keys=true, --option, align_comments=false]
@@ -0,0 +1,21 @@
1
+ The MIT License
2
+
3
+ Copyright (c) 2026 user27182
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,168 @@
1
+ Metadata-Version: 2.4
2
+ Name: type-assert
3
+ Version: 0.1.0
4
+ Summary: pytest plugin that checks a value static type and its runtime type in one assertion
5
+ Author: user27182
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/user27182/type-assert
8
+ Project-URL: Issues, https://github.com/user27182/type-assert/issues
9
+ Keywords: annotations,mypy,pyright,pytest,typing
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Framework :: Pytest
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Operating System :: MacOS
14
+ Classifier: Operating System :: Microsoft :: Windows
15
+ Classifier: Operating System :: POSIX :: Linux
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
22
+ Classifier: Topic :: Software Development :: Quality Assurance
23
+ Classifier: Topic :: Software Development :: Testing
24
+ Classifier: Typing :: Typed
25
+ Requires-Python: >=3.10
26
+ Description-Content-Type: text/markdown
27
+ License-File: LICENSE
28
+ Requires-Dist: pycroscope<0.6,>=0.5
29
+ Requires-Dist: pytest>=7
30
+ Requires-Dist: typing-extensions>=4.5
31
+ Provides-Extra: all
32
+ Requires-Dist: type-assert[mypy,pyrefly,pyright]; extra == "all"
33
+ Provides-Extra: mypy
34
+ Requires-Dist: mypy>=1.11; extra == "mypy"
35
+ Provides-Extra: pyrefly
36
+ Requires-Dist: pyrefly>=1.0; extra == "pyrefly"
37
+ Provides-Extra: pyright
38
+ Requires-Dist: pyright>=1.1.390; extra == "pyright"
39
+ Dynamic: license-file
40
+
41
+ # type-assert
42
+
43
+ pytest plugin that checks a value's static type and its runtime type in one assertion.
44
+
45
+ A type checker only ever sees the annotations. A runtime checker only ever sees the
46
+ values. Either can be right while the other is wrong, and overloaded signatures are
47
+ where they drift apart. `type-assert` pins both halves at once, from one line:
48
+
49
+ ```python
50
+ assert_types(json.loads('[1]'), Any)
51
+ assert_types(sorted({'b', 'a'}), list[str])
52
+ ```
53
+
54
+ Each line becomes two tests. One runs the expression and checks the value it produced.
55
+ The other checks what a type checker inferred for the same line. The line only passes
56
+ if the two agree.
57
+
58
+ > **Warning** — The API of this package is unstable and likely to change between
59
+ > minor versions (for example `0.1.0` to `0.2.0`). Pin the exact version you
60
+ > depend on, for example `type-assert==0.1.0`.
61
+
62
+ ## Installation
63
+
64
+ ```bash
65
+ pip install type-assert[mypy] # or [pyright], [pyrefly], or [all]
66
+ ```
67
+
68
+ The checker itself is an extra, because it should be whichever one your project
69
+ already uses.
70
+
71
+ ## Usage
72
+
73
+ Put a directory of case files somewhere in your test tree and point the plugin at it:
74
+
75
+ ```toml
76
+ [tool.pytest.ini_options]
77
+ type_assert_cases = 'tests/typing/cases'
78
+ ```
79
+
80
+ A case file is an ordinary Python module. Every top-level `assert_types` call is a case;
81
+ everything else — imports, helpers, constants — is setup shared by the cases in that
82
+ file:
83
+
84
+ ```python
85
+ from __future__ import annotations
86
+
87
+ import json
88
+ from typing import Any
89
+
90
+ from type_assert import assert_types
91
+
92
+
93
+ def payload() -> str:
94
+ """Return a document to parse."""
95
+ return '{"a": 1}'
96
+
97
+
98
+ assert_types(json.loads(payload()), Any)
99
+ assert_types(sorted({'b', 'a'}), list[str])
100
+ assert_types(''.join([]), str)
101
+ ```
102
+
103
+ Running pytest collects each case file as a test file of its own:
104
+
105
+ ```text
106
+ tests/typing/cases/basics.py::setup
107
+ tests/typing/cases/basics.py::sorted({'b', 'a'}) -> list[str] [runtime]
108
+ tests/typing/cases/basics.py::sorted({'b', 'a'}) -> list[str] [static]
109
+ ```
110
+
111
+ ## How `assert_types` does both
112
+
113
+ To a type checker, `assert_types` *is*
114
+ [`typing_extensions.assert_type`](https://typing-extensions.readthedocs.io/en/latest/#typing_extensions.assert_type),
115
+ aliased under `TYPE_CHECKING`. Checkers resolve an aliased import back to its original
116
+ definition, so the special case still applies: the inferred type must match the second
117
+ argument **exactly**, and a supertype is a failure rather than a pass.
118
+
119
+ At runtime that name is bound to a real checker instead, backed by
120
+ [pycroscope](https://pycroscope.readthedocs.io/), which walks containers exhaustively —
121
+ it catches a `None` at any position in a `list[int]`, not only the first element.
122
+
123
+ Writing the type once covers both halves, and there is no way for them to drift apart.
124
+
125
+ ## Choosing a checker
126
+
127
+ ```toml
128
+ [tool.pytest.ini_options]
129
+ type_assert_checkers = 'mypy' # the default
130
+ type_assert_checkers = 'pyright'
131
+ type_assert_checkers = 'mypy pyright pyrefly' # each with its own test
132
+ ```
133
+
134
+ Naming more than one gives every case a static test per checker, so a case has to hold
135
+ under all of them:
136
+
137
+ ```text
138
+ cases/basics.py::sorted({'b', 'a'}) -> list[str] [runtime]
139
+ cases/basics.py::sorted({'b', 'a'}) -> list[str] [static: mypy]
140
+ cases/basics.py::sorted({'b', 'a'}) -> list[str] [static: pyright]
141
+ ```
142
+
143
+ The runtime test is not repeated, since the value does not depend on who checked it.
144
+ Bear in mind that two checkers do not always infer the same type for the same
145
+ expression, so a case that satisfies one may need rewording to satisfy both.
146
+
147
+ `ty` is deliberately not supported yet: it is pre-1.0 and its output format is still
148
+ moving. Adding a backend is a single module — see `type_assert/_checkers/`.
149
+
150
+ ## Skipping a case at runtime
151
+
152
+ A case that cannot run everywhere — it crashes on a platform, or needs something that is
153
+ not always installed — is named in a `SKIP_RUNTIME` mapping in its own file:
154
+
155
+ ```python
156
+ SKIP_RUNTIME = {
157
+ 'expression exactly as written': 'why running it fails here',
158
+ }
159
+ ```
160
+
161
+ Only the runtime half is skipped; the checker still checks the case. The mapping is read
162
+ after the file's setup has run, so making an entry conditional is ordinary Python. An
163
+ entry naming an expression that no case makes fails the file's `setup` test, so a skip
164
+ cannot quietly outlive the case it was written for.
165
+
166
+ ## License
167
+
168
+ MIT
@@ -0,0 +1,128 @@
1
+ # type-assert
2
+
3
+ pytest plugin that checks a value's static type and its runtime type in one assertion.
4
+
5
+ A type checker only ever sees the annotations. A runtime checker only ever sees the
6
+ values. Either can be right while the other is wrong, and overloaded signatures are
7
+ where they drift apart. `type-assert` pins both halves at once, from one line:
8
+
9
+ ```python
10
+ assert_types(json.loads('[1]'), Any)
11
+ assert_types(sorted({'b', 'a'}), list[str])
12
+ ```
13
+
14
+ Each line becomes two tests. One runs the expression and checks the value it produced.
15
+ The other checks what a type checker inferred for the same line. The line only passes
16
+ if the two agree.
17
+
18
+ > **Warning** — The API of this package is unstable and likely to change between
19
+ > minor versions (for example `0.1.0` to `0.2.0`). Pin the exact version you
20
+ > depend on, for example `type-assert==0.1.0`.
21
+
22
+ ## Installation
23
+
24
+ ```bash
25
+ pip install type-assert[mypy] # or [pyright], [pyrefly], or [all]
26
+ ```
27
+
28
+ The checker itself is an extra, because it should be whichever one your project
29
+ already uses.
30
+
31
+ ## Usage
32
+
33
+ Put a directory of case files somewhere in your test tree and point the plugin at it:
34
+
35
+ ```toml
36
+ [tool.pytest.ini_options]
37
+ type_assert_cases = 'tests/typing/cases'
38
+ ```
39
+
40
+ A case file is an ordinary Python module. Every top-level `assert_types` call is a case;
41
+ everything else — imports, helpers, constants — is setup shared by the cases in that
42
+ file:
43
+
44
+ ```python
45
+ from __future__ import annotations
46
+
47
+ import json
48
+ from typing import Any
49
+
50
+ from type_assert import assert_types
51
+
52
+
53
+ def payload() -> str:
54
+ """Return a document to parse."""
55
+ return '{"a": 1}'
56
+
57
+
58
+ assert_types(json.loads(payload()), Any)
59
+ assert_types(sorted({'b', 'a'}), list[str])
60
+ assert_types(''.join([]), str)
61
+ ```
62
+
63
+ Running pytest collects each case file as a test file of its own:
64
+
65
+ ```text
66
+ tests/typing/cases/basics.py::setup
67
+ tests/typing/cases/basics.py::sorted({'b', 'a'}) -> list[str] [runtime]
68
+ tests/typing/cases/basics.py::sorted({'b', 'a'}) -> list[str] [static]
69
+ ```
70
+
71
+ ## How `assert_types` does both
72
+
73
+ To a type checker, `assert_types` *is*
74
+ [`typing_extensions.assert_type`](https://typing-extensions.readthedocs.io/en/latest/#typing_extensions.assert_type),
75
+ aliased under `TYPE_CHECKING`. Checkers resolve an aliased import back to its original
76
+ definition, so the special case still applies: the inferred type must match the second
77
+ argument **exactly**, and a supertype is a failure rather than a pass.
78
+
79
+ At runtime that name is bound to a real checker instead, backed by
80
+ [pycroscope](https://pycroscope.readthedocs.io/), which walks containers exhaustively —
81
+ it catches a `None` at any position in a `list[int]`, not only the first element.
82
+
83
+ Writing the type once covers both halves, and there is no way for them to drift apart.
84
+
85
+ ## Choosing a checker
86
+
87
+ ```toml
88
+ [tool.pytest.ini_options]
89
+ type_assert_checkers = 'mypy' # the default
90
+ type_assert_checkers = 'pyright'
91
+ type_assert_checkers = 'mypy pyright pyrefly' # each with its own test
92
+ ```
93
+
94
+ Naming more than one gives every case a static test per checker, so a case has to hold
95
+ under all of them:
96
+
97
+ ```text
98
+ cases/basics.py::sorted({'b', 'a'}) -> list[str] [runtime]
99
+ cases/basics.py::sorted({'b', 'a'}) -> list[str] [static: mypy]
100
+ cases/basics.py::sorted({'b', 'a'}) -> list[str] [static: pyright]
101
+ ```
102
+
103
+ The runtime test is not repeated, since the value does not depend on who checked it.
104
+ Bear in mind that two checkers do not always infer the same type for the same
105
+ expression, so a case that satisfies one may need rewording to satisfy both.
106
+
107
+ `ty` is deliberately not supported yet: it is pre-1.0 and its output format is still
108
+ moving. Adding a backend is a single module — see `type_assert/_checkers/`.
109
+
110
+ ## Skipping a case at runtime
111
+
112
+ A case that cannot run everywhere — it crashes on a platform, or needs something that is
113
+ not always installed — is named in a `SKIP_RUNTIME` mapping in its own file:
114
+
115
+ ```python
116
+ SKIP_RUNTIME = {
117
+ 'expression exactly as written': 'why running it fails here',
118
+ }
119
+ ```
120
+
121
+ Only the runtime half is skipped; the checker still checks the case. The mapping is read
122
+ after the file's setup has run, so making an entry conditional is ordinary Python. An
123
+ entry naming an expression that no case makes fails the file's `setup` test, so a skip
124
+ cannot quietly outlive the case it was written for.
125
+
126
+ ## License
127
+
128
+ MIT
@@ -0,0 +1,125 @@
1
+ [build-system]
2
+ build-backend = 'setuptools.build_meta'
3
+ requires = ['setuptools>=64', 'setuptools_scm>=8']
4
+
5
+ [dependency-groups]
6
+ dev = ['pre-commit', 'pytest-cov', { include-group = 'test' }]
7
+ test = ['mypy', 'pyrefly', 'pyright', 'pytest']
8
+
9
+ [project]
10
+ authors = [{ name = 'user27182' }]
11
+ classifiers = [
12
+ 'Development Status :: 3 - Alpha',
13
+ 'Framework :: Pytest',
14
+ 'Intended Audience :: Developers',
15
+ 'Operating System :: MacOS',
16
+ 'Operating System :: Microsoft :: Windows',
17
+ 'Operating System :: POSIX :: Linux',
18
+ 'Programming Language :: Python :: 3',
19
+ 'Programming Language :: Python :: 3.10',
20
+ 'Programming Language :: Python :: 3.11',
21
+ 'Programming Language :: Python :: 3.12',
22
+ 'Programming Language :: Python :: 3.13',
23
+ 'Programming Language :: Python :: 3.14',
24
+ 'Topic :: Software Development :: Quality Assurance',
25
+ 'Topic :: Software Development :: Testing',
26
+ 'Typing :: Typed',
27
+ ]
28
+ dependencies = ['pycroscope>=0.5,<0.6', 'pytest>=7', 'typing-extensions>=4.5']
29
+ description = 'pytest plugin that checks a value static type and its runtime type in one assertion'
30
+ dynamic = ['version']
31
+ keywords = ['annotations', 'mypy', 'pyright', 'pytest', 'typing']
32
+ license = 'MIT'
33
+ name = 'type-assert'
34
+ readme = 'README.md'
35
+ requires-python = '>=3.10'
36
+
37
+ [project.entry-points.pytest11]
38
+ type_assert = 'type_assert.plugin'
39
+
40
+ [project.optional-dependencies]
41
+ # A checker is whatever the project under test already uses; neither is required.
42
+ all = ['type-assert[mypy,pyrefly,pyright]']
43
+ mypy = ['mypy>=1.11']
44
+ pyrefly = ['pyrefly>=1.0']
45
+ pyright = ['pyright>=1.1.390']
46
+
47
+ [project.urls]
48
+ Homepage = 'https://github.com/user27182/type-assert'
49
+ Issues = 'https://github.com/user27182/type-assert/issues'
50
+
51
+ [tool.coverage.run]
52
+ branch = true
53
+ omit = ['*/_version.py']
54
+ parallel = true
55
+ source = ['type_assert']
56
+
57
+ [tool.pytest.ini_options]
58
+ addopts = '--strict-config --strict-markers'
59
+ testpaths = 'tests'
60
+
61
+ [tool.ruff]
62
+ indent-width = 4
63
+ line-length = 99
64
+
65
+ [tool.ruff.format]
66
+ docstring-code-format = true
67
+ docstring-code-line-length = 75
68
+ quote-style = 'single'
69
+
70
+ [tool.ruff.lint]
71
+ extend-select = [
72
+ 'A',
73
+ 'B',
74
+ 'C4',
75
+ 'D',
76
+ 'E',
77
+ 'EM',
78
+ 'F',
79
+ 'FA',
80
+ 'I',
81
+ 'ICN',
82
+ 'N',
83
+ 'PERF',
84
+ 'PGH',
85
+ 'PIE',
86
+ 'PT',
87
+ 'RET',
88
+ 'RSE',
89
+ 'RUF',
90
+ 'SIM',
91
+ 'T10',
92
+ 'T20',
93
+ 'TCH',
94
+ 'TID',
95
+ 'UP',
96
+ 'W',
97
+ 'YTT',
98
+ ]
99
+ ignore = ['D203', 'D211', 'D213']
100
+
101
+ [tool.ruff.lint.isort]
102
+ combine-as-imports = true
103
+ force-single-line = true
104
+ force-sort-within-sections = true
105
+ required-imports = ['from __future__ import annotations']
106
+
107
+ [tool.ruff.lint.per-file-ignores]
108
+ 'tests/*' = ['D102', 'D103']
109
+ # `Union` and `Optional` here are runtime values handed to `assert_types`, not
110
+ # annotations. Both spellings are worth covering, since users write both.
111
+ 'tests/test_assertions.py' = ['D102', 'D103', 'UP007', 'UP045']
112
+
113
+ [tool.ruff.lint.pyupgrade]
114
+ keep-runtime-typing = true
115
+
116
+ [tool.setuptools]
117
+ license-files = ['LICENSE']
118
+ packages = ['type_assert', 'type_assert._checkers']
119
+
120
+ [tool.setuptools.package-data]
121
+ type_assert = ['py.typed']
122
+
123
+ [tool.setuptools_scm]
124
+ version_scheme = 'release-branch-semver'
125
+ write_to = 'type_assert/_version.py'
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,37 @@
1
+ """Shared fixtures for the test suite."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from pathlib import Path
7
+
8
+ import pytest
9
+
10
+ pytest_plugins = ['pytester']
11
+
12
+ REPO_ROOT = Path(__file__).parent.parent
13
+
14
+
15
+ @pytest.fixture
16
+ def checker_root(tmp_path, monkeypatch):
17
+ """Return a project root the checkers can resolve `type_assert` from.
18
+
19
+ The project installs itself editable through an import finder, which neither
20
+ mypy nor pyright can follow, so point both at the source tree instead. This is
21
+ a fact about developing this package: an ordinary installation puts `type_assert`
22
+ in site-packages, where both find it with no help.
23
+ """
24
+ monkeypatch.setenv('MYPYPATH', str(REPO_ROOT))
25
+ (tmp_path / 'pyrightconfig.json').write_text(
26
+ json.dumps({'extraPaths': [str(REPO_ROOT)]}), encoding='utf-8'
27
+ )
28
+ write_pyrefly_config(tmp_path)
29
+ return tmp_path
30
+
31
+
32
+ def write_pyrefly_config(directory):
33
+ """Give `directory` a pyrefly config, which pyrefly refuses to work without."""
34
+ (directory / 'pyrefly.toml').write_text(
35
+ f'project-includes = ["cases"]\nsearch-path = [{str(REPO_ROOT)!r}]\n',
36
+ encoding='utf-8',
37
+ )