diff --git a/docs/source/_ext/add_dependencies.py b/docs/source/_ext/add_dependencies.py index 59346a5e..471b5c63 100644 --- a/docs/source/_ext/add_dependencies.py +++ b/docs/source/_ext/add_dependencies.py @@ -3,7 +3,6 @@ import os import re import sys import textwrap as tw -from contextlib import contextmanager from sphinx.application import Sphinx @@ -13,14 +12,15 @@ base_path = os.path.abspath( sys.path.insert(0, base_path) -from platypush.utils import get_plugin_name_by_class # noqa -from platypush.utils.mock import mock # noqa -from platypush.utils.reflection import IntegrationMetadata, import_file # noqa +from platypush.common.reflection import Integration # noqa +from platypush.utils import get_plugin_name_by_class, import_file # noqa +from platypush.utils.mock import auto_mocks # noqa +from platypush.utils.mock.modules import mock_imports # noqa class IntegrationEnricher: @staticmethod - def add_events(source: list[str], manifest: IntegrationMetadata, idx: int) -> int: + def add_events(source: list[str], manifest: Integration, idx: int) -> int: if not manifest.events: return idx @@ -37,7 +37,7 @@ class IntegrationEnricher: return idx + 1 @staticmethod - def add_actions(source: list[str], manifest: IntegrationMetadata, idx: int) -> int: + def add_actions(source: list[str], manifest: Integration, idx: int) -> int: if not (manifest.actions and manifest.cls): return idx @@ -60,7 +60,7 @@ class IntegrationEnricher: @classmethod def add_install_deps( - cls, source: list[str], manifest: IntegrationMetadata, idx: int + cls, source: list[str], manifest: Integration, idx: int ) -> int: deps = manifest.deps parsed_deps = { @@ -106,9 +106,7 @@ class IntegrationEnricher: return idx @classmethod - def add_description( - cls, source: list[str], manifest: IntegrationMetadata, idx: int - ) -> int: + def add_description(cls, source: list[str], manifest: Integration, idx: int) -> int: docs = ( doc for doc in ( @@ -127,7 +125,7 @@ class IntegrationEnricher: @classmethod def add_conf_snippet( - cls, source: list[str], manifest: IntegrationMetadata, idx: int + cls, source: list[str], manifest: Integration, idx: int ) -> int: source.insert( idx, @@ -163,8 +161,8 @@ class IntegrationEnricher: if not os.path.isfile(manifest_file): return - with mock_imports(): - manifest = IntegrationMetadata.from_manifest(manifest_file) + with auto_mocks(): + manifest = Integration.from_manifest(manifest_file) idx = self.add_description(src, manifest, idx=3) idx = self.add_conf_snippet(src, manifest, idx=idx) idx = self.add_install_deps(src, manifest, idx=idx) @@ -175,14 +173,6 @@ class IntegrationEnricher: source[0] = '\n'.join(src) -@contextmanager -def mock_imports(): - conf_mod = import_file(os.path.join(base_path, 'docs', 'source', 'conf.py')) - mock_mods = getattr(conf_mod, 'autodoc_mock_imports', []) - with mock(*mock_mods): - yield - - def setup(app: Sphinx): app.connect('source-read', IntegrationEnricher()) return { diff --git a/docs/source/conf.py b/docs/source/conf.py index 4b159dc6..bf50c678 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -163,9 +163,9 @@ latex_documents = [ man_pages = [(master_doc, 'platypush', 'platypush Documentation', [author], 1)] -# -- Options for Texinfo output ---------------------------------------------- +# -- Options for TexInfo output ---------------------------------------------- -# Grouping the document tree into Texinfo files. List of tuples +# Grouping the document tree into TexInfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ @@ -193,126 +193,25 @@ autodoc_default_options = { 'show-inheritance': True, } -autodoc_mock_imports = [ - 'gunicorn', - 'googlesamples.assistant.grpc.audio_helpers', - 'google.assistant.embedded', - 'google.assistant.library', - 'google.assistant.library.event', - 'google.assistant.library.file_helpers', - 'google.oauth2.credentials', - 'oauth2client', - 'apiclient', - 'tenacity', - 'smartcard', - 'Leap', - 'oauth2client', - 'rtmidi', - 'bluetooth', - 'gevent.wsgi', - 'Adafruit_IO', - 'pyclip', - 'pydbus', - 'inputs', - 'inotify', - 'omxplayer', - 'plexapi', - 'cwiid', - 'sounddevice', - 'soundfile', - 'numpy', - 'cv2', - 'nfc', - 'ndef', - 'bcrypt', - 'google', - 'feedparser', - 'kafka', - 'googlesamples', - 'icalendar', - 'httplib2', - 'mpd', - 'serial', - 'pyHS100', - 'grpc', - 'envirophat', - 'gps', - 'picamera', - 'pmw3901', - 'PIL', - 'croniter', - 'pyaudio', - 'avs', - 'PyOBEX', - 'PyOBEX.client', - 'todoist', - 'trello', - 'telegram', - 'telegram.ext', - 'pyfirmata2', - 'cups', - 'graphyte', - 'cpuinfo', - 'psutil', - 'openzwave', - 'deepspeech', - 'wave', - 'pvporcupine ', - 'pvcheetah', - 'pyotp', - 'linode_api4', - 'pyzbar', - 'tensorflow', - 'keras', - 'pandas', - 'samsungtvws', - 'paramiko', - 'luma', - 'zeroconf', - 'dbus', - 'gi', - 'gi.repository', - 'twilio', - 'Adafruit_Python_DHT', - 'RPi.GPIO', - 'RPLCD', - 'imapclient', - 'pysmartthings', - 'aiohttp', - 'watchdog', - 'pyngrok', - 'irc', - 'irc.bot', - 'irc.strings', - 'irc.client', - 'irc.connection', - 'irc.events', - 'defusedxml', - 'nio', - 'aiofiles', - 'aiofiles.os', - 'async_lru', - 'bleak', - 'bluetooth_numbers', - 'TheengsDecoder', - 'simple_websocket', - 'uvicorn', - 'websockets', - 'docutils', - 'aioxmpp', -] - sys.path.insert(0, os.path.abspath('../..')) +from platypush.utils.mock.modules import mock_imports # noqa -def skip(app, what, name, obj, skip, options): +autodoc_mock_imports = [*mock_imports] + + +# _ = app +# __ = what +# ___ = obj +# ____ = options +def _skip(_, __, name, ___, skip, ____): if name == "__init__": return False return skip def setup(app): - app.connect("autodoc-skip-member", skip) + app.connect("autodoc-skip-member", _skip) # vim:sw=4:ts=4:et: diff --git a/generate_missing_docs.py b/generate_missing_docs.py index 3d9f8285..7561b694 100644 --- a/generate_missing_docs.py +++ b/generate_missing_docs.py @@ -1,18 +1,18 @@ +import importlib +import inspect import os +import sys from typing import Iterable, Optional +import pkgutil + from platypush.backend import Backend -from platypush.context import get_plugin +from platypush.message.event import Event +from platypush.message.response import Response from platypush.plugins import Plugin from platypush.utils.manifest import Manifests -def _get_inspect_plugin(): - p = get_plugin('inspect') - assert p, 'Could not load the `inspect` plugin' - return p - - def get_all_plugins(): return sorted([mf.component_name for mf in Manifests.by_base_class(Plugin)]) @@ -22,11 +22,35 @@ def get_all_backends(): def get_all_events(): - return _get_inspect_plugin().get_all_events().output + return _get_modules(Event) def get_all_responses(): - return _get_inspect_plugin().get_all_responses().output + return _get_modules(Response) + + +def _get_modules(base_type: type): + ret = set() + base_dir = os.path.dirname(inspect.getfile(base_type)) + package = base_type.__module__ + + for _, mod_name, _ in pkgutil.walk_packages([base_dir], prefix=package + '.'): + try: + module = importlib.import_module(mod_name) + except Exception: + print('Could not import module', mod_name, file=sys.stderr) + continue + + for _, obj_type in inspect.getmembers(module): + if ( + inspect.isclass(obj_type) + and issubclass(obj_type, base_type) + # Exclude the base_type itself + and obj_type != base_type + ): + ret.add(obj_type.__module__.replace(package + '.', '', 1)) + + return list(ret) def _generate_components_doc( @@ -122,7 +146,7 @@ def generate_events_doc(): _generate_components_doc( index_name='events', package_name='message.event', - components=sorted(event for event in get_all_events().keys() if event), + components=sorted(event for event in get_all_events() if event), ) @@ -130,9 +154,7 @@ def generate_responses_doc(): _generate_components_doc( index_name='responses', package_name='message.response', - components=sorted( - response for response in get_all_responses().keys() if response - ), + components=sorted(response for response in get_all_responses() if response), ) diff --git a/platypush/app/_app.py b/platypush/app/_app.py index c11e1af0..589efb66 100644 --- a/platypush/app/_app.py +++ b/platypush/app/_app.py @@ -42,6 +42,7 @@ class Application: config_file: Optional[str] = None, workdir: Optional[str] = None, logsdir: Optional[str] = None, + cachedir: Optional[str] = None, device_id: Optional[str] = None, pidfile: Optional[str] = None, requests_to_process: Optional[int] = None, @@ -62,6 +63,8 @@ class Application: ``filename`` setting under the ``logging`` section of the configuration file is used. If not set, logging will be sent to stdout and stderr. + :param cachedir: Overrides the ``cachedir`` setting in the configuration + file (default: None). :param device_id: Override the device ID used to identify this instance. If not passed here, it is inferred from the configuration (device_id field). If not present there either, it is inferred from @@ -106,6 +109,9 @@ class Application: self.config_file, device_id=device_id, workdir=os.path.abspath(os.path.expanduser(workdir)) if workdir else None, + cachedir=os.path.abspath(os.path.expanduser(cachedir)) + if cachedir + else None, ctrl_sock=os.path.abspath(os.path.expanduser(ctrl_sock)) if ctrl_sock else None, @@ -206,6 +212,7 @@ class Application: return cls( config_file=opts.config, workdir=opts.workdir, + cachedir=opts.cachedir, logsdir=opts.logsdir, device_id=opts.device_id, pidfile=opts.pidfile, diff --git a/platypush/backend/google/fit/__init__.py b/platypush/backend/google/fit/__init__.py index 523db0a8..0f0c327e 100644 --- a/platypush/backend/google/fit/__init__.py +++ b/platypush/backend/google/fit/__init__.py @@ -34,7 +34,8 @@ class GoogleFitBackend(Backend): """ :param data_sources: Google Fit data source IDs to monitor. You can get a list of the available data sources through the - :meth:`platypush.plugins.google.fit.get_data_sources` action + :meth:`platypush.plugins.google.fit.GoogleFitPlugin.get_data_sources` + action :type data_sources: list[str] :param user_id: Google user ID to track (default: 'me') diff --git a/platypush/backend/http/webapp/src/Utils.vue b/platypush/backend/http/webapp/src/Utils.vue index 45578542..95d637c2 100644 --- a/platypush/backend/http/webapp/src/Utils.vue +++ b/platypush/backend/http/webapp/src/Utils.vue @@ -1,5 +1,6 @@ diff --git a/platypush/backend/http/webapp/src/components/elements/Autocomplete.js b/platypush/backend/http/webapp/src/components/elements/Autocomplete.js deleted file mode 100644 index 334972b7..00000000 --- a/platypush/backend/http/webapp/src/components/elements/Autocomplete.js +++ /dev/null @@ -1,128 +0,0 @@ -function autocomplete(inp, arr, listener) { - /*the autocomplete function takes two arguments, - the text field element and an array of possible autocompleted values:*/ - let currentFocus; - - /*execute a function when someone writes in the text field:*/ - inp.addEventListener("input", function() { - let a, b, i, val = this.value; - /*close any already open lists of autocompleted values*/ - closeAllLists(); - if (!val) { - return false; - } - - currentFocus = -1; - - /*create a DIV element that will contain the items (values):*/ - a = document.createElement("DIV"); - a.setAttribute("id", this.id + "autocomplete-list"); - a.setAttribute("class", "autocomplete-items"); - - /*append the DIV element as a child of the autocomplete container:*/ - this.parentNode.appendChild(a); - - /*for each item in the array...*/ - for (i = 0; i < arr.length; i++) { - /*check if the item starts with the same letters as the text field value:*/ - if (arr[i].substr(0, val.length).toUpperCase() === val.toUpperCase()) { - /*create a DIV element for each matching element:*/ - b = document.createElement("DIV"); - /*make the matching letters bold:*/ - b.innerHTML = "" + arr[i].substr(0, val.length) + ""; - b.innerHTML += arr[i].substr(val.length); - /*insert a input field that will hold the current array item's value:*/ - b.innerHTML += ""; - /*execute a function when someone clicks on the item value (DIV element):*/ - b.addEventListener("click", function(e) { - /*insert the value for the autocomplete text field:*/ - inp.value = this.getElementsByTagName("input")[0].value; - /*trigger event listener if any:*/ - if (listener) { - listener(e, inp.value); - } - /*close the list of autocompleted values, - (or any other open lists of autocompleted values:*/ - closeAllLists(); - }); - a.appendChild(b); - } - } - }); - - inp.addEventListener("keyup", function(e) { - if (["ArrowUp", "ArrowDown", "Tab", "Enter"].indexOf(e.key) >= 0) { - e.stopPropagation(); - } - - if (e.key === "Enter") { - this.blur(); - } - }); - - /*execute a function presses a key on the keyboard:*/ - inp.addEventListener("keydown", function(e) { - let x = document.getElementById(this.id + "autocomplete-list"); - if (x) x = x.getElementsByTagName("div"); - if (e.key === 'ArrowDown' || (e.key === 'Tab' && !e.shiftKey)) { - /*If the arrow DOWN key is pressed, - increase the currentFocus variable:*/ - currentFocus++; - /*and and make the current item more visible:*/ - addActive(x); - e.preventDefault(); - } else if (e.key === 'ArrowUp' || (e.key === 'Tab' && e.shiftKey)) { //up - /*If the arrow UP key is pressed, - decrease the currentFocus variable:*/ - currentFocus--; - /*and and make the current item more visible:*/ - addActive(x); - e.preventDefault(); - } else if (e.key === 'Enter') { - /*If the ENTER key is pressed, prevent the form from being submitted,*/ - if (currentFocus > -1 && x && x.length) { - e.preventDefault(); - /*and simulate a click on the "active" item:*/ - x[currentFocus].click(); - /*and restore the focus on the input element:*/ - this.focus(); - } - } - }); - - function addActive(x) { - /*a function to classify an item as "active":*/ - if (!x) return false; - /*start by removing the "active" class on all items:*/ - removeActive(x); - if (currentFocus >= x.length) currentFocus = 0; - if (currentFocus < 0) currentFocus = (x.length - 1); - /*add class "autocomplete-active":*/ - x[currentFocus].classList.add("autocomplete-active"); - } - - function removeActive(x) { - /*a function to remove the "active" class from all autocomplete items:*/ - for (let i = 0; i < x.length; i++) { - x[i].classList.remove("autocomplete-active"); - } - } - - function closeAllLists(elmnt) { - /*close all autocomplete lists in the document, - except the one passed as an argument:*/ - const x = document.getElementsByClassName("autocomplete-items"); - for (let i = 0; i < x.length; i++) { - if (elmnt !== x[i] && elmnt !== inp) { - x[i].parentNode.removeChild(x[i]); - } - } - } - - /*execute a function when someone clicks in the document:*/ - document.addEventListener("click", function (e) { - closeAllLists(e.target); - }); -} - -export default autocomplete; diff --git a/platypush/backend/http/webapp/src/components/elements/Autocomplete.vue b/platypush/backend/http/webapp/src/components/elements/Autocomplete.vue new file mode 100644 index 00000000..d63dc115 --- /dev/null +++ b/platypush/backend/http/webapp/src/components/elements/Autocomplete.vue @@ -0,0 +1,254 @@ + + + + + diff --git a/platypush/backend/http/webapp/src/components/elements/Tab.vue b/platypush/backend/http/webapp/src/components/elements/Tab.vue new file mode 100644 index 00000000..bd33aab5 --- /dev/null +++ b/platypush/backend/http/webapp/src/components/elements/Tab.vue @@ -0,0 +1,56 @@ + + + + + diff --git a/platypush/backend/http/webapp/src/components/elements/Tabs.vue b/platypush/backend/http/webapp/src/components/elements/Tabs.vue new file mode 100644 index 00000000..404d2123 --- /dev/null +++ b/platypush/backend/http/webapp/src/components/elements/Tabs.vue @@ -0,0 +1,23 @@ + + + + + diff --git a/platypush/backend/http/webapp/src/components/panels/Execute/ActionArgs.vue b/platypush/backend/http/webapp/src/components/panels/Execute/ActionArgs.vue new file mode 100644 index 00000000..d6c2f8bc --- /dev/null +++ b/platypush/backend/http/webapp/src/components/panels/Execute/ActionArgs.vue @@ -0,0 +1,133 @@ + + + + + diff --git a/platypush/backend/http/webapp/src/components/panels/Execute/ActionDoc.vue b/platypush/backend/http/webapp/src/components/panels/Execute/ActionDoc.vue new file mode 100644 index 00000000..502863d9 --- /dev/null +++ b/platypush/backend/http/webapp/src/components/panels/Execute/ActionDoc.vue @@ -0,0 +1,40 @@ + + + + + diff --git a/platypush/backend/http/webapp/src/components/panels/Execute/Argdoc.vue b/platypush/backend/http/webapp/src/components/panels/Execute/Argdoc.vue new file mode 100644 index 00000000..df52103a --- /dev/null +++ b/platypush/backend/http/webapp/src/components/panels/Execute/Argdoc.vue @@ -0,0 +1,96 @@ + + + + + diff --git a/platypush/backend/http/webapp/src/components/panels/Execute/Index.vue b/platypush/backend/http/webapp/src/components/panels/Execute/Index.vue index 9322d858..feeed7a9 100644 --- a/platypush/backend/http/webapp/src/components/panels/Execute/Index.vue +++ b/platypush/backend/http/webapp/src/components/panels/Execute/Index.vue @@ -1,26 +1,44 @@