on_node
eventon_module_node
event"}}
create_mod{{create module instance}}
event_mod_instance{{"on_instance
eventon_module_instance
event"}}
visit_mod_members{{visit module members}}
visit_cls{{enter class node}}
event_cls_node{{"on_node
eventon_class_node
event"}}
create_cls{{create class instance}}
event_cls_instance{{"on_instance
eventon_class_instance
event"}}
visit_cls_members{{visit class members}}
visit_func{{enter func node}}
event_func_node{{"on_node
eventon_function_node
event"}}
create_func{{create function instance}}
event_func_instance{{"on_instance
eventon_function_instance
event"}}
event_cls_members{{"on_members
eventon_class_members
event"}}
event_mod_members{{"on_members
eventon_module_members
event"}}
start{start} --> visit_mod
visit_mod --> event_mod_node
event_mod_node --> create_mod
create_mod --> event_mod_instance
event_mod_instance --> visit_mod_members
visit_mod_members --1--> visit_cls
visit_cls --> event_cls_node
event_cls_node --> create_cls
create_cls --> event_cls_instance
event_cls_instance --> visit_cls_members
visit_cls_members --1--> visit_func
visit_func --> event_func_node
event_func_node --> create_func
create_func --> event_func_instance
event_func_instance --> visit_cls_members
visit_cls_members --2--> event_cls_members
event_cls_members --> visit_mod_members
visit_mod_members --2--> event_mod_members
event_mod_members --> finish{finish}
class event_mod_node event
class event_mod_instance event
class event_cls_node event
class event_cls_instance event
class event_func_node event
class event_func_instance event
class event_cls_members event
class event_mod_members event
classDef event stroke:#3cc,stroke-width:2
```
Hopefully this flowchart gave you a pretty good idea
of what happens when Griffe collects data from a Python module.
The next setion will explain in more details
the different events that are triggered,
and how to hook onto them in your extensions.
### Events and hooks
There are two kinds of events in Griffe:
**load events** and **analysis events**.
Load events are scoped to the Griffe loader.
Analysis events are scoped to the visitor and inspector agents
(triggered during static and dynamic analysis).
#### Load events
There is only one **load event**:
- [`on_package_loaded`][griffe.extensions.base.Extension.on_package_loaded]
This event is triggered when the loader has finished loading a package entirely,
i.e. when all its submodules were scanned and loaded.
This event can be hooked by extensions which require the whole package to be loaded,
to be able to navigate the object tree without raising lookup errors
or alias resolution errors.
#### Analysis events
There are 3 generic **analysis events**:
- [`on_node`][griffe.extensions.base.Extension.on_node]
- [`on_instance`][griffe.extensions.base.Extension.on_instance]
- [`on_members`][griffe.extensions.base.Extension.on_members]
There are also specific **analysis events** for each object kind:
- [`on_module_node`][griffe.extensions.base.Extension.on_module_node]
- [`on_module_instance`][griffe.extensions.base.Extension.on_module_instance]
- [`on_module_members`][griffe.extensions.base.Extension.on_module_members]
- [`on_class_node`][griffe.extensions.base.Extension.on_class_node]
- [`on_class_instance`][griffe.extensions.base.Extension.on_class_instance]
- [`on_class_members`][griffe.extensions.base.Extension.on_class_members]
- [`on_function_node`][griffe.extensions.base.Extension.on_function_node]
- [`on_function_instance`][griffe.extensions.base.Extension.on_function_instance]
- [`on_attribute_node`][griffe.extensions.base.Extension.on_attribute_node]
- [`on_attribute_instance`][griffe.extensions.base.Extension.on_attribute_instance]
The "on node" events are triggered when the agent (visitor or inspector)
starts handling a node in the tree (AST or object tree).
The "on instance" events are triggered when the agent
just created an instance of [Module][griffe.dataclasses.Module],
[Class][griffe.dataclasses.Class],
[Function][griffe.dataclasses.Function],
or [Attribute][griffe.dataclasses.Attribute],
and added it as a member of its parent.
The "on members" events are triggered when the agent
just finished handling all the members of an object.
Functions and attributes do not have members,
so there are no "on members" event for these two kinds.
**Hooks** are methods that are called when a particular
event is triggered. To target a specific event,
the hook must be named after it.
**Extensions** are classes that inherit from
[Griffe's Extension base class][griffe.extensions.Extension]
and define some hooks as methods:
```python
import ast
from griffe import Extension, Object, ObjectNode
class MyExtension(Extension):
def on_instance(self, node: ast.AST | ObjectNode, obj: Object) -> None:
"""Do something with `node` and/or `obj`."""
```
Hooks are always defined as methods of a class inheriting from
[Extension][griffe.extensions.Extension], never as standalone functions.
Since hooks are declared in a class, feel free to also declare state variables
(or any other variable) in the `__init__` method:
```python
import ast
from griffe import Extension, Object, ObjectNode
class MyExtension(Extension):
def __init__(self) -> None:
super().__init__()
self.state_thingy = "initial stuff"
self.list_of_things = []
def on_instance(self, node: ast.AST | ObjectNode, obj: Object) -> None:
"""Do something with `node` and/or `obj`."""
```
### Static/dynamic support
Extensions can support both static and dynamic analysis of modules.
If a module is scanned statically, your extension hooks
will receive AST nodes (from the [ast][] module of the standard library).
If the module is scanned dynamically,
your extension hooks will receive [object nodes][griffe.ObjectNode].
To support static analysis, dynamic analysis, or both,
you can therefore check the type of the received node:
```python
import ast
from griffe import Extension, Object, ObjectNode
class MyExtension(Extension):
def on_instance(self, node: ast.AST | ObjectNode, obj: Object) -> None:
"""Do something with `node` and/or `obj`."""
if isinstance(node, ast.AST):
... # apply logic for static analysis
else:
... # apply logic for dynamic analysis
```
Since hooks also receive instantiated modules, classes, functions and attributes,
most of the time you will not need to use the `node` argument
other than for checking its type and deciding what to do based on the result.
If you do need to, read the next section explaining how to visit trees.
### Visiting trees
Extensions provide basic functionality to help you visit trees:
- [`visit`][griffe.extensions.base.Extension.visit]: call `self.visit(node)`
to start visiting an abstract syntax tree.
- [`generic_visit`][griffe.extensions.base.Extension.generic_visit]: call
`self.generic_visit(node)` to visit each subnode of a given node.
- [`inspect`][griffe.extensions.base.Extension.inspect]: call `self.inspect(node)`
to start visiting an object tree. Nodes contain references to the runtime objects,
see [`ObjectNode`][griffe.agents.nodes.ObjectNode].
- [`generic_inspect`][griffe.extensions.base.Extension.generic_inspect]: call
`self.generic_inspect(node)` to visit each subnode of a given node.
Calling `self.visit(node)` or `self.inspect(node)` will do nothing
unless you actually implement methods that handle specific types of nodes:
- for ASTs, methods must be named `visit_> > - [`Add`][ast.Add] > - [`alias`][ast.alias] > - [`And`][ast.And] > - [`AnnAssign`][ast.AnnAssign] > - [`arg`][ast.arg] > - [`arguments`][ast.arguments] > - [`Assert`][ast.Assert] > - [`Assign`][ast.Assign] > - [`AsyncFor`][ast.AsyncFor] > - [`AsyncFunctionDef`][ast.AsyncFunctionDef] > - [`AsyncWith`][ast.AsyncWith] > - [`Attribute`][ast.Attribute] > - [`AugAssign`][ast.AugAssign] > - [`Await`][ast.Await] > - [`BinOp`][ast.BinOp] > - [`BitAnd`][ast.BitAnd] > - [`BitOr`][ast.BitOr] > - [`BitXor`][ast.BitXor] > - [`BoolOp`][ast.BoolOp] > - [`Break`][ast.Break] > - `Bytes`[^1] > - [`Call`][ast.Call] > - [`ClassDef`][ast.ClassDef] > - [`Compare`][ast.Compare] > - [`comprehension`][ast.comprehension] > - [`Constant`][ast.Constant] > - [`Continue`][ast.Continue] > - [`Del`][ast.Del] > - [`Delete`][ast.Delete] > > | > > - [`Dict`][ast.Dict] > - [`DictComp`][ast.DictComp] > - [`Div`][ast.Div] > - `Ellipsis`[^1] > - [`Eq`][ast.Eq] > - [`ExceptHandler`][ast.ExceptHandler] > - [`Expr`][ast.Expr] > - `Expression`[^1] > - `ExtSlice`[^2] > - [`FloorDiv`][ast.FloorDiv] > - [`For`][ast.For] > - [`FormattedValue`][ast.FormattedValue] > - [`FunctionDef`][ast.FunctionDef] > - [`GeneratorExp`][ast.GeneratorExp] > - [`Global`][ast.Global] > - [`Gt`][ast.Gt] > - [`GtE`][ast.GtE] > - [`If`][ast.If] > - [`IfExp`][ast.IfExp] > - [`Import`][ast.Import] > - [`ImportFrom`][ast.ImportFrom] > - [`In`][ast.In] > - `Index`[^2] > - `Interactive`[^3] > - [`Invert`][ast.Invert] > - [`Is`][ast.Is] > - [`IsNot`][ast.IsNot] > - [`JoinedStr`][ast.JoinedStr] > - [`keyword`][ast.keyword] > > | > > - [`Lambda`][ast.Lambda] > - [`List`][ast.List] > - [`ListComp`][ast.ListComp] > - [`Load`][ast.Load] > - [`LShift`][ast.LShift] > - [`Lt`][ast.Lt] > - [`LtE`][ast.LtE] > - [`Match`][ast.Match] > - [`MatchAs`][ast.MatchAs] > - [`match_case`][ast.match_case] > - [`MatchClass`][ast.MatchClass] > - [`MatchMapping`][ast.MatchMapping] > - [`MatchOr`][ast.MatchOr] > - [`MatchSequence`][ast.MatchSequence] > - [`MatchSingleton`][ast.MatchSingleton] > - [`MatchStar`][ast.MatchStar] > - [`MatchValue`][ast.MatchValue] > - [`MatMult`][ast.MatMult] > - [`Mod`][ast.Mod] > - `Module`[^3] > - [`Mult`][ast.Mult] > - [`Name`][ast.Name] > - `NameConstant`[^1] > - [`NamedExpr`][ast.NamedExpr] > - [`Nonlocal`][ast.Nonlocal] > - [`Not`][ast.Not] > - [`NotEq`][ast.NotEq] > - [`NotIn`][ast.NotIn] > - `Num`[^1] > > | > > - [`Or`][ast.Or] > - [`Pass`][ast.Pass] > - `pattern`[^3] > - [`Pow`][ast.Pow] > - `Print`[^4] > - [`Raise`][ast.Raise] > - [`Return`][ast.Return] > - [`RShift`][ast.RShift] > - [`Set`][ast.Set] > - [`SetComp`][ast.SetComp] > - [`Slice`][ast.Slice] > - [`Starred`][ast.Starred] > - [`Store`][ast.Store] > - `Str`[^1] > - [`Sub`][ast.Sub] > - [`Subscript`][ast.Subscript] > - [`Try`][ast.Try] > - `TryExcept`[^5] > - `TryFinally`[^6] > - [`Tuple`][ast.Tuple] > - [`UAdd`][ast.UAdd] > - [`UnaryOp`][ast.UnaryOp] > - [`USub`][ast.USub] > - [`While`][ast.While] > - [`With`][ast.With] > - [`withitem`][ast.withitem] > - [`Yield`][ast.Yield] > - [`YieldFrom`][ast.YieldFrom] > > |
{code}
"))
```
Depending on the detected breakages, the lines might be hard to read (being too compact),
so `griffe check` also accepts a `--verbose` or `-v` option to add some space to the output:
```python exec="1" html="1"
import os
from rich.console import Console
# TODO: instead of hardcoding output, actually run griffe check?
report = """$ griffe check griffe -ssrc -b0.24.0 -a0.23.0 --verbose
[bold]src/griffe/loader.py[/]:156: GriffeLoader.resolve_aliases([#7faeff]only_exported[/]):
[#afaf72]Parameter kind was changed[/]:
Old: positional or keyword
New: keyword-only
[bold]src/griffe/loader.py[/]:156: GriffeLoader.resolve_aliases([#7faeff]only_exported[/]):
[#afaf72]Parameter default was changed[/]:
Old: True
New: None
[bold]src/griffe/loader.py[/]:156: GriffeLoader.resolve_aliases([#7faeff]only_known_modules[/]):
[#afaf72]Parameter kind was changed[/]:
Old: positional or keyword
New: keyword-only
[bold]src/griffe/loader.py[/]:156: GriffeLoader.resolve_aliases([#7faeff]only_known_modules[/]):
[#afaf72]Parameter default was changed[/]:
Old: True
New: None
[bold]src/griffe/loader.py[/]:156: GriffeLoader.resolve_aliases([#7faeff]max_iterations[/]):
[#afaf72]Parameter kind was changed[/]:
Old: positional or keyword
New: keyword-only
[bold]src/griffe/loader.py[/]:308: GriffeLoader.resolve_module_aliases([#7faeff]only_exported[/]):
[#afaf72]Parameter was removed[/]
[bold]src/griffe/loader.py[/]:308: GriffeLoader.resolve_module_aliases([#7faeff]only_known_modules[/]):
[#afaf72]Parameter was removed[/]
[bold]src/griffe/git.py[/]:39: tmp_worktree([#7faeff]commit[/]):
[#afaf72]Parameter was removed[/]
[bold]src/griffe/git.py[/]:39: tmp_worktree([#7faeff]repo[/]):
[#afaf72]Positional parameter was moved[/]:
Details: position: from 1 to 0 (-1)
[bold]src/griffe/git.py[/]:75: load_git([#7faeff]commit[/]):
[#afaf72]Parameter was removed[/]
[bold]src/griffe/git.py[/]:75: load_git([#7faeff]repo[/]):
[#afaf72]Parameter kind was changed[/]:
Old: positional or keyword
New: keyword-only
[bold]src/griffe/git.py[/]:75: load_git([#7faeff]submodules[/]):
[#afaf72]Parameter kind was changed[/]:
Old: positional or keyword
New: keyword-only
[bold]src/griffe/git.py[/]:75: load_git([#7faeff]try_relative_path[/]):
[#afaf72]Parameter was removed[/]
[bold]src/griffe/git.py[/]:75: load_git([#7faeff]extensions[/]):
[#afaf72]Parameter kind was changed[/]:
Old: positional or keyword
New: keyword-only
[bold]src/griffe/git.py[/]:75: load_git([#7faeff]search_paths[/]):
[#afaf72]Parameter kind was changed[/]:
Old: positional or keyword
New: keyword-only
[bold]src/griffe/git.py[/]:75: load_git([#7faeff]docstring_parser[/]):
[#afaf72]Parameter kind was changed[/]:
Old: positional or keyword
New: keyword-only
[bold]src/griffe/git.py[/]:75: load_git([#7faeff]docstring_options[/]):
[#afaf72]Parameter kind was changed[/]:
Old: positional or keyword
New: keyword-only
[bold]src/griffe/git.py[/]:75: load_git([#7faeff]lines_collection[/]):
[#afaf72]Parameter kind was changed[/]:
Old: positional or keyword
New: keyword-only
[bold]src/griffe/git.py[/]:75: load_git([#7faeff]modules_collection[/]):
[#afaf72]Parameter kind was changed[/]:
Old: positional or keyword
New: keyword-only
[bold]src/griffe/git.py[/]:75: load_git([#7faeff]allow_inspection[/]):
[#afaf72]Parameter kind was changed[/]:
Old: positional or keyword
New: keyword-only
"""
with open(os.devnull, "w") as devnull:
console = Console(record=True, width=80, file=devnull)
console.print(report, markup=True, highlight=False)
print(console.export_html(inline_styles=True, code_format="{code}
"))
```
python-griffe-0.40.0/docs/.overrides/ 0000755 0001750 0001750 00000000000 14556223422 017251 5 ustar carsten carsten python-griffe-0.40.0/docs/.overrides/main.html 0000644 0001750 0001750 00000001047 14556223422 021065 0 ustar carsten carsten {% extends "base.html" %}
{% block announce %}
Sponsorship
is now available!
{% include ".icons/octicons/heart-fill-16.svg" %}
—
For updates follow @pawamoy on
{% include ".icons/fontawesome/brands/mastodon.svg" %}
Fosstodon
{% endblock %}
python-griffe-0.40.0/docs/docstrings.md 0000644 0001750 0001750 00000107744 14556223422 017707 0 ustar carsten carsten # Docstrings
Griffe provides different docstring parsers allowing to extract
even more structured data from source code.
The available parsers are:
- `google`, to parse Google-style docstrings,
see [Napoleon's documentation](https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html)
- `numpy`, to parse Numpydoc docstrings,
see [Numpydoc's documentation](https://numpydoc.readthedocs.io/en/latest/format.html)
- `sphinx`, to parse Sphinx-style docstrings,
see [Sphinx's documentation](https://sphinx-rtd-tutorial.readthedocs.io/en/latest/docstrings.html)
## Syntax
Most of the time, the syntax specified in the aforementioned docs is supported.
In some cases, the original syntax is not supported, or is supported but with subtle differences.
We will try to document these differences in the following sections.
No assumption is made on the markup
used in docstrings: it's retrieved as regular text.
Tooling making use of Griffe can then choose to render
the text as if it is Markdown, or AsciiDoc, or reStructuredText, etc..
### Google-style
Sections are written like this:
```
section identifier: optional section title
section contents
```
All sections identifiers are case-insensitive.
All sections support multiple lines in descriptions,
as well as blank lines. The first line must not be blank.
Each section must be separated from contents above by a blank line.
❌ This is **invalid** and will be parsed as regular markup:
```python
Some text.
Note: # (1)!
Some information.
Blank lines allowed.
```
1. Missing blank line above.
❌ This is **invalid** and will be parsed as regular markup:
```python
Some text.
Note: # (1)!
Some information.
Blank lines allowed.
```
1. Extraneous blank line below.
✅ This is **valid** and will parsed as a text section followed by a note admonition:
```python
Some text.
Note:
Some information.
Blank lines allowed.
```
Find out possibly invalid section syntax by grepping for "reasons" in Griffe debug logs:
```bash
griffe dump -Ldebug -o/dev/null -fdgoogle your_package 2>&1 | grep reasons
```
Some sections support documenting multiple items (attributes, parameters, etc.).
When multiple items are supported, each item description can
use multiple lines, and continuation lines must be indented once
more so that the parser is able to differentiate items.
```python
def foo(a, b):
"""Foo.
Parameters:
a: Here's a.
Continuation line 1.
Continuation line 2.
b: Here's b.
"""
```
It's possible to start a description with a newline if you
find it less confusing:
```python
def foo(a, b):
"""Foo.
Parameters:
a:
Here's a.
Continuation line 1.
Continuation line 2.
b: Here's b.
"""
```
#### Parser options
The parser accepts a few options:
- `ignore_init_summary`: Ignore the first line in `__init__` methods' docstrings.
Useful when merging `__init__` docstring into class' docstrings
with mkdocstrings-python's [`merge_init_into_class`][merge_init] option. Default: false.
- `returns_multiple_items`: Parse [Returns sections](#returns) as if they contain multiple items.
It means that continuation lines must be indented. Default: true.
- `returns_named_value`: Whether to parse `thing: Description` in [Returns sections](#returns) as a name and description,
rather than a type and description. When true, type must be wrapped in parentheses: `(int): Description.`.
When false, parentheses are optional but the items cannot be named: `int: Description`. Default: true.
- `returns_type_in_property_summary`: Whether to parse the return type of properties
at the beginning of their summary: `str: Summary of the property`. Default: false.
- `trim_doctest_flags`: Remove the [doctest flags][] written as comments in `pycon` snippets within a docstring.
These flags are used to alter the behavior of [doctest][] when testing docstrings,
and should not be visible in your docs. Default: true.
- `warn_unknown_params`: Warn about parameters documented in docstrings that do not appear in the signature. Default: true.
#### Attributes
- Multiple items allowed
Attributes sections allow to document attributes of a module, class, or class instance.
They should be used in modules and classes docstrings only.
```python
"""My module.
Attributes:
foo: Description for `foo`.
bar: Description for `bar`.
"""
foo: int = 0
bar: bool = True
class MyClass:
"""My class.
Attributes:
foofoo: Description for `foofoo`.
barbar: Description for `barbar`.
"""
foofoo: int = 0
def __init__(self):
self.barbar: bool = True
```
Type annotations are fetched from the related attributes definitions.
You can override those by adding types between parentheses before the colon:
```python
"""My module.
Attributes:
foo (Integer): Description for `foo`.
bar (Boolean): Description for `bar`.
"""
```
TIP: **Types in docstrings are resolved using the docstrings' parent scope.**
When documenting an attribute with `attr_name (attr_type): Attribute description`, `attr_type`
will be resolved using the scope of the docstrings' parent object (class or module).
For example, a type of `list[str]` will be parsed just as if it was an actual Python annotation.
You can therefore use complex types (available in the current scope) in docstrings,
for example `Optional[Union[int, Tuple[float, float]]]`.
#### Functions/Methods
- Multiple items allowed
Functions or Methods sections allow to document functions of a module, or methods of a class.
They should be used in modules and classes docstrings only.
```python
"""My module.
Functions:
foo: Description for `foo`.
bar: Description for `bar`.
"""
def foo():
return "foo"
def bar(baz: int) -> int:
return baz * 2
class MyClass:
"""My class.
Methods:
foofoo: Description for `foofoo`.
barbar: Description for `barbar`.
"""
def foofoo(self):
return "foofoo"
@staticmethod
def barbar():
return "barbar"
```
It's possible to write the function/method signature as well as its name:
```python
"""
Functions:
foo(): Description for `foo`.
bar(baz=1): Description for `bar`.
"""
```
The signatures do not have to match the real ones:
you can shorten them to only show the important parameters.
#### Classes
- Multiple items allowed
Classes sections allow to document classes of a module or class.
They should be used in modules and classes docstrings only.
```python
"""My module.
Classes:
Foo: Description for `foo`.
Bar: Description for `bar`.
"""
class Foo:
...
class Bar:
def __init__(self, baz: int) -> int:
return baz * 2
class MyClass:
"""My class.
Classes:
FooFoo: Description for `foofoo`.
BarBar: Description for `barbar`.
"""
class FooFoo:
...
class BarBar:
...
```
It's possible to write the class signature as well as its name:
```python
"""
Functions:
Foo(): Description for `Foo`.
Bar(baz=1): Description for `Bar`.
"""
```
The signatures do not have to match the real ones:
you can shorten them to only show the important initialization parameters.
#### Modules
- Multiple items allowed
Modules sections allow to document submodules of a module.
They should be used in modules docstrings only.
```tree
my_pkg/
__init__.py
foo.py
bar.py
```
```python title="my_pkg/__init__.py"
"""My package.
Modules:
foo: Description for `foo`.
bar: Description for `bar`.
"""
```
#### Deprecated
Deprecated sections allow to document a deprecation that happened at a particular version.
They can be used in every docstring.
```python
"""My module.
Deprecated:
1.2: The `foo` attribute is deprecated.
"""
foo: int = 0
```
#### Examples
Examples sections allow to add examples of Python code without the use of markup code blocks.
They are a mix of prose and interactive console snippets.
They can be used in every docstring.
```python
"""My module.
Examples:
Some explanation of what is possible.
>>> print("hello!")
hello!
Blank lines delimit prose vs. console blocks.
>>> a = 0
>>> a += 1
>>> a
1
"""
```
WARNING: **Not the same as *Example* sections.**
*Example* (singular) sections are parsed as admonitions.
Console code blocks will only be understood in *Examples* (plural) sections.
#### Parameters
- Aliases: Args, Arguments, Params
- Multiple items allowed
Parameters sections allow to document parameters of a function.
They are typically used in functions docstrings, but can also be used in dataclasses docstrings.
```python
def foo(a: int, b: str):
"""Foo.
Parameters:
a: Here's a.
b: Here's b.
"""
```
```python
from dataclasses import dataclass
@dataclass
class Foo:
"""Foo.
Parameters:
a: Here's a.
b: Here's b.
"""
foo: int
bar: str
```
Type annotations are fetched from the related parameters definitions.
You can override those by adding types between parentheses before the colon:
```python
"""My function.
Parameters:
foo (Integer): Description for `foo`.
bar (String): Description for `bar`.
"""
```
TIP: **Types in docstrings are resolved using the docstrings' parent scope.**
When documenting a parameter with `param_name (param_type): Parameter description`, `param_type`
will be resolved using the scope of the function (or class).
For example, a type of `list[str]` will be parsed just as if it was an actual Python annotation.
You can therefore use complex types (available in the current scope) in docstrings,
for example `Optional[Union[int, Tuple[float, float]]]`.
#### Other Parameters
- Aliases: Keyword Args, Keyword Arguments, Other Args, Other Arguments, Other Params
- Multiple items allowed
Other parameters sections allow to document secondary parameters such as variadic keyword arguments,
or parameters that should be of lesser interest to the user.
They are used the same way Parameters sections are,
but can also be useful in decorators / to document returned callables.
```python
def foo(a, b, **kwargs):
"""Foo.
Parameters:
a: Here's a.
b: Here's b.
Other parameters:
c (int): Here's c.
d (bool): Here's d.
"""
```
```python
def foo(a, b):
"""Returns a callable.
Parameters:
a: Here's a.
b: Here's b.
Other parameters: Parameters of the returned callable:
c (int): Here's c.
d (bool): Here's d.
"""
def inner(c, d):
...
return inner
```
TIP: **Types in docstrings are resolved using the docstrings' parent scope.**
See the same tip for parameters.
#### Raises
- Aliases: Exceptions
- Multiple items allowed
Raises sections allow to document exceptions that are raised by a function.
They are usually only used in functions docstrings.
```python
def foo(a: int):
"""Foo.
Parameters:
a: A value.
Raises:
ValueError: When `a` is less than 0.
"""
if a < 0:
raise ValueError("message")
```
TIP: **Exceptions names are resolved using the function's scope.**
`ValueError` and other built-in exceptions are resolved as such.
You can document custom exception, using the names available in the current scope,
for example `my_exceptions.MyCustomException` or `MyCustomException` directly,
depending on what you imported/defined in the current module.
#### Warns
- Aliases: Warnings
- Multiple items allowed
Warns sections allow to document warnings emitted by the following code.
They are usually only used in functions docstrings.
```python
import warnings
def foo():
"""Foo.
Warns:
UserWarning: To annoy users.
"""
warnings.warn("Just messing with you.", UserWarning)
```
TIP: **Warnings names are resolved using the function's scope.**
`UserWarning` and other built-in warnings are resolved as such.
You can document custom warnings, using the names available in the current scope,
for example `my_warnings.MyCustomWarning` or `MyCustomWarning` directly,
depending on what you imported/defined in the current module.
#### Yields
- Multiple items allowed
Yields sections allow to document values that generator yield.
They should be used only in generators docstrings.
Documented items can be given a name when it makes sense.
```python
from typing import Iterator
def foo() -> Iterator[int]:
"""Foo.
Yields:
Integers from 0 to 9.
"""
for i in range(10):
yield i
```
Type annotations are fetched from the function return annotation
when the annotation is `typing.Generator` or `typing.Iterator`.
If your generator yields tuples, you can document each item of the tuple separately,
and the type annotation will be fetched accordingly:
```python
from datetime import datetime
def foo() -> Iterator[tuple[float, float, datetime]]:
"""Foo.
Yields:
x: Absissa.
y: Ordinate.
t: Time.
...
"""
...
```
Type annotations can as usual be overridden using types in parentheses
in the docstring itself:
```python
"""Foo.
Yields:
x (int): Absissa.
y (int): Ordinate.
t (int): Timestamp.
"""
```
TIP: **Types in docstrings are resolved using the docstrings' parent scope.**
See previous tips for types in docstrings.
#### Receives
- Multiple items allowed
Receives sections allow to document values that can be sent to generators
using their `send` method.
They should be used only in generators docstrings.
Documented items can be given a name when it makes sense.
```python
from typing import Generator
def foo() -> Generator[int, str, None]:
"""Foo.
Receives:
reverse: Reverse the generator if `"reverse"` is received.
Yields:
Integers from 0 to 9.
Examples:
>>> gen = foo()
>>> next(gen)
0
>>> next(gen)
1
>>> next(gen)
2
>>> gen.send("reverse")
2
>>> next(gen)
1
>>> next(gen)
0
>>> next(gen)
Traceback (most recent call last):
File "'
sponsors.forEach(function (sponsor) {
html += `
`
});
html += '
'
exclude = {"src/griffe/agents/extensions/base.py"}
src = Path(__file__).parent.parent / "src"
for path in sorted(src.rglob("*.py")):
if str(path) in exclude:
continue
module_path = path.relative_to(src).with_suffix("")
doc_path = path.relative_to(src).with_suffix(".md")
full_doc_path = Path("reference", doc_path)
parts = tuple(module_path.parts)
if parts[-1] == "__init__":
parts = parts[:-1]
doc_path = doc_path.with_name("index.md")
full_doc_path = full_doc_path.with_name("index.md")
elif parts[-1].startswith("_"):
continue
nav_parts = [f"{mod_symbol} {part}" for part in parts]
nav[tuple(nav_parts)] = doc_path.as_posix()
with mkdocs_gen_files.open(full_doc_path, "w") as fd:
ident = ".".join(parts)
fd.write(f"::: {ident}")
mkdocs_gen_files.set_edit_path(full_doc_path, ".." / path)
with mkdocs_gen_files.open("reference/SUMMARY.md", "w") as nav_file:
nav_file.writelines(nav.build_literate_nav())
python-griffe-0.40.0/scripts/insiders.py 0000644 0001750 0001750 00000014604 14556223422 020127 0 ustar carsten carsten """Functions related to Insiders funding goals."""
from __future__ import annotations
import json
import logging
import os
import posixpath
from dataclasses import dataclass
from datetime import date, datetime, timedelta
from itertools import chain
from pathlib import Path
from typing import Iterable, cast
from urllib.error import HTTPError
from urllib.parse import urljoin
from urllib.request import urlopen
import yaml
logger = logging.getLogger(f"mkdocs.logs.{__name__}")
def human_readable_amount(amount: int) -> str: # noqa: D103
str_amount = str(amount)
if len(str_amount) >= 4: # noqa: PLR2004
return f"{str_amount[:len(str_amount)-3]},{str_amount[-3:]}"
return str_amount
@dataclass
class Project:
"""Class representing an Insiders project."""
name: str
url: str
@dataclass
class Feature:
"""Class representing an Insiders feature."""
name: str
ref: str | None
since: date | None
project: Project | None
def url(self, rel_base: str = "..") -> str | None: # noqa: D102
if not self.ref:
return None
if self.project:
rel_base = self.project.url
return posixpath.join(rel_base, self.ref.lstrip("/"))
def render(self, rel_base: str = "..", *, badge: bool = False) -> None: # noqa: D102
new = ""
if badge:
recent = self.since and date.today() - self.since <= timedelta(days=60) # noqa: DTZ011
if recent:
ft_date = self.since.strftime("%B %d, %Y") # type: ignore[union-attr]
new = f' :material-alert-decagram:{{ .new-feature .vibrate title="Added on {ft_date}" }}'
project = f"[{self.project.name}]({self.project.url}) — " if self.project else ""
feature = f"[{self.name}]({self.url(rel_base)})" if self.ref else self.name
print(f"- [{'x' if self.since else ' '}] {project}{feature}{new}")
@dataclass
class Goal:
"""Class representing an Insiders goal."""
name: str
amount: int
features: list[Feature]
complete: bool = False
@property
def human_readable_amount(self) -> str: # noqa: D102
return human_readable_amount(self.amount)
def render(self, rel_base: str = "..") -> None: # noqa: D102
print(f"#### $ {self.human_readable_amount} — {self.name}\n")
for feature in self.features:
feature.render(rel_base)
print("")
def load_goals(data: str, funding: int = 0, project: Project | None = None) -> dict[int, Goal]:
"""Load goals from JSON data.
Parameters:
data: The JSON data.
funding: The current total funding, per month.
origin: The origin of the data (URL).
Returns:
A dictionaries of goals, keys being their target monthly amount.
"""
goals_data = yaml.safe_load(data)["goals"]
return {
amount: Goal(
name=goal_data["name"],
amount=amount,
complete=funding >= amount,
features=[
Feature(
name=feature_data["name"],
ref=feature_data.get("ref"),
since=feature_data.get("since")
and datetime.strptime(feature_data["since"], "%Y/%m/%d").date(), # noqa: DTZ007
project=project,
)
for feature_data in goal_data["features"]
],
)
for amount, goal_data in goals_data.items()
}
def _load_goals_from_disk(path: str, funding: int = 0) -> dict[int, Goal]:
project_dir = os.getenv("MKDOCS_CONFIG_DIR", ".")
try:
data = Path(project_dir, path).read_text()
except OSError as error:
raise RuntimeError(f"Could not load data from disk: {path}") from error
return load_goals(data, funding)
def _load_goals_from_url(source_data: tuple[str, str, str], funding: int = 0) -> dict[int, Goal]:
project_name, project_url, data_fragment = source_data
data_url = urljoin(project_url, data_fragment)
try:
with urlopen(data_url) as response: # noqa: S310
data = response.read()
except HTTPError as error:
raise RuntimeError(f"Could not load data from network: {data_url}") from error
return load_goals(data, funding, project=Project(name=project_name, url=project_url))
def _load_goals(source: str | tuple[str, str, str], funding: int = 0) -> dict[int, Goal]:
if isinstance(source, str):
return _load_goals_from_disk(source, funding)
return _load_goals_from_url(source, funding)
def funding_goals(source: str | list[str | tuple[str, str, str]], funding: int = 0) -> dict[int, Goal]:
"""Load funding goals from a given data source.
Parameters:
source: The data source (local file path or URL).
funding: The current total funding, per month.
Returns:
A dictionaries of goals, keys being their target monthly amount.
"""
if isinstance(source, str):
return _load_goals_from_disk(source, funding)
goals = {}
for src in source:
source_goals = _load_goals(src, funding)
for amount, goal in source_goals.items():
if amount not in goals:
goals[amount] = goal
else:
goals[amount].features.extend(goal.features)
return {amount: goals[amount] for amount in sorted(goals)}
def feature_list(goals: Iterable[Goal]) -> list[Feature]:
"""Extract feature list from funding goals.
Parameters:
goals: A list of funding goals.
Returns:
A list of features.
"""
return list(chain.from_iterable(goal.features for goal in goals))
def load_json(url: str) -> str | list | dict: # noqa: D103
with urlopen(url) as response: # noqa: S310
return json.loads(response.read().decode())
data_source = globals()["data_source"]
sponsor_url = "https://github.com/sponsors/pawamoy"
data_url = "https://raw.githubusercontent.com/pawamoy/sponsors/main"
numbers: dict[str, int] = load_json(f"{data_url}/numbers.json") # type: ignore[assignment]
sponsors: list[dict] = load_json(f"{data_url}/sponsors.json") # type: ignore[assignment]
current_funding = numbers["total"]
sponsors_count = numbers["count"]
goals = funding_goals(data_source, funding=current_funding)
ongoing_goals = [goal for goal in goals.values() if not goal.complete]
unreleased_features = sorted(
(ft for ft in feature_list(ongoing_goals) if ft.since),
key=lambda ft: cast(date, ft.since),
reverse=True,
)
python-griffe-0.40.0/scripts/gen_credits.py 0000644 0001750 0001750 00000011470 14556223422 020573 0 ustar carsten carsten """Script to generate the project's credits."""
from __future__ import annotations
import os
import re
import sys
from importlib.metadata import PackageNotFoundError, metadata
from itertools import chain
from pathlib import Path
from textwrap import dedent
from typing import Mapping, cast
from jinja2 import StrictUndefined
from jinja2.sandbox import SandboxedEnvironment
# TODO: Remove once support for Python 3.10 is dropped.
if sys.version_info >= (3, 11):
import tomllib
else:
import tomli as tomllib
project_dir = Path(os.getenv("MKDOCS_CONFIG_DIR", "."))
with project_dir.joinpath("pyproject.toml").open("rb") as pyproject_file:
pyproject = tomllib.load(pyproject_file)
project = pyproject["project"]
pdm = pyproject["tool"]["pdm"]
with project_dir.joinpath("pdm.lock").open("rb") as lock_file:
lock_data = tomllib.load(lock_file)
lock_pkgs = {pkg["name"].lower(): pkg for pkg in lock_data["package"]}
project_name = project["name"]
regex = re.compile(r"(?P