Better auto-generated documentation and fixed docstring warnings

This commit is contained in:
Fabio Manganiello 2022-03-03 15:15:55 +01:00
parent 7c9e9d284d
commit fdf6d8fb4e
Signed by: blacklight
GPG key ID: D90FBA7F76362774
12 changed files with 85 additions and 58 deletions

View file

@ -1,5 +1,5 @@
``platypush.message.event.chat.slack`` ``chat.slack``
====================================== ==============
.. automodule:: platypush.message.event.chat.slack .. automodule:: platypush.message.event.chat.slack
:members: :members:

View file

@ -1,5 +1,5 @@
``platypush.message.event.dbus`` ``dbus``
================================ ========
.. automodule:: platypush.message.event.dbus .. automodule:: platypush.message.event.dbus
:members: :members:

View file

@ -1,5 +1,5 @@
``platypush.message.event.gotify`` ``gotify``
================================== ==========
.. automodule:: platypush.message.event.gotify .. automodule:: platypush.message.event.gotify
:members: :members:

View file

@ -1,5 +1,5 @@
``platypush.message.event.irc`` ``irc``
=============================== =======
.. automodule:: platypush.message.event.irc .. automodule:: platypush.message.event.irc
:members: :members:

View file

@ -1,5 +1,5 @@
``platypush.message.event.ngrok`` ``ngrok``
================================= =========
.. automodule:: platypush.message.event.ngrok .. automodule:: platypush.message.event.ngrok
:members: :members:

View file

@ -1,5 +1,5 @@
``platypush.message.event.rss`` ``rss``
=============================== =======
.. automodule:: platypush.message.event.rss .. automodule:: platypush.message.event.rss
:members: :members:

View file

@ -1,5 +1,5 @@
``platypush.message.event.sun`` ``sun``
=============================== =======
.. automodule:: platypush.message.event.sun .. automodule:: platypush.message.event.sun
:members: :members:

View file

@ -1,5 +1,5 @@
``media.jellyfin`` ``media.jellyfin``
================================ ==================
.. automodule:: platypush.plugins.media.jellyfin .. automodule:: platypush.plugins.media.jellyfin
:members: :members:

View file

