https://github.com/abelcheung/pytest-revealtype-injector
Pytest plugin for replacing reveal_type() calls with static and runtime type checking result comparison
https://github.com/abelcheung/pytest-revealtype-injector
annotation dynamic-typing pytest-plugin runtime-typechecking static-typing type-check type-checker type-checking typecheck typechecker typechecking types typing
Last synced: 10 months ago
JSON representation
Pytest plugin for replacing reveal_type() calls with static and runtime type checking result comparison
- Host: GitHub
- URL: https://github.com/abelcheung/pytest-revealtype-injector
- Owner: abelcheung
- License: other
- Created: 2024-11-27T19:14:50.000Z (over 1 year ago)
- Default Branch: main
- Last Pushed: 2024-11-27T19:55:36.000Z (over 1 year ago)
- Last Synced: 2024-11-27T20:29:16.158Z (over 1 year ago)
- Topics: annotation, dynamic-typing, pytest-plugin, runtime-typechecking, static-typing, type-check, type-checker, type-checking, typecheck, typechecker, typechecking, types, typing
- Language: Python
- Homepage:
- Size: 75.2 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: COPYING
Awesome Lists containing this project
README




`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:
- Launch external static type checkers (`basesdpyright`, `pyright` and `mypy`) and store `reveal_type` results.
- Use [`typeguard`](https://github.com/agronholm/typeguard) to verify the aforementioned static type checker result _really_ matches runtime code result.
## Usage
In short: install this plugin, create test functions which calls `reveal_type()` with variable or function return result, done.
### The longer story
This plugin would be automatically enabled when launching `pytest`.
For using `reveal_type()` inside tests, there is no boiler plate code involved. Import `reveal_type` normally, like:
```python
from typing import reveal_type
```
If you care about compatibility with older pythons, use:
```python
import sys
if sys.version >= (3, 11):
from typing import reveal_type
else:
from typing_extensions import reveal_type
```
Just importing `typing` (or `typing_extensions`) module is fine too:
```python
import typing
def test_something():
x: str = 1 # type: ignore # pyright: ignore
typing.reveal_type(x) # typeguard fails here
```
Since this plugin scans for `reveal_type()` for replacement under carpet, even `import ... as ...` syntax works:
```python
import typing as typ # or...
from typing import reveal_type as rt
```
### Limitations
But there are 2 caveats.
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:
```python
def test_something():
from typing import reveal_type
x = 1
reveal_type(x) # calls vanilla reveal_type()
```
2. `reveal_type()` calls have to stay within a single line, although you can use `reveal_type` result in assertion or other purpose:
```python
x = "1"
assert reveal_type(str(int(x))) == x
```
## Disable type checker with marker
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.
Function level:
```python
@pytest.mark.notypechecker("mypy")
def test_something(self) -> None:
x = 1
reveal_type(x)
```
Class level:
```python
@pytest.mark.notypechecker("pyright")
class TestSomething:
def test_foo(self) -> None:
...
```
Module level:
```python
pytestmark = pytest.mark.notypechecker("basedpyright", "pyright")
```
Note that disabling all type checkers is disallowed, and such tests would be treated as `pytest.fail`. Disable the `reveal_type()` call instead.
## Logging
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:
```python
def test_superfluous(self) -> None:
x: list[str] = ['a', 'b', 'c', 1] # type: ignore # pyright: ignore
reveal_type(x)
```
Something like this will be shown as test result:
```
...
raise TypeCheckError(f"is not an instance of {qualified_name(origin_type)}")
E typeguard.TypeCheckError: item 3 is not an instance of str (from pyright)
------------------------------------------------------------- Captured log call -------------------------------------------------------------
INFO revealtype-injector:hooks.py:26 Replaced reveal_type() from global import with
DEBUG revealtype-injector:main.py:60 Extraction OK: code='reveal_type(x)', result='x'
========================================================== short test summary info ==========================================================
FAILED tests/runtime/test_attrib.py::TestAttrib::test_superfluous - typeguard.TypeCheckError: item 3 is not an instance of str (from pyright)
============================================================= 1 failed in 3.38s =============================================================
```
## History
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.