diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d862a37..468b6dc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,4 @@ -name: ci +name: CI on: push: diff --git a/pyproject.toml b/pyproject.toml index f3315f0..8750cab 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -80,9 +80,9 @@ defer_imports = ["src"] [tool.coverage.run] plugins = ["covdefaults"] source = ["defer_imports", "tests"] +omit = ["src/defer_imports/_typing.py", "src/defer_imports/console.py"] [tool.coverage.report] -# It's a work in progress. fail_under = 90 diff --git a/src/defer_imports/_typing.py b/src/defer_imports/_typing.py index e662821..f31bf3c 100644 --- a/src/defer_imports/_typing.py +++ b/src/defer_imports/_typing.py @@ -47,7 +47,7 @@ def final(f: object) -> object: return f -def __getattr__(name: str) -> object: # pragma: no cover # noqa: PLR0911, PLR0912 +def __getattr__(name: str) -> object: # noqa: PLR0911, PLR0912 # Let's cache the return values in the global namespace to avoid subsequent calls to __getattr__ if possible. # ---- Pure imports diff --git a/src/defer_imports/console.py b/src/defer_imports/console.py index 4d284c4..4023675 100644 --- a/src/defer_imports/console.py +++ b/src/defer_imports/console.py @@ -17,7 +17,7 @@ __all__ = ("DeferredInteractiveConsole", "interact", "instrument_ipython") -class DeferredCompile(codeop.Compile): +class _DeferredCompile(codeop.Compile): """A subclass of codeop.Compile that alters the compilation process with defer_imports's AST transformer.""" def __call__(self, source: str, filename: str, symbol: str, **kwargs: object) -> _tp.CodeType: @@ -27,11 +27,11 @@ def __call__(self, source: str, filename: str, symbol: str, **kwargs: object) -> flags &= ~codeop.PyCF_ALLOW_INCOMPLETE_INPUT # pyright: ignore assert isinstance(flags, int) - og_ast_node = compile(source, filename, symbol, flags | ast.PyCF_ONLY_AST, True) + orig_ast = compile(source, filename, symbol, flags | ast.PyCF_ONLY_AST, True) transformer = DeferredInstrumenter(source, filename, "utf-8") - new_ast_node = ast.fix_missing_locations(transformer.visit(og_ast_node)) + new_ast = ast.fix_missing_locations(transformer.visit(orig_ast)) - codeob = compile(new_ast_node, filename, symbol, flags, True) + codeob = compile(new_ast, filename, symbol, flags, True) for feature in _features: if codeob.co_flags & feature.compiler_flag: self.flags |= feature.compiler_flag @@ -54,8 +54,7 @@ def __init__(self) -> None: "@DeferredImportProxy": DeferredImportProxy, } super().__init__(local_ns) - self.compile = codeop.CommandCompiler() - self.compile.compiler = DeferredCompile() + self.compile.compiler = _DeferredCompile() def interact() -> None: @@ -67,6 +66,16 @@ def interact() -> None: DeferredInteractiveConsole().interact() +class _DeferredIPythonInstrumenter(ast.NodeTransformer): + def __init__(self): + self.actual_transformer = DeferredInstrumenter("", "", "utf-8") + + def visit(self, node: ast.AST) -> _tp.Any: + self.actual_transformer.data = node + self.actual_transformer.scope_depth = 0 + return ast.fix_missing_locations(self.actual_transformer.visit(node)) + + def instrument_ipython() -> None: """Add defer_import's compile-time AST transformer to a currently running IPython environment. @@ -79,13 +88,4 @@ def instrument_ipython() -> None: msg = "Not currently in an IPython/Jupyter environment." raise RuntimeError(msg) from None - class DeferredIPythonInstrumenter(ast.NodeTransformer): - def __init__(self): - self.actual_transformer = DeferredInstrumenter("", "", "utf-8") - - def visit(self, node: ast.AST) -> _tp.Any: - self.actual_transformer.data = node - self.actual_transformer.scope_depth = 0 - return ast.fix_missing_locations(self.actual_transformer.visit(node)) - - ipython_shell.ast_transformers.append(DeferredIPythonInstrumenter()) + ipython_shell.ast_transformers.append(_DeferredIPythonInstrumenter()) diff --git a/tests/test_deferred.py b/tests/test_deferred.py index a253006..693a823 100644 --- a/tests/test_deferred.py +++ b/tests/test_deferred.py @@ -838,7 +838,7 @@ def access_module_attr() -> object: @pytest.mark.skip(reason="Leaking patch problem is currently out of scope.") -def test_leaking_patch(tmp_path: Path): +def test_leaking_patch(tmp_path: Path): # pragma: no cover """Test a synthetic package that demonstrates the "leaking patch" problem. Source: https://github.com/bswck/slothy/tree/bd0828a8dd9af63ca5c85340a70a14a76a6b714f/tests/leaking_patch