@ -1,3 +1,4 @@
import inspect
import os import os
from platypush.backend import Backend from platypush.backend import Backend
@ -6,11 +7,17 @@ from platypush.plugins import Plugin
from platypush.utils.manifest import get_manifests from platypush.utils.manifest import get_manifests
def _get_inspect_plugin():
p = get_plugin('inspect')
assert p, 'Could not load the `inspect` plugin'
return p
def get_all_plugins(): def get_all_plugins():
manifests = {mf.component_name for mf in get_manifests(Plugin)} manifests = {mf.component_name for mf in get_manifests(Plugin)}
return { return {
plugin_name: plugin_info plugin_name: plugin_info
for plugin_name, plugin_info in get_plugin('inspect').get_all_plugins().output.items() for plugin_name, plugin_info in _get_inspect_plugin().get_all_plugins().output.items()
if plugin_name in manifests if plugin_name in manifests
} }
@ -19,17 +26,17 @@ def get_all_backends():
manifests = {mf.component_name for mf in get_manifests(Backend)} manifests = {mf.component_name for mf in get_manifests(Backend)}
return { return {
backend_name: backend_info backend_name: backend_info
for backend_name, backend_info in get_plugin('inspect').get_all_backends().output.items() for backend_name, backend_info in _get_inspect_plugin().get_all_backends().output.items()
if backend_name in manifests if backend_name in manifests
} }
def get_all_events(): def get_all_events():
return get_plugin('inspect').get_all_events().output return _get_inspect_plugin().get_all_events().output
def get_all_responses(): def get_all_responses():
return get_plugin('inspect').get_all_responses().output return _get_inspect_plugin().get_all_responses().output
# noinspection DuplicatedCode # noinspection DuplicatedCode
@ -100,16 +107,17 @@ Backends
# noinspection DuplicatedCode # noinspection DuplicatedCode
def generate_events_doc(): def generate_events_doc():
from platypush.message import event as event_module
events_index = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'docs', 'source', 'events.rst') events_index = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'docs', 'source', 'events.rst')
events_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'docs', 'source', 'platypush', 'events') events_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'docs', 'source', 'platypush', 'events')
all_events = sorted(event for event in get_all_events().keys()) all_events = sorted(event for event in get_all_events().keys() if event)
for event in all_events: for event in all_events:
event_file = os.path.join(events_dir, event[len('platypush.message.event.'):] + '.rst') event_file = os.path.join(events_dir, event + '.rst')
if not os.path.exists(event_file): if not os.path.exists(event_file):
header = '``{}``'.format(event) header = '``{}``'.format(event)
divider = '=' * len(header) divider = '=' * len(header)
body = '\n.. automodule:: {}\n :members:\n'.format(event) body = '\n.. automodule:: {}.{}\n :members:\n'.format(event_module.__name__, event)
out = '\n'.join([header, divider, body]) out = '\n'.join([header, divider, body])
with open(event_file, 'w') as f: with open(event_file, 'w') as f:
@ -127,21 +135,22 @@ Events
''') ''')
for event in all_events: for event in all_events:
f.write(' platypush/events/' + event[len('platypush.message.event.'):] + '.rst\n') f.write(' platypush/events/' + event + '.rst\n')
# noinspection DuplicatedCode # noinspection DuplicatedCode
def generate_responses_doc(): def generate_responses_doc():
from platypush.message import response as response_module
responses_index = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'docs', 'source', 'responses.rst') responses_index = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'docs', 'source', 'responses.rst')
responses_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'docs', 'source', 'platypush', 'responses') responses_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'docs', 'source', 'platypush', 'responses')
all_responses = sorted(response for response in get_all_responses().keys()) all_responses = sorted(response for response in get_all_responses().keys() if response)
for response in all_responses: for response in all_responses:
response_file = os.path.join(responses_dir, response[len('platypush.message.response.'):] + '.rst') response_file = os.path.join(responses_dir, response + '.rst')
if not os.path.exists(response_file): if not os.path.exists(response_file):
header = '``{}``'.format(response) header = '``{}``'.format(response)
divider = '=' * len(header) divider = '=' * len(header)
body = '\n.. automodule:: {}\n :members:\n'.format(response) body = '\n.. automodule:: {}.{}\n :members:\n'.format(response_module.__name__, response)
out = '\n'.join([header, divider, body]) out = '\n'.join([header, divider, body])
with open(response_file, 'w') as f: with open(response_file, 'w') as f:
@ -159,7 +168,7 @@ Responses
''') ''')
for response in all_responses: for response in all_responses:
f.write(' platypush/responses/' + response[len('platypush.message.response.'):] + '.rst\n') f.write(' platypush/responses/' + response + '.rst\n')
generate_plugins_doc() generate_plugins_doc()

View file

@ -71,7 +71,7 @@ class Message(object):
logger.warning('Could not serialize object type {}: {}: {}'.format( logger.warning('Could not serialize object type {}: {}: {}'.format(
type(obj), str(e), obj)) type(obj), str(e), obj))
def __init__(self, timestamp=None, *args, **kwargs): def __init__(self, timestamp=None, *_, **__):
self.timestamp = timestamp or time.time() self.timestamp = timestamp or time.time()
def __str__(self): def __str__(self):
@ -98,8 +98,8 @@ class Message(object):
def parse(cls, msg): def parse(cls, msg):
""" """
Parse a generic message into a key-value dictionary Parse a generic message into a key-value dictionary
Params:
msg -- Original message - can be a dictionary, a Message, :param msg: Original message. It can be a dictionary, a Message,
or a string/bytearray, as long as it's valid UTF-8 JSON or a string/bytearray, as long as it's valid UTF-8 JSON
""" """
@ -124,8 +124,8 @@ class Message(object):
def build(cls, msg): def build(cls, msg):
""" """
Builds a Message object from a dictionary. Builds a Message object from a dictionary.
Params:
msg -- The message as a key-value dictionary, Message object or JSON string :param msg: The message as a key-value dictionary, Message object or JSON string
""" """
from platypush.utils import get_message_class_by_type from platypush.utils import get_message_class_by_type

