Skip to content

Commit

Permalink
feat: Support attribute syntax in __all__ values
Browse files Browse the repository at this point in the history
Issue-316: #316
  • Loading branch information
pawamoy committed Aug 23, 2024
1 parent 86cda59 commit ad99794
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 21 deletions.
8 changes: 6 additions & 2 deletions docs/reference/api/agents.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,6 @@ Griffe is able to analyze code both statically and dynamically.

::: griffe.get_instance_names

::: griffe.ExportedName

::: griffe.get__all__

::: griffe.safe_get__all__
Expand All @@ -71,3 +69,9 @@ Griffe is able to analyze code both statically and dynamically.
::: griffe.get_value

::: griffe.safe_get_value

<!-- YORE: Bump 2: Remove line. -->
## **Deprecated API**

<!-- YORE: Bump 2: Remove line. -->
::: griffe.ExportedName
48 changes: 29 additions & 19 deletions src/_griffe/agents/nodes/exports.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,63 +9,73 @@

from _griffe.agents.nodes.values import get_value
from _griffe.enumerations import LogLevel
from _griffe.expressions import ExprName
from _griffe.logger import logger

if TYPE_CHECKING:
from _griffe.models import Module


# YORE: Bump 2: Remove block.
@dataclass
class ExportedName:
"""An intermediate class to store names."""
"""Deprecated. An intermediate class to store names.
The [`get__all__`][griffe.get__all__] function now returns instances of [`ExprName`][griffe.ExprName] instead.
"""

name: str
"""The exported name."""
parent: Module
"""The parent module."""


def _extract_constant(node: ast.Constant, parent: Module) -> list[str | ExportedName]:
return [node.value]
def _extract_attribute(node: ast.Attribute, parent: Module) -> list[str | ExprName]:
return [ExprName(name=node.attr, parent=_extract(node.value, parent)[0])]


def _extract_binop(node: ast.BinOp, parent: Module) -> list[str | ExprName]:
left = _extract(node.left, parent)
right = _extract(node.right, parent)
return left + right


def _extract_name(node: ast.Name, parent: Module) -> list[str | ExportedName]:
return [ExportedName(node.id, parent)]
def _extract_constant(node: ast.Constant, parent: Module) -> list[str | ExprName]:
return [node.value]


def _extract_starred(node: ast.Starred, parent: Module) -> list[str | ExportedName]:
return _extract(node.value, parent)
def _extract_name(node: ast.Name, parent: Module) -> list[str | ExprName]:
return [ExprName(node.id, parent)]


def _extract_sequence(node: ast.List | ast.Set | ast.Tuple, parent: Module) -> list[str | ExportedName]:
def _extract_sequence(node: ast.List | ast.Set | ast.Tuple, parent: Module) -> list[str | ExprName]:
sequence = []
for elt in node.elts:
sequence.extend(_extract(elt, parent))
return sequence


def _extract_binop(node: ast.BinOp, parent: Module) -> list[str | ExportedName]:
left = _extract(node.left, parent)
right = _extract(node.right, parent)
return left + right
def _extract_starred(node: ast.Starred, parent: Module) -> list[str | ExprName]:
return _extract(node.value, parent)


_node_map: dict[type, Callable[[Any, Module], list[str | ExportedName]]] = {
_node_map: dict[type, Callable[[Any, Module], list[str | ExprName]]] = {
ast.Attribute: _extract_attribute,
ast.BinOp: _extract_binop,
ast.Constant: _extract_constant,
ast.Name: _extract_name,
ast.Starred: _extract_starred,
ast.List: _extract_sequence,
ast.Name: _extract_name,
ast.Set: _extract_sequence,
ast.Starred: _extract_starred,
ast.Tuple: _extract_sequence,
ast.BinOp: _extract_binop,
}


def _extract(node: ast.AST, parent: Module) -> list[str | ExportedName]:
def _extract(node: ast.AST, parent: Module) -> list[str | ExprName]:
return _node_map[type(node)](node, parent)


def get__all__(node: ast.Assign | ast.AnnAssign | ast.AugAssign, parent: Module) -> list[str | ExportedName]:
def get__all__(node: ast.Assign | ast.AnnAssign | ast.AugAssign, parent: Module) -> list[str | ExprName]:
"""Get the values declared in `__all__`.
Parameters:
Expand All @@ -84,7 +94,7 @@ def safe_get__all__(
node: ast.Assign | ast.AnnAssign | ast.AugAssign,
parent: Module,
log_level: LogLevel = LogLevel.debug, # TODO: set to error when we handle more things
) -> list[str | ExportedName]:
) -> list[str | ExprName]:
"""Safely (no exception) extract values in `__all__`.
Parameters:
Expand Down
3 changes: 3 additions & 0 deletions src/griffe/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
ast_siblings,
)
from _griffe.agents.nodes.docstrings import get_docstring

# YORE: Bump 2: Replace `ExportedName, ` with `` within line.
from _griffe.agents.nodes.exports import ExportedName, get__all__, safe_get__all__
from _griffe.agents.nodes.imports import relative_to_absolute
from _griffe.agents.nodes.parameters import ParametersType, get_parameters
Expand Down Expand Up @@ -277,6 +279,7 @@
"DocstringWarn",
"DocstringYield",
"ExplanationStyle",
# YORE: Bump 2: Remove line.
"ExportedName",
"Expr",
"ExprAttribute",
Expand Down
25 changes: 25 additions & 0 deletions tests/test_visitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -360,3 +360,28 @@ def test_visiting_relative_imports_triggering_cyclic_aliases() -> None:
assert "a" not in pkg.imports
assert "b" in pkg["a"].imports
assert pkg["a"].imports["b"] == "pkg.b"


def test_parse_attributes_in__all__() -> None:
"""Parse attributes in `__all__`."""
with temporary_visited_package(
"package",
{
"__init__.py": "from package import module\n__all__ = module.__all__",
"module.py": "def hello(): ...\n__all__ = ['hello']",
},
) as package:
assert "hello" in package.exports # type: ignore[operator]


def test_parse_deep_attributes_in__all__() -> None:
"""Parse deep attributes in `__all__`."""
with temporary_visited_package(
"package",
{
"__init__.py": "from package import subpackage\n__all__ = subpackage.module.__all__",
"subpackage/__init__.py": "from package.subpackage import module",
"subpackage/module.py": "def hello(): ...\n__all__ = ['hello']",
},
) as package:
assert "hello" in package.exports # type: ignore[operator]

0 comments on commit ad99794

Please sign in to comment.