pytest-revealtype-injector 0.5.0__tar.gz → 0.6.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 (22) hide show
  1. {pytest_revealtype_injector-0.5.0 → pytest_revealtype_injector-0.6.0}/PKG-INFO +51 -9
  2. {pytest_revealtype_injector-0.5.0 → pytest_revealtype_injector-0.6.0}/README.md +49 -5
  3. {pytest_revealtype_injector-0.5.0 → pytest_revealtype_injector-0.6.0}/pyproject.toml +1 -3
  4. {pytest_revealtype_injector-0.5.0 → pytest_revealtype_injector-0.6.0}/src/pytest_revealtype_injector/__init__.py +1 -1
  5. {pytest_revealtype_injector-0.5.0 → pytest_revealtype_injector-0.6.0}/src/pytest_revealtype_injector/adapter/pyright_.py +1 -0
  6. {pytest_revealtype_injector-0.5.0 → pytest_revealtype_injector-0.6.0}/src/pytest_revealtype_injector/hooks.py +28 -1
  7. {pytest_revealtype_injector-0.5.0 → pytest_revealtype_injector-0.6.0}/src/pytest_revealtype_injector/models.py +1 -1
  8. pytest_revealtype_injector-0.6.0/tests/test_marker.py +109 -0
  9. {pytest_revealtype_injector-0.5.0 → pytest_revealtype_injector-0.6.0}/.gitignore +0 -0
  10. {pytest_revealtype_injector-0.5.0 → pytest_revealtype_injector-0.6.0}/COPYING +0 -0
  11. {pytest_revealtype_injector-0.5.0 → pytest_revealtype_injector-0.6.0}/COPYING.mit +0 -0
  12. {pytest_revealtype_injector-0.5.0 → pytest_revealtype_injector-0.6.0}/src/pytest_revealtype_injector/adapter/__init__.py +0 -0
  13. {pytest_revealtype_injector-0.5.0 → pytest_revealtype_injector-0.6.0}/src/pytest_revealtype_injector/adapter/basedpyright_.py +0 -0
  14. {pytest_revealtype_injector-0.5.0 → pytest_revealtype_injector-0.6.0}/src/pytest_revealtype_injector/adapter/mypy_.py +0 -0
  15. {pytest_revealtype_injector-0.5.0 → pytest_revealtype_injector-0.6.0}/src/pytest_revealtype_injector/log.py +0 -0
  16. {pytest_revealtype_injector-0.5.0 → pytest_revealtype_injector-0.6.0}/src/pytest_revealtype_injector/main.py +0 -0
  17. {pytest_revealtype_injector-0.5.0 → pytest_revealtype_injector-0.6.0}/src/pytest_revealtype_injector/plugin.py +0 -0
  18. {pytest_revealtype_injector-0.5.0 → pytest_revealtype_injector-0.6.0}/src/pytest_revealtype_injector/py.typed +0 -0
  19. {pytest_revealtype_injector-0.5.0 → pytest_revealtype_injector-0.6.0}/tests/conftest.py +0 -0
  20. {pytest_revealtype_injector-0.5.0 → pytest_revealtype_injector-0.6.0}/tests/test_ast_mode.py +0 -0
  21. {pytest_revealtype_injector-0.5.0 → pytest_revealtype_injector-0.6.0}/tests/test_import.py +0 -0
  22. {pytest_revealtype_injector-0.5.0 → pytest_revealtype_injector-0.6.0}/tests/test_options.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytest-revealtype-injector
3
- Version: 0.5.0
3
+ Version: 0.6.0
4
4
  Summary: Pytest plugin for replacing reveal_type() calls inside test functions with static and runtime type checking result comparison, for confirming type annotation validity.
5
5
  Project-URL: homepage, https://github.com/abelcheung/pytest-revealtype-injector
6
6
  Author-email: Abel Cheung <abelcheung@gmail.com>
@@ -18,12 +18,10 @@ Classifier: Programming Language :: Python :: 3.10
18
18
  Classifier: Programming Language :: Python :: 3.11
19
19
  Classifier: Programming Language :: Python :: 3.12
20
20
  Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
21
22
  Classifier: Topic :: Software Development :: Testing
22
23
  Classifier: Typing :: Typed
23
24
  Requires-Python: >=3.10
24
- Requires-Dist: basedpyright>=1.0
25
- Requires-Dist: mypy>=1.11.2
26
- Requires-Dist: pyright>=1.1
27
25
  Requires-Dist: pytest<9,>=7.0
28
26
  Requires-Dist: schema==0.7.7
29
27
  Requires-Dist: typeguard>=4.3
@@ -37,7 +35,7 @@ Description-Content-Type: text/markdown
37
35
 
38
36
  `pytest-revealtype-injector` is a `pytest` plugin for replacing [`reveal_type()`](https://docs.python.org/3/library/typing.html#typing.reveal_type) calls inside test functions as something more sophisticated. It does the following tasks in parallel:
39
37
 
40
- - Launch external static type checkers (`pyright` and `mypy`) and store `reveal_type` results.
38
+ - Launch external static type checkers (`basesdpyright`, `pyright` and `mypy`) and store `reveal_type` results.
41
39
  - Use [`typeguard`](https://github.com/agronholm/typeguard) to verify the aforementioned static type checker result _really_ matches runtime code result.
42
40
 
43
41
  ## Usage
@@ -54,7 +52,17 @@ For using `reveal_type()` inside tests, there is no boiler plate code involved.
54
52
  from typing import reveal_type
55
53
  ```
56
54
 
57
- Just importing `typing` module is fine too (or import `typing_extensions` for Python 3.10, because `reveal_type()` is only available officially since 3.11):
55
+ If you care about compatibility with older pythons, use:
56
+
57
+ ```python
58
+ import sys
59
+ if sys.version >= (3, 11):
60
+ from typing import reveal_type
61
+ else:
62
+ from typing_extensions import reveal_type
63
+ ```
64
+
65
+ Just importing `typing` (or `typing_extensions`) module is fine too:
58
66
 
59
67
  ```python
60
68
  import typing
@@ -64,7 +72,7 @@ def test_something():
64
72
  typing.reveal_type(x) # typeguard fails here
65
73
  ```
66
74
 
67
- Since this plugin scans for `reveal_type()` for replacement under carpet, even `import ... as ...` syntax works too:
75
+ Since this plugin scans for `reveal_type()` for replacement under carpet, even `import ... as ...` syntax works:
68
76
 
69
77
  ```python
70
78
  import typing as typ # or...
@@ -73,7 +81,7 @@ from typing import reveal_type as rt
73
81
 
74
82
  ### Limitations
75
83
 
76
- But there are 2 caveats.
84
+ But there are 3 caveats.
77
85
 
78
86
  1. This plugin only searches for global import in test files, so local import inside test function doesn't work. That means following code doesn't utilize this plugin at all:
79
87
 
@@ -84,7 +92,41 @@ def test_something():
84
92
  reveal_type(x) # calls vanilla reveal_type()
85
93
  ```
86
94
 
87
- 2. `reveal_type()` calls have to stay in a single line, without anything else. This limitation comes from using [`eval` mode in AST parsing](https://docs.python.org/3/library/ast.html#ast.Expression).
95
+ 2. `reveal_type()` calls have to stay within a single line, although you can use `reveal_type` result in assertion or other purpose:
96
+
97
+ ```python
98
+ x = "1"
99
+ assert reveal_type(str(int(x))) == x
100
+ ```
101
+
102
+ 3. This plugin does not enlist any type checker as dependency, because any of them can be optionally disabled with pytest marker (see below) or command line option. It is up to application or library authors to include suitable type checker(s) as dependency themselves.
103
+
104
+ ## Disable type checker with marker
105
+
106
+ Using [pytest marker](https://docs.pytest.org/en/stable/example/markers.html), it is possible to disable usage of certain type checker for specific test. All 3 types of markers (function, class and module level) are supported.
107
+
108
+ Function level:
109
+ ```python
110
+ @pytest.mark.notypechecker("mypy")
111
+ def test_something(self) -> None:
112
+ x = 1
113
+ reveal_type(x)
114
+ ```
115
+
116
+ Class level:
117
+ ```python
118
+ @pytest.mark.notypechecker("pyright")
119
+ class TestSomething:
120
+ def test_foo(self) -> None:
121
+ ...
122
+ ```
123
+
124
+ Module level:
125
+ ```python
126
+ pytestmark = pytest.mark.notypechecker("basedpyright", "pyright")
127
+ ```
128
+
129
+ Note that disabling all type checkers is disallowed, and such tests would be treated as `pytest.fail`. Disable the `reveal_type()` call instead.
88
130
 
89
131
  ## Logging
90
132
 
@@ -5,7 +5,7 @@
5
5
 
6
6
  `pytest-revealtype-injector` is a `pytest` plugin for replacing [`reveal_type()`](https://docs.python.org/3/library/typing.html#typing.reveal_type) calls inside test functions as something more sophisticated. It does the following tasks in parallel:
7
7
 
8
- - Launch external static type checkers (`pyright` and `mypy`) and store `reveal_type` results.
8
+ - Launch external static type checkers (`basesdpyright`, `pyright` and `mypy`) and store `reveal_type` results.
9
9
  - Use [`typeguard`](https://github.com/agronholm/typeguard) to verify the aforementioned static type checker result _really_ matches runtime code result.
10
10
 
11
11
  ## Usage
@@ -22,7 +22,17 @@ For using `reveal_type()` inside tests, there is no boiler plate code involved.
22
22
  from typing import reveal_type
23
23
  ```
24
24
 
25
- Just importing `typing` module is fine too (or import `typing_extensions` for Python 3.10, because `reveal_type()` is only available officially since 3.11):
25
+ If you care about compatibility with older pythons, use:
26
+
27
+ ```python
28
+ import sys
29
+ if sys.version >= (3, 11):
30
+ from typing import reveal_type
31
+ else:
32
+ from typing_extensions import reveal_type
33
+ ```
34
+
35
+ Just importing `typing` (or `typing_extensions`) module is fine too:
26
36
 
27
37
  ```python
28
38
  import typing
@@ -32,7 +42,7 @@ def test_something():
32
42
  typing.reveal_type(x) # typeguard fails here
33
43
  ```
34
44
 
35
- Since this plugin scans for `reveal_type()` for replacement under carpet, even `import ... as ...` syntax works too:
45
+ Since this plugin scans for `reveal_type()` for replacement under carpet, even `import ... as ...` syntax works:
36
46
 
37
47
  ```python
38
48
  import typing as typ # or...
@@ -41,7 +51,7 @@ from typing import reveal_type as rt
41
51
 
42
52
  ### Limitations
43
53
 
44
- But there are 2 caveats.
54
+ But there are 3 caveats.
45
55
 
46
56
  1. This plugin only searches for global import in test files, so local import inside test function doesn't work. That means following code doesn't utilize this plugin at all:
47
57
 
@@ -52,7 +62,41 @@ def test_something():
52
62
  reveal_type(x) # calls vanilla reveal_type()
53
63
  ```
54
64
 
55
- 2. `reveal_type()` calls have to stay in a single line, without anything else. This limitation comes from using [`eval` mode in AST parsing](https://docs.python.org/3/library/ast.html#ast.Expression).
65
+ 2. `reveal_type()` calls have to stay within a single line, although you can use `reveal_type` result in assertion or other purpose:
66
+
67
+ ```python
68
+ x = "1"
69
+ assert reveal_type(str(int(x))) == x
70
+ ```
71
+
72
+ 3. This plugin does not enlist any type checker as dependency, because any of them can be optionally disabled with pytest marker (see below) or command line option. It is up to application or library authors to include suitable type checker(s) as dependency themselves.
73
+
74
+ ## Disable type checker with marker
75
+
76
+ Using [pytest marker](https://docs.pytest.org/en/stable/example/markers.html), it is possible to disable usage of certain type checker for specific test. All 3 types of markers (function, class and module level) are supported.
77
+
78
+ Function level:
79
+ ```python
80
+ @pytest.mark.notypechecker("mypy")
81
+ def test_something(self) -> None:
82
+ x = 1
83
+ reveal_type(x)
84
+ ```
85
+
86
+ Class level:
87
+ ```python
88
+ @pytest.mark.notypechecker("pyright")
89
+ class TestSomething:
90
+ def test_foo(self) -> None:
91
+ ...
92
+ ```
93
+
94
+ Module level:
95
+ ```python
96
+ pytestmark = pytest.mark.notypechecker("basedpyright", "pyright")
97
+ ```
98
+
99
+ Note that disabling all type checkers is disallowed, and such tests would be treated as `pytest.fail`. Disable the `reveal_type()` call instead.
56
100
 
57
101
  ## Logging
58
102
 
@@ -16,9 +16,6 @@ license = 'MIT'
16
16
  license-files = ['COPYING*']
17
17
  dependencies = [
18
18
  'typing_extensions >= 4.0; python_version < "3.11"',
19
- 'mypy >= 1.11.2',
20
- 'pyright >= 1.1',
21
- 'basedpyright >= 1.0',
22
19
  'pytest >=7.0,<9',
23
20
  'typeguard >= 4.3',
24
21
  # schema with annotation support is still unreleased
@@ -50,6 +47,7 @@ classifiers = [
50
47
  'Programming Language :: Python :: 3.11',
51
48
  'Programming Language :: Python :: 3.12',
52
49
  'Programming Language :: Python :: 3.13',
50
+ 'Programming Language :: Python :: 3.14',
53
51
  'Topic :: Software Development :: Testing',
54
52
  'Typing :: Typed',
55
53
  ]
@@ -1,3 +1,3 @@
1
1
  """Pytest plugin for replacing reveal_type() calls inside test functions with static and runtime type checking result comparison, for confirming type annotation validity.""" # noqa: E501
2
2
 
3
- __version__ = "0.5.0"
3
+ __version__ = "0.6.0"
@@ -53,6 +53,7 @@ class _PyrightDiagItem(TypedDict):
53
53
  range: _PyrightDiagRange
54
54
  rule: NotRequired[str]
55
55
 
56
+
56
57
  class NameCollector(NameCollectorBase):
57
58
  type_checker = "pyright"
58
59
  # Pre-register common used bare names from typing
@@ -16,8 +16,22 @@ adapter_stash_key: pytest.StashKey[set[TypeCheckerAdapter]]
16
16
 
17
17
  def pytest_pyfunc_call(pyfuncitem: pytest.Function) -> None:
18
18
  assert pyfuncitem.module is not None
19
- adapters = pyfuncitem.config.stash[adapter_stash_key].copy()
19
+ adp_stash = pyfuncitem.config.stash[adapter_stash_key]
20
20
 
21
+ # Disable type checker based on marker
22
+ mark = pyfuncitem.get_closest_marker("notypechecker")
23
+ if mark:
24
+ disabled_adapters = {a.id for a in adp_stash if a.id in mark.args}
25
+ for a in disabled_adapters:
26
+ _logger.info(f"{a} adapter disabled by 'notypechecker' marker")
27
+ adapters = {a for a in adp_stash if a.id not in disabled_adapters}
28
+ else:
29
+ adapters = {a for a in adp_stash}
30
+
31
+ if not adapters:
32
+ pytest.fail("All type checkers have been disabled.")
33
+
34
+ # Replace reveal_type() with our own function
21
35
  for name in dir(pyfuncitem.module):
22
36
  if name.startswith("__") or name.startswith("@py"):
23
37
  continue
@@ -54,6 +68,8 @@ def pytest_pyfunc_call(pyfuncitem: pytest.Function) -> None:
54
68
 
55
69
  def pytest_collection_finish(session: pytest.Session) -> None:
56
70
  files = {i.path for i in session.items}
71
+ if not files:
72
+ return
57
73
  for adp in session.config.stash[adapter_stash_key]:
58
74
  try:
59
75
  adp.run_typechecker_on(files)
@@ -86,13 +102,16 @@ def pytest_addoption(parser: pytest.Parser) -> None:
86
102
 
87
103
 
88
104
  def pytest_configure(config: pytest.Config) -> None:
105
+ # Globally disable adapters using command line options
89
106
  global adapter_stash_key
90
107
  adapter_stash_key = pytest.StashKey[set[TypeCheckerAdapter]]()
91
108
  config.stash[adapter_stash_key] = set()
92
109
  verbosity = config.get_verbosity(config.VERBOSITY_TEST_CASES)
93
110
  log.set_verbosity(verbosity)
94
111
  to_be_disabled = cast(list[str], config.getoption("revealtype_disable_adapter"))
112
+ all_ids: list[str] = []
95
113
  for klass in adapter.get_adapter_classes():
114
+ all_ids.append(klass.id)
96
115
  if klass.id in to_be_disabled:
97
116
  _logger.info(f"({klass.id}) adapter disabled with command line option")
98
117
  continue
@@ -100,3 +119,11 @@ def pytest_configure(config: pytest.Config) -> None:
100
119
  adp.set_config_file(config)
101
120
  adp.log_verbosity = verbosity
102
121
  config.stash[adapter_stash_key].add(adp)
122
+
123
+ # Marker to disable adapters on demand
124
+ config.addinivalue_line(
125
+ "markers",
126
+ "notypechecker(name, ...): mark reveal_type() test to disable "
127
+ "specified type checker(s). Following type checkers are supported: "
128
+ + ", ".join(all_ids),
129
+ )
@@ -15,7 +15,7 @@ from typing import (
15
15
  )
16
16
 
17
17
  import pytest
18
- from _pytest.config import Notset
18
+ from _pytest.config import Notset # pyright: ignore[reportPrivateImportUsage]
19
19
  from schema import Schema
20
20
 
21
21
  from .log import get_logger
@@ -0,0 +1,109 @@
1
+ from __future__ import annotations
2
+
3
+ import pytest
4
+
5
+
6
+ class TestFuncAndGlobalMarker:
7
+ PYPROJECT_TOML = """
8
+ [tool.pyright]
9
+ reportUnreachable = false
10
+ defineConstant = {"MYPY" = false}
11
+
12
+ [tool.mypy]
13
+ always_true = ["MYPY"]
14
+ """
15
+ TEST_CONTENT = """
16
+ import sys
17
+ import pytest
18
+ from typing import cast
19
+ if sys.version_info >= (3, 11):
20
+ from typing import reveal_type
21
+ else:
22
+ from typing_extensions import reveal_type
23
+
24
+ # pytestmark = pytest.mark.notypechecker('mypy')
25
+
26
+ # @pytest.mark.notypechecker('mypy')
27
+ def test_foo() -> None:
28
+ MYPY = False
29
+ if MYPY:
30
+ x = cast(str, 1) # type: ignore[assignment]
31
+ else:
32
+ x = 1
33
+ reveal_type(x)
34
+ """
35
+
36
+ def test_vanilla(self, pytester: pytest.Pytester) -> None:
37
+ pytester.makeconftest("pytest_plugins = ['pytest_revealtype_injector.plugin']")
38
+ pytester.makepyprojecttoml(self.PYPROJECT_TOML)
39
+ pytester.makepyfile( # pyright: ignore[reportUnknownMemberType]
40
+ self.TEST_CONTENT
41
+ )
42
+ result = pytester.runpytest("--tb=short", "-vv")
43
+ result.assert_outcomes(passed=0, failed=1)
44
+
45
+ def test_function_marker(self, pytester: pytest.Pytester) -> None:
46
+ pytester.makeconftest("pytest_plugins = ['pytest_revealtype_injector.plugin']")
47
+ pytester.makepyprojecttoml(self.PYPROJECT_TOML)
48
+ pytester.makepyfile( # pyright: ignore[reportUnknownMemberType]
49
+ self.TEST_CONTENT.replace("# @pytest.mark", "@pytest.mark")
50
+ )
51
+ result = pytester.runpytest("--tb=short", "-vv")
52
+ result.assert_outcomes(passed=1, failed=0)
53
+
54
+ def test_global_marker(self, pytester: pytest.Pytester) -> None:
55
+ pytester.makeconftest("pytest_plugins = ['pytest_revealtype_injector.plugin']")
56
+ pytester.makepyprojecttoml(self.PYPROJECT_TOML)
57
+ pytester.makepyfile( # pyright: ignore[reportUnknownMemberType]
58
+ self.TEST_CONTENT.replace("# pytestmark", "pytestmark")
59
+ )
60
+ result = pytester.runpytest("--tb=short", "-vv")
61
+ result.assert_outcomes(passed=1, failed=0)
62
+
63
+
64
+ class TestClassMarker:
65
+ PYPROJECT_TOML = """
66
+ [tool.pyright]
67
+ reportUnreachable = false
68
+ defineConstant = {"MYPY" = false}
69
+
70
+ [tool.mypy]
71
+ always_true = ["MYPY"]
72
+ """
73
+ TEST_CONTENT = """
74
+ import sys
75
+ import pytest
76
+ from typing import cast
77
+ if sys.version_info >= (3, 11):
78
+ from typing import reveal_type
79
+ else:
80
+ from typing_extensions import reveal_type
81
+
82
+ # @pytest.mark.notypechecker('pyright', 'basedpyright')
83
+ class TestFoo:
84
+ def test_foo(self) -> None:
85
+ MYPY = False
86
+ if MYPY:
87
+ x = 1
88
+ else:
89
+ x = cast(str, 1) # pyright: ignore[reportInvalidCast]
90
+ reveal_type(x)
91
+ """
92
+
93
+ def test_vanilla(self, pytester: pytest.Pytester) -> None:
94
+ pytester.makeconftest("pytest_plugins = ['pytest_revealtype_injector.plugin']")
95
+ pytester.makepyprojecttoml(self.PYPROJECT_TOML)
96
+ pytester.makepyfile( # pyright: ignore[reportUnknownMemberType]
97
+ self.TEST_CONTENT
98
+ )
99
+ result = pytester.runpytest("--tb=short", "-vv")
100
+ result.assert_outcomes(passed=0, failed=1)
101
+
102
+ def test_marker_applied(self, pytester: pytest.Pytester) -> None:
103
+ pytester.makeconftest("pytest_plugins = ['pytest_revealtype_injector.plugin']")
104
+ pytester.makepyprojecttoml(self.PYPROJECT_TOML)
105
+ pytester.makepyfile( # pyright: ignore[reportUnknownMemberType]
106
+ self.TEST_CONTENT.replace("# @pytest.mark", "@pytest.mark")
107
+ )
108
+ result = pytester.runpytest("--tb=short", "-vv")
109
+ result.assert_outcomes(passed=1, failed=0)