Moved all API stuff to flask_labthings

This commit is contained in:
jtc42 2020-01-06 17:03:05 +00:00
parent 6bf178750b
commit 851d70842c
12 changed files with 96 additions and 109 deletions

View file

@ -3,9 +3,8 @@ from functools import wraps, update_wrapper
from flask import make_response
from openflexure_microscope.common.labthings_core.utilities import rupdate
from openflexure_microscope.common.labthings_core.spec import update_spec
from openflexure_microscope.common.labthings_core.decorators import doc, doc_response
from .spec import update_spec
def unpack(value):
@ -76,3 +75,27 @@ class use_kwargs(use_args):
"""
kwargs["as_kwargs"] = True
use_args.__init__(self, schema, **kwargs)
class doc(object):
def __init__(self, **kwargs):
self.kwargs = kwargs
def __call__(self, f):
# Pass params to call function attribute for external access
update_spec(f, self.kwargs)
return f
class doc_response(object):
def __init__(self, code, description, **kwargs):
self.code = code
self.description = description
self.kwargs = kwargs
def __call__(self, f):
# Pass params to call function attribute for external access
f.__apispec__ = f.__dict__.get("__apispec__", {})
d = {"responses": {self.code: {"description": self.description, **self.kwargs}}}
rupdate(f.__apispec__, d)
return f

View file

@ -1 +1 @@
from openflexure_microscope.common.labthings_core.fields import *
from marshmallow.fields import *

View file

@ -2,16 +2,15 @@ from flask import url_for, jsonify
from apispec import APISpec
from apispec.ext.marshmallow import MarshmallowPlugin
from . import EXTENSION_NAME
from .extensions import BaseExtension
from .utilities import description_from_view
from .spec import view2path
from .views.extensions import ExtensionList
from .views.tasks import TaskList, TaskResource
from openflexure_microscope.common.labthings_core.utilities import get_docstring
from openflexure_microscope.common.labthings_core.spec import view2path
from . import EXTENSION_NAME
import logging

View file

@ -1,12 +1,27 @@
from flask.views import MethodView
from openflexure_microscope.common.labthings_core.resource import BaseResource
class Resource(MethodView, BaseResource):
"""Currently identical to MethodView
class Resource(MethodView):
"""
A LabThing Resource class should make use of functions get(), put(), post(), and delete()
corresponding to HTTP methods.
These functions will allow for automated documentation generation
"""
methods = ["get", "post", "put", "delete"]
endpoint = None
def __init__(self, *args, **kwargs):
MethodView.__init__(self, *args, **kwargs)
def doc(self):
docs = {"operations": {}}
if hasattr(self, "__apispec__"):
docs.update(self.__apispec__)
for meth in BaseResource.methods:
if hasattr(self, meth) and hasattr(getattr(self, meth), "__apispec__"):
docs["operations"][meth] = {}
docs["operations"][meth] = getattr(self, meth).__apispec__
return docs

View file

@ -1 +1,39 @@
from openflexure_microscope.common.labthings_core.schema import *
# -*- coding: utf-8 -*-
import flask
import marshmallow
MARSHMALLOW_VERSION_INFO = tuple(
[int(part) for part in marshmallow.__version__.split(".") if part.isdigit()]
)
sentinel = object()
class Schema(marshmallow.Schema):
"""Base serializer with which to define custom serializers.
See `marshmallow.Schema` for more details about the `Schema` API.
"""
def jsonify(self, obj, many=sentinel, *args, **kwargs):
"""Return a JSON response containing the serialized data.
:param obj: Object to serialize.
:param bool many: Whether `obj` should be serialized as an instance
or as a collection. If unset, defaults to the value of the
`many` attribute on this Schema.
:param kwargs: Additional keyword arguments passed to `flask.jsonify`.
.. versionchanged:: 0.6.0
Takes the same arguments as `marshmallow.Schema.dump`. Additional
keyword arguments are passed to `flask.jsonify`.
.. versionchanged:: 0.6.3
The `many` argument for this method defaults to the value of
the `many` attribute on the Schema. Previously, the `many`
argument of this method defaulted to False, regardless of the
value of `Schema.many`.
"""
if many is sentinel:
many = self.many
if MARSHMALLOW_VERSION_INFO[0] >= 3:
data = self.dump(obj, many=many)
else:
data = self.dump(obj, many=many).data
return flask.jsonify(data, *args, **kwargs)

View file

