pytest-revealtype-injector 0.5.1__tar.gz → 0.6.1__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.1 → pytest_revealtype_injector-0.6.1}/PKG-INFO +32 -5
  2. {pytest_revealtype_injector-0.5.1 → pytest_revealtype_injector-0.6.1}/README.md +30 -1
  3. {pytest_revealtype_injector-0.5.1 → pytest_revealtype_injector-0.6.1}/pyproject.toml +1 -3
  4. {pytest_revealtype_injector-0.5.1 → pytest_revealtype_injector-0.6.1}/src/pytest_revealtype_injector/__init__.py +1 -1
  5. {pytest_revealtype_injector-0.5.1 → pytest_revealtype_injector-0.6.1}/src/pytest_revealtype_injector/adapter/mypy_.py +4 -0
  6. {pytest_revealtype_injector-0.5.1 → pytest_revealtype_injector-0.6.1}/src/pytest_revealtype_injector/adapter/pyright_.py +1 -0
  7. {pytest_revealtype_injector-0.5.1 → pytest_revealtype_injector-0.6.1}/src/pytest_revealtype_injector/hooks.py +26 -1
  8. {pytest_revealtype_injector-0.5.1 → pytest_revealtype_injector-0.6.1}/src/pytest_revealtype_injector/models.py +1 -1
  9. pytest_revealtype_injector-0.6.1/tests/test_marker.py +109 -0
  10. {pytest_revealtype_injector-0.5.1 → pytest_revealtype_injector-0.6.1}/.gitignore +0 -0
  11. {pytest_revealtype_injector-0.5.1 → pytest_revealtype_injector-0.6.1}/COPYING +0 -0
  12. {pytest_revealtype_injector-0.5.1 → pytest_revealtype_injector-0.6.1}/COPYING.mit +0 -0
  13. {pytest_revealtype_injector-0.5.1 → pytest_revealtype_injector-0.6.1}/src/pytest_revealtype_injector/adapter/__init__.py +0 -0
  14. {pytest_revealtype_injector-0.5.1 → pytest_revealtype_injector-0.6.1}/src/pytest_revealtype_injector/adapter/basedpyright_.py +0 -0
  15. {pytest_revealtype_injector-0.5.1 → pytest_revealtype_injector-0.6.1}/src/pytest_revealtype_injector/log.py +0 -0
  16. {pytest_revealtype_injector-0.5.1 → pytest_revealtype_injector-0.6.1}/src/pytest_revealtype_injector/main.py +0 -0
  17. {pytest_revealtype_injector-0.5.1 → pytest_revealtype_injector-0.6.1}/src/pytest_revealtype_injector/plugin.py +0 -0
  18. {pytest_revealtype_injector-0.5.1 → pytest_revealtype_injector-0.6.1}/src/pytest_revealtype_injector/py.typed +0 -0
  19. {pytest_revealtype_injector-0.5.1 → pytest_revealtype_injector-0.6.1}/tests/conftest.py +0 -0
  20. {pytest_revealtype_injector-0.5.1 → pytest_revealtype_injector-0.6.1}/tests/test_ast_mode.py +0 -0
  21. {pytest_revealtype_injector-0.5.1 → pytest_revealtype_injector-0.6.1}/tests/test_import.py +0 -0
  22. {pytest_revealtype_injector-0.5.1 → pytest_revealtype_injector-0.6.1}/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.1
3
+ Version: 0.6.1
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
@@ -83,7 +81,7 @@ from typing import reveal_type as rt
83
81
 
84
82
  ### Limitations
85
83
 
86
- But there are 2 caveats.
84
+ But there are 3 caveats.
87
85
 
88
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:
89
87
 
@@ -101,6 +99,35 @@ x = "1"
101
99
  assert reveal_type(str(int(x))) == x
102
100
  ```
103
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.
130
+
104
131
  ## Logging
105
132
 
106
133
  This plugin uses standard [`logging`](https://docs.python.org/3/library/logging.html) internally. `pytest -v` can be used to reveal `INFO` and `DEBUG` logs. Given following example:
@@ -51,7 +51,7 @@ from typing import reveal_type as rt
51
51
 
52
52
  ### Limitations
53
53
 
54
- But there are 2 caveats.
54
+ But there are 3 caveats.
55
55
 
56
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:
57
57
 
@@ -69,6 +69,35 @@ x = "1"
69
69
  assert reveal_type(str(int(x))) == x
70
70
  ```
71
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.
100
+
72
101
  ## Logging
73
102
 
74
103
  This plugin uses standard [`logging`](https://docs.python.org/3/library/logging.html) internally. `pytest -v` can be used to reveal `INFO` and `DEBUG` logs. Given following example:
@@ -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.1"
3
+ __version__ = "0.6.1"
@@ -125,6 +125,10 @@ class NameCollector(NameCollectorBase):
125
125
  # something like "test_elem_class_lookup.FooClass@97".
126
126
  # Return only the left operand after processing.
127
127
  def visit_BinOp(self, node: ast.BinOp) -> ast.expr:
128
+ if isinstance(node.op, ast.BitOr): # union
129
+ node.left = self.visit(node.left)
130
+ node.right = self.visit(node.right)
131
+ return node
128
132
  if isinstance(node.op, ast.MatMult) and isinstance(node.right, ast.Constant):
129
133
  return cast("ast.expr", self.visit(node.left))
130
134
  # For expression that haven't been accounted for, just don't
@@ -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
@@ -88,13 +102,16 @@ def pytest_addoption(parser: pytest.Parser) -> None:
88
102
 
89
103
 
90
104
  def pytest_configure(config: pytest.Config) -> None:
105
+ # Globally disable adapters using command line options
91
106
  global adapter_stash_key
92
107
  adapter_stash_key = pytest.StashKey[set[TypeCheckerAdapter]]()
93
108
  config.stash[adapter_stash_key] = set()
94
109
  verbosity = config.get_verbosity(config.VERBOSITY_TEST_CASES)
95
110
  log.set_verbosity(verbosity)
96
111
  to_be_disabled = cast(list[str], config.getoption("revealtype_disable_adapter"))
112
+ all_ids: list[str] = []
97
113
  for klass in adapter.get_adapter_classes():
114
+ all_ids.append(klass.id)
98
115
  if klass.id in to_be_disabled:
99
116
  _logger.info(f"({klass.id}) adapter disabled with command line option")
100
117
  continue
@@ -102,3 +119,11 @@ def pytest_configure(config: pytest.Config) -> None:
102
119
  adp.set_config_file(config)
103
120
  adp.log_verbosity = verbosity
104
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)