Cleaned code structure
This commit is contained in:
parent
79abc3fee3
commit
70149e9732
20 changed files with 196 additions and 332 deletions
|
|
@ -7,6 +7,7 @@ from openflexure_microscope.common.flask_labthings.decorators import (
|
|||
doc,
|
||||
doc_response,
|
||||
)
|
||||
|
||||
from openflexure_microscope.common.flask_labthings import fields
|
||||
from openflexure_microscope.utilities import filter_dict
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,10 @@
|
|||
from openflexure_microscope.api.utilities import JsonResponse
|
||||
from openflexure_microscope.utilities import get_by_path, set_by_path, create_from_path
|
||||
|
||||
from openflexure_microscope.common.labthings_core.utilities import (
|
||||
get_by_path,
|
||||
set_by_path,
|
||||
create_from_path,
|
||||
)
|
||||
|
||||
from openflexure_microscope.common.flask_labthings.find import find_device
|
||||
from openflexure_microscope.common.flask_labthings.resource import Resource
|
||||
|
|
|
|||
|
|
@ -1,5 +1,10 @@
|
|||
from openflexure_microscope.api.utilities import gen, JsonResponse
|
||||
from openflexure_microscope.utilities import get_by_path, set_by_path, create_from_path
|
||||
|
||||
from openflexure_microscope.common.labthings_core.utilities import (
|
||||
get_by_path,
|
||||
set_by_path,
|
||||
create_from_path,
|
||||
)
|
||||
|
||||
from openflexure_microscope.common.flask_labthings.find import find_device
|
||||
from openflexure_microscope.common.flask_labthings.resource import Resource
|
||||
|
|
|
|||
|
|
@ -2,8 +2,10 @@ from webargs import flaskparser
|
|||
from functools import wraps, update_wrapper
|
||||
from flask import make_response
|
||||
|
||||
from .utilities import rupdate
|
||||
from .spec import update_spec
|
||||
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
|
||||
|
||||
|
||||
def unpack(value):
|
||||
|
|
@ -74,27 +76,3 @@ 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
|
||||
|
|
|
|||
|
|
@ -7,8 +7,14 @@ import sys
|
|||
import os
|
||||
import glob
|
||||
|
||||
from openflexure_microscope.common.labthings_core.utilities import get_docstring
|
||||
from openflexure_microscope.utilities import camel_to_snake, snake_to_spine
|
||||
from openflexure_microscope.common.labthings_core.utilities import (
|
||||
get_docstring,
|
||||
camel_to_snake,
|
||||
snake_to_spine,
|
||||
)
|
||||
|
||||
|
||||
# TODO: Move into core?
|
||||
|
||||
|
||||
class BaseExtension:
|
||||
|
|
@ -134,7 +140,7 @@ def find_instances_in_module(module, class_to_find):
|
|||
return objs
|
||||
|
||||
|
||||
def find_extensions_in_file(extension_path, module_name="extensions"):
|
||||
def find_extensions_in_file(extension_path: str, module_name="extensions"):
|
||||
logging.debug(f"Loading extensions from {extension_path}")
|
||||
|
||||
spec = util.spec_from_file_location(module_name, extension_path)
|
||||
|
|
@ -149,7 +155,7 @@ def find_extensions_in_file(extension_path, module_name="extensions"):
|
|||
return find_instances_in_module(mod, BaseExtension)
|
||||
|
||||
|
||||
def find_extensions(extension_dir, module_name="extensions"):
|
||||
def find_extensions(extension_dir: str, module_name="extensions"):
|
||||
logging.debug(f"Loading extensions from {extension_dir}")
|
||||
|
||||
extensions = []
|
||||
|
|
|
|||
|
|
@ -1,175 +1 @@
|
|||
from marshmallow.fields import *
|
||||
from marshmallow import missing
|
||||
import re
|
||||
from flask import url_for
|
||||
from flask.views import View
|
||||
|
||||
_tpl_pattern = re.compile(r"\s*<\s*(\S*)\s*>\s*")
|
||||
|
||||
|
||||
def _tpl(val):
|
||||
"""Return value within ``< >`` if possible, else return ``None``."""
|
||||
match = _tpl_pattern.match(val)
|
||||
if match:
|
||||
return match.groups()[0]
|
||||
return None
|
||||
|
||||
|
||||
def _get_value(obj, key, default=missing):
|
||||
"""Slightly-modified version of marshmallow.utils.get_value.
|
||||
If a dot-delimited ``key`` is passed and any attribute in the
|
||||
path is `None`, return `None`.
|
||||
"""
|
||||
if "." in key:
|
||||
return _get_value_for_keys(obj, key.split("."), default)
|
||||
else:
|
||||
return _get_value_for_key(obj, key, default)
|
||||
|
||||
|
||||
def _get_value_for_keys(obj, keys, default):
|
||||
if len(keys) == 1:
|
||||
return _get_value_for_key(obj, keys[0], default)
|
||||
else:
|
||||
value = _get_value_for_key(obj, keys[0], default)
|
||||
# XXX This differs from the marshmallow implementation
|
||||
if value is None:
|
||||
return None
|
||||
return _get_value_for_keys(value, keys[1:], default)
|
||||
|
||||
|
||||
def _get_value_for_key(obj, key, default):
|
||||
if not hasattr(obj, "__getitem__"):
|
||||
return getattr(obj, key, default)
|
||||
|
||||
try:
|
||||
return obj[key]
|
||||
except (KeyError, IndexError, TypeError, AttributeError):
|
||||
return getattr(obj, key, default)
|
||||
|
||||
|
||||
class URLFor(Field):
|
||||
"""Field that outputs the URL for an endpoint. Acts identically to
|
||||
Flask's ``url_for`` function, except that arguments can be pulled from the
|
||||
object to be serialized.
|
||||
Usage: ::
|
||||
url = URLFor('author_get', id='<id>')
|
||||
https_url = URLFor('author_get', id='<id>', _scheme='https', _external=True)
|
||||
:param str endpoint: Flask endpoint name.
|
||||
:param kwargs: Same keyword arguments as Flask's url_for, except string
|
||||
arguments enclosed in `< >` will be interpreted as attributes to pull
|
||||
from the object.
|
||||
"""
|
||||
|
||||
_CHECK_ATTRIBUTE = False
|
||||
|
||||
def __init__(self, endpoint, **kwargs):
|
||||
# Handle the case where endpoint is an attached flask View of any kind
|
||||
if isinstance(endpoint, type) and issubclass(endpoint, View):
|
||||
self.view_class = endpoint
|
||||
self.endpoint = None
|
||||
# Handle cases where endpoint is passed directly as a string
|
||||
elif type(endpoint) == str:
|
||||
self.view_class = None
|
||||
self.endpoint = endpoint
|
||||
else:
|
||||
raise RuntimeError(
|
||||
f"Endpoint {endpoint} is not a valid Flask view or endpoint string."
|
||||
)
|
||||
self.params = kwargs
|
||||
Field.__init__(self, **kwargs)
|
||||
|
||||
def _serialize(self, value, key, obj):
|
||||
"""Output the URL for the endpoint, given the kwargs passed to
|
||||
``__init__``.
|
||||
"""
|
||||
# Get endpoint from view_class, if needed
|
||||
if self.view_class and not self.endpoint:
|
||||
if hasattr(self.view_class, "endpoint"):
|
||||
self.endpoint = self.view_class.endpoint
|
||||
else:
|
||||
raise RuntimeError(
|
||||
f"Resource {self.endpoint} has not been added to a LabThing application. Unable to generate URL."
|
||||
)
|
||||
|
||||
# Generate URL for
|
||||
param_values = {}
|
||||
for name, attr_tpl in self.params.items():
|
||||
attr_name = _tpl(str(attr_tpl))
|
||||
if attr_name:
|
||||
attribute_value = _get_value(obj, attr_name, default=missing)
|
||||
if attribute_value is None:
|
||||
return None
|
||||
if attribute_value is not missing:
|
||||
param_values[name] = attribute_value
|
||||
else:
|
||||
raise AttributeError(
|
||||
"{attr_name!r} is not a valid "
|
||||
"attribute of {obj!r}".format(attr_name=attr_name, obj=obj)
|
||||
)
|
||||
else:
|
||||
param_values[name] = attr_tpl
|
||||
return url_for(self.endpoint, **param_values)
|
||||
|
||||
|
||||
UrlFor = URLFor
|
||||
|
||||
|
||||
class AbsoluteURLFor(URLFor):
|
||||
"""Field that outputs the absolute URL for an endpoint."""
|
||||
|
||||
def __init__(self, endpoint, **kwargs):
|
||||
kwargs["_external"] = True
|
||||
URLFor.__init__(self, endpoint=endpoint, **kwargs)
|
||||
|
||||
|
||||
AbsoluteUrlFor = AbsoluteURLFor
|
||||
|
||||
|
||||
def _rapply(d, func, *args, **kwargs):
|
||||
"""Apply a function to all values in a dictionary or list of dictionaries, recursively."""
|
||||
if isinstance(d, (tuple, list)):
|
||||
return [_rapply(each, func, *args, **kwargs) for each in d]
|
||||
if isinstance(d, dict):
|
||||
return {key: _rapply(value, func, *args, **kwargs) for key, value in d.items()}
|
||||
else:
|
||||
return func(d, *args, **kwargs)
|
||||
|
||||
|
||||
def _url_val(val, key, obj, **kwargs):
|
||||
"""Function applied by `HyperlinksField` to get the correct value in the
|
||||
schema.
|
||||
"""
|
||||
if isinstance(val, URLFor):
|
||||
return val.serialize(key, obj, **kwargs)
|
||||
else:
|
||||
return val
|
||||
|
||||
|
||||
class Hyperlinks(Field):
|
||||
"""Field that outputs a dictionary of hyperlinks,
|
||||
given a dictionary schema with :class:`~flask_marshmallow.fields.URLFor`
|
||||
objects as values.
|
||||
Example: ::
|
||||
_links = Hyperlinks({
|
||||
'self': URLFor('author', id='<id>'),
|
||||
'collection': URLFor('author_list'),
|
||||
})
|
||||
`URLFor` objects can be nested within the dictionary. ::
|
||||
_links = Hyperlinks({
|
||||
'self': {
|
||||
'href': URLFor('book', id='<id>'),
|
||||
'title': 'book detail'
|
||||
}
|
||||
})
|
||||
:param dict schema: A dict that maps names to
|
||||
:class:`~fields.URLFor` fields.
|
||||
"""
|
||||
|
||||
_CHECK_ATTRIBUTE = False
|
||||
|
||||
def __init__(self, schema, **kwargs):
|
||||
self.schema = schema
|
||||
Field.__init__(self, **kwargs)
|
||||
|
||||
def _serialize(self, value, attr, obj):
|
||||
return _rapply(self.schema, _url_val, key=attr, obj=obj)
|
||||
from openflexure_microscope.common.labthings_core.fields import *
|
||||
|
|
|
|||
|
|
@ -3,14 +3,13 @@ from apispec import APISpec
|
|||
from apispec.ext.marshmallow import MarshmallowPlugin
|
||||
|
||||
from .extensions import BaseExtension
|
||||
from .views.extensions import ExtensionListResource
|
||||
from .views.tasks import TaskList, TaskResource
|
||||
|
||||
from .spec import view2path
|
||||
|
||||
from .utilities import description_from_view
|
||||
|
||||
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
|
||||
|
||||
|
|
@ -114,8 +113,8 @@ class LabThing(object):
|
|||
)
|
||||
|
||||
# Add extension overview
|
||||
self.add_resource(ExtensionListResource, "/extensions")
|
||||
self.register_property(ExtensionListResource)
|
||||
self.add_resource(ExtensionList, "/extensions")
|
||||
self.register_property(ExtensionList)
|
||||
# Add task routes
|
||||
self.add_resource(TaskList, "/tasks")
|
||||
self.register_property(TaskList)
|
||||
|
|
@ -320,8 +319,8 @@ class LabThing(object):
|
|||
"methods": ["GET"],
|
||||
},
|
||||
"extensions": {
|
||||
"href": self.url_for(ExtensionListResource, _external=True),
|
||||
**description_from_view(ExtensionListResource),
|
||||
"href": self.url_for(ExtensionList, _external=True),
|
||||
**description_from_view(ExtensionList),
|
||||
},
|
||||
"tasks": {
|
||||
"href": self.url_for(TaskList, _external=True),
|
||||
|
|
|
|||
|
|
@ -1,23 +1,12 @@
|
|||
from flask.views import MethodView
|
||||
from openflexure_microscope.common.labthings_core.resource import BaseResource
|
||||
|
||||
|
||||
class Resource(MethodView):
|
||||
class Resource(MethodView, BaseResource):
|
||||
"""Currently identical to MethodView
|
||||
"""
|
||||
|
||||
endpoint = None
|
||||
methods = ["get", "post", "put", "delete"]
|
||||
|
||||
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 Resource.methods:
|
||||
if hasattr(self, meth) and hasattr(getattr(self, meth), "__apispec__"):
|
||||
docs["operations"][meth] = {}
|
||||
docs["operations"][meth] = getattr(self, meth).__apispec__
|
||||
return docs
|
||||
|
|
|
|||
|
|
@ -1,39 +1 @@
|
|||
# -*- 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)
|
||||
from openflexure_microscope.common.labthings_core.schema import *
|
||||
|
|
|
|||
|
|
@ -2,10 +2,6 @@ from openflexure_microscope.common.labthings_core.utilities import (
|
|||
get_docstring,
|
||||
get_summary,
|
||||
)
|
||||
from .schema import Schema, marshmallow, MARSHMALLOW_VERSION_INFO
|
||||
import collections.abc
|
||||
|
||||
import logging
|
||||
|
||||
|
||||
def description_from_view(view_class):
|
||||
|
|
@ -18,14 +14,3 @@ def description_from_view(view_class):
|
|||
d = {"methods": methods, "description": summary}
|
||||
|
||||
return d
|
||||
|
||||
|
||||
def rupdate(d, u):
|
||||
for k, v in u.items():
|
||||
if isinstance(v, collections.abc.Mapping):
|
||||
if not k in d:
|
||||
d[k] = {}
|
||||
d[k] = rupdate(d.get(k, {}), v)
|
||||
else:
|
||||
d[k] = v
|
||||
return d
|
||||
|
|
|
|||
|
|
@ -3,13 +3,15 @@ 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 ..utilities import description_from_view
|
||||
|
||||
from openflexure_microscope.common.flask_labthings.resource import Resource
|
||||
from openflexure_microscope.common.flask_labthings.find import registered_extensions
|
||||
from openflexure_microscope.common.flask_labthings.schema import Schema
|
||||
from openflexure_microscope.common.flask_labthings.decorators import marshal_with
|
||||
from openflexure_microscope.common.flask_labthings import fields
|
||||
from ..resource import Resource
|
||||
from ..find import registered_extensions
|
||||
|
||||
from ..decorators import marshal_with
|
||||
from marshmallow import pre_dump
|
||||
|
||||
from flask import jsonify, url_for
|
||||
|
|
@ -48,7 +50,7 @@ class ExtensionSchema(Schema):
|
|||
return data
|
||||
|
||||
|
||||
class ExtensionListResource(Resource):
|
||||
class ExtensionList(Resource):
|
||||
"""
|
||||
List and basic documentation for all enabled Extensions
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
from flask import abort, url_for
|
||||
|
||||
from openflexure_microscope.common.flask_labthings.schema import Schema
|
||||
from openflexure_microscope.common.flask_labthings.decorators import marshal_with
|
||||
from openflexure_microscope.common.flask_labthings import fields
|
||||
from openflexure_microscope.common.labthings_core import tasks
|
||||
from openflexure_microscope.common.flask_labthings.resource import Resource
|
||||
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
|
||||
|
||||
|
||||
|
|
|
|||
26
openflexure_microscope/common/labthings_core/decorators.py
Normal file
26
openflexure_microscope/common/labthings_core/decorators.py
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
from .spec import update_spec
|
||||
from .utilities import rupdate
|
||||
|
||||
|
||||
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
|
||||
1
openflexure_microscope/common/labthings_core/fields.py
Normal file
1
openflexure_microscope/common/labthings_core/fields.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
from marshmallow.fields import *
|
||||
20
openflexure_microscope/common/labthings_core/resource.py
Normal file
20
openflexure_microscope/common/labthings_core/resource.py
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
class BaseResource:
|
||||
"""
|
||||
A LabThing Resource class should, regardless of the underlying framework, 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"]
|
||||
|
||||
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
|
||||
39
openflexure_microscope/common/labthings_core/schema.py
Normal file
39
openflexure_microscope/common/labthings_core/schema.py
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
# -*- 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)
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
from .resource import Resource
|
||||
from .resource import BaseResource
|
||||
from .utilities import rupdate
|
||||
from apispec import APISpec
|
||||
from apispec.ext.marshmallow import MarshmallowPlugin
|
||||
|
|
@ -20,7 +20,7 @@ def update_spec(obj, spec):
|
|||
return obj.__apispec__
|
||||
|
||||
|
||||
def view2path(rule: str, view: Resource, spec: APISpec):
|
||||
def view2path(rule: str, view: BaseResource, spec: APISpec):
|
||||
params = {
|
||||
"path": rule, # TODO: Validate this slightly (leading / etc)
|
||||
"operations": view2operations(view, spec),
|
||||
|
|
@ -35,9 +35,9 @@ def view2path(rule: str, view: Resource, spec: APISpec):
|
|||
return params
|
||||
|
||||
|
||||
def view2operations(view: Resource, spec: APISpec, populate_default: bool = True):
|
||||
def view2operations(view: BaseResource, spec: APISpec, populate_default: bool = True):
|
||||
ops = {}
|
||||
for method in Resource.methods:
|
||||
for method in BaseResource.methods:
|
||||
if hasattr(view, method):
|
||||
# Populate with default responses
|
||||
if populate_default:
|
||||
|
|
@ -1,3 +1,10 @@
|
|||
import collections.abc
|
||||
import re
|
||||
import base64
|
||||
import operator
|
||||
from functools import reduce
|
||||
|
||||
|
||||
def get_docstring(obj):
|
||||
ds = obj.__doc__
|
||||
if ds:
|
||||
|
|
@ -8,3 +15,49 @@ def get_docstring(obj):
|
|||
|
||||
def get_summary(obj):
|
||||
return get_docstring(obj).partition("\n")[0].strip()
|
||||
|
||||
|
||||
def rupdate(d, u):
|
||||
for k, v in u.items():
|
||||
if isinstance(v, collections.abc.Mapping):
|
||||
if not k in d:
|
||||
d[k] = {}
|
||||
d[k] = rupdate(d.get(k, {}), v)
|
||||
else:
|
||||
d[k] = v
|
||||
return d
|
||||
|
||||
|
||||
def get_by_path(root, items):
|
||||
"""Access a nested object in root by item sequence."""
|
||||
return reduce(operator.getitem, items, root)
|
||||
|
||||
|
||||
def set_by_path(root, items, value):
|
||||
"""Set a value in a nested object in root by item sequence."""
|
||||
get_by_path(root, items[:-1])[items[-1]] = value
|
||||
|
||||
|
||||
def create_from_path(items):
|
||||
tree_dict = {}
|
||||
for key in reversed(items):
|
||||
tree_dict = {key: tree_dict}
|
||||
return tree_dict
|
||||
|
||||
|
||||
def bottom_level_name(obj):
|
||||
return obj.__name__.split(".")[-1]
|
||||
|
||||
|
||||
def camel_to_snake(name):
|
||||
s1 = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", name)
|
||||
return re.sub("([a-z0-9])([A-Z])", r"\1_\2", s1).lower()
|
||||
|
||||
|
||||
def camel_to_spine(name):
|
||||
s1 = re.sub("(.)([A-Z][a-z]+)", r"\1-\2", name)
|
||||
return re.sub("([a-z0-9])([A-Z])", r"\1-\2", s1).lower()
|
||||
|
||||
|
||||
def snake_to_spine(name):
|
||||
return name.replace("_", "-")
|
||||
|
|
|
|||
|
|
@ -12,9 +12,10 @@ from openflexure_microscope.camera.base import BaseCamera
|
|||
from openflexure_microscope.camera.mock import MockStreamer
|
||||
|
||||
from openflexure_microscope.utilities import serialise_array_b64
|
||||
from openflexure_microscope.common.labthings_core.lock import CompositeLock
|
||||
from openflexure_microscope.config import user_settings
|
||||
|
||||
from openflexure_microscope.common.labthings_core.lock import CompositeLock
|
||||
|
||||
|
||||
class Microscope:
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -20,41 +20,6 @@ def serialise_array_b64(npy_arr):
|
|||
return b64_string, dtype, shape
|
||||
|
||||
|
||||
def get_by_path(root, items):
|
||||
"""Access a nested object in root by item sequence."""
|
||||
return reduce(operator.getitem, items, root)
|
||||
|
||||
|
||||
def set_by_path(root, items, value):
|
||||
"""Set a value in a nested object in root by item sequence."""
|
||||
get_by_path(root, items[:-1])[items[-1]] = value
|
||||
|
||||
|
||||
def create_from_path(items):
|
||||
tree_dict = {}
|
||||
for key in reversed(items):
|
||||
tree_dict = {key: tree_dict}
|
||||
return tree_dict
|
||||
|
||||
|
||||
def bottom_level_name(obj):
|
||||
return obj.__name__.split(".")[-1]
|
||||
|
||||
|
||||
def camel_to_snake(name):
|
||||
s1 = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", name)
|
||||
return re.sub("([a-z0-9])([A-Z])", r"\1_\2", s1).lower()
|
||||
|
||||
|
||||
def camel_to_spine(name):
|
||||
s1 = re.sub("(.)([A-Z][a-z]+)", r"\1-\2", name)
|
||||
return re.sub("([a-z0-9])([A-Z])", r"\1-\2", s1).lower()
|
||||
|
||||
|
||||
def snake_to_spine(name):
|
||||
return name.replace("_", "-")
|
||||
|
||||
|
||||
@contextmanager
|
||||
def set_properties(obj, **kwargs):
|
||||
"""A context manager to set, then reset, certain properties of an object.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue