[Reflection] Expand RST extensions from object docstrings too.

This commit is contained in:
Fabio Manganiello 2023-10-16 00:21:49 +02:00
parent 0fe1c2768b
commit d1afb88b80
Signed by untrusted user: blacklight
GPG key ID: D90FBA7F76362774
4 changed files with 42 additions and 24 deletions

View file

@ -15,6 +15,7 @@ from platypush.utils import (
) )
from platypush.utils.manifest import Manifest, ManifestType, Dependencies from platypush.utils.manifest import Manifest, ManifestType, Dependencies
from .._parser import DocstringParser
from .._serialize import Serializable from .._serialize import Serializable
from . import Constructor, Action from . import Constructor, Action
from .component import Component from .component import Component
@ -22,7 +23,7 @@ from .constants import doc_base_url
@dataclass @dataclass
class Integration(Component, Serializable): class Integration(Component, DocstringParser, Serializable):
""" """
Represents the metadata of an integration (plugin or backend). Represents the metadata of an integration (plugin or backend).
""" """
@ -139,7 +140,7 @@ class Integration(Component, Serializable):
obj = cls( obj = cls(
name=name, name=name,
type=type, type=type,
doc=inspect.getdoc(type), doc=cls._expand_rst_extensions(inspect.getdoc(type) or '', type) or None,
constructor=Constructor.parse(type), constructor=Constructor.parse(type),
actions={ actions={
name: Action.parse(getattr(type, name)) name: Action.parse(getattr(type, name))

View file

@ -105,7 +105,7 @@ class DocstringParser(Serializable, RstExtensionsMixin):
return return
# Expand any custom RST extensions # Expand any custom RST extensions
line = cls._expand_rst_extensions(line, ctx) line = cls._expand_rst_extensions(line, ctx.obj)
# Update the return type docstring if required # Update the return type docstring if required
m = cls._return_doc_re.match(line) m = cls._return_doc_re.match(line)

View file

@ -2,11 +2,11 @@ import importlib
import logging import logging
import re import re
import textwrap as tw import textwrap as tw
from typing import Callable, Union
from platypush.utils import get_defining_class from platypush.utils import get_defining_class
from .._model.constants import doc_base_url from .._model.constants import doc_base_url
from .context import ParseContext
# pylint: disable=too-few-public-methods # pylint: disable=too-few-public-methods
@ -20,6 +20,7 @@ class RstExtensionsMixin:
for name, regex in { for name, regex in {
"class": "(:class:`(?P<name>[^`]+)`)", "class": "(:class:`(?P<name>[^`]+)`)",
"method": "(:meth:`(?P<name>[^`]+)`)", "method": "(:meth:`(?P<name>[^`]+)`)",
"module": "(:mod:`(?P<name>[^`]+)`)",
"function": "(:func:`(?P<name>[^`]+)`)", "function": "(:func:`(?P<name>[^`]+)`)",
"schema": r"^((?P<indent>\s*)(?P<before>.*)" "schema": r"^((?P<indent>\s*)(?P<before>.*)"
r"(\.\. schema:: (?P<name>[\w.]+)\s*" r"(\.\. schema:: (?P<name>[\w.]+)\s*"
@ -30,29 +31,34 @@ class RstExtensionsMixin:
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@classmethod @classmethod
def _expand_rst_extensions(cls, docstr: str, ctx: ParseContext) -> str: def _expand_rst_extensions(cls, docstr: str, obj: Union[Callable, type]) -> str:
""" """
Expand the known reStructuredText extensions in a docstring. Expand the known reStructuredText extensions in a docstring.
""" """
for ex_name, regex in cls._rst_extensions.items(): for ex_name, regex in cls._rst_extensions.items():
while True:
match = regex.search(docstr) match = regex.search(docstr)
if not match: if not match:
continue break
try: try:
docstr = ( docstr = cls._expand_rst_extension(docstr, ex_name, match, obj)
cls._expand_schema(docstr, match)
if ex_name == "schema"
else cls._expand_module(docstr, ex_name, match, ctx)
)
except Exception as e: except Exception as e:
cls.logger.warning( cls.logger.warning(
"Could not import module %s: %s", match.group("name"), e "Could not import module %s: %s", match.group("name"), e
) )
continue
return docstr return docstr
@classmethod
def _expand_rst_extension(
cls, docstr: str, ex_name: str, match: re.Match[str], obj: Union[Callable, type]
) -> str:
if ex_name == "schema":
return cls._expand_schema(docstr, match)
return cls._expand_module(docstr, ex_name, match, obj)
@classmethod @classmethod
def _expand_schema(cls, docstr: str, match: re.Match) -> str: def _expand_schema(cls, docstr: str, match: re.Match) -> str:
from marshmallow import missing from marshmallow import missing
@ -129,13 +135,19 @@ class RstExtensionsMixin:
@classmethod @classmethod
def _expand_module( def _expand_module(
cls, docstr: str, ex_name: str, match: re.Match, ctx: ParseContext cls,
docstr: str,
ex_name: str,
match: re.Match,
obj: Union[Callable, type],
) -> str: ) -> str:
value = match.group("name") value = match.group("name")
modname = obj_name = url_path = None modname = obj_name = url_path = None
if value.startswith("."): if ex_name == "module":
base_cls = get_defining_class(ctx.obj) modname = obj_name = value
elif value.startswith("."):
base_cls = get_defining_class(obj)
if base_cls: if base_cls:
modname = base_cls.__module__ modname = base_cls.__module__
obj_name = f'{base_cls.__qualname__}.{value[1:]}' obj_name = f'{base_cls.__qualname__}.{value[1:]}'
@ -150,7 +162,7 @@ class RstExtensionsMixin:
if modname.startswith("platypush.plugins"): if modname.startswith("platypush.plugins"):
url_path = "plugins/" + ".".join(modname.split(".")[2:]) url_path = "plugins/" + ".".join(modname.split(".")[2:])
elif modname.startswith("platypush.backend"): elif modname.startswith("platypush.backend"):
url_path = "backends/" + ".".join(modname.split(".")[2:]) url_path = "backend/" + ".".join(modname.split(".")[2:])
elif modname.startswith("platypush.message.event"): elif modname.startswith("platypush.message.event"):
url_path = "events/" + ".".join(modname.split(".")[3:]) url_path = "events/" + ".".join(modname.split(".")[3:])
elif modname.startswith("platypush.message.response"): elif modname.startswith("platypush.message.response"):
@ -159,7 +171,9 @@ class RstExtensionsMixin:
if url_path: if url_path:
docstr = docstr.replace( docstr = docstr.replace(
match.group(0), match.group(0),
f"`{obj_name} <{doc_base_url}/{url_path}.html#{modname}.{obj_name}>`_", f"`{obj_name} <{doc_base_url}/{url_path}.html"
+ (f"#{modname}.{obj_name}" if ex_name != "module" else "")
+ ">`_",
) )
else: else:
docstr = docstr.replace(match.group(0), f"``{value}``") docstr = docstr.replace(match.group(0), f"``{value}``")

View file

@ -713,6 +713,9 @@ def get_defining_class(meth) -> Optional[type]:
This is the best way I could find of answering the question "given a bound This is the best way I could find of answering the question "given a bound
method, get the class that defined it", method, get the class that defined it",
""" """
if isinstance(meth, type):
return meth
if isinstance(meth, functools.partial): if isinstance(meth, functools.partial):
return get_defining_class(meth.func) return get_defining_class(meth.func)