Skip to content

Commit

Permalink
feat: Handle properties setter and deleter
Browse files Browse the repository at this point in the history
  • Loading branch information
pawamoy committed Apr 15, 2022
1 parent 927bbd9 commit 50a4490
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 0 deletions.
31 changes: 31 additions & 0 deletions src/griffe/agents/visitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,29 @@ def decorators_to_labels(self, decorators: list[Decorator]) -> set[str]: # noqa
labels.add("cached")
return labels

def get_base_property(self, decorators: list[Decorator]) -> tuple[Function | None, str | None]:
"""Check decorators to return the base property in case of setters and deleters.
Parameters:
decorators: The decorators to check.
Returns:
base_property: The property for which the setter/deleted is set.
property_function: Either `"setter"` or `"deleter"`.
"""
for decorator in decorators:
names = decorator.value.split(".")
with suppress(ValueError):
base_name, base_function = names
property_setter_or_deleter = (
base_function in {"setter", "deleter"}
and base_name in self.current.members
and self.current[base_name].has_labels({"property"})
)
if property_setter_or_deleter:
return self.current[base_name], base_function
return None, None

def handle_function(self, node: ast.AsyncFunctionDef | ast.FunctionDef, labels: set | None = None): # noqa: WPS231
"""Handle a function definition node.
Expand Down Expand Up @@ -313,6 +336,7 @@ def handle_function(self, node: ast.AsyncFunctionDef | ast.FunctionDef, labels:
lineno = node.lineno

labels |= self.decorators_to_labels(decorators)
base_property, property_function = self.get_base_property(decorators)

# handle parameters
parameters = Parameters()
Expand Down Expand Up @@ -401,6 +425,13 @@ def handle_function(self, node: ast.AsyncFunctionDef | ast.FunctionDef, labels:

if overload:
self.overloads[function.path].append(function)
elif base_property is not None:
if property_function == "setter":
base_property.setter = function
base_property.labels.add("writable")
elif property_function == "deleter":
base_property.deleter = function
base_property.labels.add("deletable")
else:
self.current[node.name] = function
if self.overloads[function.path]:
Expand Down
20 changes: 20 additions & 0 deletions tests/test_visitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,26 @@ def f(self):
assert label in module["A.f"].labels


def test_handle_property_setter_and_deleter():
"""Assert property setters and deleters are supported."""
code = """
class A:
def __init__(self): self._thing = 0
@property
def thing(self): return self._thing
@thing.setter
def thing(self, value): self._thing = value
@thing.deleter
def thing(self): del self._thing
"""
with temporary_visited_module(code) as module:
assert module["A.thing"].has_labels(["property", "writable", "deletable"])
assert module["A.thing"].setter.is_function
assert module["A.thing"].deleter.is_function


@pytest.mark.parametrize(
"decorator",
Expand Down

0 comments on commit 50a4490

Please sign in to comment.