Skip to content

Commit

Permalink
refactor: Split members API in two parts: producer and consumer
Browse files Browse the repository at this point in the history
The consumer API can be used
when navigating or modifying the tree
once it was built by Griffe and extensions.

The producer API must be used
when building the objects tree,
so by Griffe itself and by extensions.

Using the consumer API while building
is risky because the consumer API
does ~~magic~~ convenient things
and trigger too-early computation
of different things like inherited members.
  • Loading branch information
pawamoy committed Jun 30, 2023
1 parent 08bbe09 commit 2269449
Show file tree
Hide file tree
Showing 9 changed files with 232 additions and 73 deletions.
8 changes: 4 additions & 4 deletions src/griffe/agents/inspector.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ def generic_inspect(self, node: ObjectNode) -> None:
else:
child_name = getattr(child.obj, "__name__", child.name)
target_path = f"{child_module_path}.{child_name}"
self.current[child.name] = Alias(child.name, target_path)
self.current.set_member(child.name, Alias(child.name, target_path))
else:
self.inspect(child)

Expand Down Expand Up @@ -306,7 +306,7 @@ def inspect_class(self, node: ObjectNode) -> None:
docstring=self._get_docstring(node),
bases=bases,
)
self.current[node.name] = class_
self.current.set_member(node.name, class_)
self.current = class_
self.generic_inspect(node)
self.current = self.current.parent # type: ignore[assignment]
Expand Down Expand Up @@ -434,7 +434,7 @@ def handle_function(self, node: ObjectNode, labels: set | None = None) -> None:
docstring=self._get_docstring(node),
)
obj.labels |= labels
self.current[node.name] = obj
self.current.set_member(node.name, obj)

