Skip to content

Commit

Permalink
feat: Add runtime attribute to objects/aliases and handle type guar…
Browse files Browse the repository at this point in the history
…ded objects

Issue #42: #42
  • Loading branch information
pawamoy committed Apr 15, 2022
1 parent ebd3e09 commit 2f2a04e
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 2 deletions.
20 changes: 20 additions & 0 deletions src/griffe/agents/visitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ def __init__(
self.docstring_options: dict[str, Any] = docstring_options or {}
self.lines_collection: LinesCollection = lines_collection or LinesCollection()
self.modules_collection: ModulesCollection = modules_collection or ModulesCollection()
self.type_guarded: bool = False

def _get_docstring(self, node: ast.AST, strict: bool = False) -> Docstring | None:
value, lineno, endlineno = get_docstring(node, strict=strict)
Expand Down Expand Up @@ -230,6 +231,7 @@ def visit_classdef(self, node: ast.ClassDef) -> None:
docstring=self._get_docstring(node),
decorators=decorators,
bases=bases, # type: ignore[arg-type]
runtime=not self.type_guarded,
)
self.current[node.name] = class_
self.current = class_
Expand Down Expand Up @@ -340,6 +342,7 @@ def handle_function(self, node: ast.AsyncFunctionDef | ast.FunctionDef, labels:
returns=get_annotation(node.returns, parent=self.current),
decorators=decorators,
docstring=self._get_docstring(node),
runtime=not self.type_guarded,
)
self.current[node.name] = function

Expand Down Expand Up @@ -381,6 +384,7 @@ def visit_import(self, node: ast.Import) -> None:
alias_path,
lineno=node.lineno,
endlineno=node.end_lineno, # type: ignore[attr-defined]
runtime=not self.type_guarded,
)

def visit_importfrom(self, node: ast.ImportFrom) -> None: # noqa: WPS231
Expand Down Expand Up @@ -410,6 +414,7 @@ def visit_importfrom(self, node: ast.ImportFrom) -> None: # noqa: WPS231
alias_path, # type: ignore[arg-type]
lineno=node.lineno,
endlineno=node.end_lineno, # type: ignore[attr-defined]
runtime=not self.type_guarded,
)

def handle_attribute( # noqa: WPS231
Expand Down Expand Up @@ -477,6 +482,7 @@ def handle_attribute( # noqa: WPS231
lineno=node.lineno,
endlineno=node.end_lineno, # type: ignore[union-attr]
docstring=docstring,
runtime=not self.type_guarded,
)
attribute.labels |= labels
parent[name] = attribute
Expand All @@ -501,6 +507,20 @@ def visit_annassign(self, node: ast.AnnAssign) -> None:
"""
self.handle_attribute(node, get_annotation(node.annotation, parent=self.current))

def visit_if(self, node: ast.If) -> None:
"""Visit an "if" node.
Parameters:
node: The node to visit.
"""
if isinstance(node.parent, (ast.Module, ast.ClassDef)): # type: ignore[attr-defined]
with suppress(KeyError): # unhandled AST nodes
condition = get_annotation(node.test, self.current)
if str(condition) in {"typing.TYPE_CHECKING", "TYPE_CHECKING"}:
self.type_guarded = True
self.generic_visit(node)
self.type_guarded = False


_patched = False

Expand Down
11 changes: 10 additions & 1 deletion src/griffe/dataclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,7 @@ def __init__(
*,
lineno: int | None = None,
endlineno: int | None = None,
runtime: bool = True,
docstring: Docstring | None = None,
parent: Module | Class | None = None,
lines_collection: LinesCollection | None = None,
Expand All @@ -317,6 +318,7 @@ def __init__(
name: The object name, as declared in the code.
lineno: The object starting line, or None for modules. Lines start at 1.
endlineno: The object ending line (inclusive), or None for modules.
runtime: Whether this object is present at runtime or not.
docstring: The object docstring.
parent: The object parent.
lines_collection: A collection of source code lines.
Expand All @@ -332,6 +334,7 @@ def __init__(
self.imports: dict[str, str] = {}
self.exports: set[str] | None = None
self.aliases: dict[str, Alias] = {}
self.runtime: bool = runtime
self._lines_collection: LinesCollection | None = lines_collection
self._modules_collection: ModulesCollection | None = modules_collection

Expand Down Expand Up @@ -366,7 +369,8 @@ def member_is_exported(self, member: Object | Alias, explicitely: bool = True) -
By exported, we mean that the object is included in the `__all__` attribute
of its parent module or class. When `_all__` is not defined,
we consider the member to be *implicitely* exported,
unless it's a module and it was not imported.
unless it's a module and it was not imported,
and unless it's not defined at runtime.
Parameters:
member: The member to verify.
Expand All @@ -375,6 +379,8 @@ def member_is_exported(self, member: Object | Alias, explicitely: bool = True) -
Returns:
True or False.
"""
if not member.runtime:
return False
if self.exports is None:
return not explicitely and (member.is_alias or not member.is_module or member.name in self.imports)
return member.name in self.exports
Expand Down Expand Up @@ -756,6 +762,7 @@ def __init__(
*,
lineno: int | None = None,
endlineno: int | None = None,
runtime: bool = True,
parent: Module | Class | None = None,
) -> None:
"""Initialize the alias.
Expand All @@ -766,6 +773,7 @@ def __init__(
If it's an object, or even another alias, the target is immediately set.
lineno: The alias starting line number.
endlineno: The alias ending line number.
runtime: Whether this alias is present at runtime or not.
parent: The alias parent.
"""
self.name: str = name
Expand All @@ -780,6 +788,7 @@ def __init__(
target.aliases[self.path] = self
self.alias_lineno: int | None = lineno
self.alias_endlineno: int | None = endlineno
self.runtime: bool = runtime
self._parent: Module | Class | None = parent
self._passed_through: bool = False

Expand Down
36 changes: 35 additions & 1 deletion tests/test_visitor.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
"""Test visit mechanisms."""

from textwrap import dedent

import pytest

from tests.helpers import temporary_visited_module
from griffe.loader import GriffeLoader
from tests.helpers import temporary_pypackage, temporary_visited_module

# import sys
# import hypothesmith as hs
Expand Down Expand Up @@ -65,3 +68,34 @@ def test_building_value_from_nodes(expression):
with temporary_visited_module(f"a = {expression}") as module:
assert "a" in module.members
assert module["a"].value == expression


def test_not_defined_at_runtime():
"""Assert that objects not defined at runtime are not added to wildcards expansions."""
with temporary_pypackage("package", ["module_a.py", "module_b.py", "module_c.py"]) as tmp_package:
tmp_package.path.joinpath("__init__.py").write_text("from package.module_a import *")
tmp_package.path.joinpath("module_a.py").write_text(
dedent(
"""
import typing
from typing import TYPE_CHECKING
from package.module_b import CONST_B
from package.module_c import CONST_C
if typing.TYPE_CHECKING: # always false
from package.module_b import TYPE_B
if TYPE_CHECKING: # always false
from package.module_c import TYPE_C
"""
)
)
tmp_package.path.joinpath("module_b.py").write_text("CONST_B = 'hi'\nTYPE_B = str")
tmp_package.path.joinpath("module_c.py").write_text("CONST_C = 'ho'\nTYPE_C = str")
loader = GriffeLoader(search_paths=[tmp_package.tmpdir])
package = loader.load_module(tmp_package.name)
loader.resolve_aliases()
assert "CONST_B" in package.members
assert "CONST_C" in package.members
assert "TYPE_B" not in package.members
assert "TYPE_C" not in package.members

0 comments on commit 2f2a04e

Please sign in to comment.