From 2f1209823e71d6e5bb802b01c2808127b25f4f6f Mon Sep 17 00:00:00 2001 From: Richard Date: Thu, 1 Jul 2021 15:06:04 +0100 Subject: [PATCH 1/6] Swap MethodView for View in builder.static_from Static views created with `static_from` had no metadata in the OpenAPI representation. This is fixed by subclassing our custom View. There was a circular import problem, which I have fixed by removing the `import builder` from `__init__.py` This wasn't used, and only appeared in `__all__`. It appears that removing the import has changed nothing; `from labthings.views import *` still imports the `builder` module. --- src/labthings/views/__init__.py | 1 - src/labthings/views/builder.py | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/labthings/views/__init__.py b/src/labthings/views/__init__.py index 68604f8b..5640df58 100644 --- a/src/labthings/views/__init__.py +++ b/src/labthings/views/__init__.py @@ -18,7 +18,6 @@ build_action_schema, ) from ..utilities import unpack -from . import builder, op __all__ = ["MethodView", "View", "ActionView", "PropertyView", "op", "builder"] diff --git a/src/labthings/views/builder.py b/src/labthings/views/builder.py index 03bccc6b..d6868a3f 100644 --- a/src/labthings/views/builder.py +++ b/src/labthings/views/builder.py @@ -4,10 +4,10 @@ from typing import Type from flask import abort, send_file -from flask.views import MethodView +from . import View -def static_from(static_folder: str, name=None) -> Type[MethodView]: +def static_from(static_folder: str, name=None) -> Type[View]: """ :param static_folder: str: :param name: (Default value = None) @@ -37,6 +37,6 @@ def _get(_, path=""): return send_file(indexes[0]) # Generate a basic property class - generated_class = type(name, (MethodView, object), {"get": _get}) + generated_class = type(name, (View, object), {"get": _get}) return generated_class From 559b769fe2000eb3eb66ea7033f7803a38014a76 Mon Sep 17 00:00:00 2001 From: Richard Date: Thu, 1 Jul 2021 16:27:18 +0100 Subject: [PATCH 2/6] Manage indentation better in docstrings get_docstring used to discard all indentation. It now uses `inspect.cleandoc()` to remove spurious indents but preserve intentional ones, which results in more nicely formatted markdown docstrings. I have not changed the default remove_newlines=True but I am quite tempted to do so. I'm not convinced by the current trailing space if remove_newlines is True, but have not changed this behaviour because there's no advantage to doing so... --- src/labthings/utilities.py | 9 +++++---- tests/test_utilities.py | 4 ++++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/labthings/utilities.py b/src/labthings/utilities.py index 9a4407cd..f41a0cde 100644 --- a/src/labthings/utilities.py +++ b/src/labthings/utilities.py @@ -1,4 +1,5 @@ import copy +import inspect import operator import os import re @@ -152,12 +153,12 @@ def get_docstring(obj: Any, remove_newlines=True) -> str: """ ds = obj.__doc__ - if ds: + if not ds: + return "" + if remove_newlines: stripped = [line.strip() for line in ds.splitlines() if line] - if not remove_newlines: - return "\n".join(stripped) return " ".join(stripped).replace("\n", " ").replace("\r", "") - return "" + return inspect.cleandoc(ds) # Strip spurious indentation/newlines def get_summary(obj: Any) -> str: diff --git a/tests/test_utilities.py b/tests/test_utilities.py index fecd6856..eab062a4 100644 --- a/tests/test_utilities.py +++ b/tests/test_utilities.py @@ -30,6 +30,10 @@ def test_get_docstring(example_class): utilities.get_docstring(example_class) == "First line of class docstring. Second line of class docstring. " ) + assert ( + utilities.get_docstring(example_class, remove_newlines=False) + == "First line of class docstring.\nSecond line of class docstring." + ) assert utilities.get_docstring(example_class.class_method) == ( "First line of class method docstring. Second line of class method docstring. " From 05b27475b6dcd6a1846b38d05460e9c90389306e Mon Sep 17 00:00:00 2001 From: Richard Date: Tue, 6 Jul 2021 15:28:21 +0100 Subject: [PATCH 3/6] Allow per-Operation docstrings Previously, GET and POST requests both used the same description, which doesn't make sense when they do different things. I've changed this to use` __doc__` or `description` attributes on the `get` or `post` method if available. --- src/labthings/apispec/plugins.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/labthings/apispec/plugins.py b/src/labthings/apispec/plugins.py index 7d9459b6..0addd4e3 100644 --- a/src/labthings/apispec/plugins.py +++ b/src/labthings/apispec/plugins.py @@ -60,14 +60,19 @@ def spec_for_interaction(cls, interaction): for method in http_method_funcs: if hasattr(interaction, method): + property = getattr(interaction, method) d[method] = { - "description": getattr(interaction, "description", None) - or get_docstring(interaction), - "summary": getattr(interaction, "summary", None) + "description": getattr(property, "description", None) + or get_docstring(property, remove_newlines=False) + or getattr(interaction, "description", None) + or get_docstring(interaction, remove_newlines=False), + "summary": getattr(property, "summary", None) + or get_summary(property) + or getattr(interaction, "summary", None) or get_summary(interaction), "tags": list(interaction.get_tags()), "responses": { - "default": { + "5XX": { "description": "Unexpected error", "content": { "application/json": { From 6819f23925d6d54f116d20fe4fa2fc6454590542 Mon Sep 17 00:00:00 2001 From: Richard Date: Tue, 6 Jul 2021 15:28:47 +0100 Subject: [PATCH 4/6] More helpful docstring for ActionView.get --- src/labthings/views/__init__.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/labthings/views/__init__.py b/src/labthings/views/__init__.py index 5640df58..066044f0 100644 --- a/src/labthings/views/__init__.py +++ b/src/labthings/views/__init__.py @@ -171,7 +171,12 @@ def __init_subclass__(cls): @classmethod def get(cls): """ - Default method for GET requests. Returns the action queue (including already finished actions) for this action + List running and completed actions. + + Actions are run with `POST` requests. See the `POST` method for this URL for + details of the action. Sending a `GET` request to an action endpoint will return + action descriptions for each time the action has been run, including whether they + have completed, and any return values. """ queue_schema = build_action_schema(cls.schema, cls.args)(many=True) return queue_schema.dump(cls._deque) From c52a66d97a4ff9bf147af5b8661e420f3447c134 Mon Sep 17 00:00:00 2001 From: Richard Date: Tue, 6 Jul 2021 15:41:40 +0100 Subject: [PATCH 5/6] blackened --- src/labthings/utilities.py | 2 +- src/labthings/views/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/labthings/utilities.py b/src/labthings/utilities.py index f41a0cde..35d5c710 100644 --- a/src/labthings/utilities.py +++ b/src/labthings/utilities.py @@ -158,7 +158,7 @@ def get_docstring(obj: Any, remove_newlines=True) -> str: if remove_newlines: stripped = [line.strip() for line in ds.splitlines() if line] return " ".join(stripped).replace("\n", " ").replace("\r", "") - return inspect.cleandoc(ds) # Strip spurious indentation/newlines + return inspect.cleandoc(ds) # Strip spurious indentation/newlines def get_summary(obj: Any) -> str: diff --git a/src/labthings/views/__init__.py b/src/labthings/views/__init__.py index 066044f0..d5c198b9 100644 --- a/src/labthings/views/__init__.py +++ b/src/labthings/views/__init__.py @@ -173,7 +173,7 @@ def get(cls): """ List running and completed actions. - Actions are run with `POST` requests. See the `POST` method for this URL for + Actions are run with `POST` requests. See the `POST` method for this URL for details of the action. Sending a `GET` request to an action endpoint will return action descriptions for each time the action has been run, including whether they have completed, and any return values. From 8ddfaec2d214c792b4f3e4fb7c3faed7b8487b96 Mon Sep 17 00:00:00 2001 From: Richard Date: Tue, 6 Jul 2021 15:45:06 +0100 Subject: [PATCH 6/6] linter fixes --- src/labthings/apispec/plugins.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/labthings/apispec/plugins.py b/src/labthings/apispec/plugins.py index 0addd4e3..88adc45b 100644 --- a/src/labthings/apispec/plugins.py +++ b/src/labthings/apispec/plugins.py @@ -60,14 +60,14 @@ def spec_for_interaction(cls, interaction): for method in http_method_funcs: if hasattr(interaction, method): - property = getattr(interaction, method) + prop = getattr(interaction, method) d[method] = { - "description": getattr(property, "description", None) - or get_docstring(property, remove_newlines=False) + "description": getattr(prop, "description", None) + or get_docstring(prop, remove_newlines=False) or getattr(interaction, "description", None) or get_docstring(interaction, remove_newlines=False), - "summary": getattr(property, "summary", None) - or get_summary(property) + "summary": getattr(prop, "summary", None) + or get_summary(prop) or getattr(interaction, "summary", None) or get_summary(interaction), "tags": list(interaction.get_tags()),