View file

@ -4,6 +4,7 @@ import json
import pkgutil import pkgutil
import re import re
import threading import threading
from abc import ABC, abstractmethod
from typing import Optional from typing import Optional
import platypush.backend # lgtm [py/import-and-import-from] import platypush.backend # lgtm [py/import-and-import-from]
@ -19,8 +20,7 @@ from platypush.message.response import Response
from platypush.utils import get_decorators from platypush.utils import get_decorators
# noinspection PyTypeChecker class Model(ABC):
class Model:
def __str__(self): def __str__(self):
return json.dumps(dict(self), indent=2, sort_keys=True) return json.dumps(dict(self), indent=2, sort_keys=True)
@ -37,6 +37,10 @@ class Model:
return docutils.core.publish_parts(doc, writer_name='html')['html_body'] return docutils.core.publish_parts(doc, writer_name='html')['html_body']
@abstractmethod
def __iter__(self):
raise NotImplementedError()
class ProcedureEncoder(json.JSONEncoder): class ProcedureEncoder(json.JSONEncoder):
def default(self, o): def default(self, o):
@ -49,7 +53,7 @@ class ProcedureEncoder(json.JSONEncoder):
class BackendModel(Model): class BackendModel(Model):
def __init__(self, backend, prefix='', html_doc: bool = False): def __init__(self, backend, prefix='', html_doc: Optional[bool] = False):
self.name = backend.__module__[len(prefix):] self.name = backend.__module__[len(prefix):]
self.html_doc = html_doc self.html_doc = html_doc
self.doc = self.to_html(backend.__doc__) if html_doc and backend.__doc__ else backend.__doc__ self.doc = self.to_html(backend.__doc__) if html_doc and backend.__doc__ else backend.__doc__
@ -60,11 +64,11 @@ class BackendModel(Model):
class PluginModel(Model): class PluginModel(Model):
def __init__(self, plugin, prefix='', html_doc: bool = False): def __init__(self, plugin, prefix='', html_doc: Optional[bool] = False):
self.name = plugin.__module__[len(prefix):] self.name = plugin.__module__[len(prefix):]
self.html_doc = html_doc self.html_doc = html_doc
self.doc = self.to_html(plugin.__doc__) if html_doc and plugin.__doc__ else plugin.__doc__ self.doc = self.to_html(plugin.__doc__) if html_doc and plugin.__doc__ else plugin.__doc__
self.actions = {action_name: ActionModel(getattr(plugin, action_name), html_doc=html_doc) self.actions = {action_name: ActionModel(getattr(plugin, action_name), html_doc=html_doc or False)
for action_name in get_decorators(plugin, climb_class_hierarchy=True).get('action', [])} for action_name in get_decorators(plugin, climb_class_hierarchy=True).get('action', [])}
def __iter__(self): def __iter__(self):
@ -77,8 +81,8 @@ class PluginModel(Model):
class EventModel(Model): class EventModel(Model):
def __init__(self, event, html_doc: bool = False): def __init__(self, event, prefix='', html_doc: Optional[bool] = False):
self.package = event.__module__ self.package = event.__module__[len(prefix):]
self.name = event.__name__ self.name = event.__name__
self.html_doc = html_doc self.html_doc = html_doc
self.doc = self.to_html(event.__doc__) if html_doc and event.__doc__ else event.__doc__ self.doc = self.to_html(event.__doc__) if html_doc and event.__doc__ else event.__doc__
@ -89,8 +93,8 @@ class EventModel(Model):
class ResponseModel(Model): class ResponseModel(Model):
def __init__(self, response, html_doc: bool = False): def __init__(self, response, prefix='', html_doc: Optional[bool] = False):
self.package = response.__module__ self.package = response.__module__[len(prefix):]
self.name = response.__name__ self.name = response.__name__
self.html_doc = html_doc self.html_doc = html_doc
self.doc = self.to_html(response.__doc__) if html_doc and response.__doc__ else response.__doc__ self.doc = self.to_html(response.__doc__) if html_doc and response.__doc__ else response.__doc__
@ -187,7 +191,7 @@ class InspectPlugin(Plugin):
for _, modname, _ in pkgutil.walk_packages(path=package.__path__, for _, modname, _ in pkgutil.walk_packages(path=package.__path__,
prefix=prefix, prefix=prefix,
onerror=lambda x: None): onerror=lambda _: None):
try: try:
module = importlib.import_module(modname) module = importlib.import_module(modname)
except Exception as e: except Exception as e:
@ -207,7 +211,7 @@ class InspectPlugin(Plugin):
for _, modname, _ in pkgutil.walk_packages(path=package.__path__, for _, modname, _ in pkgutil.walk_packages(path=package.__path__,
prefix=prefix, prefix=prefix,
onerror=lambda x: None): onerror=lambda _: None):
try: try:
module = importlib.import_module(modname) module = importlib.import_module(modname)
except Exception as e: except Exception as e:
@ -226,7 +230,7 @@ class InspectPlugin(Plugin):
for _, modname, _ in pkgutil.walk_packages(path=package.__path__, for _, modname, _ in pkgutil.walk_packages(path=package.__path__,
prefix=prefix, prefix=prefix,
onerror=lambda x: None): onerror=lambda _: None):
try: try:
module = importlib.import_module(modname) module = importlib.import_module(modname)
except Exception as e: except Exception as e:
@ -238,7 +242,7 @@ class InspectPlugin(Plugin):
continue continue
if inspect.isclass(obj) and issubclass(obj, Event) and obj != Event: if inspect.isclass(obj) and issubclass(obj, Event) and obj != Event:
event = EventModel(event=obj, html_doc=self._html_doc) event = EventModel(event=obj, html_doc=self._html_doc, prefix=prefix)
if event.package not in self._events: if event.package not in self._events:
self._events[event.package] = {event.name: event} self._events[event.package] = {event.name: event}
else: else:
@ -250,7 +254,7 @@ class InspectPlugin(Plugin):
for _, modname, _ in pkgutil.walk_packages(path=package.__path__, for _, modname, _ in pkgutil.walk_packages(path=package.__path__,
prefix=prefix, prefix=prefix,
onerror=lambda x: None): onerror=lambda _: None):
try: try:
module = importlib.import_module(modname) module = importlib.import_module(modname)
except Exception as e: except Exception as e:
@ -262,14 +266,14 @@ class InspectPlugin(Plugin):
continue continue
if inspect.isclass(obj) and issubclass(obj, Response) and obj != Response: if inspect.isclass(obj) and issubclass(obj, Response) and obj != Response:
response = ResponseModel(response=obj, html_doc=self._html_doc) response = ResponseModel(response=obj, html_doc=self._html_doc, prefix=prefix)
if response.package not in self._responses: if response.package not in self._responses:
self._responses[response.package] = {response.name: response} self._responses[response.package] = {response.name: response}
else: else:
self._responses[response.package][response.name] = response self._responses[response.package][response.name] = response
@action @action
def get_all_plugins(self, html_doc: bool = None): def get_all_plugins(self, html_doc: Optional[bool] = None):
""" """
:param html_doc: If True then the docstring will be parsed into HTML (default: False) :param html_doc: If True then the docstring will be parsed into HTML (default: False)
""" """
@ -284,7 +288,7 @@ class InspectPlugin(Plugin):
}) })
@action @action
def get_all_backends(self, html_doc: bool = None): def get_all_backends(self, html_doc: Optional[bool] = None):
""" """
:param html_doc: If True then the docstring will be parsed into HTML (default: False) :param html_doc: If True then the docstring will be parsed into HTML (default: False)
""" """
@ -299,7 +303,7 @@ class InspectPlugin(Plugin):
}) })
@action @action
def get_all_events(self, html_doc: bool = None): def get_all_events(self, html_doc: Optional[bool] = None):
""" """
:param html_doc: If True then the docstring will be parsed into HTML (default: False) :param html_doc: If True then the docstring will be parsed into HTML (default: False)
""" """
@ -311,13 +315,13 @@ class InspectPlugin(Plugin):
return json.dumps({ return json.dumps({
package: { package: {
name: dict(event) name: dict(event)
for name, event in self._events[package].items() for name, event in events.items()
} }
for package, events in self._events.items() for package, events in self._events.items()
}) })
@action @action
def get_all_responses(self, html_doc: bool = None): def get_all_responses(self, html_doc: Optional[bool] = None):
""" """
:param html_doc: If True then the docstring will be parsed into HTML (default: False) :param html_doc: If True then the docstring will be parsed into HTML (default: False)
""" """
@ -329,9 +333,9 @@ class InspectPlugin(Plugin):
return json.dumps({ return json.dumps({
package: { package: {
name: dict(event) name: dict(event)
for name, event in self._responses[package].items() for name, event in responses.items()
} }
for package, events in self._responses.items() for package, responses in self._responses.items()
}) })
@action @action
@ -342,7 +346,7 @@ class InspectPlugin(Plugin):
return json.loads(json.dumps(Config.get_procedures(), cls=ProcedureEncoder)) return json.loads(json.dumps(Config.get_procedures(), cls=ProcedureEncoder))
@action @action
def get_config(self, entry: Optional[str] = None) -> dict: def get_config(self, entry: Optional[str] = None) -> Optional[dict]:
""" """
Return the configuration of the application or of a section. Return the configuration of the application or of a section.

