pytest-revealtype-injector 0.6.3__tar.gz → 0.7.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 (24) hide show
  1. {pytest_revealtype_injector-0.6.3 → pytest_revealtype_injector-0.7.0}/PKG-INFO +4 -2
  2. {pytest_revealtype_injector-0.6.3 → pytest_revealtype_injector-0.7.0}/README.md +3 -0
  3. {pytest_revealtype_injector-0.6.3 → pytest_revealtype_injector-0.7.0}/src/pytest_revealtype_injector/__init__.py +1 -1
  4. {pytest_revealtype_injector-0.6.3 → pytest_revealtype_injector-0.7.0}/src/pytest_revealtype_injector/adapter/__init__.py +14 -4
  5. {pytest_revealtype_injector-0.6.3 → pytest_revealtype_injector-0.7.0}/src/pytest_revealtype_injector/adapter/mypy_.py +1 -1
  6. pytest_revealtype_injector-0.7.0/src/pytest_revealtype_injector/adapter/pyrefly_.py +133 -0
  7. {pytest_revealtype_injector-0.6.3 → pytest_revealtype_injector-0.7.0}/src/pytest_revealtype_injector/adapter/pyright_.py +2 -29
  8. pytest_revealtype_injector-0.7.0/src/pytest_revealtype_injector/adapter/ty_.py +162 -0
  9. {pytest_revealtype_injector-0.6.3 → pytest_revealtype_injector-0.7.0}/src/pytest_revealtype_injector/hooks.py +9 -3
  10. {pytest_revealtype_injector-0.6.3 → pytest_revealtype_injector-0.7.0}/src/pytest_revealtype_injector/main.py +1 -1
  11. {pytest_revealtype_injector-0.6.3 → pytest_revealtype_injector-0.7.0}/src/pytest_revealtype_injector/models.py +27 -0
  12. {pytest_revealtype_injector-0.6.3 → pytest_revealtype_injector-0.7.0}/tests/test_marker.py +1 -1
  13. {pytest_revealtype_injector-0.6.3 → pytest_revealtype_injector-0.7.0}/tests/test_options.py +1 -1
  14. pytest_revealtype_injector-0.6.3/COPYING +0 -6
  15. {pytest_revealtype_injector-0.6.3 → pytest_revealtype_injector-0.7.0}/.gitignore +0 -0
  16. {pytest_revealtype_injector-0.6.3 → pytest_revealtype_injector-0.7.0}/COPYING.mit +0 -0
  17. {pytest_revealtype_injector-0.6.3 → pytest_revealtype_injector-0.7.0}/pyproject.toml +0 -0
  18. {pytest_revealtype_injector-0.6.3 → pytest_revealtype_injector-0.7.0}/src/pytest_revealtype_injector/adapter/basedpyright_.py +0 -0
  19. {pytest_revealtype_injector-0.6.3 → pytest_revealtype_injector-0.7.0}/src/pytest_revealtype_injector/log.py +0 -0
  20. {pytest_revealtype_injector-0.6.3 → pytest_revealtype_injector-0.7.0}/src/pytest_revealtype_injector/plugin.py +0 -0
  21. {pytest_revealtype_injector-0.6.3 → pytest_revealtype_injector-0.7.0}/src/pytest_revealtype_injector/py.typed +0 -0
  22. {pytest_revealtype_injector-0.6.3 → pytest_revealtype_injector-0.7.0}/tests/conftest.py +0 -0
  23. {pytest_revealtype_injector-0.6.3 → pytest_revealtype_injector-0.7.0}/tests/test_ast_mode.py +0 -0
  24. {pytest_revealtype_injector-0.6.3 → pytest_revealtype_injector-0.7.0}/tests/test_import.py +0 -0
@@ -1,11 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytest-revealtype-injector
3
- Version: 0.6.3
3
+ Version: 0.7.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>
7
7
  License-Expression: MIT
8
- License-File: COPYING
9
8
  License-File: COPYING.mit
10
9
  Keywords: annotation,dynamic-typing,pytest,reveal_type,static-typing,stub,stubs,type-checking,types,typing
11
10
  Classifier: Development Status :: 4 - Beta
