diff --git a/quartodoc/_griffe_compat.py b/quartodoc/_griffe_compat/__init__.py similarity index 81% rename from quartodoc/_griffe_compat.py rename to quartodoc/_griffe_compat/__init__.py index af59b70..b281e11 100644 --- a/quartodoc/_griffe_compat.py +++ b/quartodoc/_griffe_compat/__init__.py @@ -4,9 +4,9 @@ from griffe import GriffeLoader from griffe import ModulesCollection, LinesCollection - import _griffe.models as dataclasses - import _griffe.docstrings.models as docstrings - import _griffe.expressions as expressions + from . import dataclasses + from . import docstrings + from . import expressions from griffe import Parser, parse, parse_numpy from griffe import AliasResolutionError diff --git a/quartodoc/_griffe_compat/_generate_stubs.py b/quartodoc/_griffe_compat/_generate_stubs.py new file mode 100644 index 0000000..663ff6e --- /dev/null +++ b/quartodoc/_griffe_compat/_generate_stubs.py @@ -0,0 +1,71 @@ +"""Generate the submodules which import parts of griffe (e.g. dataclasses.py) + +This process balances having to import specific objects from griffe, against +importing everything via the top-level griffe module. It does this by generating +modules for objects commonly used together in quartodoc. + +* dataclasses: represent python objects +* docstrings: represent python docstrings +* expressions: represent annotation expressions + +Run using: python -m quartodoc._griffe_compat._generate_stubs +""" + +import ast +import black +import copy +import griffe +import inspect +from pathlib import Path + + +def fetch_submodule(ast_body, submodule: str): + ast_imports = [ + obj + for obj in ast_body + if isinstance(obj, ast.ImportFrom) and obj.module == submodule + ] + + if len(ast_imports) > 1: + raise Exception(f"Found {len(ast_imports)} imports for {submodule}") + elif not ast_imports: + raise Exception(f"Could not find import for {submodule}") + + return ast_imports[0] + + +def code_for_imports(mod_code: str, submodules: list[str]) -> str: + res = [] + mod = ast.parse(mod_code) + for submod in submodules: + expr = fetch_submodule(mod.body, submod) + expr.module = "griffe" + new_expr = copy.copy(expr) + new_expr.module = "griffe" + res.append(ast.unparse(new_expr)) + + return black.format_str( + "# flake8: noqa\n\n" + "\n".join(res), mode=black.FileMode() + ) + + +def generate_griffe_stub(out_path: Path, mod, submodules: list[str]): + res = code_for_imports(inspect.getsource(mod), submodules) + out_path.write_text(res) + + +MAPPINGS = { + "dataclasses": [ + "_griffe.models", + "_griffe.mixins", + "_griffe.enumerations", + ], + "docstrings": ["_griffe.docstrings.models"], + "expressions": ["_griffe.expressions"], +} + +if __name__ == "__main__": + for out_name, submodules in MAPPINGS.items(): + generate_griffe_stub( + Path(__file__).parent / f"{out_name}.py", griffe, submodules + ) diff --git a/quartodoc/_griffe_compat/dataclasses.py b/quartodoc/_griffe_compat/dataclasses.py new file mode 100644 index 0000000..06db2cf --- /dev/null +++ b/quartodoc/_griffe_compat/dataclasses.py @@ -0,0 +1,31 @@ +# flake8: noqa + +from griffe import ( + Alias, + Attribute, + Class, + Decorator, + Docstring, + Function, + Module, + Object, + Parameter, + Parameters, +) +from griffe import ( + DelMembersMixin, + GetMembersMixin, + ObjectAliasMixin, + SerializationMixin, + SetMembersMixin, +) +from griffe import ( + BreakageKind, + DocstringSectionKind, + ExplanationStyle, + Kind, + LogLevel, + ObjectKind, + ParameterKind, + Parser, +) diff --git a/quartodoc/_griffe_compat/docstrings.py b/quartodoc/_griffe_compat/docstrings.py new file mode 100644 index 0000000..f0fc772 --- /dev/null +++ b/quartodoc/_griffe_compat/docstrings.py @@ -0,0 +1,34 @@ +# flake8: noqa + +from griffe import ( + DocstringAdmonition, + DocstringAttribute, + DocstringClass, + DocstringDeprecated, + DocstringElement, + DocstringFunction, + DocstringModule, + DocstringNamedElement, + DocstringParameter, + DocstringRaise, + DocstringReceive, + DocstringReturn, + DocstringSection, + DocstringSectionAdmonition, + DocstringSectionAttributes, + DocstringSectionClasses, + DocstringSectionDeprecated, + DocstringSectionExamples, + DocstringSectionFunctions, + DocstringSectionModules, + DocstringSectionOtherParameters, + DocstringSectionParameters, + DocstringSectionRaises, + DocstringSectionReceives, + DocstringSectionReturns, + DocstringSectionText, + DocstringSectionWarns, + DocstringSectionYields, + DocstringWarn, + DocstringYield, +) diff --git a/quartodoc/_griffe_compat/expressions.py b/quartodoc/_griffe_compat/expressions.py new file mode 100644 index 0000000..33be537 --- /dev/null +++ b/quartodoc/_griffe_compat/expressions.py @@ -0,0 +1,44 @@ +# flake8: noqa + +from griffe import ( + Expr, + ExprAttribute, + ExprBinOp, + ExprBoolOp, + ExprCall, + ExprCompare, + ExprComprehension, + ExprConstant, + ExprDict, + ExprDictComp, + ExprExtSlice, + ExprFormatted, + ExprGeneratorExp, + ExprIfExp, + ExprJoinedStr, + ExprKeyword, + ExprLambda, + ExprList, + ExprListComp, + ExprName, + ExprNamedExpr, + ExprParameter, + ExprSet, + ExprSetComp, + ExprSlice, + ExprSubscript, + ExprTuple, + ExprUnaryOp, + ExprVarKeyword, + ExprVarPositional, + ExprYield, + ExprYieldFrom, + get_annotation, + get_base_class, + get_condition, + get_expression, + safe_get_annotation, + safe_get_base_class, + safe_get_condition, + safe_get_expression, +) diff --git a/quartodoc/ast.py b/quartodoc/ast.py index c923181..4ddfcf8 100644 --- a/quartodoc/ast.py +++ b/quartodoc/ast.py @@ -4,6 +4,7 @@ from ._griffe_compat import docstrings as ds from ._griffe_compat import dataclasses as dc +from ._griffe_compat import AliasResolutionError from enum import Enum from dataclasses import dataclass @@ -210,7 +211,7 @@ def fields(el: dc.Object): def fields(el: dc.ObjectAliasMixin): try: return fields(el.target) - except dc.AliasResolutionError: + except AliasResolutionError: warnings.warn( f"Could not resolve Alias target `{el.target_path}`." " This often occurs because the module was not loaded (e.g. it is a"