View file

@ -182,7 +182,7 @@ class MediaJellyfinPlugin(Plugin):
:param genres: Filter results by (a list of) genres. :param genres: Filter results by (a list of) genres.
:param tags: Filter results by (a list of) tags. :param tags: Filter results by (a list of) tags.
:param years: Filter results by (a list of) years. :param years: Filter results by (a list of) years.
:return: .. schema:: jellyfin.JellyfinArtistSchema(many=True) :return: .. schema:: media.jellyfin.JellyfinArtistSchema(many=True)
""" """
return self._query( return self._query(
'/Artists', schema_class=JellyfinArtistSchema, '/Artists', schema_class=JellyfinArtistSchema,
@ -196,7 +196,7 @@ class MediaJellyfinPlugin(Plugin):
""" """
Get the list of collections associated to the user on the server (Movies, Series, Channels etc.) Get the list of collections associated to the user on the server (Movies, Series, Channels etc.)
:return: .. schema:: jellyfin.JellyfinCollectionSchema(many=True) :return: .. schema:: media.jellyfin.JellyfinCollectionSchema(many=True)
""" """
return self._query( return self._query(
f'/Users/{self._user_id}/Items', f'/Users/{self._user_id}/Items',
@ -242,6 +242,20 @@ class MediaJellyfinPlugin(Plugin):
:param genres: Filter results by (a list of) genres. :param genres: Filter results by (a list of) genres.
:param tags: Filter results by (a list of) tags. :param tags: Filter results by (a list of) tags.
:param years: Filter results by (a list of) years. :param years: Filter results by (a list of) years.
:return: The list of matching results.
Schema for artists:
.. schema:: media.jellyfin.JellyfinArtistSchema
Schema for collections:
.. schema:: media.jellyfin.JellyfinCollectionSchema
Schema for movies:
.. schema:: media.jellyfin.JellyfinMovieSchema
Schema for episodes:
.. schema:: media.jellyfin.JellyfinEpisodeSchema
""" """
if collection: if collection:
collections = self.get_collections().output # type: ignore collections = self.get_collections().output # type: ignore