[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: 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 .._parser import DocstringParser
from .._serialize import Serializable
from . import Constructor, Action
from .component import Component
@ -22,7 +23,7 @@ from .constants import doc_base_url
@dataclass
class Integration(Component, Serializable):
class Integration(Component, DocstringParser, Serializable):
"""
Represents the metadata of an integration (plugin or backend).
"""
@ -139,7 +140,7 @@ class Integration(Component, Serializable):
obj = cls(
name=name,
type=type,
doc=inspect.getdoc(type),
doc=cls._expand_rst_extensions(inspect.getdoc(type) or '', type) or None,
constructor=Constructor.parse(type),
actions={
name: Action.parse(getattr(type, name))

View file

@ -105,7 +105,7 @@ class DocstringParser(Serializable, RstExtensionsMixin):
return
# 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
m = cls._return_doc_re.match(line)

View file

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