forked from platypush/platypush
[Reflection] Expand RST extensions from object docstrings too.
This commit is contained in:
parent
0fe1c2768b
commit
d1afb88b80
4 changed files with 42 additions and 24 deletions
|
@ -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))
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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}``")
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue