From fdf6d8fb4ed343423b3739d5da47d9d9b278737e Mon Sep 17 00:00:00 2001 From: Fabio Manganiello Date: Thu, 3 Mar 2022 15:15:55 +0100 Subject: [PATCH] Better auto-generated documentation and fixed docstring warnings --- docs/source/platypush/events/chat.slack.rst | 4 +- docs/source/platypush/events/dbus.rst | 4 +- docs/source/platypush/events/gotify.rst | 4 +- docs/source/platypush/events/irc.rst | 4 +- docs/source/platypush/events/ngrok.rst | 4 +- docs/source/platypush/events/rss.rst | 4 +- docs/source/platypush/events/sun.rst | 4 +- .../platypush/plugins/media.jellyfin.rst | 2 +- generate_missing_docs.py | 33 +++++++----- platypush/message/__init__.py | 12 ++--- platypush/plugins/inspect/__init__.py | 50 ++++++++++--------- platypush/plugins/media/jellyfin/__init__.py | 18 ++++++- 12 files changed, 85 insertions(+), 58 deletions(-) diff --git a/docs/source/platypush/events/chat.slack.rst b/docs/source/platypush/events/chat.slack.rst index eb8e8dba9..2ec92aef0 100644 --- a/docs/source/platypush/events/chat.slack.rst +++ b/docs/source/platypush/events/chat.slack.rst @@ -1,5 +1,5 @@ -``platypush.message.event.chat.slack`` -====================================== +``chat.slack`` +============== .. automodule:: platypush.message.event.chat.slack :members: diff --git a/docs/source/platypush/events/dbus.rst b/docs/source/platypush/events/dbus.rst index c053f4021..c44aec397 100644 --- a/docs/source/platypush/events/dbus.rst +++ b/docs/source/platypush/events/dbus.rst @@ -1,5 +1,5 @@ -``platypush.message.event.dbus`` -================================ +``dbus`` +======== .. automodule:: platypush.message.event.dbus :members: diff --git a/docs/source/platypush/events/gotify.rst b/docs/source/platypush/events/gotify.rst index 82b661bd4..afe674ab9 100644 --- a/docs/source/platypush/events/gotify.rst +++ b/docs/source/platypush/events/gotify.rst @@ -1,5 +1,5 @@ -``platypush.message.event.gotify`` -================================== +``gotify`` +========== .. automodule:: platypush.message.event.gotify :members: diff --git a/docs/source/platypush/events/irc.rst b/docs/source/platypush/events/irc.rst index 7eab9d025..c6ba0c308 100644 --- a/docs/source/platypush/events/irc.rst +++ b/docs/source/platypush/events/irc.rst @@ -1,5 +1,5 @@ -``platypush.message.event.irc`` -=============================== +``irc`` +======= .. automodule:: platypush.message.event.irc :members: diff --git a/docs/source/platypush/events/ngrok.rst b/docs/source/platypush/events/ngrok.rst index 3585933f7..f2108cf58 100644 --- a/docs/source/platypush/events/ngrok.rst +++ b/docs/source/platypush/events/ngrok.rst @@ -1,5 +1,5 @@ -``platypush.message.event.ngrok`` -================================= +``ngrok`` +========= .. automodule:: platypush.message.event.ngrok :members: diff --git a/docs/source/platypush/events/rss.rst b/docs/source/platypush/events/rss.rst index cbd31237d..b699bdacf 100644 --- a/docs/source/platypush/events/rss.rst +++ b/docs/source/platypush/events/rss.rst @@ -1,5 +1,5 @@ -``platypush.message.event.rss`` -=============================== +``rss`` +======= .. automodule:: platypush.message.event.rss :members: diff --git a/docs/source/platypush/events/sun.rst b/docs/source/platypush/events/sun.rst index 135e60d25..a58ea0629 100644 --- a/docs/source/platypush/events/sun.rst +++ b/docs/source/platypush/events/sun.rst @@ -1,5 +1,5 @@ -``platypush.message.event.sun`` -=============================== +``sun`` +======= .. automodule:: platypush.message.event.sun :members: diff --git a/docs/source/platypush/plugins/media.jellyfin.rst b/docs/source/platypush/plugins/media.jellyfin.rst index 7a9eacc6d..74b8eed95 100644 --- a/docs/source/platypush/plugins/media.jellyfin.rst +++ b/docs/source/platypush/plugins/media.jellyfin.rst @@ -1,5 +1,5 @@ ``media.jellyfin`` -================================ +================== .. automodule:: platypush.plugins.media.jellyfin :members: diff --git a/generate_missing_docs.py b/generate_missing_docs.py index bce44186a..9c80d93d6 100644 --- a/generate_missing_docs.py +++ b/generate_missing_docs.py @@ -1,3 +1,4 @@ +import inspect import os from platypush.backend import Backend @@ -6,11 +7,17 @@ from platypush.plugins import Plugin 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(): manifests = {mf.component_name for mf in get_manifests(Plugin)} return { 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 } @@ -19,17 +26,17 @@ def get_all_backends(): manifests = {mf.component_name for mf in get_manifests(Backend)} return { 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 } def get_all_events(): - return get_plugin('inspect').get_all_events().output + return _get_inspect_plugin().get_all_events().output def get_all_responses(): - return get_plugin('inspect').get_all_responses().output + return _get_inspect_plugin().get_all_responses().output # noinspection DuplicatedCode @@ -100,16 +107,17 @@ Backends # noinspection DuplicatedCode 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_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: - 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): header = '``{}``'.format(event) 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]) with open(event_file, 'w') as f: @@ -127,21 +135,22 @@ 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 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_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: - 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): header = '``{}``'.format(response) 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]) with open(response_file, 'w') as f: @@ -159,7 +168,7 @@ 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() diff --git a/platypush/message/__init__.py b/platypush/message/__init__.py index c79bee4b1..c32f65b0d 100644 --- a/platypush/message/__init__.py +++ b/platypush/message/__init__.py @@ -71,7 +71,7 @@ class Message(object): logger.warning('Could not serialize object type {}: {}: {}'.format( type(obj), str(e), obj)) - def __init__(self, timestamp=None, *args, **kwargs): + def __init__(self, timestamp=None, *_, **__): self.timestamp = timestamp or time.time() def __str__(self): @@ -98,9 +98,9 @@ class Message(object): def parse(cls, msg): """ Parse a generic message into a key-value dictionary - Params: - msg -- Original message - can be a dictionary, a Message, - or a string/bytearray, as long as it's valid UTF-8 JSON + + :param msg: Original message. It can be a dictionary, a Message, + or a string/bytearray, as long as it's valid UTF-8 JSON """ if isinstance(msg, cls): @@ -124,8 +124,8 @@ class Message(object): def build(cls, msg): """ 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 diff --git a/platypush/plugins/inspect/__init__.py b/platypush/plugins/inspect/__init__.py index 5271e82f6..5a9c1761b 100644 --- a/platypush/plugins/inspect/__init__.py +++ b/platypush/plugins/inspect/__init__.py @@ -4,6 +4,7 @@ import json import pkgutil import re import threading +from abc import ABC, abstractmethod from typing import Optional 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 -# noinspection PyTypeChecker -class Model: +class Model(ABC): def __str__(self): 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'] + @abstractmethod + def __iter__(self): + raise NotImplementedError() + class ProcedureEncoder(json.JSONEncoder): def default(self, o): @@ -49,7 +53,7 @@ class ProcedureEncoder(json.JSONEncoder): 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.html_doc = html_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): - 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.html_doc = html_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', [])} def __iter__(self): @@ -77,8 +81,8 @@ class PluginModel(Model): class EventModel(Model): - def __init__(self, event, html_doc: bool = False): - self.package = event.__module__ + def __init__(self, event, prefix='', html_doc: Optional[bool] = False): + self.package = event.__module__[len(prefix):] self.name = event.__name__ self.html_doc = html_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): - def __init__(self, response, html_doc: bool = False): - self.package = response.__module__ + def __init__(self, response, prefix='', html_doc: Optional[bool] = False): + self.package = response.__module__[len(prefix):] self.name = response.__name__ self.html_doc = html_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__, prefix=prefix, - onerror=lambda x: None): + onerror=lambda _: None): try: module = importlib.import_module(modname) except Exception as e: @@ -207,7 +211,7 @@ class InspectPlugin(Plugin): for _, modname, _ in pkgutil.walk_packages(path=package.__path__, prefix=prefix, - onerror=lambda x: None): + onerror=lambda _: None): try: module = importlib.import_module(modname) except Exception as e: @@ -226,7 +230,7 @@ class InspectPlugin(Plugin): for _, modname, _ in pkgutil.walk_packages(path=package.__path__, prefix=prefix, - onerror=lambda x: None): + onerror=lambda _: None): try: module = importlib.import_module(modname) except Exception as e: @@ -238,7 +242,7 @@ class InspectPlugin(Plugin): continue 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: self._events[event.package] = {event.name: event} else: @@ -250,7 +254,7 @@ class InspectPlugin(Plugin): for _, modname, _ in pkgutil.walk_packages(path=package.__path__, prefix=prefix, - onerror=lambda x: None): + onerror=lambda _: None): try: module = importlib.import_module(modname) except Exception as e: @@ -262,14 +266,14 @@ class InspectPlugin(Plugin): continue 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: self._responses[response.package] = {response.name: response} else: self._responses[response.package][response.name] = response @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) """ @@ -284,7 +288,7 @@ class InspectPlugin(Plugin): }) @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) """ @@ -299,7 +303,7 @@ class InspectPlugin(Plugin): }) @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) """ @@ -311,13 +315,13 @@ class InspectPlugin(Plugin): return json.dumps({ package: { name: dict(event) - for name, event in self._events[package].items() + for name, event in events.items() } for package, events in self._events.items() }) @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) """ @@ -329,9 +333,9 @@ class InspectPlugin(Plugin): return json.dumps({ package: { 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 @@ -342,7 +346,7 @@ class InspectPlugin(Plugin): return json.loads(json.dumps(Config.get_procedures(), cls=ProcedureEncoder)) @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. diff --git a/platypush/plugins/media/jellyfin/__init__.py b/platypush/plugins/media/jellyfin/__init__.py index 97f5259f5..69cf856a7 100644 --- a/platypush/plugins/media/jellyfin/__init__.py +++ b/platypush/plugins/media/jellyfin/__init__.py @@ -182,7 +182,7 @@ class MediaJellyfinPlugin(Plugin): :param genres: Filter results by (a list of) genres. :param tags: Filter results by (a list of) tags. :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( '/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.) - :return: .. schema:: jellyfin.JellyfinCollectionSchema(many=True) + :return: .. schema:: media.jellyfin.JellyfinCollectionSchema(many=True) """ return self._query( f'/Users/{self._user_id}/Items', @@ -242,6 +242,20 @@ class MediaJellyfinPlugin(Plugin): :param genres: Filter results by (a list of) genres. :param tags: Filter results by (a list of) tags. :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: collections = self.get_collections().output # type: ignore