def inspect_attribute(self, node: ObjectNode) -> None:
"""Inspect an attribute.
Expand Down Expand Up @@ -481,7 +481,7 @@ def handle_attribute(self, node: ObjectNode, annotation: str | Name | Expression
docstring=docstring,
)
attribute.labels |= labels
parent[node.name] = attribute
parent.set_member(node.name, attribute)

if node.name == "__all__":
parent.exports = set(node.obj)
Expand Down
38 changes: 22 additions & 16 deletions src/griffe/agents/visitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ def visit_classdef(self, node: ast.ClassDef) -> None:
runtime=not self.type_guarded,
)
class_.labels |= self.decorators_to_labels(decorators)
self.current[node.name] = class_
self.current.set_member(node.name, class_)
self.current = class_
self.generic_visit(node)
self.current = self.current.parent # type: ignore[assignment]
Expand Down Expand Up @@ -304,10 +304,10 @@ def get_base_property(self, decorators: list[Decorator]) -> tuple[Function | Non
property_setter_or_deleter = (
base_function in {"setter", "deleter"}
and base_name in self.current.members
and self.current[base_name].has_labels({"property"})
and self.current.get_member(base_name).has_labels({"property"})
)
if property_setter_or_deleter:
return self.current[base_name], base_function
return self.current.get_member(base_name), base_function
return None, None

def handle_function(self, node: ast.AsyncFunctionDef | ast.FunctionDef, labels: set | None = None) -> None:
Expand Down Expand Up @@ -356,7 +356,7 @@ def handle_function(self, node: ast.AsyncFunctionDef | ast.FunctionDef, labels:
runtime=not self.type_guarded,
)
attribute.labels |= labels
self.current[node.name] = attribute
self.current.set_member(node.name, attribute)
return

base_property, property_function = self.get_base_property(decorators)
Expand Down Expand Up @@ -461,7 +461,7 @@ def handle_function(self, node: ast.AsyncFunctionDef | ast.FunctionDef, labels:
base_property.deleter = function
base_property.labels.add("deletable")
else:
self.current[node.name] = function
self.current.set_member(node.name, function)
if self.current.kind in {Kind.MODULE, Kind.CLASS} and self.current.overloads[function.name]:
function.overloads = self.current.overloads[function.name]
del self.current.overloads[function.name]
Expand Down Expand Up @@ -499,12 +499,15 @@ def visit_import(self, node: ast.Import) -> None:
alias_path = name.name
alias_name = name.asname or alias_path.split(".", 1)[0]
self.current.imports[alias_name] = alias_path
self.current[alias_name] = Alias(
self.current.set_member(
alias_name,
alias_path,
lineno=node.lineno,
endlineno=node.end_lineno, # type: ignore[attr-defined]
runtime=not self.type_guarded,
Alias(
alias_name,
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:
Expand All @@ -528,12 +531,15 @@ def visit_importfrom(self, node: ast.ImportFrom) -> None:
else:
alias_name = name.asname or name.name
self.current.imports[alias_name] = alias_path
self.current[alias_name] = Alias(
self.current.set_member(
alias_name,
alias_path, # type: ignore[arg-type]
lineno=node.lineno,
endlineno=node.end_lineno, # type: ignore[attr-defined]
runtime=not self.type_guarded,
Alias(
alias_name,
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(
Expand Down Expand Up @@ -626,7 +632,7 @@ def handle_attribute(
runtime=not self.type_guarded,
)
attribute.labels |= labels
parent[name] = attribute
parent.set_member(name, attribute)

if name == "__all__":
with suppress(AttributeError):
Expand Down
45 changes: 41 additions & 4 deletions src/griffe/dataclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,11 @@ def is_kind(self, kind: str | Kind | set[str | Kind]) -> bool:

@cached_property
def inherited_members(self) -> dict[str, Alias]:
"""Members that are inherited from base classes."""
"""Members that are inherited from base classes.
This method is part of the consumer API:
do not use when producing Griffe trees!
"""
if not isinstance(self, Class):
return {}
inherited_members = {}
Expand All @@ -448,7 +452,11 @@ def inherited_members(self) -> dict[str, Alias]:

@property
def all_members(self) -> dict[str, Object | Alias]:
"""All members (declared and inherited)."""
"""All members (declared and inherited).
This method is part of the consumer API:
do not use when producing Griffe trees!
"""
return {**self.inherited_members, **self.members}

@property
Expand Down Expand Up @@ -503,6 +511,9 @@ def filter_members(self, *predicates: Callable[[Object | Alias], bool]) -> dict[
def modules(self) -> dict[str, Module]:
"""Return the module members.
This method is part of the consumer API:
do not use when producing Griffe trees!
Returns:
A dictionary of modules.
"""
Expand All @@ -512,6 +523,9 @@ def modules(self) -> dict[str, Module]:
def classes(self) -> dict[str, Class]:
"""Return the class members.
This method is part of the consumer API:
do not use when producing Griffe trees!
Returns:
A dictionary of classes.
"""
Expand All @@ -521,6 +535,9 @@ def classes(self) -> dict[str, Class]:
def functions(self) -> dict[str, Function]:
"""Return the function members.
This method is part of the consumer API:
do not use when producing Griffe trees!
Returns:
A dictionary of functions.
"""
Expand All @@ -530,6 +547,9 @@ def functions(self) -> dict[str, Function]:
def attributes(self) -> dict[str, Attribute]:
"""Return the attribute members.
This method is part of the consumer API:
do not use when producing Griffe trees!
Returns:
A dictionary of attributes.
"""
Expand Down Expand Up @@ -870,6 +890,10 @@ def __setitem__(self, key: str | tuple[str, ...], value: Object | Alias):
# not handled by __getattr__
self.target[key] = value

def __delitem__(self, key: str | tuple[str, ...]):
# not handled by __getattr__
del self.target[key]

def __len__(self) -> int:
return 1

Expand Down Expand Up @@ -1072,6 +1096,15 @@ def source(self) -> str: # noqa: D102
def resolve(self, name: str) -> str: # noqa: D102
return self.final_target.resolve(name)

def get_member(self, key: str | Sequence[str]) -> Object | Alias: # noqa: D102
return self.final_target.get_member(key)

def set_member(self, key: str | Sequence[str], value: Object | Alias) -> None: # noqa: D102
return self.final_target.set_member(key, value)

def del_member(self, key: str | Sequence[str]) -> None: # noqa: D102
return self.final_target.del_member(key)

# SPECIFIC MODULE/CLASS/FUNCTION/ATTRIBUTE PROXIES ---------------

@property
Expand Down Expand Up @@ -1198,7 +1231,7 @@ def resolve_target(self) -> None:

def _resolve_target(self) -> None:
try:
resolved = self.modules_collection[self.target_path]
resolved = self.modules_collection.get_member(self.target_path)
except KeyError as error:
raise AliasResolutionError(self) from error
if resolved is self:
Expand Down Expand Up @@ -1435,7 +1468,11 @@ def parameters(self) -> Parameters:

@cached_property
def resolved_bases(self) -> list[Object]:
"""Resolved class bases."""
"""Resolved class bases.
This method is part of the consumer API:
do not use when producing Griffe trees!
"""
resolved_bases = []
for base in self.bases:
if isinstance(base, str):
Expand Down
4 changes: 2 additions & 2 deletions src/griffe/encoders.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ def _load_parameter(obj_dict: dict[str, Any]) -> Parameter:
def _load_module(obj_dict: dict[str, Any]) -> Module:
module = Module(name=obj_dict["name"], filepath=Path(obj_dict["filepath"]), docstring=_load_docstring(obj_dict))
for module_member in obj_dict.get("members", []):
module[module_member.name] = module_member
module.set_member(module_member.name, module_member)
module.labels |= set(obj_dict.get("labels", ()))
return module

Expand All @@ -156,7 +156,7 @@ def _load_class(obj_dict: dict[str, Any]) -> Class:
bases=[_load_annotation(_) for _ in obj_dict["bases"]],
)
for class_member in obj_dict.get("members", []):
class_[class_member.name] = class_member
class_.set_member(class_member.name, class_member)
class_.labels |= set(obj_dict.get("labels", ()))
return class_

Expand Down
2 changes: 1 addition & 1 deletion src/griffe/extensions/hybrid.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ def attach(self, visitor: Visitor) -> None: # noqa: D102

def visit(self, node: ast.AST) -> None: # noqa: D102
try:
just_visited = self.visitor.current[node.name] # type: ignore[attr-defined]
just_visited = self.visitor.current.get_member(node.name) # type: ignore[attr-defined]
except (KeyError, AttributeError, TypeError):
return
if self.object_paths and not any(op.search(just_visited.path) for op in self.object_paths):
Expand Down
Loading

0 comments on commit 2269449

Please sign in to comment.