Skip to content

Commit

Permalink
docs: Add how-to support custom decorators
Browse files Browse the repository at this point in the history
  • Loading branch information
pawamoy committed Oct 23, 2024
1 parent 4ef4649 commit 25c8424
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 0 deletions.
61 changes: 61 additions & 0 deletions docs/guide/users/how-to/support-decorators.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Supporting custom decorators

Griffe aims to support the Python language itself, as well as its standard library. It means that built-in objects and objects from the standard library that can be used or are often used as decorators, should be supported natively or through official extensions, for example `@property`, `@functools.cache`, `@warnings.deprecated`, etc.

Custom decorators however (the ones you define in your code-base) won't be supported by default, at least statically (dynamic analysis might be able to support them), because Griffe doesn't try to infer anything more than the obvious. Griffe is not a type-checker and so doesn't have the same inference abilities.

Therefore, to support your own decorators (at least statically), you have to write Griffe extensions. Don't worry, extensions that support custom decorators are generally super easy to write.

---

Lets assume we have a decorator whose path is `my_package.utils.enhance`. It is used throughout our code base like so:

```python
from my_package.utils import enhance

@enhance
def my_function() -> ...:
...
```

Start by creating an extensions module (a simple Python file) somewhere in your repository, if you don't already have one. Within it, create an extension class:

```python
import griffe


class MyDecorator(griffe.Extension):
"""An extension to suport my decorator."""
```

Now we can declare the [`on_instance`][griffe.Extension.on_instance] hook, which receives any kind of Griffe object ([`Module`][griffe.Module], [`Class`][griffe.Class], [`Function`][griffe.Function], [`Attribute`][griffe.Attribute]), or we could use a kind-specific hook such as [`on_module_instance`][griffe.Extension.on_module_instance], [`on_class_instance`][griffe.Extension.on_class_instance], [`on_function_instance`][griffe.Extension.on_function_instance] and [`on_attribute_instance`][griffe.Extension.on_attribute_instance]. For example, if you know your decorator is only ever used on class declarations, it would make sense to use `on_class_instance`.

For the example, lets use the `on_function_instance` hook, which receives `Function` instances.

```python hl_lines="7-8"
import griffe


class MyDecorator(griffe.Extension):
"""An extension to suport my decorator."""

def on_function_instance(self, *, func: griffe.Function, **kwargs) -> None:
...
```

In this hook, we check if our function is decorated with our custom decorator:

```python hl_lines="8-10"
import griffe


class MyDecorator(griffe.Extension):
"""An extension to suport my decorator."""

def on_function_instance(self, *, func: griffe.Function, **kwargs) -> None:
for decorator in func.decorators:
if decorator.callable_path == "my_package.utils.enhance":
... # Update the function attributes.
```

Now all that is left to do is to actually write the code that updates the function according to what the decorator is doing. We could update the function's docstring, or its return type, or its parameters: it all depends on your decorator and what it does to the objects it decorates.
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ nav:
- Docstrings: guide/users/recommendations/docstrings.md
- How-to:
- Parse docstrings: guide/users/how-to/parse-docstrings.md
- Support custom decorators: guide/users/how-to/support-decorators.md
- Selectively inspect objects: guide/users/how-to/selectively-inspect.md
- Contributor guide:
- guide/contributors.md
Expand Down

0 comments on commit 25c8424

Please sign in to comment.