From 69583d2e1507d4bec3de1107ddc343952f16873d Mon Sep 17 00:00:00 2001 From: Fabio Manganiello Date: Mon, 10 May 2021 18:42:13 +0200 Subject: [PATCH] Added support for Marshmallow schemas in responses --- docs/source/_ext/sphinx_marshmallow.py | 59 ++++++++++++++++++++++++++ docs/source/conf.py | 7 +++ platypush/schemas/__init__.py | 0 requirements.txt | 3 ++ setup.py | 1 + 5 files changed, 70 insertions(+) create mode 100644 docs/source/_ext/sphinx_marshmallow.py create mode 100644 platypush/schemas/__init__.py diff --git a/docs/source/_ext/sphinx_marshmallow.py b/docs/source/_ext/sphinx_marshmallow.py new file mode 100644 index 00000000..0eef29e0 --- /dev/null +++ b/docs/source/_ext/sphinx_marshmallow.py @@ -0,0 +1,59 @@ +import importlib +import json +import os +import re +import sys +from typing import Union, List + +from docutils import nodes +from docutils.parsers.rst import Directive + + +class SchemaDirective(Directive): + """ + Support for response/message schemas in the docs. Format: ``.. schema:: rel_path.SchemaClass(arg1=value1, ...)``, + where ``rel_path`` is the path of the schema relative to ``platypush/schemas``. + """ + has_content = True + _schema_regex = re.compile(r'^\s*(.+?)\s*(\((.+?)\))?\s*$') + _schemas_path = os.path.abspath( + os.path.join( + os.path.dirname(os.path.relpath(__file__)), '..', '..', '..', 'platypush', 'schemas')) + + sys.path.insert(0, _schemas_path) + + @staticmethod + def _get_field_value(field) -> str: + metadata = getattr(field, 'metadata', {}) + return metadata.get('example', metadata.get('description', str(field.__class__.__name__).lower())) + + def _parse_schema(self) -> Union[dict, List[dict]]: + m = self._schema_regex.match('\n'.join(self.content)) + schema_module_name = '.'.join(['platypush.schemas', *(m.group(1).split('.')[:-1])]) + schema_module = importlib.import_module(schema_module_name) + schema_class = getattr(schema_module, m.group(1).split('.')[-1]) + schema_args = eval(f'dict({m.group(3)})') + schema = schema_class(**schema_args) + output = { + name: self._get_field_value(field) + for name, field in schema.fields.items() + if not field.load_only + } + + return [output] if schema.many else output + + def run(self): + content = json.dumps(self._parse_schema(), sort_keys=True, indent=2) + block = nodes.literal_block(content, content) + block['language'] = 'json' + return [block] + + +def setup(app): + app.add_directive('schema', SchemaDirective) + + return { + 'version': '0.1', + 'parallel_read_safe': True, + 'parallel_write_safe': True, + } diff --git a/docs/source/conf.py b/docs/source/conf.py index 26c32c9e..766c348a 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -18,6 +18,7 @@ import sys # import os # import sys # sys.path.insert(0, os.path.abspath('.')) +sys.path.insert(0, os.path.abspath("./_ext")) # -- Project information ----------------------------------------------------- @@ -50,6 +51,7 @@ extensions = [ 'sphinx.ext.viewcode', 'sphinx.ext.githubpages', 'sphinx_rtd_theme', + 'sphinx_marshmallow', ] # Add any paths that contain templates here, relative to this directory. @@ -134,6 +136,11 @@ html_theme_options = { 'title': 'Firefox Extension', 'internal': False, }, + { + 'href': 'https://f-droid.org/en/packages/tech.platypush.platypush/', + 'title': 'Android App', + 'internal': False, + }, ], } diff --git a/platypush/schemas/__init__.py b/platypush/schemas/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/requirements.txt b/requirements.txt index cc70ede2..40505559 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,6 +8,9 @@ # YAML configuration support pyyaml +# Responses and schemas +marshmallow + # Support for setting thread/process name # python-prctl diff --git a/setup.py b/setup.py index ab12e0bd..61108333 100755 --- a/setup.py +++ b/setup.py @@ -66,6 +66,7 @@ setup( 'python-dateutil', 'cryptography', 'pyjwt', + 'marshmallow', ], extras_require={