pytest-revealtype-injector 0.7.0__py3-none-any.whl → 0.8.0__py3-none-any.whl

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.
@@ -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.7.0"
3
+ __version__ = "0.8.0"
@@ -5,11 +5,11 @@ import importlib
5
5
  import json
6
6
  import pathlib
7
7
  import re
8
+ import sys
8
9
  from collections.abc import (
9
10
  Iterable,
10
11
  )
11
12
  from typing import (
12
- ForwardRef,
13
13
  Literal,
14
14
  TypedDict,
15
15
  cast,
@@ -27,6 +27,11 @@ from ..models import (
27
27
  VarType,
28
28
  )
29
29
 
30
+ if sys.version_info >= (3, 14):
31
+ from annotationlib import ForwardRef
32
+ else:
33
+ from typing import ForwardRef
34
+
30
35
  _logger = get_logger()
31
36
 
32
37
 
@@ -8,12 +8,11 @@ import shutil
8
8
  import subprocess
9
9
  import sys
10
10
  from collections.abc import Iterable
11
- from typing import ForwardRef, Literal, TypedDict, cast
12
-
13
- if sys.version_info >= (3, 11):
14
- from typing import TypedDict
15
- else:
16
- from typing_extensions import TypedDict
11
+ from typing import (
12
+ Literal,
13
+ TypedDict,
14
+ cast,
15
+ )
17
16
 
18
17
  import schema as s
19
18
 
@@ -26,6 +25,16 @@ from ..models import (
26
25
  VarType,
27
26
  )
28
27
 
28
+ if sys.version_info >= (3, 11):
29
+ from typing import TypedDict
30
+ else:
31
+ from typing_extensions import TypedDict
32
+
33
+ if sys.version_info >= (3, 14):
34
+ from annotationlib import ForwardRef
35
+ else:
36
+ from typing import ForwardRef
37
+
29
38
  _logger = get_logger()
30
39
 
31
40
 
@@ -53,15 +62,16 @@ class NameCollector(BareNameCollector):
53
62
  # resolves to a pytest function or method
54
63
  return ast.Name(id=node.attr, ctx=node.ctx)
55
64
 
65
+
56
66
  class PyreflyAdapter(TypeCheckerAdapter):
57
67
  id = "pyrefly"
58
68
  _executable = "pyrefly"
59
- _type_mesg_re = re.compile(r'revealed type: (?P<type>.+)')
69
+ _type_mesg_re = re.compile(r"revealed type: (?P<type>.+)")
60
70
  _namecollector_class = NameCollector
61
71
  _schema = s.Schema({
62
72
  "line": int,
63
73
  "column": int,
64
- "stop_line": int,
74
+ "stop_line": int,
65
75
  "stop_column": int,
66
76
  "path": str,
67
77
  "code": int,
@@ -79,8 +89,8 @@ class PyreflyAdapter(TypeCheckerAdapter):
79
89
  raise FileNotFoundError(f"{self._executable} is required to run test suite")
80
90
 
81
91
  cmd.extend(["check", "--output-format", "json"])
82
- if True: # TODO Needs to detect python version from project
83
- cmd.extend(["--python-version", "3.11"])
92
+ if self.config_file is not None:
93
+ cmd.extend(["-c", str(self.config_file)])
84
94
  cmd.extend(str(p) for p in paths)
85
95
 
86
96
  _logger.debug(f"({self.id}) Run command: {cmd}")
@@ -90,12 +100,19 @@ class PyreflyAdapter(TypeCheckerAdapter):
90
100
  # of exit status
91
101
  if proc.returncode > 0:
92
102
  raise TypeCheckerError(
93
- "{} error with exit code {}: {}".format(self.id, proc.returncode, proc.stderr.decode()), None, None)
103
+ "{} error with exit code {}: {}".format(
104
+ self.id, proc.returncode, proc.stderr.decode()
105
+ ),
106
+ None,
107
+ None,
108
+ )
94
109
 
95
110
  try:
96
111
  report = json.loads(proc.stdout)
97
112
  except Exception as e:
98
- raise TypeCheckerError(f"Failed to parse pyrefly JSON output: {e}", None, None) from e
113
+ raise TypeCheckerError(
114
+ f"Failed to parse pyrefly JSON output: {e}", None, None
115
+ ) from e
99
116
 
100
117
  assert isinstance(report, dict) and "errors" in report
101
118
  items = cast(list[_PyreflyDiagItem], report["errors"])
@@ -10,16 +10,10 @@ from collections.abc import (
10
10
  Iterable,
11
11
  )
12
12
  from typing import (
13
- ForwardRef,
14
13
  Literal,
15
14
  cast,
16
15
  )
17
16
 
18
- if sys.version_info >= (3, 11):
19
- from typing import NotRequired, TypedDict
20
- else:
21
- from typing_extensions import NotRequired, TypedDict
22
-
23
17
  import schema as s
24
18
 
25
19
  from ..log import get_logger
@@ -31,6 +25,16 @@ from ..models import (
31
25
  VarType,
32
26
  )
33
27
 
28
+ if sys.version_info >= (3, 11):
29
+ from typing import NotRequired, TypedDict
30
+ else:
31
+ from typing_extensions import NotRequired, TypedDict
32
+
33
+ if sys.version_info >= (3, 14):
34
+ from annotationlib import ForwardRef
35
+ else:
36
+ from typing import ForwardRef
37
+
34
38
  _logger = get_logger()
35
39
 
36
40
 
@@ -20,20 +20,36 @@ def pytest_pyfunc_call(pyfuncitem: pytest.Function) -> Iterator[None]:
20
20
  assert pyfuncitem.module is not None
21
21
  adp_stash = pyfuncitem.config.stash[adapter_stash_key]
22
22
 
23
- # Disable type checker based on marker
24
- mark = pyfuncitem.get_closest_marker("notypechecker")
25
- if mark:
26
- disabled_adapters = {a.id for a in adp_stash if a.id in mark.args}
23
+ # Marker-based adapter filtering
24
+ notype_mark = pyfuncitem.get_closest_marker("notypechecker")
25
+ only_mark = pyfuncitem.get_closest_marker("onlytypechecker")
26
+
27
+ if notype_mark and only_mark:
28
+ pytest.fail(
29
+ "Cannot use both 'notypechecker' and 'onlytypechecker' markers on the same test."
30
+ )
31
+
32
+ if only_mark:
33
+ enabled_adapters = {a.id for a in adp_stash if a.id in only_mark.args}
34
+ for a in enabled_adapters:
35
+ _logger.info(
36
+ f"{a} adapter enabled by 'onlytypechecker' marker in {pyfuncitem.name} test"
37
+ )
38
+ adapters = {a for a in adp_stash if a.id in enabled_adapters}
39
+
40
+ elif notype_mark:
41
+ disabled_adapters = {a.id for a in adp_stash if a.id in notype_mark.args}
27
42
  for a in disabled_adapters:
28
43
  _logger.info(
29
44
  f"{a} adapter disabled by 'notypechecker' marker in {pyfuncitem.name} test"
30
45
  )
31
46
  adapters = {a for a in adp_stash if a.id not in disabled_adapters}
47
+
32
48
  else:
33
49
  adapters = {a for a in adp_stash}
34
50
 
35
51
  if not adapters:
36
- pytest.fail("All type checkers have been disabled.")
52
+ pytest.fail("No type checker is enabled.")
37
53
 
38
54
  # Monkeypatch reveal_type() with our own function, to guarantee
39
55
  # each test func can receive different adapters
@@ -139,3 +155,10 @@ def pytest_configure(config: pytest.Config) -> None:
139
155
  "specified type checker(s). Following type checkers are supported: "
140
156
  + ", ".join(all_ids),
141
157
  )
158
+ # Marker to enable only specified adapters for a test
159
+ config.addinivalue_line(
160
+ "markers",
161
+ "onlytypechecker(name, ...): mark reveal_type() test to enable "
162
+ "only the specified type checker(s). Following type checkers are supported: "
163
+ + ", ".join(all_ids),
164
+ )
@@ -4,7 +4,6 @@ import pathlib
4
4
  import sys
5
5
  from typing import (
6
6
  Any,
7
- ForwardRef,
8
7
  TypeVar,
9
8
  )
10
9
 
@@ -22,6 +21,12 @@ from .models import (
22
21
  VarType,
23
22
  )
24
23
 
24
+ if sys.version_info >= (3, 14):
25
+ from annotationlib import ForwardRef
26
+ else:
27
+ from typing import ForwardRef
28
+
29
+
25
30
  _T = TypeVar("_T")
26
31
  _logger = log.get_logger()
27
32
 
@@ -5,11 +5,11 @@ import ast
5
5
  import importlib
6
6
  import pathlib
7
7
  import re
8
+ import sys
8
9
  from collections.abc import Iterable
9
10
  from typing import (
10
11
  Any,
11
12
  ClassVar,
12
- ForwardRef,
13
13
  NamedTuple,
14
14
  TypeVar,
15
15
  cast,
@@ -21,6 +21,11 @@ from schema import Schema
21
21
 
22
22
  from .log import get_logger
23
23
 
24
+ if sys.version_info >= (3, 14):
25
+ from annotationlib import ForwardRef
26
+ else:
27
+ from typing import ForwardRef
28
+
24
29
 
25
30
  class FilePos(NamedTuple):
26
31
  file: str
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytest-revealtype-injector
3
- Version: 0.7.0
3
+ Version: 0.8.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>
@@ -34,12 +34,17 @@ Description-Content-Type: text/markdown
34
34
 
35
35
  `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:
36
36
 
37
- - Launch external static type checkers (`basesdpyright`, `pyright` and `mypy`) and store `reveal_type` results.
37
+ - Launch external static type checkers and store `reveal_type` results.
38
38
  - Use [`typeguard`](https://github.com/agronholm/typeguard) to verify the aforementioned static type checker result _really_ matches runtime code result.
39
39
 
40
40
  ## Usage
41
41
 
42
- In short: install this plugin, create test functions which calls `reveal_type()` with variable or function return result, done.
42
+ TL;DR:
43
+
44
+ 1. Install this plugin
45
+ 2. Install type checkers: `basedpyright`, `mypy`, `pyrefly`, `pyright`, `ty`
46
+ - Disable any unwanted with `--revealtype-disable-adapter=<ADAPTER>` pytest CLI option
47
+ 3. Create `pytest` functions which call `reveal_type()` with variable or function return result
43
48
 
44
49
  ### The longer story
45
50
 
@@ -78,9 +83,11 @@ import typing as typ # or...
78
83
  from typing import reveal_type as rt
79
84
  ```
80
85
 
86
+ To supply config file specific for certain type checker, use `--revealtype-<ADAPTER>-config=<FILE>` pytest CLI option. For example, `--revealtype-pyrefly-config=tests/pyrefly.toml` instructs pyrefly to use `pyrefly.toml` under `tests` folder to override project root config.
87
+
81
88
  ### Limitations
82
89
 
83
- But there are 3 caveats.
90
+ There are 3 caveats.
84
91
 
85
92
  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:
86
93
 
@@ -125,7 +132,15 @@ Module level:
125
132
  pytestmark = pytest.mark.notypechecker("basedpyright", "pyright")
126
133
  ```
127
134
 
128
- Note that disabling all type checkers is disallowed, and such tests would be treated as `pytest.fail`. Disable the `reveal_type()` call instead.
135
+ Conversely, it is possible to only turn on usage of specific type checkers with `onlytypechecker` marker and exclude all others:
136
+
137
+ ```python
138
+ @pytest.mark.onlytypechecker("mypy")
139
+ def test_for_mypy() -> None:
140
+ ......
141
+ ```
142
+
143
+ Note that disabling all type checkers is disallowed, and such tests would be treated as failure. Disable the `reveal_type()` call instead.
129
144
 
130
145
  ## Logging
131
146
 
@@ -0,0 +1,18 @@
1
+ pytest_revealtype_injector/__init__.py,sha256=Ph1EXPASRvuYCAS50jr7ujTT5qdEzD2lco0ygFyIf0E,211
2
+ pytest_revealtype_injector/hooks.py,sha256=7eHjIdD_PCXr9Wwtjlnmgk7QrSui1dQJVGKmCkGo9JQ,5907
3
+ pytest_revealtype_injector/log.py,sha256=Ptd3yp1H1GlUum6BAwHc9cdyeGmaY8XYf0jp6qJmG4M,418
4
+ pytest_revealtype_injector/main.py,sha256=vXRtZ3ITY2FcUMzA4MFTXeOR33-qn0uTP1XNgaHP7ro,5771
5
+ pytest_revealtype_injector/models.py,sha256=_Dpi4Y8rkThh8zIAf2RAzdF7sv0ldUnKdYMcp9IhPFA,6245
6
+ pytest_revealtype_injector/plugin.py,sha256=fkI6yF0dFVba0jEikIrsRp1NUQd2ohWLq4x2lSvFyH0,211
7
+ pytest_revealtype_injector/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ pytest_revealtype_injector/adapter/__init__.py,sha256=8RvzccYXKBjj3718Io8_lqiL35O5mfHprKobwO8ioPI,762
9
+ pytest_revealtype_injector/adapter/basedpyright_.py,sha256=8LX7GmJmg4OZ3LKO5WoQ7Ocub6Lxi3HTStIorMApzUA,466
10
+ pytest_revealtype_injector/adapter/mypy_.py,sha256=NJAY5UGxVLoCznp1XRzaYG1eybI6URCbfvqzhO7GHpA,9427
11
+ pytest_revealtype_injector/adapter/pyrefly_.py,sha256=3hVznpIuHaTGqnkWjNa_t_Kigy33AnFam7AmiYgtp5Y,4396
12
+ pytest_revealtype_injector/adapter/pyright_.py,sha256=Zz8RDlmpeUySH0xNnfn5K4PzrTxERtAuerdCqvRJwmU,4197
13
+ pytest_revealtype_injector/adapter/ty_.py,sha256=fRkIj-xi0KzeJtP_RfmWIrNFFDDDH66mzV7OzuhXnUg,4658
14
+ pytest_revealtype_injector-0.8.0.dist-info/METADATA,sha256=LUOruluwG01O2Lc_tSCOC3-Fa7CKy2xKpDIizbHR5hI,7574
15
+ pytest_revealtype_injector-0.8.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
16
+ pytest_revealtype_injector-0.8.0.dist-info/entry_points.txt,sha256=UfOm7y3WQnOoGV1mgTMb42MI6iBRPIl88FJiAOnt6SY,74
17
+ pytest_revealtype_injector-0.8.0.dist-info/licenses/COPYING.mit,sha256=IzYEFDIOECyuupg_B3O9FvgjnU9i4JtambpbleoYHdQ,1060
18
+ pytest_revealtype_injector-0.8.0.dist-info/RECORD,,
@@ -1,18 +0,0 @@
1
- pytest_revealtype_injector/__init__.py,sha256=RIqY1Ox_ec-Yvnq9T-Q1NRChH1IHydvUAhR6Qb_6yFw,211
2
- pytest_revealtype_injector/hooks.py,sha256=ls6CK8BfZ0kr5YIRzTJ2e4NCGhDHYLf46JN_cqLassU,5036
3
- pytest_revealtype_injector/log.py,sha256=Ptd3yp1H1GlUum6BAwHc9cdyeGmaY8XYf0jp6qJmG4M,418
4
- pytest_revealtype_injector/main.py,sha256=k8B0plBAXolo994sfzQfpjGhytb6Vz3YRKa192s0x_s,5672
5
- pytest_revealtype_injector/models.py,sha256=cujU9GT5_khsFYqyeBxBhA5cmZKYoA1YmmheNSUO2R4,6136
6
- pytest_revealtype_injector/plugin.py,sha256=fkI6yF0dFVba0jEikIrsRp1NUQd2ohWLq4x2lSvFyH0,211
7
- pytest_revealtype_injector/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- pytest_revealtype_injector/adapter/__init__.py,sha256=8RvzccYXKBjj3718Io8_lqiL35O5mfHprKobwO8ioPI,762
9
- pytest_revealtype_injector/adapter/basedpyright_.py,sha256=8LX7GmJmg4OZ3LKO5WoQ7Ocub6Lxi3HTStIorMApzUA,466
10
- pytest_revealtype_injector/adapter/mypy_.py,sha256=5J2SlbT78dS4KmMRted5RzYv5G7KGCbTH1JgJ_FkXHQ,9318
11
- pytest_revealtype_injector/adapter/pyrefly_.py,sha256=F3r1lwA8pJdOPZqMrV60UvRowGYRbq6x2YwoTjxuU24,4192
12
- pytest_revealtype_injector/adapter/pyright_.py,sha256=7yYruxrom11m-g-qZTwhmmo3zTchRrNxB7YGv9bHMEA,4099
13
- pytest_revealtype_injector/adapter/ty_.py,sha256=fRkIj-xi0KzeJtP_RfmWIrNFFDDDH66mzV7OzuhXnUg,4658
14
- pytest_revealtype_injector-0.7.0.dist-info/METADATA,sha256=8rvNDzCseFGLZBpMD15ESD1NPRYlEHZ1qmRYX542rBY,6959
15
- pytest_revealtype_injector-0.7.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
16
- pytest_revealtype_injector-0.7.0.dist-info/entry_points.txt,sha256=UfOm7y3WQnOoGV1mgTMb42MI6iBRPIl88FJiAOnt6SY,74
17
- pytest_revealtype_injector-0.7.0.dist-info/licenses/COPYING.mit,sha256=IzYEFDIOECyuupg_B3O9FvgjnU9i4JtambpbleoYHdQ,1060
18
- pytest_revealtype_injector-0.7.0.dist-info/RECORD,,