From b9fe9e2e0f9987e47c61a143e67feae756e2c927 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Mon, 13 Nov 2023 16:28:41 +0100 Subject: [PATCH 1/2] refactor: Run static analysis only after the whole package was loaded Issue #7: https://github.com/mkdocstrings/griffe-typingdoc/issues/7 --- docs/examples.md | 10 ++ docs/examples/simple.py | 42 ++++---- pyproject.toml | 2 +- src/griffe_typingdoc/_docstrings.py | 2 +- src/griffe_typingdoc/_dynamic.py | 90 ++++++++++++++++- src/griffe_typingdoc/_extension.py | 143 ++++++++++++++++++---------- src/griffe_typingdoc/_static.py | 80 ++++++++++------ tests/test_extension.py | 122 +++++++++++++++++++++++- 8 files changed, 379 insertions(+), 112 deletions(-) diff --git a/docs/examples.md b/docs/examples.md index b5dd7f9..9284887 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -1,3 +1,8 @@ +--- +hide: +- navigation +--- + # Examples ## Simple @@ -16,6 +21,11 @@ ## Enhanced +> WARNING: **Non-standard features** +The "enhanced" features are not part of PEP 727. +They just serve as an example to show what would be possible +if the PEP was enhanced to account for more use-cases. + /// details | `enhanced` Python module type: example diff --git a/docs/examples/simple.py b/docs/examples/simple.py index 29cf5f8..a84427c 100644 --- a/docs/examples/simple.py +++ b/docs/examples/simple.py @@ -41,13 +41,11 @@ def other_parameters( # Documenting yielded and received values, replacing Yields and Receives sections: -def generator() -> ( - Generator[ - Annotated[int, Doc("Yielded integers.")], - Annotated[int, Doc("Received integers.")], - Annotated[int, Doc("Final returned value.")], - ] -): +def generator() -> Generator[ + Annotated[int, Doc("Yielded integers.")], + Annotated[int, Doc("Received integers.")], + Annotated[int, Doc("Final returned value.")], +]: """Showing off generators.""" @@ -57,20 +55,18 @@ def iterator() -> Iterator[Annotated[int, Doc("Yielded integers.")]]: # Advanced use-case: documenting multiple yielded/received/returned values: -def return_tuple() -> ( - Generator[ - tuple[ - Annotated[int, Doc("First element of the yielded value.")], - Annotated[float, Doc("Second element of the yielded value.")], - ], - tuple[ - Annotated[int, Doc("First element of the received value.")], - Annotated[float, Doc("Second element of the received value.")], - ], - tuple[ - Annotated[int, Doc("First element of the returned value.")], - Annotated[float, Doc("Second element of the returned value.")], - ], - ] -): +def return_tuple() -> Generator[ + tuple[ + Annotated[int, Doc("First element of the yielded value.")], + Annotated[float, Doc("Second element of the yielded value.")], + ], + tuple[ + Annotated[int, Doc("First element of the received value.")], + Annotated[float, Doc("Second element of the received value.")], + ], + tuple[ + Annotated[int, Doc("First element of the returned value.")], + Annotated[float, Doc("Second element of the returned value.")], + ], +]: """Showing off tuples as yield/receive/return values.""" diff --git a/pyproject.toml b/pyproject.toml index 6a071b0..6625f8b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,7 +29,7 @@ classifiers = [ "Typing :: Typed", ] dependencies = [ - "griffe>=0.35", + "griffe>=0.38", "typing-extensions>=4.7", ] diff --git a/src/griffe_typingdoc/_docstrings.py b/src/griffe_typingdoc/_docstrings.py index ed6be39..69e5082 100644 --- a/src/griffe_typingdoc/_docstrings.py +++ b/src/griffe_typingdoc/_docstrings.py @@ -39,7 +39,7 @@ def _to_parameters_section(params_dict: dict[str, dict[str, Any]], func: Functio name=param_name, description=param_doc["description"], annotation=param_doc["annotation"], - value=func.parameters[param_name].default, # type: ignore[arg-type] + value=func.parameters[param_name].default, ) for param_name, param_doc in params_dict.items() ], diff --git a/src/griffe_typingdoc/_dynamic.py b/src/griffe_typingdoc/_dynamic.py index ea89dc6..2e91248 100644 --- a/src/griffe_typingdoc/_dynamic.py +++ b/src/griffe_typingdoc/_dynamic.py @@ -10,7 +10,16 @@ if TYPE_CHECKING: from griffe import Attribute, Function, ObjectNode - from griffe.docstrings.dataclasses import DocstringSectionParameters + from griffe.docstrings.dataclasses import ( + DocstringSectionAdmonition, + DocstringSectionOtherParameters, + DocstringSectionParameters, + DocstringSectionRaises, + DocstringSectionReceives, + DocstringSectionReturns, + DocstringSectionWarns, + DocstringSectionYields, + ) def _hints(node: ObjectNode) -> dict[str, str]: @@ -29,11 +38,16 @@ def _doc(name: str, hints: dict[str, Any]) -> str | None: return None -def _attribute_docs(node: ObjectNode, attr: Attribute) -> str: +def _attribute_docs(attr: Attribute, *, node: ObjectNode, **kwargs: Any) -> str: # noqa: ARG001 return _doc(attr.name, _hints(node)) or "" -def _parameters_docs(node: ObjectNode, func: Function) -> DocstringSectionParameters | None: +def _parameters_docs( + func: Function, + *, + node: ObjectNode, + **kwargs: Any, # noqa: ARG001 +) -> DocstringSectionParameters | None: hints = _hints(node) params_doc: dict[str, dict[str, Any]] = { name: {"description": _doc(name, hints)} for name in hints if name != "return" @@ -41,3 +55,73 @@ def _parameters_docs(node: ObjectNode, func: Function) -> DocstringSectionParame if params_doc: return _to_parameters_section(params_doc, func) return None + + +# FIXME: Implement this function. +def _other_parameters_docs( + func: Function, # noqa: ARG001 + *, + node: ObjectNode, # noqa: ARG001 + **kwargs: Any, # noqa: ARG001 +) -> DocstringSectionOtherParameters | None: + return None + + +# FIXME: Implement this function. +def _deprecated_docs( + attr_or_func: Attribute | Function, # noqa: ARG001 + *, + node: ObjectNode, # noqa: ARG001 + **kwargs: Any, # noqa: ARG001 +) -> DocstringSectionAdmonition | None: + return None + + +# FIXME: Implement this function. +def _raises_docs( + attr_or_func: Attribute | Function, # noqa: ARG001 + *, + node: ObjectNode, # noqa: ARG001 + **kwargs: Any, # noqa: ARG001 +) -> DocstringSectionRaises | None: + return None + + +# FIXME: Implement this function. +def _warns_docs( + attr_or_func: Attribute | Function, # noqa: ARG001 + *, + node: ObjectNode, # noqa: ARG001 + **kwargs: Any, # noqa: ARG001 +) -> DocstringSectionWarns | None: + return None + + +# FIXME: Implement this function. +def _yields_docs( + func: Function, # noqa: ARG001 + *, + node: ObjectNode, # noqa: ARG001 + **kwargs: Any, # noqa: ARG001 +) -> DocstringSectionYields | None: + return None + + +# FIXME: Implement this function. +def _receives_docs( + func: Function, # noqa: ARG001 + *, + node: ObjectNode, # noqa: ARG001 + **kwargs: Any, # noqa: ARG001 +) -> DocstringSectionReceives | None: + return None + + +# FIXME: Implement this function. +def _returns_docs( + func: Function, # noqa: ARG001 + *, + node: ObjectNode, # noqa: ARG001 + **kwargs: Any, # noqa: ARG001 +) -> DocstringSectionReturns | None: + return None diff --git a/src/griffe_typingdoc/_extension.py b/src/griffe_typingdoc/_extension.py index 4a64ad2..ed73570 100644 --- a/src/griffe_typingdoc/_extension.py +++ b/src/griffe_typingdoc/_extension.py @@ -11,33 +11,28 @@ if TYPE_CHECKING: import ast - from griffe.dataclasses import Attribute - from typing_extensions import Annotated, Doc # type: ignore[attr-defined] + from griffe.dataclasses import Attribute, Module, Object + from typing_extensions import Annotated, Doc class TypingDocExtension(Extension): """Griffe extension that reads documentation from `typing.Doc`.""" - def on_attribute_instance( - self, - *, - node: Annotated[ - ast.AST | ObjectNode, - Doc("The object/AST node describing the attribute or its definition."), - ], - attr: Annotated[ - Attribute, - Doc("The Griffe attribute just instantiated."), - ], - ) -> None: - """Post-process Griffe attributes to create their docstring.""" - module = _dynamic if isinstance(node, ObjectNode) else _static + def __init__(self) -> None: + self._handled: set[str] = set() + + def _handle_attribute(self, attr: Attribute, /, *, node: ObjectNode | None = None) -> None: + if attr.path in self._handled: + return + self._handled.add(attr.path) + + module = _dynamic if node else _static new_sections = ( - docstring := module._attribute_docs(node, attr), - deprecated_section := module._deprecated_docs(node, attr), - raises_section := module._raises_docs(node, attr), - warns_section := module._warns_docs(node, attr), + docstring := module._attribute_docs(attr, node=node), + deprecated_section := module._deprecated_docs(attr, node=node), + raises_section := module._raises_docs(attr, node=node), + warns_section := module._warns_docs(attr, node=node), ) if not any(new_sections): @@ -48,45 +43,31 @@ def on_attribute_instance( sections = attr.docstring.parsed - if deprecated_section := module._deprecated_docs(node, attr): + if deprecated_section: sections.insert(0, deprecated_section) - if raises_section := module._raises_docs(node, attr): + if raises_section: sections.append(raises_section) - if warns_section := module._warns_docs(node, attr): + if warns_section: sections.append(warns_section) - def on_function_instance( - self, - *, - node: Annotated[ - ast.AST | ObjectNode, - Doc("The object/AST node describing the function or its definition."), - ], - func: Annotated[ - Function, - Doc( - # Multiline docstring to test de-indentation. - """ - The Griffe function just instantiated. - """, - ), - ], - ) -> None: - """Post-process Griffe functions to add a parameters section.""" - module = _dynamic if isinstance(node, ObjectNode) else _static + def _handle_function(self, func: Function, /, *, node: ObjectNode | None = None) -> None: + if func.path in self._handled: + return + self._handled.add(func.path) + + module = _dynamic if node else _static - yields_section, receives_section, returns_section = module._yrr_docs(node, func) new_sections = ( - deprecated_section := module._deprecated_docs(node, func), - params_section := module._parameters_docs(node, func), - other_params_section := module._other_parameters_docs(node, func), - warns_section := module._warns_docs(node, func), - raises_section := module._raises_docs(node, func), - yields_section, - receives_section, - returns_section, + deprecated_section := module._deprecated_docs(func, node=node), + params_section := module._parameters_docs(func, node=node), + other_params_section := module._other_parameters_docs(func, node=node), + warns_section := module._warns_docs(func, node=node), + raises_section := module._raises_docs(func, node=node), + yields_section := module._yields_docs(func, node=node), + receives_section := module._receives_docs(func, node=node), + returns_section := module._returns_docs(func, node=node), ) if not any(new_sections): @@ -120,3 +101,63 @@ def on_function_instance( if returns_section: sections.append(returns_section) + + def _handle_object(self, obj: Object) -> None: + if obj.is_alias: + return + if obj.is_module or obj.is_class: + for member in obj.members.values(): + self._handle_object(member) + elif obj.is_function: + self._handle_function(obj) + elif obj.is_attribute: + self._handle_attribute(obj) + + def on_package_loaded( + self, + *, + pkg: Annotated[ + Module, + Doc("The top-level module representing a package."), + ], + ) -> None: + """Post-process Griffe packages recursively (non-yet handled objects only).""" + self._handle_object(pkg) + + def on_function_instance( + self, + *, + node: Annotated[ + ast.AST | ObjectNode, + Doc("The object/AST node describing the function or its definition."), + ], + func: Annotated[ + Function, + Doc("""The Griffe function just instantiated."""), + ], + ) -> None: + """Post-process Griffe functions to add a parameters section. + + It applies only for dynamic analysis. + """ + if isinstance(node, ObjectNode): + self._handle_function(func, node=node) + + def on_attribute_instance( + self, + *, + node: Annotated[ + ast.AST | ObjectNode, + Doc("The object/AST node describing the attribute or its definition."), + ], + attr: Annotated[ + Attribute, + Doc("The Griffe attribute just instantiated."), + ], + ) -> None: + """Post-process Griffe attributes to create their docstring. + + It applies only for dynamic analysis. + """ + if isinstance(node, ObjectNode): + self._handle_attribute(attr, node=node) diff --git a/src/griffe_typingdoc/_static.py b/src/griffe_typingdoc/_static.py index aad4b39..e8158d9 100644 --- a/src/griffe_typingdoc/_static.py +++ b/src/griffe_typingdoc/_static.py @@ -23,8 +23,6 @@ ) if TYPE_CHECKING: - import ast - from griffe import Function from griffe.dataclasses import Attribute from griffe.docstrings.dataclasses import ( @@ -99,14 +97,14 @@ def _metadata(annotation: str | Expr | None) -> dict[str, Any]: return metadata -def _attribute_docs(node: ast.AST, attr: Attribute) -> str: # noqa: ARG001 +def _attribute_docs(attr: Attribute, **kwargs: Any) -> str: # noqa: ARG001 return _metadata(attr.annotation).get("doc", "") -def _parameters_docs(node: ast.AST, func: Function) -> DocstringSectionParameters | None: # noqa: ARG001 +def _parameters_docs(func: Function, **kwargs: Any) -> DocstringSectionParameters | None: # noqa: ARG001 params_doc: dict[str, dict[str, Any]] = defaultdict(dict) for parameter in _no_self_params(func): - stars = {ParameterKind.var_positional: "*", ParameterKind.var_keyword: "**"}.get(parameter.kind, "") # type: ignore[arg-type] + stars = {ParameterKind.var_positional: "*", ParameterKind.var_keyword: "**"}.get(parameter.kind, "") param_name = f"{stars}{parameter.name}" metadata = _metadata(parameter.annotation) description = f'{metadata.get("deprecated", "")} {metadata.get("doc", "")}'.lstrip() @@ -117,7 +115,7 @@ def _parameters_docs(node: ast.AST, func: Function) -> DocstringSectionParameter return None -def _other_parameters_docs(node: ast.AST, func: Function) -> DocstringSectionParameters | None: # noqa: ARG001 +def _other_parameters_docs(func: Function, **kwargs: Any) -> DocstringSectionParameters | None: # noqa: ARG001 for parameter in func.parameters: if parameter.kind is ParameterKind.var_keyword: annotation = parameter.annotation @@ -125,12 +123,12 @@ def _other_parameters_docs(node: ast.AST, func: Function) -> DocstringSectionPar "typing.Annotated", "typing_extensions.Annotated", }: - annotation = annotation.slice.elements[0] # type: ignore[attr-defined] + annotation = annotation.slice.elements[0] if isinstance(annotation, ExprSubscript) and annotation.canonical_path in { "typing.Unpack", "typing_extensions.Unpack", }: - typed_dict = annotation.slice.parent.get_member(annotation.slice.name) # type: ignore[attr-defined] + typed_dict = annotation.slice.parent.get_member(annotation.slice.name) params_doc = { attr.name: {"annotation": attr.annotation, "description": _metadata(attr.annotation).get("doc", "")} for attr in typed_dict.members.values() @@ -141,72 +139,92 @@ def _other_parameters_docs(node: ast.AST, func: Function) -> DocstringSectionPar return None -def _yrr_docs( - node: ast.AST, # noqa: ARG001 - func: Function, -) -> tuple[DocstringSectionYields | None, DocstringSectionReceives | None, DocstringSectionReturns | None]: +def _yields_docs(func: Function, **kwargs: Any) -> DocstringSectionYields | None: # noqa: ARG001 yields_section = None - receives_section = None - returns_section = None + yield_annotation = None annotation = func.returns - yield_annotation = None - receive_annotation = None - return_annotation = None - if isinstance(annotation, ExprSubscript): if annotation.canonical_path in {"typing.Generator", "typing_extensions.Generator"}: - yield_annotation = annotation.slice.elements[0] # type: ignore[attr-defined] - receive_annotation = annotation.slice.elements[1] # type: ignore[attr-defined] - return_annotation = annotation.slice.elements[2] # type: ignore[attr-defined] + yield_annotation = annotation.slice.elements[0] elif annotation.canonical_path in {"typing.Iterator", "typing_extensions.Iterator"}: yield_annotation = annotation.slice if yield_annotation: if isinstance(yield_annotation, ExprSubscript) and yield_annotation.is_tuple: - yield_elements = yield_annotation.slice.elements # type: ignore[attr-defined] + yield_elements = yield_annotation.slice.elements else: yield_elements = [yield_annotation] yields_section = _to_yields_section({"annotation": element, **_metadata(element)} for element in yield_elements) + return yields_section + + +def _receives_docs(func: Function, **kwargs: Any) -> DocstringSectionReceives | None: # noqa: ARG001 + receives_section = None + receive_annotation = None + + annotation = func.returns + + if isinstance(annotation, ExprSubscript) and annotation.canonical_path in { + "typing.Generator", + "typing_extensions.Generator", + }: + receive_annotation = annotation.slice.elements[1] + if receive_annotation: if isinstance(receive_annotation, ExprSubscript) and receive_annotation.is_tuple: - receive_elements = receive_annotation.slice.elements # type: ignore[attr-defined] + receive_elements = receive_annotation.slice.elements else: receive_elements = [receive_annotation] receives_section = _to_receives_section( {"annotation": element, **_metadata(element)} for element in receive_elements ) + return receives_section + + +def _returns_docs(func: Function, **kwargs: Any) -> DocstringSectionReturns | None: # noqa: ARG001 + returns_section = None + return_annotation = None + + annotation = func.returns + + if isinstance(annotation, ExprSubscript) and annotation.canonical_path in { + "typing.Generator", + "typing_extensions.Generator", + }: + return_annotation = annotation.slice.elements[2] + if return_annotation: if isinstance(return_annotation, ExprSubscript) and return_annotation.is_tuple: - return_elements = return_annotation.slice.elements # type: ignore[attr-defined] + return_elements = return_annotation.slice.elements else: return_elements = [return_annotation] returns_section = _to_returns_section( {"annotation": element, **_metadata(element)} for element in return_elements ) - return yields_section, receives_section, returns_section + return returns_section -def _warns_docs(node: ast.AST, attr_or_func: Attribute | Function) -> DocstringSectionWarns | None: # noqa: ARG001 +def _warns_docs(attr_or_func: Attribute | Function, **kwargs: Any) -> DocstringSectionWarns | None: # noqa: ARG001 if attr_or_func.is_attribute: annotation = attr_or_func.annotation elif attr_or_func.is_function: - annotation = attr_or_func.returns # type: ignore[union-attr] + annotation = attr_or_func.returns metadata = _metadata(annotation) if metadata["warns"]: return _to_warns_section({"annotation": warned[0], "description": warned[1]} for warned in metadata["warns"]) return None -def _raises_docs(node: ast.AST, attr_or_func: Attribute | Function) -> DocstringSectionRaises | None: # noqa: ARG001 +def _raises_docs(attr_or_func: Attribute | Function, **kwargs: Any) -> DocstringSectionRaises | None: # noqa: ARG001 if attr_or_func.is_attribute: annotation = attr_or_func.annotation elif attr_or_func.is_function: - annotation = attr_or_func.returns # type: ignore[union-attr] + annotation = attr_or_func.returns metadata = _metadata(annotation) if metadata["raises"]: return _to_raises_section({"annotation": raised[0], "description": raised[1]} for raised in metadata["raises"]) @@ -214,13 +232,13 @@ def _raises_docs(node: ast.AST, attr_or_func: Attribute | Function) -> Docstring def _deprecated_docs( - node: ast.AST, # noqa: ARG001 attr_or_func: Attribute | Function, + **kwargs: Any, # noqa: ARG001 ) -> DocstringSectionAdmonition | None: if attr_or_func.is_attribute: annotation = attr_or_func.annotation elif attr_or_func.is_function: - annotation = attr_or_func.returns # type: ignore[union-attr] + annotation = attr_or_func.returns metadata = _metadata(annotation) if "deprecated" in metadata: return _to_deprecated_section({"description": metadata["deprecated"]}) diff --git a/tests/test_extension.py b/tests/test_extension.py index 664dda9..c4341c1 100644 --- a/tests/test_extension.py +++ b/tests/test_extension.py @@ -3,15 +3,133 @@ from griffe.docstrings.dataclasses import DocstringSectionKind from griffe.extensions import Extensions from griffe.loader import GriffeLoader +from griffe.tests import temporary_visited_package from griffe_typingdoc import TypingDocExtension +typing_imports = "from typing import Annotated, Doc, Generator, Iterator, Name, NotRequired, Raises, TypedDict, Unpack, Warns" +warning_imports = "from warnings import deprecated" -def test_extension() -> None: + +def test_extension_on_itself() -> None: """Load our own package using the extension, assert a parameters section is added to the parsed docstring.""" loader = GriffeLoader(extensions=Extensions(TypingDocExtension())) - typingdoc = loader.load_module("griffe_typingdoc") + typingdoc = loader.load("griffe_typingdoc") sections = typingdoc["TypingDocExtension.on_function_instance"].docstring.parsed assert len(sections) == 2 assert sections[1].kind is DocstringSectionKind.parameters assert sections[1].value[1].description == "The Griffe function just instantiated." + + +def test_attribute_doc() -> None: + """Read documentation for attributes.""" + with temporary_visited_package( + "package", + modules={"__init__.py": f"{typing_imports}\na: Annotated[str, Doc('Hello.')]"}, + extensions=Extensions(TypingDocExtension()), + ) as package: + assert package["a"].docstring.value == "Hello." + + +def test_parameter_doc() -> None: + """Read documentation for parameters.""" + with temporary_visited_package( + "package", + modules={"__init__.py": f"{typing_imports}\ndef f(a: Annotated[str, Doc('Hello.')]): ..."}, + extensions=Extensions(TypingDocExtension()), + ) as package: + assert package["f"].docstring.parsed[1].value[0].description == "Hello." + + + +def test_other_parameter_doc() -> None: + """Read documentation for other parameters, in unpack/typeddict annotations.""" + with temporary_visited_package( + "package", + modules={ + "__init__.py": f""" + {typing_imports} + class OtherParameters(TypedDict, total=False): + param1: Annotated[NotRequired[str], Doc("Hello.")] + + def f(**kwargs: Annotated[Unpack[OtherParameters], Doc("See other parameters.")]): + ... + """, + }, + extensions=Extensions(TypingDocExtension()), + ) as package: + assert package["f"].docstring.parsed[2].value[0].description == "Hello." + + +def test_iterator_doc() -> None: + """Read documentation in iterator annotations.""" + with temporary_visited_package( + "package", + modules={ + "__init__.py": f""" + {typing_imports} + def f() -> Iterator[Annotated[int, Doc("Yielded hello.")]]: + ... + """, + }, + extensions=Extensions(TypingDocExtension()), + ) as package: + assert package["f"].docstring.parsed[1].value[0].description == "Yielded hello." + + +def test_generator_doc() -> None: + """Read documentation in generator annotations.""" + with temporary_visited_package( + "package", + modules={ + "__init__.py": f""" + {typing_imports} + def f() -> Generator[ + Annotated[int, Doc("Yielded hello.")], + Annotated[int, Doc("Received hello.")], + Annotated[int, Doc("Returned hello.")], + ]: + ... + """, + }, + extensions=Extensions(TypingDocExtension()), + ) as package: + sections = package["f"].docstring.parsed + assert sections[1].value[0].description == "Yielded hello." + assert sections[2].value[0].description == "Received hello." + assert sections[3].value[0].description == "Returned hello." + + +def test_generator_tuples() -> None: + """Read documentation in generator annotations (in tuples).""" + with temporary_visited_package( + "package", + modules={ + "__init__.py": f""" + {typing_imports} + def f() -> Generator[ + tuple[ + Annotated[int, Doc("First yielded.")], + Annotated[float, Doc("Second yielded.")], + ], + tuple[ + Annotated[int, Doc("First received.")], + Annotated[float, Doc("Second received.")], + ], + tuple[ + Annotated[int, Doc("First returned.")], + Annotated[float, Doc("Second returned.")], + ], + ]: + ... + """, + }, + extensions=Extensions(TypingDocExtension()), + ) as package: + sections = package["f"].docstring.parsed + assert sections[1].value[0].description == "First yielded." + assert sections[1].value[1].description == "Second yielded." + assert sections[2].value[0].description == "First received." + assert sections[2].value[1].description == "Second received." + assert sections[3].value[0].description == "First returned." + assert sections[3].value[1].description == "Second returned." From 0c6132935ec6a3ffd2ce0cd671fb678087817d26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Mazzucotelli?= Date: Mon, 13 Nov 2023 17:02:40 +0100 Subject: [PATCH 2/2] fixup! refactor: Run static analysis only after the whole package was loaded --- src/griffe_typingdoc/_docstrings.py | 2 +- src/griffe_typingdoc/_extension.py | 6 +++--- src/griffe_typingdoc/_static.py | 24 ++++++++++++------------ 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/griffe_typingdoc/_docstrings.py b/src/griffe_typingdoc/_docstrings.py index 69e5082..ed6be39 100644 --- a/src/griffe_typingdoc/_docstrings.py +++ b/src/griffe_typingdoc/_docstrings.py @@ -39,7 +39,7 @@ def _to_parameters_section(params_dict: dict[str, dict[str, Any]], func: Functio name=param_name, description=param_doc["description"], annotation=param_doc["annotation"], - value=func.parameters[param_name].default, + value=func.parameters[param_name].default, # type: ignore[arg-type] ) for param_name, param_doc in params_dict.items() ], diff --git a/src/griffe_typingdoc/_extension.py b/src/griffe_typingdoc/_extension.py index ed73570..bfd5d1b 100644 --- a/src/griffe_typingdoc/_extension.py +++ b/src/griffe_typingdoc/_extension.py @@ -107,11 +107,11 @@ def _handle_object(self, obj: Object) -> None: return if obj.is_module or obj.is_class: for member in obj.members.values(): - self._handle_object(member) + self._handle_object(member) # type: ignore[arg-type] elif obj.is_function: - self._handle_function(obj) + self._handle_function(obj) # type: ignore[arg-type] elif obj.is_attribute: - self._handle_attribute(obj) + self._handle_attribute(obj) # type: ignore[arg-type] def on_package_loaded( self, diff --git a/src/griffe_typingdoc/_static.py b/src/griffe_typingdoc/_static.py index e8158d9..a5bdecd 100644 --- a/src/griffe_typingdoc/_static.py +++ b/src/griffe_typingdoc/_static.py @@ -104,7 +104,7 @@ def _attribute_docs(attr: Attribute, **kwargs: Any) -> str: # noqa: ARG001 def _parameters_docs(func: Function, **kwargs: Any) -> DocstringSectionParameters | None: # noqa: ARG001 params_doc: dict[str, dict[str, Any]] = defaultdict(dict) for parameter in _no_self_params(func): - stars = {ParameterKind.var_positional: "*", ParameterKind.var_keyword: "**"}.get(parameter.kind, "") + stars = {ParameterKind.var_positional: "*", ParameterKind.var_keyword: "**"}.get(parameter.kind, "") # type: ignore[arg-type] param_name = f"{stars}{parameter.name}" metadata = _metadata(parameter.annotation) description = f'{metadata.get("deprecated", "")} {metadata.get("doc", "")}'.lstrip() @@ -123,12 +123,12 @@ def _other_parameters_docs(func: Function, **kwargs: Any) -> DocstringSectionPar "typing.Annotated", "typing_extensions.Annotated", }: - annotation = annotation.slice.elements[0] + annotation = annotation.slice.elements[0] # type: ignore[attr-defined] if isinstance(annotation, ExprSubscript) and annotation.canonical_path in { "typing.Unpack", "typing_extensions.Unpack", }: - typed_dict = annotation.slice.parent.get_member(annotation.slice.name) + typed_dict = annotation.slice.parent.get_member(annotation.slice.name) # type: ignore[attr-defined] params_doc = { attr.name: {"annotation": attr.annotation, "description": _metadata(attr.annotation).get("doc", "")} for attr in typed_dict.members.values() @@ -147,13 +147,13 @@ def _yields_docs(func: Function, **kwargs: Any) -> DocstringSectionYields | None if isinstance(annotation, ExprSubscript): if annotation.canonical_path in {"typing.Generator", "typing_extensions.Generator"}: - yield_annotation = annotation.slice.elements[0] + yield_annotation = annotation.slice.elements[0] # type: ignore[attr-defined] elif annotation.canonical_path in {"typing.Iterator", "typing_extensions.Iterator"}: yield_annotation = annotation.slice if yield_annotation: if isinstance(yield_annotation, ExprSubscript) and yield_annotation.is_tuple: - yield_elements = yield_annotation.slice.elements + yield_elements = yield_annotation.slice.elements # type: ignore[attr-defined] else: yield_elements = [yield_annotation] yields_section = _to_yields_section({"annotation": element, **_metadata(element)} for element in yield_elements) @@ -171,11 +171,11 @@ def _receives_docs(func: Function, **kwargs: Any) -> DocstringSectionReceives | "typing.Generator", "typing_extensions.Generator", }: - receive_annotation = annotation.slice.elements[1] + receive_annotation = annotation.slice.elements[1] # type: ignore[attr-defined] if receive_annotation: if isinstance(receive_annotation, ExprSubscript) and receive_annotation.is_tuple: - receive_elements = receive_annotation.slice.elements + receive_elements = receive_annotation.slice.elements # type: ignore[attr-defined] else: receive_elements = [receive_annotation] receives_section = _to_receives_section( @@ -195,11 +195,11 @@ def _returns_docs(func: Function, **kwargs: Any) -> DocstringSectionReturns | No "typing.Generator", "typing_extensions.Generator", }: - return_annotation = annotation.slice.elements[2] + return_annotation = annotation.slice.elements[2] # type: ignore[attr-defined] if return_annotation: if isinstance(return_annotation, ExprSubscript) and return_annotation.is_tuple: - return_elements = return_annotation.slice.elements + return_elements = return_annotation.slice.elements # type: ignore[attr-defined] else: return_elements = [return_annotation] returns_section = _to_returns_section( @@ -213,7 +213,7 @@ def _warns_docs(attr_or_func: Attribute | Function, **kwargs: Any) -> DocstringS if attr_or_func.is_attribute: annotation = attr_or_func.annotation elif attr_or_func.is_function: - annotation = attr_or_func.returns + annotation = attr_or_func.returns # type: ignore[union-attr] metadata = _metadata(annotation) if metadata["warns"]: return _to_warns_section({"annotation": warned[0], "description": warned[1]} for warned in metadata["warns"]) @@ -224,7 +224,7 @@ def _raises_docs(attr_or_func: Attribute | Function, **kwargs: Any) -> Docstring if attr_or_func.is_attribute: annotation = attr_or_func.annotation elif attr_or_func.is_function: - annotation = attr_or_func.returns + annotation = attr_or_func.returns # type: ignore[union-attr] metadata = _metadata(annotation) if metadata["raises"]: return _to_raises_section({"annotation": raised[0], "description": raised[1]} for raised in metadata["raises"]) @@ -238,7 +238,7 @@ def _deprecated_docs( if attr_or_func.is_attribute: annotation = attr_or_func.annotation elif attr_or_func.is_function: - annotation = attr_or_func.returns + annotation = attr_or_func.returns # type: ignore[union-attr] metadata = _metadata(annotation) if "deprecated" in metadata: return _to_deprecated_section({"description": metadata["deprecated"]})