@@ -156,3 +155,6 @@ FAILED tests/runtime/test_attrib.py::TestAttrib::test_superfluous - typeguard.Ty
156
155
  ## History
157
156
 
158
157
  This pytest plugin starts its life as part of testsuite related utilities within [`types-lxml`](https://github.com/abelcheung/types-lxml). As `lxml` is a `cython` project and probably never incorporate inline python annotation in future, there is need to compare runtime result to static type checker output for discrepancy. As time goes by, it starts to make sense to manage as an independent project.
158
+
159
+ License-wise, originally it was part of `types-lxml` project, which is released under Apache-2.0 license. But as the sole author, it is at my own discretion to follow pytest
160
+ license (which is MIT) because this project is taking shape as a pytest plugin.
@@ -126,3 +126,6 @@ FAILED tests/runtime/test_attrib.py::TestAttrib::test_superfluous - typeguard.Ty
126
126
  ## History
127
127
 
128
128
  This pytest plugin starts its life as part of testsuite related utilities within [`types-lxml`](https://github.com/abelcheung/types-lxml). As `lxml` is a `cython` project and probably never incorporate inline python annotation in future, there is need to compare runtime result to static type checker output for discrepancy. As time goes by, it starts to make sense to manage as an independent project.
129
+
130
+ License-wise, originally it was part of `types-lxml` project, which is released under Apache-2.0 license. But as the sole author, it is at my own discretion to follow pytest
131
+ license (which is MIT) because this project is taking shape as a pytest plugin.
@@ -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.6.3"
3
+ __version__ = "0.7.0"
@@ -1,22 +1,32 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from ..models import TypeCheckerAdapter
4
- from . import basedpyright_, mypy_, pyright_
4
+ from . import (
5
+ basedpyright_,
6
+ mypy_,
7
+ pyrefly_,
8
+ pyright_,
9
+ ty_,
10
+ )
5
11
 
6
12
 
7
13
  # Hardcode will do for now, it's not like we're going to have more
8
- # adapters soon. Pyre and PyType are not there yet.
14
+ # adapters rapidly or make it user extensible.
9
15
  def generate() -> set[TypeCheckerAdapter]:
10
16
  return {
11
17
  basedpyright_.generate_adapter(),
12
- pyright_.generate_adapter(),
13
18
  mypy_.generate_adapter(),
19
+ pyrefly_.generate_adapter(),
20
+ pyright_.generate_adapter(),
21
+ ty_.generate_adapter(),
14
22
  }
15
23
 
16
24
 
17
25
  def get_adapter_classes() -> list[type[TypeCheckerAdapter]]:
18
26
  return [
19
27
  basedpyright_.BasedPyrightAdapter,
20
- pyright_.PyrightAdapter,
21
28
  mypy_.MypyAdapter,
29
+ pyrefly_.PyreflyAdapter,
30
+ pyright_.PyrightAdapter,
31
+ ty_.TyAdapter,
22
32
  ]
@@ -150,7 +150,7 @@ def _strip_unwanted_char(input: str) -> str:
150
150
  _ = ast.parse(result)
151
151
  except SyntaxError as e:
152
152
  assert e.offset is not None
153
- result = result[:e.offset-1] + result[e.offset:]
153
+ result = result[: e.offset - 1] + result[e.offset :]
154
154
  else:
155
155
  return result
156
156
 
@@ -0,0 +1,133 @@
1
+ from __future__ import annotations
2
+
3
+ import ast
4
+ import json
5
+ import pathlib
6
+ import re
7
+ import shutil
8
+ import subprocess
9
+ import sys
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
17
+
18
+ import schema as s
19
+
20
+ from ..log import get_logger
21
+ from ..models import (
22
+ BareNameCollector,
23
+ FilePos,
24
+ TypeCheckerAdapter,
25
+ TypeCheckerError,
26
+ VarType,
27
+ )
28
+
29
+ _logger = get_logger()
30
+
31
+
32
+ class _PyreflyDiagItem(TypedDict):
33
+ line: int
34
+ column: int
35
+ stop_line: int
36
+ stop_column: int
37
+ path: str
38
+ code: int
39
+ name: str
40
+ description: str
41
+ concise_description: str
42
+ severity: Literal["error", "warn", "info"]
43
+
44
+
45
+ class NameCollector(BareNameCollector):
46
+ type_checker = "pyrefly"
47
+
48
+ # Pyrefly renders local class as Attribute nodes. Override to
49
+ # only use bare names.
50
+ def visit_Attribute(self, node: ast.Attribute) -> ast.expr:
51
+ self.modified = True
52
+ # TODO should have been more robust by checking if node.value
53
+ # resolves to a pytest function or method
54
+ return ast.Name(id=node.attr, ctx=node.ctx)
55
+
56
+ class PyreflyAdapter(TypeCheckerAdapter):
57
+ id = "pyrefly"
58
+ _executable = "pyrefly"
59
+ _type_mesg_re = re.compile(r'revealed type: (?P<type>.+)')
60
+ _namecollector_class = NameCollector
61
+ _schema = s.Schema({
62
+ "line": int,
63
+ "column": int,
64
+ "stop_line": int,
65
+ "stop_column": int,
66
+ "path": str,
67
+ "code": int,
68
+ "name": str,
69
+ "description": str,
70
+ "concise_description": str,
71
+ "severity": s.Or(s.Schema("error"), s.Schema("warn"), s.Schema("info")),
72
+ })
73
+
74
+ def run_typechecker_on(self, paths: Iterable[pathlib.Path]) -> None:
75
+ cmd: list[str] = []
76
+ if shutil.which(self._executable) is not None:
77
+ cmd.append(self._executable)
78
+ else:
79
+ raise FileNotFoundError(f"{self._executable} is required to run test suite")
80
+
81
+ cmd.extend(["check", "--output-format", "json"])
82
+ if True: # TODO Needs to detect python version from project
83
+ cmd.extend(["--python-version", "3.11"])
84
+ cmd.extend(str(p) for p in paths)
85
+
86
+ _logger.debug(f"({self.id}) Run command: {cmd}")
87
+ proc = subprocess.run(cmd, capture_output=True)
88
+ # Return code: 1=normal error, 2=facebook reserved, 3=internal error
89
+ # Pyrefly unconditionally outputs error count to stderr regardless
90
+ # of exit status
91
+ if proc.returncode > 0:
92
+ raise TypeCheckerError(
93
+ "{} error with exit code {}: {}".format(self.id, proc.returncode, proc.stderr.decode()), None, None)
94
+
95
+ try:
96
+ report = json.loads(proc.stdout)
97
+ except Exception as e:
98
+ raise TypeCheckerError(f"Failed to parse pyrefly JSON output: {e}", None, None) from e
99
+
100
+ assert isinstance(report, dict) and "errors" in report
101
+ items = cast(list[_PyreflyDiagItem], report["errors"])
102
+
103
+ _logger.info(
104
+ "({}) Return code = {}, diagnostic count = {}.{}".format(
105
+ self.id,
106
+ proc.returncode,
107
+ len(items),
108
+ " pytest -vv shows all items." if self.log_verbosity < 2 else "",
109
+ )
110
+ )
111
+
112
+ for item in items:
113
+ diag = cast(_PyreflyDiagItem, self._schema.validate(item))
114
+ if self.log_verbosity >= 2:
115
+ _logger.debug(f"({self.id}) {diag}")
116
+
117
+ if diag["name"] != "reveal-type":
118
+ continue
119
+ if (m := self._type_mesg_re.fullmatch(diag["description"])) is None:
120
+ raise TypeCheckerError(
121
+ f"({self.id}) unexpected reveal-type message: {diag['description']}",
122
+ diag["path"],
123
+ diag["line"],
124
+ )
125
+
126
+ filename = pathlib.Path(diag["path"]).name
127
+ lineno = diag["line"]
128
+ pos = FilePos(filename, lineno)
129
+ self.typechecker_result[pos] = VarType(None, ForwardRef(m["type"]))
130
+
131
+
132
+ def generate_adapter() -> TypeCheckerAdapter:
133
+ return PyreflyAdapter()
@@ -1,6 +1,5 @@
1
1
  from __future__ import annotations
2
2
 
3
- import ast
4
3
  import json
5
4
  import pathlib
6
5
  import re
@@ -13,7 +12,6 @@ from collections.abc import (
13
12
  from typing import (
14
13
  ForwardRef,
15
14
  Literal,
16
- TypeVar,
17
15
  cast,
18
16
  )
19
17
 
@@ -26,8 +24,8 @@ import schema as s
26
24
 
27
25
  from ..log import get_logger
28
26
  from ..models import (
27
+ BareNameCollector,
29
28
  FilePos,
30
- NameCollectorBase,
31
29
  TypeCheckerAdapter,
32
30
  TypeCheckerError,
33
31
  VarType,
@@ -54,33 +52,8 @@ class _PyrightDiagItem(TypedDict):
54
52
  rule: NotRequired[str]
55
53
 
56
54
 
57
- class NameCollector(NameCollectorBase):
55
+ class NameCollector(BareNameCollector):
58
56
  type_checker = "pyright"
59
- # Pre-register common used bare names from typing
60
- collected = NameCollectorBase.collected | {
61
- k: v
62
- for k, v in NameCollectorBase.collected["typing"].__dict__.items()
63
- if k[0].isupper() and not isinstance(v, TypeVar)
64
- }
65
-
66
- # Pyright inferred type results always contain bare names only,
67
- # so don't need to bother with visit_Attribute()
68
- def visit_Name(self, node: ast.Name) -> ast.Name:
69
- name = node.id
70
- try:
71
- eval(name, self._globalns, self._localns | self.collected)
72
- except NameError:
73
- for m in ("typing", "typing_extensions"):
74
- if not hasattr(self.collected[m], name):
75
- continue
76
- obj = getattr(self.collected[m], name)
77
- self.collected[name] = obj
78
- _logger.debug(
79
- f"{self.type_checker} NameCollector resolved '{name}' as {obj}"
80
- )
81
- return node
82
- raise
83
- return node
84
57
 
85
58
 
86
59
  class PyrightAdapter(TypeCheckerAdapter):
@@ -0,0 +1,162 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import pathlib
5
+ import re
6
+ import shutil
7
+ import subprocess
8
+ import sys
9
+ from collections.abc import Iterable
10
+ from typing import cast
11
+
12
+ import schema as s
13
+
14
+ from ..log import get_logger
15
+ from ..models import (
16
+ BareNameCollector,
17
+ FilePos,
18
+ TypeCheckerAdapter,
19
+ TypeCheckerError,
20
+ VarType,
21
+ )
22
+
23
+ if sys.version_info >= (3, 11):
24
+ from typing import TypedDict
25
+ else:
26
+ from typing_extensions import TypedDict
27
+
28
+ if sys.version_info >= (3, 14):
29
+ from annotationlib import ForwardRef
30
+ else:
31
+ from typing import ForwardRef
32
+
33
+ _logger = get_logger()
34
+
35
+
36
+ class _TyDiagPosition(TypedDict):
37
+ line: int
38
+ column: int
39
+
40
+
41
+ class _TyDiagRange(TypedDict):
42
+ begin: _TyDiagPosition
43
+ end: _TyDiagPosition
44
+
45
+
46
+ class _TyDiagLocation(TypedDict):
47
+ path: str
48
+ positions: _TyDiagRange
49
+
50
+
51
+ class _TyDiagItem(TypedDict):
52
+ check_name: str
53
+ description: str
54
+ severity: str
55
+ fingerprint: str
56
+ location: _TyDiagLocation
57
+
58
+
59
+ class NameCollector(BareNameCollector):
60
+ type_checker = "ty"
61
+
62
+
63
+ class TyAdapter(TypeCheckerAdapter):
64
+ id = "ty"
65
+ _executable = "ty"
66
+ _type_mesg_re = re.compile(r'Revealed type: `(?P<type>.+?)`')
67
+ _namecollector_class = BareNameCollector
68
+ _schema = s.Schema({
69
+ "check_name": str,
70
+ "description": str,
71
+ "severity": s.Or(
72
+ s.Schema("info"),
73
+ s.Schema("minor"),
74
+ s.Schema("major"),
75
+ ),
76
+ "fingerprint": str,
77
+ "location": {
78
+ "path": str,
79
+ "positions": {
80
+ "begin": {"line": int, "column": int},
81
+ "end" : {"line": int, "column": int},
82
+ }
83
+ },
84
+ })
85
+
86
+ def run_typechecker_on(self, paths: Iterable[pathlib.Path]) -> None:
87
+ if shutil.which(self._executable) is None:
88
+ raise FileNotFoundError(f"{self._executable} is required to run test suite")
89
+
90
+ cmd = [
91
+ self._executable,
92
+ "check",
93
+ "--no-progress",
94
+ "--output-format",
95
+ "gitlab",
96
+ ]
97
+ cmd.extend(str(p) for p in paths)
98
+
99
+ if self.config_file is not None:
100
+ cmd.extend(["--config-file", str(self.config_file)])
101
+
102
+ _logger.debug(f"({self.id}) Run command: {cmd}")
103
+ proc = subprocess.run(cmd, capture_output=True)
104
+ if proc.returncode == 101: # internal error
105
+ raise TypeCheckerError(proc.stderr.decode(), None, None)
106
+
107
+ report = json.loads(proc.stdout)
108
+ _logger.info(
109
+ "({}) Return code = {}, diagnostic count = {}.{}".format(
110
+ self.id,
111
+ proc.returncode,
112
+ len(report),
113
+ " pytest -vv shows all items." if self.log_verbosity < 2 else "",
114
+ )
115
+ )
116
+
117
+ for item in report:
118
+ diag = cast(_TyDiagItem, self._schema.validate(item))
119
+ if self.log_verbosity >= 2:
120
+ _logger.debug(f"({self.id}) {diag}")
121
+ if diag["severity"] != ("major" if proc.returncode else "info"):
122
+ continue
123
+ match proc.returncode:
124
+ case 1 | 2:
125
+ filename, lineno = (
126
+ pathlib.Path(diag["location"]["path"]).name,
127
+ diag["location"]["positions"]["begin"]["line"],
128
+ )
129
+ raise TypeCheckerError(
130
+ "{} error with exit code {}: {}".format(
131
+ self.id,
132
+ proc.returncode,
133
+ diag["description"],
134
+ ),
135
+ filename,
136
+ lineno,
137
+ diag["check_name"],
138
+ )
139
+ case 0:
140
+ pass
141
+ case _: # Some future error code
142
+ raise TypeCheckerError(
143
+ "Unknown {} error with exit code {}: {}".format(
144
+ self.id,
145
+ proc.returncode,
146
+ proc.stderr.decode(),
147
+ ),
148
+ None,
149
+ None,
150
+ )
151
+
152
+ filename, lineno = (
153
+ pathlib.Path(diag["location"]["path"]).name,
154
+ diag["location"]["positions"]["begin"]["line"],
155
+ )
156
+ if (m := self._type_mesg_re.search(diag["description"])) is None:
157
+ continue
158
+ pos = FilePos(filename, lineno)
159
+ self.typechecker_result[pos] = VarType(None, ForwardRef(m["type"]))
160
+
161
+ def generate_adapter() -> TypeCheckerAdapter:
162
+ return TyAdapter()
@@ -25,7 +25,9 @@ def pytest_pyfunc_call(pyfuncitem: pytest.Function) -> Iterator[None]:
25
25
  if mark:
26
26
  disabled_adapters = {a.id for a in adp_stash if a.id in mark.args}
27
27
  for a in disabled_adapters:
28
- _logger.info(f"{a} adapter disabled by 'notypechecker' marker in {pyfuncitem.name} test")
28
+ _logger.info(
29
+ f"{a} adapter disabled by 'notypechecker' marker in {pyfuncitem.name} test"
30
+ )
29
31
  adapters = {a for a in adp_stash if a.id not in disabled_adapters}
30
32
  else:
31
33
  adapters = {a for a in adp_stash}
@@ -53,7 +55,9 @@ def pytest_pyfunc_call(pyfuncitem: pytest.Function) -> Iterator[None]:
53
55
  rt_funcname=name,
54
56
  )
55
57
  mp.setattr(pyfuncitem.module, name, injected)
56
- _logger.info(f"Replaced {name}() from global import with {injected} in {pyfuncitem.name} test")
58
+ _logger.info(
59
+ f"Replaced {name}() from global import with {injected} in {pyfuncitem.name} test"
60
+ )
57
61
  break
58
62
 
59
63
  elif inspect.ismodule(item):
@@ -66,7 +70,9 @@ def pytest_pyfunc_call(pyfuncitem: pytest.Function) -> Iterator[None]:
66
70
  rt_funcname=f"{name}.reveal_type",
67
71
  )
68
72
  mp.setattr(item, "reveal_type", injected)
69
- _logger.info(f"Replaced {name}.reveal_type() with {injected} in {pyfuncitem.name} test")
73
+ _logger.info(
74
+ f"Replaced {name}.reveal_type() with {injected} in {pyfuncitem.name} test"
75
+ )
70
76
  break
71
77
 
72
78
  return cast(None, (yield)) # type: ignore[redundant-cast]
@@ -136,7 +136,7 @@ def revealtype_injector(
136
136
  walker = adp.create_collector(globalns, localns)
137
137
  try:
138
138
  evaluated = eval(ref.__forward_arg__, globalns, localns | walker.collected)
139
- except (TypeError, NameError):
139
+ except (TypeError, NameError, AttributeError):
140
140
  old_ast = ast.parse(ref.__forward_arg__, mode="eval")
141
141
  new_ast = walker.visit(old_ast)
142
142
  if walker.modified:
@@ -11,6 +11,7 @@ from typing import (
11
11
  ClassVar,
12
12
  ForwardRef,
13
13
  NamedTuple,
14
+ TypeVar,
14
15
  cast,
15
16
  )
16
17
 
@@ -98,6 +99,32 @@ class NameCollectorBase(ast.NodeTransformer):
98
99
  return node
99
100
 
100
101
 
102
+ # Some type checkers always produce bare names only,
103
+ # so we can skip Attribute nodes entirely
104
+ class BareNameCollector(NameCollectorBase):
105
+ type_checker = ""
106
+ # Pre-register common used bare names from typing
107
+ collected = NameCollectorBase.collected | {
108
+ k: v
109
+ for k, v in NameCollectorBase.collected["typing"].__dict__.items()
110
+ if k[0].isupper() and not isinstance(v, TypeVar)
111
+ }
112
+
113
+ def visit_Name(self, node: ast.Name) -> ast.Name:
114
+ name = node.id
115
+ try:
116
+ eval(name, self._globalns, self._localns | self.collected)
117
+ except NameError:
118
+ for m in ("typing", "typing_extensions"):
119
+ if not hasattr(self.collected[m], name):
120
+ continue
121
+ obj = getattr(self.collected[m], name)
122
+ self.collected[name] = obj
123
+ return node
124
+ raise
125
+ return node
126
+
127
+
101
128
  class TypeCheckerAdapter:
102
129
  # Subclasses need to specify default values for below
103
130
  id: ClassVar[str]
@@ -79,7 +79,7 @@ class TestClassMarker:
79
79
  else:
80
80
  from typing_extensions import reveal_type
81
81
 
82
- # @pytest.mark.notypechecker('pyright', 'basedpyright')
82
+ # @pytest.mark.notypechecker('pyright', 'basedpyright', 'ty')
83
83
  class TestFoo:
84
84
  def test_foo(self) -> None:
85
85
  MYPY = False
@@ -60,7 +60,7 @@ class TestDisableTypeChecker:
60
60
  """
61
61
  )
62
62
  content_masked = self.content_fail.format(
63
- "pyright: ignore[reportAssignmentType]",
63
+ "pyright: ignore[reportAssignmentType] # ty: ignore[invalid-assignment] # pyrefly: ignore[bad-assignment]",
64
64
  )
65
65
  pytester.makepyfile( # pyright: ignore[reportUnknownMemberType]
66
66
  content_masked
@@ -1,6 +0,0 @@
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.