-
Notifications
You must be signed in to change notification settings - Fork 23
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
113 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,3 +23,4 @@ | |
make_norm_type, | ||
normalize_type, | ||
) | ||
from .type_evaler import exec_type_checking |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import ast | ||
import inspect | ||
from collections.abc import Callable, Sequence | ||
from types import ModuleType | ||
|
||
|
||
def make_fragments_collector(*, typing_modules: Sequence[str]) -> Callable[[ast.Module], list[ast.stmt]]: | ||
def check_condition(expr: ast.expr) -> bool: | ||
# searches for `TYPE_CHECKING` | ||
if isinstance(expr, ast.Name) and isinstance(expr.ctx, ast.Load): | ||
return True | ||
|
||
# searches for `typing.TYPE_CHECKING` | ||
if ( # noqa: SIM103 | ||
isinstance(expr, ast.Attribute) | ||
and expr.attr == "TYPE_CHECKING" | ||
and isinstance(expr.ctx, ast.Load) | ||
and isinstance(expr.value, ast.Name) | ||
and expr.value.id in typing_modules | ||
and isinstance(expr.value.ctx, ast.Load) | ||
): | ||
return True | ||
return False | ||
|
||
def collect_type_checking_only_fragments(module: ast.Module) -> list[ast.stmt]: | ||
fragments = [] | ||
for stmt in module.body: | ||
if isinstance(stmt, ast.If) and not stmt.orelse and check_condition(stmt.test): | ||
fragments.extend(stmt.body) | ||
|
||
return fragments | ||
|
||
return collect_type_checking_only_fragments | ||
|
||
|
||
default_collector = make_fragments_collector(typing_modules=["typing"]) | ||
|
||
|
||
def exec_type_checking( | ||
module: ModuleType, | ||
*, | ||
collector: Callable[[ast.Module], list[ast.stmt]] = default_collector, | ||
) -> None: | ||
source = inspect.getsource(module) | ||
fragments = collector(ast.parse(source)) | ||
code = compile(ast.Module(fragments, type_ignores=[]), f"<exec_type_checking of {module}>", "exec") | ||
namespace = module.__dict__.copy() | ||
exec(code, namespace) # noqa: S102 | ||
for k, v in namespace.items(): | ||
if not hasattr(module, k): | ||
setattr(module, k, v) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
from adaptix._internal.type_tools import exec_type_checking | ||
|
||
__all__ = ( | ||
"exec_type_checking", | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import typing | ||
from collections.abc import Sequence | ||
from typing import TYPE_CHECKING | ||
|
||
if TYPE_CHECKING: | ||
IntSeq = Sequence[int] | ||
|
||
|
||
if typing.TYPE_CHECKING: | ||
StrSeq = Sequence[str] | ||
|
||
|
||
class Foo: | ||
a: bool | ||
b: "IntSeq" | ||
c: "StrSeq" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
from collections.abc import Sequence | ||
from pathlib import Path | ||
|
||
from tests_helpers.misc import import_local_module, temp_module | ||
|
||
from adaptix._internal.type_tools import get_all_type_hints | ||
from adaptix.type_tools import exec_type_checking | ||
|
||
|
||
def test_exec_type_checking(): | ||
module = import_local_module(Path(__file__).with_name("data_type_checking.py")) | ||
with temp_module(module): | ||
exec_type_checking(module) | ||
assert get_all_type_hints(module.Foo) == { | ||
"a": bool, | ||
"b": Sequence[int], | ||
"c": Sequence[str], | ||
} |