pytest-revealtype-injector 0.1.1__tar.gz → 0.2.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 (17) hide show
  1. pytest_revealtype_injector-0.2.1/COPYING +6 -0
  2. pytest_revealtype_injector-0.1.1/LICENSE → pytest_revealtype_injector-0.2.1/COPYING.mit +1 -11
  3. {pytest_revealtype_injector-0.1.1 → pytest_revealtype_injector-0.2.1}/PKG-INFO +11 -4
  4. {pytest_revealtype_injector-0.1.1 → pytest_revealtype_injector-0.2.1}/README.md +5 -0
  5. {pytest_revealtype_injector-0.1.1 → pytest_revealtype_injector-0.2.1}/pyproject.toml +14 -8
  6. {pytest_revealtype_injector-0.1.1 → pytest_revealtype_injector-0.2.1}/src/pytest_revealtype_injector/__init__.py +1 -1
  7. {pytest_revealtype_injector-0.1.1 → pytest_revealtype_injector-0.2.1}/src/pytest_revealtype_injector/adapter/mypy_.py +16 -2
  8. {pytest_revealtype_injector-0.1.1 → pytest_revealtype_injector-0.2.1}/src/pytest_revealtype_injector/adapter/pyright_.py +41 -12
  9. {pytest_revealtype_injector-0.1.1 → pytest_revealtype_injector-0.2.1}/src/pytest_revealtype_injector/models.py +2 -0
  10. pytest_revealtype_injector-0.2.1/tests/conftest.py +1 -0
  11. pytest_revealtype_injector-0.2.1/tests/test_basic.py +31 -0
  12. {pytest_revealtype_injector-0.1.1 → pytest_revealtype_injector-0.2.1}/.gitignore +0 -0
  13. {pytest_revealtype_injector-0.1.1 → pytest_revealtype_injector-0.2.1}/src/pytest_revealtype_injector/adapter/__init__.py +0 -0
  14. {pytest_revealtype_injector-0.1.1 → pytest_revealtype_injector-0.2.1}/src/pytest_revealtype_injector/hooks.py +0 -0
  15. {pytest_revealtype_injector-0.1.1 → pytest_revealtype_injector-0.2.1}/src/pytest_revealtype_injector/main.py +0 -0
  16. {pytest_revealtype_injector-0.1.1 → pytest_revealtype_injector-0.2.1}/src/pytest_revealtype_injector/plugin.py +0 -0
  17. {pytest_revealtype_injector-0.1.1 → pytest_revealtype_injector-0.2.1}/src/pytest_revealtype_injector/py.typed +0 -0
@@ -0,0 +1,6 @@
1
+ pytest-revealtype-injector is released under MIT license (see COPYING.mit).
2
+
3
+ Original source code comes from part of types-lxml project, which is
4
+ release under Apache-2.0 license. But as the sole author of all source
5
+ code inside this repository, it is at my own discretion to follow pytest
6
+ license because this project is taking shape as a pytest plugin.
@@ -1,4 +1,4 @@
1
- Copyright (c) 2023-2025 Abel Cheung
1
+ Copyright (c) 2023-2024 Abel Cheung
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  of this software and associated documentation files (the "Software"), to deal
@@ -17,13 +17,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
17
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
18
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
19
  SOFTWARE.
20
-
21
-
22
- ---------------------
23
-
24
- pytest-revealtype-injector is released under MIT license (see above).
25
-
26
- Original source code comes from part of types-lxml project, which is released
27
- under Apache-2.0 license. But as the sole author of all relevant source code, it
28
- is at my own discretion to follow pytest license because this project is taking
29
- form of a pytest plugin.
@@ -1,15 +1,16 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: pytest-revealtype-injector
3
- Version: 0.1.1
3
+ Version: 0.2.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>
7
- License: MIT
7
+ License-Expression: MIT
8
+ License-File: COPYING
9
+ License-File: COPYING.mit
8
10
  Keywords: annotation,dynamic-typing,pytest,reveal_type,static-typing,stub,stubs,type-checking,types,typing
9
11
  Classifier: Development Status :: 4 - Beta
10
12
  Classifier: Framework :: Pytest
11
13
  Classifier: Intended Audience :: Developers
