From 70149e973237c59e32c66dd0c694225c82962a1d Mon Sep 17 00:00:00 2001 From: jtc42 Date: Mon, 6 Jan 2020 16:45:38 +0000 Subject: [PATCH] Cleaned code structure --- .../api/v2/views/actions/camera.py | 1 + openflexure_microscope/api/v2/views/state.py | 7 +- .../api/v2/views/streams.py | 7 +- .../common/flask_labthings/decorators.py | 30 +-- .../common/flask_labthings/extensions.py | 14 +- .../common/flask_labthings/fields.py | 176 +----------------- .../common/flask_labthings/labthing.py | 17 +- .../common/flask_labthings/resource.py | 15 +- .../common/flask_labthings/schema.py | 40 +--- .../common/flask_labthings/utilities.py | 15 -- .../flask_labthings/views/extensions.py | 14 +- .../common/flask_labthings/views/tasks.py | 7 +- .../common/labthings_core/decorators.py | 26 +++ .../common/labthings_core/fields.py | 1 + .../common/labthings_core/resource.py | 20 ++ .../common/labthings_core/schema.py | 39 ++++ .../spec.py | 8 +- .../common/labthings_core/utilities.py | 53 ++++++ openflexure_microscope/microscope.py | 3 +- openflexure_microscope/utilities.py | 35 ---- 20 files changed, 196 insertions(+), 332 deletions(-) create mode 100644 openflexure_microscope/common/labthings_core/decorators.py create mode 100644 openflexure_microscope/common/labthings_core/fields.py create mode 100644 openflexure_microscope/common/labthings_core/resource.py create mode 100644 openflexure_microscope/common/labthings_core/schema.py rename openflexure_microscope/common/{flask_labthings => labthings_core}/spec.py (94%) diff --git a/openflexure_microscope/api/v2/views/actions/camera.py b/openflexure_microscope/api/v2/views/actions/camera.py index 7ae5635c..900f3f17 100644 --- a/openflexure_microscope/api/v2/views/actions/camera.py +++ b/openflexure_microscope/api/v2/views/actions/camera.py @@ -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 diff --git a/openflexure_microscope/api/v2/views/state.py b/openflexure_microscope/api/v2/views/state.py index 84c37850..e1ae2745 100644 --- a/openflexure_microscope/api/v2/views/state.py +++ b/openflexure_microscope/api/v2/views/state.py @@ -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 diff --git a/openflexure_microscope/api/v2/views/streams.py b/openflexure_microscope/api/v2/views/streams.py index 51d8ecb1..70cb8d3a 100644 --- a/openflexure_microscope/api/v2/views/streams.py +++ b/openflexure_microscope/api/v2/views/streams.py @@ -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 diff --git a/openflexure_microscope/common/flask_labthings/decorators.py b/openflexure_microscope/common/flask_labthings/decorators.py index dbab2caa..fa8e9ac0 100644 --- a/openflexure_microscope/common/flask_labthings/decorators.py +++ b/openflexure_microscope/common/flask_labthings/decorators.py @@ -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 diff --git a/openflexure_microscope/common/flask_labthings/extensions.py b/openflexure_microscope/common/flask_labthings/extensions.py index 66dbfc4e..dabc65f1 100644 --- a/openflexure_microscope/common/flask_labthings/extensions.py +++ b/openflexure_microscope/common/flask_labthings/extensions.py @@ -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 = [] diff --git a/openflexure_microscope/common/flask_labthings/fields.py b/openflexure_microscope/common/flask_labthings/fields.py index 32dbfb18..7d413696 100644 --- a/openflexure_microscope/common/flask_labthings/fields.py +++ b/openflexure_microscope/common/flask_labthings/fields.py @@ -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='') - https_url = URLFor('author_get', 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=''), - 'collection': URLFor('author_list'), - }) - `URLFor` objects can be nested within the dictionary. :: - _links = Hyperlinks({ - 'self': { - 'href': URLFor('book', 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 * diff --git a/openflexure_microscope/common/flask_labthings/labthing.py b/openflexure_microscope/common/flask_labthings/labthing.py index 46ca187d..b1a64db3 100644 --- a/openflexure_microscope/common/flask_labthings/labthing.py +++ b/openflexure_microscope/common/flask_labthings/labthing.py @@ -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), diff --git a/openflexure_microscope/common/flask_labthings/resource.py b/openflexure_microscope/common/flask_labthings/resource.py index fd2575e2..6f598adb 100644 --- a/openflexure_microscope/common/flask_labthings/resource.py +++ b/openflexure_microscope/common/flask_labthings/resource.py @@ -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 diff --git a/openflexure_microscope/common/flask_labthings/schema.py b/openflexure_microscope/common/flask_labthings/schema.py index 83294a0e..b9ae889a 100644 --- a/openflexure_microscope/common/flask_labthings/schema.py +++ b/openflexure_microscope/common/flask_labthings/schema.py @@ -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 * diff --git a/openflexure_microscope/common/flask_labthings/utilities.py b/openflexure_microscope/common/flask_labthings/utilities.py index 4eff13ef..a0b152e5 100644 --- a/openflexure_microscope/common/flask_labthings/utilities.py +++ b/openflexure_microscope/common/flask_labthings/utilities.py @@ -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 diff --git a/openflexure_microscope/common/flask_labthings/views/extensions.py b/openflexure_microscope/common/flask_labthings/views/extensions.py index f4f7c749..a4fcca41 100644 --- a/openflexure_microscope/common/flask_labthings/views/extensions.py +++ b/openflexure_microscope/common/flask_labthings/views/extensions.py @@ -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 """ diff --git a/openflexure_microscope/common/flask_labthings/views/tasks.py b/openflexure_microscope/common/flask_labthings/views/tasks.py index 8c00ec07..310a6d2a 100644 --- a/openflexure_microscope/common/flask_labthings/views/tasks.py +++ b/openflexure_microscope/common/flask_labthings/views/tasks.py @@ -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 diff --git a/openflexure_microscope/common/labthings_core/decorators.py b/openflexure_microscope/common/labthings_core/decorators.py new file mode 100644 index 00000000..0fa8775e --- /dev/null +++ b/openflexure_microscope/common/labthings_core/decorators.py @@ -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 diff --git a/openflexure_microscope/common/labthings_core/fields.py b/openflexure_microscope/common/labthings_core/fields.py new file mode 100644 index 00000000..b8eb3d36 --- /dev/null +++ b/openflexure_microscope/common/labthings_core/fields.py @@ -0,0 +1 @@ +from marshmallow.fields import * diff --git a/openflexure_microscope/common/labthings_core/resource.py b/openflexure_microscope/common/labthings_core/resource.py new file mode 100644 index 00000000..57882a94 --- /dev/null +++ b/openflexure_microscope/common/labthings_core/resource.py @@ -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 diff --git a/openflexure_microscope/common/labthings_core/schema.py b/openflexure_microscope/common/labthings_core/schema.py new file mode 100644 index 00000000..83294a0e --- /dev/null +++ b/openflexure_microscope/common/labthings_core/schema.py @@ -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) diff --git a/openflexure_microscope/common/flask_labthings/spec.py b/openflexure_microscope/common/labthings_core/spec.py similarity index 94% rename from openflexure_microscope/common/flask_labthings/spec.py rename to openflexure_microscope/common/labthings_core/spec.py index dd53defa..99ba0015 100644 --- a/openflexure_microscope/common/flask_labthings/spec.py +++ b/openflexure_microscope/common/labthings_core/spec.py @@ -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: diff --git a/openflexure_microscope/common/labthings_core/utilities.py b/openflexure_microscope/common/labthings_core/utilities.py index f2795517..1d0ae94a 100644 --- a/openflexure_microscope/common/labthings_core/utilities.py +++ b/openflexure_microscope/common/labthings_core/utilities.py @@ -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("_", "-") diff --git a/openflexure_microscope/microscope.py b/openflexure_microscope/microscope.py index 2624d658..e57e1882 100644 --- a/openflexure_microscope/microscope.py +++ b/openflexure_microscope/microscope.py @@ -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: """ diff --git a/openflexure_microscope/utilities.py b/openflexure_microscope/utilities.py index 0cf33c5c..64379f4d 100644 --- a/openflexure_microscope/utilities.py +++ b/openflexure_microscope/utilities.py @@ -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.