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 untrusted user: 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
:members:

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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()

View file

@ -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

View file

@ -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.

View file

@ -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