12
- Classifier: License :: OSI Approved :: MIT License
13
14
  Classifier: Programming Language :: Python
14
15
  Classifier: Programming Language :: Python :: 3
15
16
  Classifier: Programming Language :: Python :: 3 :: Only
@@ -23,9 +24,15 @@ Requires-Python: >=3.10
23
24
  Requires-Dist: mypy>=1.11.2
24
25
  Requires-Dist: pyright~=1.1
25
26
  Requires-Dist: pytest>=7.0
27
+ Requires-Dist: schema==0.7.7
26
28
  Requires-Dist: typeguard~=4.3
27
29
  Description-Content-Type: text/markdown
28
30
 
31
+ ![PyPI - Version](https://img.shields.io/pypi/v/pytest-revealtype-injector)
32
+ ![GitHub Release Date](https://img.shields.io/github/release-date/abelcheung/pytest-revealtype-injector)
33
+ ![Python Version from PEP 621 TOML](https://img.shields.io/python/required-version-toml?tomlFilePath=https%3A%2F%2Fgithub.com%2Fabelcheung%2Fpytest-revealtype-injector%2Fraw%2Frefs%2Fheads%2Fmain%2Fpyproject.toml)
34
+ ![PyPI - Wheel](https://img.shields.io/pypi/wheel/pytest-revealtype-injector)
35
+
29
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:
30
37
 
31
38
  - Launch external static type checkers (`pyright` and `mypy`) and store `reveal_type` results.
@@ -1,3 +1,8 @@
1
+ ![PyPI - Version](https://img.shields.io/pypi/v/pytest-revealtype-injector)
2
+ ![GitHub Release Date](https://img.shields.io/github/release-date/abelcheung/pytest-revealtype-injector)
3
+ ![Python Version from PEP 621 TOML](https://img.shields.io/python/required-version-toml?tomlFilePath=https%3A%2F%2Fgithub.com%2Fabelcheung%2Fpytest-revealtype-injector%2Fraw%2Frefs%2Fheads%2Fmain%2Fpyproject.toml)
4
+ ![PyPI - Wheel](https://img.shields.io/pypi/wheel/pytest-revealtype-injector)
5
+
1
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:
2
7
 
3
8
  - Launch external static type checkers (`pyright` and `mypy`) and store `reveal_type` results.
@@ -1,7 +1,8 @@
1
1
  #:schema https://json.schemastore.org/pyproject.json
2
2
 
3
3
  [build-system]
4
- requires = ['hatchling']
4
+ # replace with 'hatchling >= 1.27.0' when things are sorted out
5
+ requires = ['hatchling @ git+https://github.com/pypa/hatch.git@72d57279ac8fa58b1981734b58d55cf607e84656#subdirectory=backend']
5
6
  build-backend = 'hatchling.build'
6
7
 
7
8
  [project]
@@ -12,12 +13,15 @@ test functions with static and runtime type checking result comparison,
12
13
  for confirming type annotation validity."""
13
14
  readme = 'README.md'
14
15
  requires-python = '>=3.10'
15
- license = {text = 'MIT'}
16
+ license = 'MIT'
17
+ license-files = ['COPYING*']
16
18
  dependencies = [
17
19
  'mypy >= 1.11.2',
18
20
  'pyright ~= 1.1',
19
21
  'pytest >= 7.0',
20
- 'typeguard ~= 4.3'
22
+ 'typeguard ~= 4.3',
23
+ # schema with annotation support is still unreleased
24
+ 'schema == 0.7.7',
21
25
  ]
22
26
  keywords = [
23
27
  'pytest',
@@ -45,7 +49,6 @@ classifiers = [
45
49
  'Programming Language :: Python :: 3.11',
46
50
  'Programming Language :: Python :: 3.12',
47
51
  'Programming Language :: Python :: 3.13',
48
- 'License :: OSI Approved :: MIT License',
49
52
  'Topic :: Software Development :: Testing',
50
53
  'Typing :: Typed',
51
54
  ]
@@ -75,11 +78,13 @@ packages = ["src/pytest_revealtype_injector"]
75
78
  typeCheckingMode = 'strict'
76
79
  enableTypeIgnoreComments = false
77
80
  deprecateTypingAliases = true
81
+ reportMissingTypeStubs = false
78
82
 
79
83
  [tool.mypy]
80
84
  mypy_path = "$MYPY_CONFIG_FILE_DIR/src"
81
85
  packages = "pytest_revealtype_injector"
82
86
  strict = true
87
+ ignore_missing_imports = true
83
88
 
84
89
  [tool.ruff]
85
90
  target-version = "py312"
@@ -117,12 +122,12 @@ addopts = [
117
122
  "--tb=short",
118
123
  "--import-mode=importlib",
119
124
  ]
120
- markers = [
121
- "slow: marks tests as slow",
122
- ]
123
125
  testpaths = [
124
126
  "tests",
125
127
  ]
128
+ pythonpath = [
129
+ "src",
130
+ ]
126
131
 
127
132
  # We only use version determination logic from python-semantic-release,
128
133
  # and never does any permanent change with it
@@ -132,5 +137,6 @@ major_on_zero = false # switch on for 1.0.0
132
137
 
133
138
  [tool.semantic_release.changelog]
134
139
  exclude_commit_patterns = [
135
- 'Merge pull request #\d+ from',
140
+ '^Merge pull request #\d+ from',
141
+ '^(build|ci|style)(\(.+?\))?: ',
136
142
  ]
@@ -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.1.1"
3
+ __version__ = "0.2.1"
@@ -17,6 +17,7 @@ from typing import (
17
17
 
18
18
  import mypy.api
19
19
  import pytest
20
+ import schema as s
20
21
 
21
22
  from ..models import (
22
23
  FilePos,
@@ -124,6 +125,19 @@ class _MypyAdapter(TypeCheckerAdapter):
124
125
  id = "mypy"
125
126
  typechecker_result = {}
126
127
  _type_mesg_re = re.compile(r'^Revealed type is "(?P<type>.+?)"$')
128
+ _schema = s.Schema({
129
+ "file": str,
130
+ "line": int,
131
+ "column": int,
132
+ "message": str,
133
+ "hint": s.Or(str, s.Schema(None)),
134
+ "code": str,
135
+ "severity": s.Or(
136
+ s.Schema("note"),
137
+ s.Schema("warning"),
138
+ s.Schema("error"),
139
+ ),
140
+ })
127
141
 
128
142
  @classmethod
129
143
  def run_typechecker_on(cls, paths: Iterable[pathlib.Path]) -> None:
@@ -148,8 +162,8 @@ class _MypyAdapter(TypeCheckerAdapter):
148
162
  # So-called mypy json output is merely a line-by-line
149
163
  # transformation of plain text output into json object
150
164
  for line in stdout.splitlines():
151
- # TODO Mypy json schema validation
152
- diag = cast(_MypyDiagObj, json.loads(line))
165
+ obj = json.loads(line)
166
+ diag = cast(_MypyDiagObj, cls._schema.validate(obj))
153
167
  filename = pathlib.Path(diag["file"]).name
154
168
  pos = FilePos(filename, diag["line"])
155
169
  if diag["severity"] != "note":
@@ -11,9 +11,13 @@ from collections.abc import (
11
11
  from typing import (
12
12
  Any,
13
13
  ForwardRef,
14
+ Literal,
15
+ TypedDict,
16
+ cast,
14
17
  )
15
18
 
16
19
  import pytest
20
+ import schema as s
17
21
 
18
22
  from ..models import (
19
23
  FilePos,
@@ -27,6 +31,21 @@ _logger = logging.getLogger(__name__)
27
31
  _logger.setLevel(logging.INFO)
28
32
 
29
33
 
34
+ class _PyrightDiagPosition(TypedDict):
35
+ line: int
36
+ character: int
37
+
38
+ class _PyrightDiagRange(TypedDict):
39
+ start: _PyrightDiagPosition
40
+ end: _PyrightDiagPosition
41
+
42
+ class _PyrightDiagItem(TypedDict):
43
+ file: str
44
+ severity: Literal["information", "warning", "error"]
45
+ message: str
46
+ range: _PyrightDiagRange
47
+
48
+
30
49
  class _NameCollector(NameCollectorBase):
31
50
  # Pyright inferred type results always contain bare names only,
32
51
  # so don't need to bother with visit_Attribute()
@@ -47,6 +66,21 @@ class _PyrightAdapter(TypeCheckerAdapter):
47
66
  id = "pyright"
48
67
  typechecker_result = {}
49
68
  _type_mesg_re = re.compile('^Type of "(?P<var>.+?)" is "(?P<type>.+?)"$')
69
+ # We only care about diagnostic messages that contain type information.
70
+ # Metadata not specified here.
71
+ _schema = s.Schema({
72
+ "file": str,
73
+ "severity": s.Or(
74
+ s.Schema("information"),
75
+ s.Schema("warning"),
76
+ s.Schema("error"),
77
+ ),
78
+ "message": str,
79
+ "range": {
80
+ "start": {"line": int, "character": int},
81
+ "end": {"line": int, "character": int},
82
+ },
83
+ })
50
84
 
51
85
  @classmethod
52
86
  def run_typechecker_on(cls, paths: Iterable[pathlib.Path]) -> None:
@@ -67,22 +101,17 @@ class _PyrightAdapter(TypeCheckerAdapter):
67
101
  if len(proc.stderr):
68
102
  raise TypeCheckerError(proc.stderr.decode(), None, None)
69
103
 
70
- # TODO Pyright json schema validation
71
104
  report = json.loads(proc.stdout)
72
- if proc.returncode:
73
- for diag in report["generalDiagnostics"]:
74
- if diag["severity"] != "error":
75
- continue
76
- # Pyright report lineno is 0-based,
77
- # OTOH python frame lineno is 1-based
78
- lineno = diag["range"]["start"]["line"] + 1
79
- filename = pathlib.Path(diag["file"]).name
80
- raise TypeCheckerError(diag["message"], filename, lineno)
81
- for diag in report["generalDiagnostics"]:
82
- if diag["severity"] != "information":
105
+ for item in report["generalDiagnostics"]:
106
+ diag = cast(_PyrightDiagItem, cls._schema.validate(item))
107
+ if diag["severity"] != ("error" if proc.returncode else "information"):
83
108
  continue
109
+ # Pyright report lineno is 0-based, while
110
+ # python frame lineno is 1-based
84
111
  lineno = diag["range"]["start"]["line"] + 1
85
112
  filename = pathlib.Path(diag["file"]).name
113
+ if proc.returncode:
114
+ raise TypeCheckerError(diag["message"], filename, lineno)
86
115
  if (m := cls._type_mesg_re.match(diag["message"])) is None:
87
116
  continue
88
117
  pos = FilePos(filename, lineno)
@@ -15,6 +15,7 @@ from typing import (
15
15
  )
16
16
 
17
17
  import pytest
18
+ from schema import Schema
18
19
 
19
20
 
20
21
  class FilePos(NamedTuple):
@@ -91,6 +92,7 @@ class TypeCheckerAdapter:
91
92
  # {('file.py', 10): ('var_name', 'list[str]'), ...}
92
93
  typechecker_result: ClassVar[dict[FilePos, VarType]]
93
94
  _type_mesg_re: ClassVar[re.Pattern[str]]
95
+ _schema: ClassVar[Schema]
94
96
 
95
97
  @classmethod
96
98
  @abc.abstractmethod
@@ -0,0 +1 @@
1
+ pytest_plugins = ["pytester"]
@@ -0,0 +1,31 @@
1
+ import pytest
2
+
3
+
4
+ def test_basic(pytester: pytest.Pytester):
5
+ pytester.makeconftest(
6
+ "pytest_plugins = ['pytest_revealtype_injector.plugin']"
7
+ )
8
+
9
+ pytester.makepyfile( # pyright: ignore[reportUnknownMemberType]
10
+ """
11
+ import sys
12
+ import pytest
13
+ from typeguard import TypeCheckError
14
+
15
+ if sys.version_info >= (3, 11):
16
+ from typing import reveal_type
17
+ else:
18
+ from typing_extensions import reveal_type
19
+
20
+ def test_inferred():
21
+ x = 1
22
+ reveal_type(x)
23
+
24
+ def test_bad_inline_hint():
25
+ x: str = 1 # type: ignore # pyright: ignore
26
+ with pytest.raises(TypeCheckError, match='is not an instance of str'):
27
+ reveal_type(x)
28
+ """
29
+ )
30
+ result = pytester.runpytest()
31
+ result.assert_outcomes(passed=2)