@ -0,0 +1,137 @@
from .resource import Resource
from apispec import APISpec
from apispec.ext.marshmallow import MarshmallowPlugin
from openflexure_microscope.common.labthings_core.utilities import (
get_docstring,
get_summary,
rupdate,
)
from .fields import Field
from marshmallow import Schema as BaseSchema
from collections import Mapping
def update_spec(obj, spec):
obj.__apispec__ = obj.__dict__.get("__apispec__", {})
rupdate(obj.__apispec__, spec)
return obj.__apispec__
def view2path(rule: str, view: Resource, spec: APISpec):
params = {
"path": rule, # TODO: Validate this slightly (leading / etc)
"operations": view2operations(view, spec),
"description": get_docstring(view),
"summary": get_summary(view),
}
if hasattr(view, "__apispec__"):
# Recursively update params
rupdate(params, view.__apispec__)
return params
def view2operations(view: Resource, spec: APISpec, populate_default: bool = True):
ops = {}
for method in Resource.methods:
if hasattr(view, method):
# Populate with default responses
if populate_default:
ops[method] = {
"responses": {
200: {
"description": get_summary(getattr(view, method))
or "Success"
},
404: {"description": "Resource not found"},
}
}
else:
ops[method] = {}
rupdate(
ops[method],
{
"description": get_docstring(getattr(view, method)),
"summary": get_summary(getattr(view, method)),
},
)
if hasattr(getattr(view, method), "__apispec__"):
rupdate(
ops[method], doc2operation(getattr(view, method).__apispec__, spec)
)
return ops
def doc2operation(apispec: dict, spec: APISpec):
op = {}
if "_params" in apispec:
rupdate(
op,
{
"requestBody": {
"content": {
"application/json": {
"schema": convert_schema(apispec.get("_params"), spec)
}
}
}
},
)
if "_schema" in apispec:
rupdate(
op,
{
"responses": {
200: {
"content": {
"application/json": {
"schema": convert_schema(apispec.get("_schema"), spec)
}
}
}
}
},
)
for key, val in apispec.items():
if not key in ["_params", "_schema"]:
op[key] = val
return op
def convert_schema(schema, spec: APISpec):
if isinstance(schema, BaseSchema):
return schema
elif isinstance(schema, Mapping):
return map2properties(schema, spec)
else:
raise TypeError(
"Unsupported schema type. Ensure schema is a Schema class, or dictionary of Field objects"
)
def map2properties(schema, spec: APISpec):
marshmallow_plugin = next(
plugin for plugin in spec.plugins if isinstance(plugin, MarshmallowPlugin)
)
converter = marshmallow_plugin.converter
d = {}
for k, v in schema.items():
if isinstance(v, Field):
d[k] = converter.field2property(v)
elif isinstance(v, Mapping):
d[k] = map2properties(v, spec)
else:
d[k] = v
return {"properties": d}

View file

@ -2,9 +2,9 @@
Top-level representation of attached and enabled Extensions
"""
from openflexure_microscope.common.labthings_core.utilities import get_docstring
from openflexure_microscope.common.labthings_core.schema import Schema
from openflexure_microscope.common.labthings_core import fields
from openflexure_microscope.common.flask_labthings.utilities import get_docstring
from openflexure_microscope.common.flask_labthings.schema import Schema
from openflexure_microscope.common.flask_labthings import fields
from ..utilities import description_from_view
@ -38,9 +38,7 @@ class ExtensionSchema(Schema):
view_rule = view_data["rule"]
# Make links dictionary if it doesn't yet exist
d[view_id] = {
"href": url_for(
ExtensionList.endpoint, **view_kwargs, _external=True
)
"href": url_for(ExtensionList.endpoint, **view_kwargs, _external=True)
+ view_rule,
**description_from_view(view_cls),
}

View file

@ -2,13 +2,13 @@ from flask import abort, url_for
from openflexure_microscope.common.flask_labthings.decorators import marshal_with
from openflexure_microscope.common.flask_labthings.resource import Resource
from openflexure_microscope.common.flask_labthings.schema import Schema
from openflexure_microscope.common.flask_labthings import fields
from openflexure_microscope.common.flask_labthings.utilities import (
description_from_view,
)
from openflexure_microscope.common.labthings_core import tasks
from openflexure_microscope.common.labthings_core.schema import Schema
from openflexure_microscope.common.labthings_core import fields
from marshmallow import pre_dump