Moved to a LabThings dependency for server
This commit is contained in:
parent
a1223d8b28
commit
36334ed743
65 changed files with 546 additions and 2364 deletions
|
|
@ -10,6 +10,8 @@ if "%SPHINXBUILD%" == "" (
|
|||
set SOURCEDIR=source
|
||||
set BUILDDIR=build
|
||||
|
||||
set SPHINXOPTS="-E"
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
%SPHINXBUILD% >NUL 2>NUL
|
||||
|
|
|
|||
|
|
@ -3,5 +3,5 @@ Classes and Modules
|
|||
|
||||
Extension class
|
||||
---------------
|
||||
.. autoclass:: openflexure_microscope.common.flask_labthings.extensions.BaseExtension
|
||||
.. autoclass:: labthings.server.extensions.BaseExtension
|
||||
:members:
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ from openflexure_microscope.plugins import MicroscopePlugin
|
|||
from openflexure_microscope.api.views import MicroscopeViewPlugin
|
||||
from openflexure_microscope.api.utilities import JsonResponse
|
||||
|
||||
from openflexure_microscope.common.labthings_core.tasks import taskify
|
||||
from labthings.core.tasks import taskify
|
||||
|
||||
import os
|
||||
import time
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ Tasks are introduced to manage long-running functions in a way that does not blo
|
|||
the use of tasks, long-running functions would block an HTTP response until the function returns, often
|
||||
resulting in a timeout.
|
||||
|
||||
To prevent this, any function can be offloaded to a background task. This is done through the :py:meth:`openflexure_microscope.common.tasks.taskify`
|
||||
To prevent this, any function can be offloaded to a background task. This is done through the :py:meth:`labthings.tasks.taskify`
|
||||
function, by calling ``taskify(<long_running_function>)(*args, **kwargs)``. Internally, the ``tasks`` submodule stores a list
|
||||
of all requested tasks, which themselves contain a ``state`` dictionary. This dictionary stores the status of the task (if it
|
||||
is idle, running, error, or success), information about the start and end times, a unique task ID, and the return value of
|
||||
|
|
@ -38,7 +38,7 @@ An example of a long running task may look like:
|
|||
.. code-block:: python
|
||||
|
||||
from openflexure_microscope.plugins import MicroscopeViewPlugin
|
||||
from openflexure_microscope.common.tasks import taskify
|
||||
from labthings.tasks import taskify
|
||||
|
||||
class MyPlugin(MicroscopeViewPlugin):
|
||||
def post(self):
|
||||
|
|
@ -63,7 +63,7 @@ the microscope hardware. For example, even if the stage is not actively moving (
|
|||
within a tile scan), another user should not be able to move the microscope, interrupting the task. Thread locks act
|
||||
to prevent this.
|
||||
|
||||
The camera and stage both contain an instance of :py:class:`openflexure_microscope.common.lock.StrictLock`, named ``lock``.
|
||||
The camera and stage both contain an instance of :py:class:`labthings.lock.StrictLock`, named ``lock``.
|
||||
Built-in functions such as capture and move will always acquire this lock for the duration of the function. This ensures
|
||||
that, for example, simultaneous attemps to move do not occur.
|
||||
|
||||
|
|
@ -77,7 +77,7 @@ For example, a timelapse plugin may look like:
|
|||
from openflexure_microscope.api.v1.views import MicroscopeViewPlugin
|
||||
from openflexure_microscope.api.utilities import JsonResponse
|
||||
|
||||
from openflexure_microscope.common.tasks import taskify
|
||||
from labthings.tasks import taskify
|
||||
|
||||
|
||||
### MICROSCOPE PLUGIN ###
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
Basic extension structure
|
||||
=========================
|
||||
|
||||
An extension starts as a simple instance of :py:class:`openflexure_microscope.common.flask_labthings.extensions.BaseExtension`.
|
||||
An extension starts as a simple instance of :py:class:`labthings.server.extensions.BaseExtension`.
|
||||
Each extension is described by a single ``BaseExtension`` instance, containing any number of methods, API views, and additional hardware components.
|
||||
|
||||
In order to access the currently running microscope object, use the :py:func:`openflexure_microscope.common.flask_labthings.find` function, with the argument ``"org.openflexure.microscope"``. Likewise, any new components attached by other extensions can be found using their full name, as above.
|
||||
In order to access the currently running microscope object, use the :py:func:`labthings.server.find` function, with the argument ``"org.openflexure.microscope"``. Likewise, any new components attached by other extensions can be found using their full name, as above.
|
||||
|
||||
A simple extension file, with no API views but application-available methods may look like:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from openflexure_microscope.common.flask_labthings.extensions import BaseExtension
|
||||
from openflexure_microscope.common.flask_labthings.find import find_component
|
||||
from labthings.server.extensions import BaseExtension
|
||||
from labthings.server.find import find_component
|
||||
|
||||
|
||||
def identify():
|
||||
|
|
@ -50,7 +50,7 @@ Once this extension is loaded, any other extensions will have access to your met
|
|||
|
||||
.. code-block:: python
|
||||
|
||||
from openflexure_microscope.common.flask_labthings.find import find_extension
|
||||
from labthings.server.find import find_extension
|
||||
|
||||
def test_extension_method():
|
||||
# Find your extension. Returns None if it hasn't been found.
|
||||
|
|
|
|||
|
|
@ -4,4 +4,3 @@ __version__ = "0.1.0"
|
|||
from .microscope import Microscope
|
||||
from . import config
|
||||
from . import utilities
|
||||
from . import common
|
||||
|
|
|
|||
|
|
@ -22,8 +22,8 @@ from openflexure_microscope.paths import (
|
|||
settings_file_path,
|
||||
)
|
||||
|
||||
from openflexure_microscope.common.flask_labthings.quick import create_app
|
||||
from openflexure_microscope.common.flask_labthings.extensions import find_extensions
|
||||
from labthings.server.quick import create_app
|
||||
from labthings.server.extensions import find_extensions
|
||||
|
||||
from openflexure_microscope.api.microscope import default_microscope as api_microscope
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
from openflexure_microscope.common.flask_labthings.find import find_component
|
||||
from openflexure_microscope.common.flask_labthings.extensions import BaseExtension
|
||||
from openflexure_microscope.common.flask_labthings.view import View
|
||||
from openflexure_microscope.common.flask_labthings.decorators import (
|
||||
from labthings.server.find import find_component
|
||||
from labthings.server.extensions import BaseExtension
|
||||
from labthings.server.view import View
|
||||
from labthings.server.decorators import (
|
||||
ThingAction,
|
||||
ThingProperty,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
from openflexure_microscope.common.flask_labthings.view import View
|
||||
from openflexure_microscope.common.flask_labthings.find import find_component
|
||||
from openflexure_microscope.common.flask_labthings.extensions import BaseExtension
|
||||
from openflexure_microscope.common.flask_labthings.decorators import (
|
||||
from labthings.server.view import View
|
||||
from labthings.server.find import find_component
|
||||
from labthings.server.extensions import BaseExtension
|
||||
from labthings.server.decorators import (
|
||||
marshal_task,
|
||||
ThingAction,
|
||||
)
|
||||
|
||||
from openflexure_microscope.common.labthings_core.tasks import taskify
|
||||
from labthings.core.tasks import taskify
|
||||
|
||||
from flask import abort
|
||||
|
||||
|
|
|
|||
|
|
@ -5,21 +5,21 @@ from typing import Tuple
|
|||
from functools import reduce
|
||||
|
||||
from openflexure_microscope.camera.base import generate_basename
|
||||
from openflexure_microscope.common.flask_labthings.find import (
|
||||
from labthings.server.find import (
|
||||
find_component,
|
||||
find_extension,
|
||||
)
|
||||
from openflexure_microscope.common.flask_labthings.extensions import BaseExtension
|
||||
from openflexure_microscope.common.flask_labthings.decorators import (
|
||||
from labthings.server.extensions import BaseExtension
|
||||
from labthings.server.decorators import (
|
||||
marshal_task,
|
||||
use_args,
|
||||
ThingAction,
|
||||
)
|
||||
from openflexure_microscope.common.flask_labthings import fields
|
||||
from labthings.server import fields
|
||||
|
||||
from openflexure_microscope.devel import taskify, abort, update_task_progress
|
||||
|
||||
from openflexure_microscope.common.flask_labthings.view import View
|
||||
from labthings.server.view import View
|
||||
import time
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -14,10 +14,10 @@ import zipfile
|
|||
import tempfile
|
||||
import logging
|
||||
|
||||
from openflexure_microscope.common.flask_labthings.find import find_component
|
||||
from openflexure_microscope.common.flask_labthings.view import View
|
||||
from openflexure_microscope.common.flask_labthings.extensions import BaseExtension
|
||||
from openflexure_microscope.common.flask_labthings.decorators import (
|
||||
from labthings.server.find import find_component
|
||||
from labthings.server.view import View
|
||||
from labthings.server.extensions import BaseExtension
|
||||
from labthings.server.decorators import (
|
||||
ThingAction,
|
||||
ThingProperty,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
from openflexure_microscope.common.flask_labthings.view import View
|
||||
from openflexure_microscope.common.flask_labthings.extensions import BaseExtension
|
||||
from openflexure_microscope.common.flask_labthings.decorators import (
|
||||
from labthings.server.view import View
|
||||
from labthings.server.extensions import BaseExtension
|
||||
from labthings.server.decorators import (
|
||||
ThingAction,
|
||||
ThingProperty,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@ Top-level representation of enabled actions
|
|||
|
||||
from . import camera, stage, system
|
||||
|
||||
from openflexure_microscope.common.flask_labthings.view import View
|
||||
from openflexure_microscope.common.flask_labthings.find import current_labthing
|
||||
from openflexure_microscope.common.flask_labthings.utilities import description_from_view
|
||||
from openflexure_microscope.common.flask_labthings.decorators import Tag
|
||||
from labthings.server.view import View
|
||||
from labthings.server.find import current_labthing
|
||||
from labthings.server.utilities import description_from_view
|
||||
from labthings.server.decorators import Tag
|
||||
|
||||
_actions = {
|
||||
"capture": {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
from openflexure_microscope.api.utilities import get_bool, JsonResponse
|
||||
from openflexure_microscope.common.flask_labthings.view import View
|
||||
from openflexure_microscope.common.flask_labthings.find import find_component
|
||||
from openflexure_microscope.common.flask_labthings.decorators import (
|
||||
from labthings.server.view import View
|
||||
from labthings.server.find import find_component
|
||||
from labthings.server.decorators import (
|
||||
use_args,
|
||||
marshal_with,
|
||||
doc,
|
||||
|
|
@ -10,7 +10,7 @@ from openflexure_microscope.common.flask_labthings.decorators import (
|
|||
doc_response,
|
||||
)
|
||||
|
||||
from openflexure_microscope.common.flask_labthings import fields
|
||||
from labthings.server import fields
|
||||
from openflexure_microscope.utilities import filter_dict
|
||||
|
||||
from openflexure_microscope.api.v2.views.captures import capture_schema
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
from openflexure_microscope.api.utilities import JsonResponse
|
||||
from openflexure_microscope.common.flask_labthings.view import View
|
||||
from openflexure_microscope.common.flask_labthings.find import find_component
|
||||
from openflexure_microscope.common.flask_labthings.decorators import (
|
||||
from labthings.server.view import View
|
||||
from labthings.server.find import find_component
|
||||
from labthings.server.decorators import (
|
||||
use_args,
|
||||
marshal_with,
|
||||
doc,
|
||||
ThingAction,
|
||||
)
|
||||
from openflexure_microscope.common.flask_labthings import fields
|
||||
from labthings.server import fields
|
||||
|
||||
from openflexure_microscope.utilities import axes_to_array, filter_dict
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
from openflexure_microscope.common.flask_labthings.view import View
|
||||
from labthings.server.view import View
|
||||
import subprocess
|
||||
import os
|
||||
from sys import platform
|
||||
|
||||
from openflexure_microscope.common.flask_labthings.decorators import (
|
||||
from labthings.server.decorators import (
|
||||
ThingAction,
|
||||
doc_response,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -3,15 +3,15 @@ from flask import abort, request, redirect, url_for, send_file, jsonify
|
|||
|
||||
from openflexure_microscope.api.utilities import get_bool, JsonResponse
|
||||
|
||||
from openflexure_microscope.common.flask_labthings.schema import Schema
|
||||
from openflexure_microscope.common.flask_labthings import fields
|
||||
from openflexure_microscope.common.flask_labthings.view import View
|
||||
from openflexure_microscope.common.flask_labthings.utilities import (
|
||||
from labthings.server.schema import Schema
|
||||
from labthings.server import fields
|
||||
from labthings.server.view import View
|
||||
from labthings.server.utilities import (
|
||||
description_from_view,
|
||||
)
|
||||
from openflexure_microscope.common.flask_labthings.decorators import marshal_with, doc_response, Tag, ThingProperty
|
||||
from labthings.server.decorators import marshal_with, doc_response, Tag, ThingProperty
|
||||
|
||||
from openflexure_microscope.common.flask_labthings.find import find_component
|
||||
from labthings.server.find import find_component
|
||||
|
||||
from marshmallow import pre_dump
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
from openflexure_microscope.api.utilities import JsonResponse
|
||||
|
||||
from openflexure_microscope.common.labthings_core.utilities import (
|
||||
from labthings.core.utilities import (
|
||||
get_by_path,
|
||||
set_by_path,
|
||||
create_from_path,
|
||||
)
|
||||
|
||||
from openflexure_microscope.common.flask_labthings.find import find_component
|
||||
from openflexure_microscope.common.flask_labthings.view import View
|
||||
from labthings.server.find import find_component
|
||||
from labthings.server.view import View
|
||||
|
||||
from openflexure_microscope.common.flask_labthings.decorators import ThingProperty, Tag, doc_response
|
||||
from labthings.server.decorators import ThingProperty, Tag, doc_response
|
||||
|
||||
from flask import jsonify, request, abort
|
||||
import logging
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
from openflexure_microscope.api.utilities import gen, JsonResponse
|
||||
|
||||
from openflexure_microscope.common.labthings_core.utilities import (
|
||||
from labthings.core.utilities import (
|
||||
get_by_path,
|
||||
set_by_path,
|
||||
create_from_path,
|
||||
)
|
||||
|
||||
from openflexure_microscope.common.flask_labthings.find import find_component
|
||||
from openflexure_microscope.common.flask_labthings.view import View
|
||||
from openflexure_microscope.common.flask_labthings.decorators import doc_response, ThingProperty
|
||||
from labthings.server.find import find_component
|
||||
from labthings.server.view import View
|
||||
from labthings.server.decorators import doc_response, ThingProperty
|
||||
|
||||
from flask import Response
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ from abc import ABCMeta, abstractmethod
|
|||
|
||||
from .capture import CaptureObject
|
||||
from openflexure_microscope.utilities import entry_by_uuid
|
||||
from openflexure_microscope.common.labthings_core.lock import StrictLock
|
||||
from labthings.core.lock import StrictLock
|
||||
|
||||
from openflexure_microscope.paths import data_file_path
|
||||
|
||||
|
|
@ -95,7 +95,7 @@ class BaseCamera(metaclass=ABCMeta):
|
|||
Attributes:
|
||||
thread: Background thread reading frames from camera
|
||||
camera: Camera object
|
||||
lock (:py:class:`openflexure_microscope.common.lock.StrictLock`): Strict lock controlling thread
|
||||
lock (:py:class:`labthings.lock.StrictLock`): Strict lock controlling thread
|
||||
access to stage hardware
|
||||
frame (bytes): Current frame is stored here by background thread
|
||||
last_access (time): Time of last client access to the camera
|
||||
|
|
|
|||
|
|
@ -1,2 +0,0 @@
|
|||
from . import flask_labthings
|
||||
from .labthings_core import tasks, lock
|
||||
|
|
@ -1 +0,0 @@
|
|||
EXTENSION_NAME = "flask-labthings"
|
||||
|
|
@ -1,178 +0,0 @@
|
|||
from webargs import flaskparser
|
||||
from functools import wraps, update_wrapper
|
||||
from flask import make_response
|
||||
from http import HTTPStatus
|
||||
|
||||
from openflexure_microscope.common.labthings_core.utilities import rupdate
|
||||
|
||||
from .spec import update_spec
|
||||
from .schema import TaskSchema
|
||||
|
||||
|
||||
def unpack(value):
|
||||
"""Return a three tuple of data, code, and headers"""
|
||||
if not isinstance(value, tuple):
|
||||
return value, 200, {}
|
||||
|
||||
try:
|
||||
data, code, headers = value
|
||||
return data, code, headers
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
try:
|
||||
data, code = value
|
||||
return data, code, {}
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
return value, 200, {}
|
||||
|
||||
|
||||
class marshal_with(object):
|
||||
def __init__(self, schema, code=200):
|
||||
"""
|
||||
:param schema: a dict of whose keys will make up the final
|
||||
serialized response output
|
||||
"""
|
||||
self.schema = schema
|
||||
self.code = code
|
||||
|
||||
def __call__(self, f):
|
||||
# Pass params to call function attribute for external access
|
||||
update_spec(f, {"_schema": {self.code: self.schema}})
|
||||
# Wrapper function
|
||||
@wraps(f)
|
||||
def wrapper(*args, **kwargs):
|
||||
resp = f(*args, **kwargs)
|
||||
if isinstance(resp, tuple):
|
||||
data, code, headers = unpack(resp)
|
||||
return make_response(self.schema.jsonify(data), code, headers)
|
||||
else:
|
||||
return make_response(self.schema.jsonify(resp))
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
def marshal_task(f):
|
||||
# Pass params to call function attribute for external access
|
||||
update_spec(f, {"responses": {201: {"description": "Task started successfully"}}})
|
||||
update_spec(f, {"_schema": {201: TaskSchema()}})
|
||||
# Wrapper function
|
||||
@wraps(f)
|
||||
def wrapper(*args, **kwargs):
|
||||
resp = f(*args, **kwargs)
|
||||
if isinstance(resp, tuple):
|
||||
data, code, headers = unpack(resp)
|
||||
return make_response(TaskSchema().jsonify(data), code, headers)
|
||||
else:
|
||||
return make_response(TaskSchema().jsonify(resp))
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
def ThingAction(viewcls):
|
||||
# Pass params to call function attribute for external access
|
||||
update_spec(viewcls, {"tags": ["actions"]})
|
||||
update_spec(viewcls, {"_groups": ["actions"]})
|
||||
return viewcls
|
||||
|
||||
|
||||
thing_action = ThingAction
|
||||
|
||||
|
||||
def ThingProperty(viewcls):
|
||||
# Pass params to call function attribute for external access
|
||||
update_spec(viewcls, {"tags": ["properties"]})
|
||||
update_spec(viewcls, {"_groups": ["properties"]})
|
||||
return viewcls
|
||||
|
||||
|
||||
thing_property = ThingProperty
|
||||
|
||||
|
||||
class use_args(object):
|
||||
def __init__(self, schema, **kwargs):
|
||||
"""
|
||||
Equivalent to webargs.flask_parser.use_args
|
||||
"""
|
||||
self.schema = schema
|
||||
self.wrapper = flaskparser.use_args(schema, **kwargs)
|
||||
|
||||
def __call__(self, f):
|
||||
# Pass params to call function attribute for external access
|
||||
update_spec(f, {"_params": self.schema})
|
||||
# Wrapper function
|
||||
update_wrapper(self.wrapper, f)
|
||||
return self.wrapper(f)
|
||||
|
||||
|
||||
class use_kwargs(use_args):
|
||||
def __init__(self, schema, **kwargs):
|
||||
"""
|
||||
Equivalent to webargs.flask_parser.use_kwargs
|
||||
"""
|
||||
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
|
||||
|
||||
|
||||
doc = Doc
|
||||
|
||||
|
||||
class Tag(object):
|
||||
def __init__(self, tags):
|
||||
if isinstance(tags, str):
|
||||
self.tags = [tags]
|
||||
elif isinstance(tags, list) and all([isinstance(e, str) for e in tags]):
|
||||
self.tags = tags
|
||||
else:
|
||||
raise TypeError("Tags must be a string or list of strings")
|
||||
|
||||
def __call__(self, f):
|
||||
# Pass params to call function attribute for external access
|
||||
update_spec(f, {"tags": self.tags})
|
||||
return f
|
||||
|
||||
|
||||
tag = Tag
|
||||
|
||||
|
||||
class doc_response(object):
|
||||
def __init__(self, code, description=None, mimetype=None, **kwargs):
|
||||
self.code = code
|
||||
self.description = description
|
||||
self.kwargs = kwargs
|
||||
self.mimetype = mimetype
|
||||
|
||||
self.response_dict = {
|
||||
"responses": {
|
||||
self.code: {
|
||||
"description": self.description or HTTPStatus(self.code).phrase,
|
||||
**self.kwargs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.mimetype:
|
||||
self.response_dict.update({
|
||||
"responses": {
|
||||
self.code: {
|
||||
"content": {self.mimetype: {}}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
def __call__(self, f):
|
||||
# Pass params to call function attribute for external access
|
||||
update_spec(f, self.response_dict)
|
||||
return f
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
from flask import jsonify, escape
|
||||
from werkzeug.exceptions import default_exceptions
|
||||
from werkzeug.exceptions import HTTPException
|
||||
|
||||
|
||||
class JSONExceptionHandler(object):
|
||||
|
||||
"""
|
||||
A class to be registered as a Flask error handler,
|
||||
converts error codes into a JSON response
|
||||
"""
|
||||
|
||||
def __init__(self, app=None):
|
||||
if app:
|
||||
self.init_app(app)
|
||||
|
||||
def std_handler(self, error):
|
||||
if isinstance(error, HTTPException):
|
||||
message = error.description
|
||||
elif hasattr(error, "message"):
|
||||
message = error.message
|
||||
else:
|
||||
message = str(error)
|
||||
|
||||
status_code = error.code if isinstance(error, HTTPException) else 500
|
||||
|
||||
response = {"code": status_code, "message": escape(message)}
|
||||
return jsonify(response), status_code
|
||||
|
||||
def init_app(self, app):
|
||||
self.app = app
|
||||
self.register(HTTPException)
|
||||
for code, v in default_exceptions.items():
|
||||
self.register(code)
|
||||
|
||||
def register(self, exception_or_code, handler=None):
|
||||
self.app.errorhandler(exception_or_code)(handler or self.std_handler)
|
||||
|
|
@ -1,148 +0,0 @@
|
|||
import logging
|
||||
import collections
|
||||
import copy
|
||||
import traceback
|
||||
|
||||
from importlib import util
|
||||
import sys
|
||||
import os
|
||||
import glob
|
||||
|
||||
from openflexure_microscope.common.labthings_core.utilities import (
|
||||
get_docstring,
|
||||
camel_to_snake,
|
||||
snake_to_spine,
|
||||
)
|
||||
|
||||
|
||||
class BaseExtension:
|
||||
"""
|
||||
Parent class for all extensions.
|
||||
|
||||
Handles binding route views and forms.
|
||||
"""
|
||||
|
||||
# TODO: Allow adding components to extensions
|
||||
|
||||
def __init__(self, name: str, description="", version="0.0.0"):
|
||||
self._views = (
|
||||
{}
|
||||
) # Key: Full, Python-safe ID. Val: Original rule, and view class
|
||||
self._rules = {} # Key: Original rule. Val: View class
|
||||
self._meta = {} # Extra metadata to add to the extension description
|
||||
|
||||
self._cls = str(self) # String description of extension instance
|
||||
|
||||
self.actions = []
|
||||
self.properties = []
|
||||
|
||||
self._name = name
|
||||
self.description = get_docstring(self)
|
||||
self.version = str(version)
|
||||
|
||||
self.methods = {}
|
||||
|
||||
@property
|
||||
def views(self):
|
||||
return self._views
|
||||
|
||||
def add_view(self, view_class, rule, **kwargs):
|
||||
# Remove all leading slashes from view route
|
||||
cleaned_rule = rule
|
||||
while cleaned_rule[0] == "/":
|
||||
cleaned_rule = cleaned_rule[1:]
|
||||
|
||||
# Expand the rule to include extension name
|
||||
full_rule = "/{}/{}".format(self._name_uri_safe, cleaned_rule)
|
||||
|
||||
view_id = cleaned_rule.replace("/", "_").replace("<", "").replace(">", "")
|
||||
|
||||
# Store route information in a dictionary
|
||||
d = {"rule": full_rule, "view": view_class, "kwargs": kwargs}
|
||||
|
||||
# Add view to private views dictionary
|
||||
self._views[view_id] = d
|
||||
# Store the rule expansion information
|
||||
self._rules[rule] = self._views[view_id]
|
||||
|
||||
@property
|
||||
def meta(self):
|
||||
d = {}
|
||||
for k, v in self._meta.items():
|
||||
if callable(v):
|
||||
d[k] = v()
|
||||
else:
|
||||
d[k] = v
|
||||
return d
|
||||
|
||||
def add_meta(self, key, val):
|
||||
self._meta[key] = val
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def _name_python_safe(self):
|
||||
name = camel_to_snake(self._name) # Camel to snake
|
||||
name = name.replace(" ", "_") # Spaces to snake
|
||||
return name
|
||||
|
||||
@property
|
||||
def _name_uri_safe(self):
|
||||
return snake_to_spine(self._name_python_safe)
|
||||
|
||||
def add_method(self, method, method_name):
|
||||
self.methods[method_name] = method
|
||||
|
||||
if not hasattr(self, method_name):
|
||||
setattr(self, method_name, method)
|
||||
else:
|
||||
logging.warning(
|
||||
"Unable to bind method to extension. Method name already exists."
|
||||
)
|
||||
|
||||
|
||||
def find_instances_in_module(module, class_to_find):
|
||||
objs = []
|
||||
for attribute in dir(module):
|
||||
if not attribute.startswith("__"):
|
||||
if isinstance(getattr(module, attribute), class_to_find):
|
||||
logging.debug(f"Found extension {getattr(module, attribute).name}")
|
||||
objs.append(getattr(module, attribute))
|
||||
return objs
|
||||
|
||||
|
||||
def find_extensions_in_file(extension_path: str, module_name="extensions") -> list:
|
||||
logging.debug(f"Loading extensions from {extension_path}")
|
||||
|
||||
spec = util.spec_from_file_location(module_name, extension_path)
|
||||
mod = util.module_from_spec(spec)
|
||||
sys.modules[spec.name] = mod
|
||||
|
||||
try:
|
||||
spec.loader.exec_module(mod)
|
||||
except Exception as e:
|
||||
logging.error(
|
||||
f"Exception in extension path {extension_path}: \n{traceback.format_exc()}"
|
||||
)
|
||||
return []
|
||||
else:
|
||||
if hasattr(mod, "__extensions__"):
|
||||
return [getattr(mod, ext_name) for ext_name in mod.__extensions__]
|
||||
else:
|
||||
return find_instances_in_module(mod, BaseExtension)
|
||||
|
||||
|
||||
def find_extensions(extension_dir: str, module_name="extensions"):
|
||||
logging.debug(f"Loading extensions from {extension_dir}")
|
||||
|
||||
extensions = []
|
||||
extension_paths = glob.glob(os.path.join(extension_dir, "*.py"))
|
||||
|
||||
for extension_path in extension_paths:
|
||||
extensions.extend(
|
||||
find_extensions_in_file(extension_path, module_name=module_name)
|
||||
)
|
||||
|
||||
return extensions
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
# Marshmallow fields
|
||||
from marshmallow.fields import *
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
import logging
|
||||
from flask import current_app
|
||||
|
||||
from . import EXTENSION_NAME
|
||||
|
||||
|
||||
def current_labthing():
|
||||
app = current_app._get_current_object()
|
||||
if not app:
|
||||
return None
|
||||
logging.debug("Active app extensions:")
|
||||
logging.debug(app.extensions)
|
||||
logging.debug("Active labthing:")
|
||||
logging.debug(app.extensions[EXTENSION_NAME])
|
||||
return app.extensions[EXTENSION_NAME]
|
||||
|
||||
|
||||
def registered_extensions(labthing_instance=None):
|
||||
if not labthing_instance:
|
||||
labthing_instance = current_labthing()
|
||||
return labthing_instance.extensions
|
||||
|
||||
|
||||
def registered_components(labthing_instance=None):
|
||||
if not labthing_instance:
|
||||
labthing_instance = current_labthing()
|
||||
return labthing_instance.components
|
||||
|
||||
|
||||
def find_component(device_name, labthing_instance=None):
|
||||
if not labthing_instance:
|
||||
labthing_instance = current_labthing()
|
||||
|
||||
if device_name in labthing_instance.components:
|
||||
return labthing_instance.components[device_name]
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def find_extension(extension_name, labthing_instance=None):
|
||||
if not labthing_instance:
|
||||
labthing_instance = current_labthing()
|
||||
|
||||
logging.debug("Current labthing:")
|
||||
logging.debug(current_labthing())
|
||||
|
||||
if extension_name in labthing_instance.extensions:
|
||||
return labthing_instance.extensions[extension_name]
|
||||
else:
|
||||
return None
|
||||
|
|
@ -1,288 +0,0 @@
|
|||
from flask import url_for, jsonify
|
||||
from apispec import APISpec
|
||||
from apispec.ext.marshmallow import MarshmallowPlugin
|
||||
|
||||
from . import EXTENSION_NAME # TODO: Move into .names
|
||||
from .names import TASK_ENDPOINT, TASK_LIST_ENDPOINT, EXTENSION_LIST_ENDPOINT
|
||||
from .extensions import BaseExtension
|
||||
from .utilities import description_from_view
|
||||
from .spec import rule2path, get_spec
|
||||
from .decorators import tag
|
||||
|
||||
from .views.extensions import ExtensionList
|
||||
from .views.tasks import TaskList, TaskView
|
||||
from .views.docs import docs_blueprint, SwaggerUIView, W3CThingDescriptionView
|
||||
|
||||
from openflexure_microscope.common.labthings_core.utilities import get_docstring
|
||||
|
||||
import logging
|
||||
|
||||
|
||||
class LabThing(object):
|
||||
def __init__(
|
||||
self,
|
||||
app=None,
|
||||
prefix: str = "",
|
||||
title: str = "",
|
||||
description: str = "",
|
||||
version: str = "0.0.0",
|
||||
):
|
||||
self.app = app
|
||||
|
||||
self.components = {}
|
||||
|
||||
self.extensions = {}
|
||||
|
||||
self.views = []
|
||||
self.properties = {}
|
||||
self.actions = {}
|
||||
|
||||
self.custom_root_links = {}
|
||||
|
||||
self.endpoints = set()
|
||||
|
||||
self.url_prefix = prefix
|
||||
self._description = description
|
||||
self._title = title
|
||||
self._version = version
|
||||
|
||||
# Store handlers for things like errors and CORS
|
||||
self.handlers = {}
|
||||
|
||||
self.spec = APISpec(
|
||||
title=self.title,
|
||||
version=self.version,
|
||||
openapi_version="3.0.2",
|
||||
plugins=[MarshmallowPlugin()],
|
||||
)
|
||||
|
||||
if app is not None:
|
||||
self.init_app(app)
|
||||
|
||||
@property
|
||||
def description(self,):
|
||||
return self._description
|
||||
|
||||
@description.setter
|
||||
def description(self, description: str):
|
||||
self._description = description
|
||||
self.spec.description = description
|
||||
|
||||
@property
|
||||
def title(self,):
|
||||
return self._title
|
||||
|
||||
@title.setter
|
||||
def title(self, title: str):
|
||||
self._title = title
|
||||
self.spec.title = title
|
||||
|
||||
@property
|
||||
def version(self,):
|
||||
return str(self._version)
|
||||
|
||||
@version.setter
|
||||
def version(self, version: str):
|
||||
self._version = version
|
||||
self.spec.version = version
|
||||
|
||||
### Flask stuff
|
||||
|
||||
def init_app(self, app):
|
||||
app.teardown_appcontext(self.teardown)
|
||||
|
||||
# Register Flask extension
|
||||
app.extensions = getattr(app, "extensions", {})
|
||||
app.extensions[EXTENSION_NAME] = self
|
||||
|
||||
# Add resources, if registered before tying to a Flask app
|
||||
if len(self.views) > 0:
|
||||
for resource, urls, endpoint, kwargs in self.views:
|
||||
self._register_view(app, resource, *urls, endpoint=endpoint, **kwargs)
|
||||
|
||||
# Create base routes
|
||||
self._create_base_routes()
|
||||
|
||||
def teardown(self, exception):
|
||||
pass
|
||||
|
||||
def _create_base_routes(self):
|
||||
# Add root representation
|
||||
self.app.add_url_rule(self._complete_url("/", ""), "rootrep", self.rootrep)
|
||||
# Add thing descriptions
|
||||
self.app.register_blueprint(docs_blueprint, url_prefix=self.url_prefix)
|
||||
|
||||
# Add extension overview
|
||||
self.add_view(ExtensionList, "/extensions", endpoint=EXTENSION_LIST_ENDPOINT)
|
||||
# Add task routes
|
||||
self.add_view(TaskList, "/tasks", endpoint=TASK_LIST_ENDPOINT)
|
||||
self.add_view(TaskView, "/tasks/<id>", endpoint=TASK_ENDPOINT)
|
||||
|
||||
### Device stuff
|
||||
|
||||
def add_component(self, device_object, device_name: str):
|
||||
self.components[device_name] = device_object
|
||||
|
||||
### Extension stuff
|
||||
|
||||
def register_extension(self, extension_object):
|
||||
if isinstance(extension_object, BaseExtension):
|
||||
self.extensions[extension_object.name] = extension_object
|
||||
else:
|
||||
raise TypeError("Extension object must be an instance of BaseExtension")
|
||||
|
||||
for extension_view_id, extension_view in extension_object.views.items():
|
||||
# Add route to the extensions blueprint
|
||||
self.add_view(
|
||||
tag("extensions")(extension_view["view"]),
|
||||
"/extensions" + extension_view["rule"],
|
||||
**extension_view["kwargs"],
|
||||
)
|
||||
|
||||
### Resource stuff
|
||||
|
||||
def _complete_url(self, url_part, registration_prefix):
|
||||
"""This method is used to defer the construction of the final url in
|
||||
the case that the Api is created with a Blueprint.
|
||||
:param url_part: The part of the url the endpoint is registered with
|
||||
:param registration_prefix: The part of the url contributed by the
|
||||
blueprint. Generally speaking, BlueprintSetupState.url_prefix
|
||||
"""
|
||||
parts = [registration_prefix, self.url_prefix, url_part]
|
||||
return "".join([part for part in parts if part])
|
||||
|
||||
def add_view(self, resource, *urls, endpoint=None, **kwargs):
|
||||
"""Adds a view to the api.
|
||||
:param resource: the class name of your resource
|
||||
:type resource: :class:`Type[Resource]`
|
||||
:param urls: one or more url routes to match for the resource, standard
|
||||
flask routing rules apply. Any url variables will be
|
||||
passed to the resource method as args.
|
||||
:type urls: str
|
||||
:param endpoint: endpoint name (defaults to :meth:`Resource.__name__`
|
||||
Can be used to reference this route in :class:`fields.Url` fields
|
||||
:type endpoint: str
|
||||
:param resource_class_args: args to be forwarded to the constructor of
|
||||
the resource.
|
||||
:type resource_class_args: tuple
|
||||
:param resource_class_kwargs: kwargs to be forwarded to the constructor
|
||||
of the resource.
|
||||
:type resource_class_kwargs: dict
|
||||
Additional keyword arguments not specified above will be passed as-is
|
||||
to :meth:`flask.Flask.add_url_rule`.
|
||||
Examples::
|
||||
api.add_resource(HelloWorld, '/', '/hello')
|
||||
api.add_resource(Foo, '/foo', endpoint="foo")
|
||||
api.add_resource(FooSpecial, '/special/foo', endpoint="foo")
|
||||
"""
|
||||
endpoint = endpoint or resource.__name__.lower()
|
||||
|
||||
logging.debug(f"{endpoint}: {type(resource)}")
|
||||
|
||||
if self.app is not None:
|
||||
self._register_view(self.app, resource, *urls, endpoint=endpoint, **kwargs)
|
||||
|
||||
self.views.append((resource, urls, endpoint, kwargs))
|
||||
|
||||
def view(self, *urls, **kwargs):
|
||||
def decorator(cls):
|
||||
self.add_view(cls, *urls, **kwargs)
|
||||
return cls
|
||||
|
||||
return decorator
|
||||
|
||||
def _register_view(self, app, view, *urls, endpoint=None, **kwargs):
|
||||
endpoint = endpoint or view.__name__.lower()
|
||||
self.endpoints.add(endpoint)
|
||||
resource_class_args = kwargs.pop("resource_class_args", ())
|
||||
resource_class_kwargs = kwargs.pop("resource_class_kwargs", {})
|
||||
|
||||
# NOTE: 'view_functions' is cleaned up from Blueprint class in Flask 1.0
|
||||
if endpoint in getattr(app, "view_functions", {}):
|
||||
previous_view_class = app.view_functions[endpoint].__dict__["view_class"]
|
||||
|
||||
# if you override the endpoint with a different class, avoid the collision by raising an exception
|
||||
if previous_view_class != view:
|
||||
raise ValueError(
|
||||
"This endpoint (%s) is already set to the class %s."
|
||||
% (endpoint, previous_view_class.__name__)
|
||||
)
|
||||
|
||||
view.endpoint = endpoint
|
||||
resource_func = view.as_view(
|
||||
endpoint, *resource_class_args, **resource_class_kwargs
|
||||
)
|
||||
|
||||
for url in urls:
|
||||
# If we've got no Blueprint, just build a url with no prefix
|
||||
rule = self._complete_url(url, "")
|
||||
# Add the url to the application or blueprint
|
||||
app.add_url_rule(rule, view_func=resource_func, **kwargs)
|
||||
# Add the resource to our API spec
|
||||
#self.spec.path(**view2path(rule, view, self.spec))
|
||||
|
||||
# TEST: Getting Flask rule objects
|
||||
flask_rules = app.url_map._rules_by_endpoint.get(endpoint)
|
||||
for flask_rule in flask_rules:
|
||||
self.spec.path(**rule2path(flask_rule, view, self.spec))
|
||||
|
||||
# Handle resource groups listed in API spec
|
||||
view_spec = get_spec(view)
|
||||
view_groups = view_spec.get("_groups", {})
|
||||
if "actions" in view_groups:
|
||||
self.actions[view.endpoint] = view
|
||||
if "properties" in view_groups:
|
||||
self.properties[view.endpoint] = view
|
||||
|
||||
### Utilities
|
||||
|
||||
def url_for(self, view, **values):
|
||||
"""Generates a URL to the given resource.
|
||||
Works like :func:`flask.url_for`."""
|
||||
endpoint = view.endpoint
|
||||
return url_for(endpoint, **values)
|
||||
|
||||
def owns_endpoint(self, endpoint):
|
||||
return endpoint in self.endpoints
|
||||
|
||||
def add_root_link(self, view, title, kwargs={}):
|
||||
self.custom_root_links[title] = (view, kwargs)
|
||||
|
||||
### Description
|
||||
def rootrep(self):
|
||||
"""
|
||||
Root representation
|
||||
"""
|
||||
# TODO: Allow custom root representations
|
||||
|
||||
rr = {
|
||||
"id": url_for("rootrep", _external=True),
|
||||
"title": self.title,
|
||||
"description": self.description,
|
||||
"links": {
|
||||
"thingDescription": {
|
||||
"href": url_for("labthings_docs.w3c_td", _external=True),
|
||||
"description": get_docstring(W3CThingDescriptionView),
|
||||
},
|
||||
"swaggerUI": {
|
||||
"href": url_for("labthings_docs.swagger_ui", _external=True),
|
||||
**description_from_view(SwaggerUIView),
|
||||
},
|
||||
"extensions": {
|
||||
"href": self.url_for(ExtensionList, _external=True),
|
||||
**description_from_view(ExtensionList),
|
||||
},
|
||||
"tasks": {
|
||||
"href": self.url_for(TaskList, _external=True),
|
||||
**description_from_view(TaskList),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for title, (view, kwargs) in self.custom_root_links.items():
|
||||
rr["links"][title] = {
|
||||
"href": self.url_for(view, **kwargs, _external=True),
|
||||
**description_from_view(view),
|
||||
}
|
||||
|
||||
return jsonify(rr)
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
TASK_ENDPOINT = "labthing_task"
|
||||
TASK_LIST_ENDPOINT = "labthing_task_list"
|
||||
EXTENSION_LIST_ENDPOINT = "labthing_extension_list"
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
from flask import Flask
|
||||
from flask_cors import CORS
|
||||
|
||||
from .labthing import LabThing
|
||||
from .exceptions import JSONExceptionHandler
|
||||
|
||||
|
||||
def create_app(
|
||||
import_name,
|
||||
prefix: str = "",
|
||||
title: str = "",
|
||||
description: str = "",
|
||||
version: str = "0.0.0",
|
||||
handle_errors: bool = True,
|
||||
handle_cors: bool = True,
|
||||
flask_kwargs: dict = {},
|
||||
):
|
||||
app = Flask(import_name, **flask_kwargs)
|
||||
app.url_map.strict_slashes = False
|
||||
|
||||
# Handle CORS
|
||||
if handle_cors:
|
||||
cors_handler = CORS(app, resources=f"{prefix}/*")
|
||||
|
||||
# Handle errors
|
||||
if handle_errors:
|
||||
error_handler = JSONExceptionHandler()
|
||||
error_handler.init_app(app)
|
||||
|
||||
# Create a LabThing
|
||||
labthing = LabThing(
|
||||
app, prefix=prefix, title=title, description=description, version=str(version)
|
||||
)
|
||||
|
||||
# Store references to added-in handlers
|
||||
if cors_handler:
|
||||
labthing.handlers["cors"] = cors_handler
|
||||
if error_handler:
|
||||
labthing.handlers["error"] = error_handler
|
||||
|
||||
return app, labthing
|
||||
|
|
@ -1,93 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from flask import jsonify, url_for
|
||||
import marshmallow
|
||||
|
||||
from .names import TASK_ENDPOINT, TASK_LIST_ENDPOINT, EXTENSION_LIST_ENDPOINT
|
||||
from .utilities import view_class_from_endpoint, description_from_view
|
||||
from . import fields
|
||||
|
||||
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 jsonify(data, *args, **kwargs)
|
||||
|
||||
|
||||
class TaskSchema(Schema):
|
||||
_ID = fields.String(data_key="id")
|
||||
target_string = fields.String(data_key="function")
|
||||
_status = fields.String(data_key="status")
|
||||
progress = fields.String()
|
||||
data = fields.Raw()
|
||||
_return_value = fields.Raw(data_key="return")
|
||||
_start_time = fields.String(data_key="start_time")
|
||||
_end_time = fields.String(data_key="end_time")
|
||||
|
||||
links = fields.Dict()
|
||||
|
||||
@marshmallow.pre_dump
|
||||
def generate_links(self, data, **kwargs):
|
||||
data.links = {
|
||||
"self": {
|
||||
"href": url_for(TASK_ENDPOINT, id=data.id, _external=True),
|
||||
"mimetype": "application/json",
|
||||
**description_from_view(view_class_from_endpoint(TASK_ENDPOINT)),
|
||||
}
|
||||
}
|
||||
return data
|
||||
|
||||
|
||||
class ExtensionSchema(Schema):
|
||||
name = fields.String(data_key="title")
|
||||
_name_python_safe = fields.String(data_key="pythonName")
|
||||
_cls = fields.String(data_key="pythonObject")
|
||||
meta = fields.Dict()
|
||||
description = fields.String()
|
||||
|
||||
links = fields.Dict()
|
||||
|
||||
@marshmallow.pre_dump
|
||||
def generate_links(self, data, **kwargs):
|
||||
d = {}
|
||||
for view_id, view_data in data.views.items():
|
||||
view_cls = view_data["view"]
|
||||
view_rule = view_data["rule"]
|
||||
# Make links dictionary if it doesn't yet exist
|
||||
d[view_id] = {
|
||||
"href": url_for(EXTENSION_LIST_ENDPOINT, _external=True) + view_rule,
|
||||
**description_from_view(view_cls),
|
||||
}
|
||||
|
||||
data.links = d
|
||||
|
||||
return data
|
||||
|
|
@ -1,164 +0,0 @@
|
|||
from ..view import View
|
||||
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 .paths import rule_to_path, rule_to_params
|
||||
|
||||
from werkzeug.routing import Rule
|
||||
from collections import Mapping
|
||||
from http import HTTPStatus
|
||||
|
||||
|
||||
def update_spec(obj, spec):
|
||||
obj.__apispec__ = obj.__dict__.get("__apispec__", {})
|
||||
rupdate(obj.__apispec__, spec)
|
||||
return obj.__apispec__
|
||||
|
||||
|
||||
def get_spec(obj):
|
||||
obj.__apispec__ = obj.__dict__.get("__apispec__", {})
|
||||
return obj.__apispec__
|
||||
|
||||
|
||||
def rule2path(rule: Rule, view: View, spec: APISpec):
|
||||
params = {
|
||||
"path": rule_to_path(rule),
|
||||
"operations": view2operations(view, spec),
|
||||
"description": get_docstring(view),
|
||||
"summary": get_summary(view),
|
||||
}
|
||||
|
||||
# Add URL arguments
|
||||
if rule.arguments:
|
||||
for op in params.get("operations").keys():
|
||||
params["operations"][op].update({
|
||||
"parameters": rule_to_params(rule)
|
||||
})
|
||||
|
||||
# Add extra parameters
|
||||
if hasattr(view, "__apispec__"):
|
||||
# Recursively update params
|
||||
rupdate(params, view.__apispec__)
|
||||
|
||||
return params
|
||||
|
||||
|
||||
def view2operations(view: View, spec: APISpec):
|
||||
# Operations inherit tags from parent
|
||||
inherited_tags = []
|
||||
if hasattr(view, "__apispec__"):
|
||||
inherited_tags = getattr(view, "__apispec__").get("tags", [])
|
||||
|
||||
# Build dictionary of operations (HTTP methods)
|
||||
ops = {}
|
||||
for method in View.methods:
|
||||
if hasattr(view, method):
|
||||
ops[method] = {}
|
||||
|
||||
rupdate(
|
||||
ops[method],
|
||||
{
|
||||
"description": get_docstring(getattr(view, method)),
|
||||
"summary": get_summary(getattr(view, method)),
|
||||
"tags": inherited_tags,
|
||||
},
|
||||
)
|
||||
|
||||
rupdate(ops[method], method2operation(getattr(view, method), spec))
|
||||
|
||||
return ops
|
||||
|
||||
|
||||
def method2operation(method: callable, spec: APISpec):
|
||||
if hasattr(method, "__apispec__"):
|
||||
apispec = getattr(method, "__apispec__")
|
||||
else:
|
||||
apispec = {}
|
||||
|
||||
op = {}
|
||||
if "_params" in apispec:
|
||||
rupdate(
|
||||
op,
|
||||
{
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": convert_schema(apispec.get("_params"), spec)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
if "_schema" in apispec:
|
||||
for code, schema in apispec.get("_schema", {}).items():
|
||||
rupdate(
|
||||
op,
|
||||
{
|
||||
"responses": {
|
||||
code: {
|
||||
"description": HTTPStatus(code).phrase,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": convert_schema(schema, spec)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
else:
|
||||
# If no explicit responses are known, populate with defaults
|
||||
rupdate(
|
||||
op,
|
||||
{
|
||||
"responses": {
|
||||
200: {"description": get_summary(method) or HTTPStatus(200).phrase}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
# Bung in any extra swagger fields supplied
|
||||
for key, val in apispec.items():
|
||||
if not key in ["_params", "_schema"]:
|
||||
rupdate(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}
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
import re
|
||||
|
||||
import werkzeug.routing
|
||||
|
||||
PATH_RE = re.compile(r'<(?:[^:<>]+:)?([^<>]+)>')
|
||||
|
||||
|
||||
def rule_to_path(rule):
|
||||
return PATH_RE.sub(r'{\1}', rule.rule)
|
||||
|
||||
|
||||
# Conversion map of werkzeug rule converters to Javascript schema types
|
||||
CONVERTER_MAPPING = {
|
||||
werkzeug.routing.UnicodeConverter: ('string', None),
|
||||
werkzeug.routing.IntegerConverter: ('integer', 'int32'),
|
||||
werkzeug.routing.FloatConverter: ('number', 'float'),
|
||||
}
|
||||
|
||||
DEFAULT_TYPE = ('string', None)
|
||||
|
||||
|
||||
def rule_to_params(rule, overrides=None):
|
||||
overrides = (overrides or {})
|
||||
result = [
|
||||
argument_to_param(argument, rule, overrides.get(argument, {}))
|
||||
for argument in rule.arguments
|
||||
]
|
||||
for key in overrides.keys():
|
||||
if overrides[key].get('in') in ('header', 'query'):
|
||||
overrides[key]['name'] = overrides[key].get('name', key)
|
||||
result.append(overrides[key])
|
||||
return result
|
||||
|
||||
|
||||
def argument_to_param(argument, rule, override=None):
|
||||
param = {
|
||||
'in': 'path',
|
||||
'name': argument,
|
||||
'required': True,
|
||||
}
|
||||
type_, format_ = CONVERTER_MAPPING.get(type(rule._converters[argument]), DEFAULT_TYPE)
|
||||
param['type'] = type_
|
||||
if format_ is not None:
|
||||
param['format'] = format_
|
||||
if rule.defaults and argument in rule.defaults:
|
||||
param['default'] = rule.defaults[argument]
|
||||
param.update(override or {})
|
||||
return param
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
# Marshmallow fields to JSON schema types
|
||||
# Note: We shouldn't ever need to use this directly. We should go via the apispec converter
|
||||
from apispec.ext.marshmallow.field_converter import DEFAULT_FIELD_MAPPING
|
||||
|
||||
from . import fields
|
||||
|
||||
# Extra standard library Python types
|
||||
from datetime import date, datetime, time, timedelta
|
||||
from decimal import Decimal
|
||||
from typing import Dict, List, Tuple, Union
|
||||
from uuid import UUID
|
||||
|
||||
"""
|
||||
TODO: Use this to convert arbitrary dictionary into its own schema, for W3C TD
|
||||
|
||||
First: Convert Python non-builtins to builtins using DEFAULT_BUILTIN_CONVERSIONS
|
||||
Then match types of each element to Field using DEFAULT_TYPE_MAPPING
|
||||
Finally convert Fields to JSON using converter (preferred due to extra metadata), or DEFAULT_FIELD_MAPPING
|
||||
"""
|
||||
# Python types to Marshmallow fields
|
||||
DEFAULT_TYPE_MAPPING = {
|
||||
bool: fields.Boolean,
|
||||
date: fields.Date,
|
||||
datetime: fields.DateTime,
|
||||
Decimal: fields.Decimal,
|
||||
float: fields.Float,
|
||||
int: fields.Integer,
|
||||
str: fields.String,
|
||||
time: fields.Time,
|
||||
timedelta: fields.TimeDelta,
|
||||
UUID: fields.UUID,
|
||||
dict: fields.Dict,
|
||||
Dict: fields.Dict,
|
||||
}
|
||||
|
||||
# Functions to handle conversion of common Python types into serialisable Python types
|
||||
|
||||
|
||||
def ndarray_to_list(o):
|
||||
return o.tolist()
|
||||
|
||||
|
||||
def to_int(o):
|
||||
return int(o)
|
||||
|
||||
|
||||
def to_float(o):
|
||||
return float(o)
|
||||
|
||||
|
||||
def to_string(o):
|
||||
return str(o)
|
||||
|
||||
|
||||
# Map of Python type conversions
|
||||
DEFAULT_BUILTIN_CONVERSIONS = {
|
||||
"numpy.ndarray": ndarray_to_list,
|
||||
"numpy.int": to_int,
|
||||
"fractions.Fraction": to_float,
|
||||
}
|
||||
|
||||
# TODO: Deserialiser with inverse defaults
|
||||
# TODO: Option to switch to .npy serialisation/deserialisation (or look for a better common array format)
|
||||
|
||||
# Use with [x.__module__+"."+x.__name__ for x in inspect.getmro(type(POSSIBLE_MATCHER))]
|
||||
# Resulting array will contain strings with the same format as keys in DEFAULT_BUILTIN_CONVERSIONS
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
from openflexure_microscope.common.labthings_core.utilities import (
|
||||
get_docstring,
|
||||
get_summary,
|
||||
)
|
||||
|
||||
from .view import View
|
||||
|
||||
from flask import current_app
|
||||
|
||||
|
||||
def description_from_view(view_class):
|
||||
summary = get_summary(view_class)
|
||||
|
||||
methods = []
|
||||
for method_key in View.methods:
|
||||
if hasattr(view_class, method_key):
|
||||
methods.append(method_key.upper())
|
||||
|
||||
# If no class summary was given, try using summaries from method functions
|
||||
if not summary:
|
||||
summary = get_summary(getattr(view_class, method_key))
|
||||
|
||||
d = {"methods": methods, "description": summary}
|
||||
|
||||
return d
|
||||
|
||||
|
||||
def view_class_from_endpoint(endpoint: str):
|
||||
return current_app.view_functions[endpoint].view_class
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
from flask.views import MethodView
|
||||
|
||||
|
||||
class View(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 View.methods:
|
||||
if hasattr(self, meth) and hasattr(getattr(self, meth), "__apispec__"):
|
||||
docs["operations"][meth] = {}
|
||||
docs["operations"][meth] = getattr(self, meth).__apispec__
|
||||
return docs
|
||||
|
|
@ -1,113 +0,0 @@
|
|||
from flask import abort, url_for, jsonify, render_template, Blueprint, current_app, request
|
||||
|
||||
from openflexure_microscope.common.labthings_core.utilities import get_docstring
|
||||
|
||||
from ...view import View
|
||||
from ...find import current_labthing
|
||||
from ...spec import rule_to_path, rule_to_params
|
||||
|
||||
import os
|
||||
|
||||
|
||||
class APISpecView(View):
|
||||
"""
|
||||
OpenAPI v3 documentation
|
||||
"""
|
||||
|
||||
def get(self):
|
||||
"""
|
||||
OpenAPI v3 documentation
|
||||
"""
|
||||
return jsonify(current_labthing().spec.to_dict())
|
||||
|
||||
|
||||
class SwaggerUIView(View):
|
||||
"""
|
||||
Swagger UI documentation
|
||||
"""
|
||||
|
||||
def get(self):
|
||||
return render_template("swagger-ui.html")
|
||||
|
||||
|
||||
class W3CThingDescriptionView(View):
|
||||
"""
|
||||
W3C-style Thing Description
|
||||
"""
|
||||
|
||||
def get(self):
|
||||
base_url = request.host_url.rstrip('/')
|
||||
|
||||
props = {}
|
||||
for key, prop in current_labthing().properties.items():
|
||||
prop_rules = current_app.url_map._rules_by_endpoint.get(prop.endpoint)
|
||||
prop_urls = [rule_to_path(rule) for rule in prop_rules]
|
||||
|
||||
props[key] = {}
|
||||
props[key]["title"] = prop.__name__
|
||||
# TODO: Get description from __apispec__ preferentially
|
||||
props[key]["description"] = get_docstring(prop) or (
|
||||
get_docstring(prop.get) if hasattr(prop, "get") else ""
|
||||
)
|
||||
props[key]["readOnly"] = not (
|
||||
hasattr(prop, "post") or hasattr(prop, "put") or hasattr(prop, "delete")
|
||||
)
|
||||
props[key]["writeOnly"] = not hasattr(prop, "get")
|
||||
props[key]["links"] = [
|
||||
{"href": f"{base_url}{url}"} for url in prop_urls
|
||||
]
|
||||
|
||||
props[key]["uriVariables"] = {}
|
||||
for prop_rule in prop_rules:
|
||||
params = rule_to_params(prop_rule)
|
||||
params_dict = {}
|
||||
for param in params:
|
||||
params_dict.update({
|
||||
param.get("name"): {
|
||||
"type": param.get("type")
|
||||
}
|
||||
})
|
||||
props[key]["uriVariables"].update(params_dict)
|
||||
if not props[key]["uriVariables"]:
|
||||
del props[key]["uriVariables"]
|
||||
|
||||
actions = {}
|
||||
for key, action in current_labthing().actions.items():
|
||||
action_rules = current_app.url_map._rules_by_endpoint.get(action.endpoint)
|
||||
action_urls = [rule_to_path(rule) for rule in action_rules]
|
||||
|
||||
actions[key] = {}
|
||||
actions[key]["title"] = action.__name__
|
||||
# TODO: Get description from __apispec__ preferentially
|
||||
actions[key]["description"] = get_docstring(action) or (
|
||||
get_docstring(action.post) if hasattr(action, "post") else ""
|
||||
)
|
||||
actions[key]["links"] = [
|
||||
{"href": f"{base_url}{url}"} for url in action_urls
|
||||
]
|
||||
|
||||
td = {
|
||||
"@context": "https://www.w3.org/2019/wot/td/v1",
|
||||
"id": url_for("labthings_docs.w3c_td", _external=True),
|
||||
"title": current_labthing().title,
|
||||
"description": current_labthing().description,
|
||||
"properties": props,
|
||||
"actions": actions,
|
||||
}
|
||||
|
||||
return jsonify(td)
|
||||
|
||||
|
||||
docs_blueprint = Blueprint(
|
||||
"labthings_docs", __name__, static_folder="./static", template_folder="./templates"
|
||||
)
|
||||
|
||||
docs_blueprint.add_url_rule(
|
||||
"/swagger", view_func=APISpecView.as_view("swagger_json")
|
||||
)
|
||||
docs_blueprint.add_url_rule(
|
||||
"/swagger-ui", view_func=SwaggerUIView.as_view("swagger_ui")
|
||||
)
|
||||
docs_blueprint.add_url_rule(
|
||||
"/td", view_func=W3CThingDescriptionView.as_view("w3c_td")
|
||||
)
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 665 B |
Binary file not shown.
|
Before Width: | Height: | Size: 628 B |
|
|
@ -1,60 +0,0 @@
|
|||
<!-- HTML for static distribution bundle build -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Swagger UI</title>
|
||||
<link rel="stylesheet" type="text/css" href="./swagger-ui.css" >
|
||||
<link rel="icon" type="image/png" href="./favicon-32x32.png" sizes="32x32" />
|
||||
<link rel="icon" type="image/png" href="./favicon-16x16.png" sizes="16x16" />
|
||||
<style>
|
||||
html
|
||||
{
|
||||
box-sizing: border-box;
|
||||
overflow: -moz-scrollbars-vertical;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
*,
|
||||
*:before,
|
||||
*:after
|
||||
{
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
body
|
||||
{
|
||||
margin:0;
|
||||
background: #fafafa;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="swagger-ui"></div>
|
||||
|
||||
<script src="./swagger-ui-bundle.js"> </script>
|
||||
<script src="./swagger-ui-standalone-preset.js"> </script>
|
||||
<script>
|
||||
window.onload = function() {
|
||||
// Begin Swagger UI call region
|
||||
const ui = SwaggerUIBundle({
|
||||
url: "https://petstore.swagger.io/v2/swagger.json",
|
||||
dom_id: '#swagger-ui',
|
||||
deepLinking: true,
|
||||
presets: [
|
||||
SwaggerUIBundle.presets.apis,
|
||||
SwaggerUIStandalonePreset
|
||||
],
|
||||
plugins: [
|
||||
SwaggerUIBundle.plugins.DownloadUrl
|
||||
],
|
||||
layout: "StandaloneLayout"
|
||||
})
|
||||
// End Swagger UI call region
|
||||
|
||||
window.ui = ui
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
<!doctype html>
|
||||
<html lang="en-US">
|
||||
<title>Swagger UI: OAuth2 Redirect</title>
|
||||
<body onload="run()">
|
||||
</body>
|
||||
</html>
|
||||
<script>
|
||||
'use strict';
|
||||
function run () {
|
||||
var oauth2 = window.opener.swaggerUIRedirectOauth2;
|
||||
var sentState = oauth2.state;
|
||||
var redirectUrl = oauth2.redirectUrl;
|
||||
var isValid, qp, arr;
|
||||
|
||||
if (/code|token|error/.test(window.location.hash)) {
|
||||
qp = window.location.hash.substring(1);
|
||||
} else {
|
||||
qp = location.search.substring(1);
|
||||
}
|
||||
|
||||
arr = qp.split("&")
|
||||
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';})
|
||||
qp = qp ? JSON.parse('{' + arr.join() + '}',
|
||||
function (key, value) {
|
||||
return key === "" ? value : decodeURIComponent(value)
|
||||
}
|
||||
) : {}
|
||||
|
||||
isValid = qp.state === sentState
|
||||
|
||||
if ((
|
||||
oauth2.auth.schema.get("flow") === "accessCode"||
|
||||
oauth2.auth.schema.get("flow") === "authorizationCode"
|
||||
) && !oauth2.auth.code) {
|
||||
if (!isValid) {
|
||||
oauth2.errCb({
|
||||
authId: oauth2.auth.name,
|
||||
source: "auth",
|
||||
level: "warning",
|
||||
message: "Authorization may be unsafe, passed state was changed in server Passed state wasn't returned from auth server"
|
||||
});
|
||||
}
|
||||
|
||||
if (qp.code) {
|
||||
delete oauth2.state;
|
||||
oauth2.auth.code = qp.code;
|
||||
oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
|
||||
} else {
|
||||
let oauthErrorMsg
|
||||
if (qp.error) {
|
||||
oauthErrorMsg = "["+qp.error+"]: " +
|
||||
(qp.error_description ? qp.error_description+ ". " : "no accessCode received from the server. ") +
|
||||
(qp.error_uri ? "More info: "+qp.error_uri : "");
|
||||
}
|
||||
|
||||
oauth2.errCb({
|
||||
authId: oauth2.auth.name,
|
||||
source: "auth",
|
||||
level: "error",
|
||||
message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server"
|
||||
});
|
||||
}
|
||||
} else {
|
||||
oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid, redirectUrl: redirectUrl});
|
||||
}
|
||||
window.close();
|
||||
}
|
||||
</script>
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1,38 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Swagger UI</title>
|
||||
<link rel="icon" type="image/png" href="{{ url_for('labthings_docs.static', filename='favicon-32x32.png') }}"
|
||||
sizes="32x32" />
|
||||
<link rel="icon" type="image/png" href="{{ url_for('labthings_docs.static', filename='favicon-16x16.png') }}"
|
||||
sizes="16x16" />
|
||||
<link href="{{ url_for('labthings_docs.static', filename='swagger-ui.css') }}" rel="stylesheet" type="text/css" />
|
||||
</head>
|
||||
|
||||
<body class="swagger-section">
|
||||
<div id="message-bar" class="swagger-ui-wrap" data-sw-translate> </div>
|
||||
<div id="swagger-ui-container" class="swagger-ui-wrap"></div>
|
||||
<script src="{{ url_for('labthings_docs.static', filename='swagger-ui-bundle.js') }}" type="text/javascript"></script>
|
||||
<script src="{{ url_for('labthings_docs.static', filename='swagger-ui-standalone-preset.js') }}"
|
||||
type="text/javascript"></script>
|
||||
<script type="text/javascript">
|
||||
var ui = SwaggerUIBundle({
|
||||
url: "{{ url_for('labthings_docs.swagger_json') }}",
|
||||
dom_id: '#swagger-ui-container',
|
||||
deepLinking: true,
|
||||
presets: [
|
||||
SwaggerUIBundle.presets.apis,
|
||||
SwaggerUIStandalonePreset
|
||||
],
|
||||
plugins: [
|
||||
SwaggerUIBundle.plugins.DownloadUrl
|
||||
],
|
||||
layout: "BaseLayout"
|
||||
})
|
||||
window.ui = ui
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
"""
|
||||
Top-level representation of attached and enabled Extensions
|
||||
"""
|
||||
from ..view import View
|
||||
from ..find import registered_extensions
|
||||
from ..schema import ExtensionSchema
|
||||
from ..decorators import marshal_with, ThingProperty
|
||||
|
||||
|
||||
@ThingProperty
|
||||
class ExtensionList(View):
|
||||
"""
|
||||
List and basic documentation for all enabled Extensions
|
||||
"""
|
||||
|
||||
@marshal_with(ExtensionSchema(many=True))
|
||||
def get(self):
|
||||
"""
|
||||
Return the current Extension forms
|
||||
|
||||
Returns an array of present Extension forms (describing Extension user interfaces.)
|
||||
Please note, this is *not* a list of all enabled Extensions, only those with associated
|
||||
user interface forms.
|
||||
|
||||
A complete list of enabled Extensions can be found in the microscope state.
|
||||
|
||||
"""
|
||||
return registered_extensions().values()
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
from flask import abort, url_for
|
||||
|
||||
from ..decorators import marshal_with, ThingProperty, Tag
|
||||
from ..view import View
|
||||
from ..schema import TaskSchema
|
||||
|
||||
from openflexure_microscope.common.labthings_core import tasks
|
||||
|
||||
|
||||
@ThingProperty
|
||||
@Tag("tasks")
|
||||
class TaskList(View):
|
||||
@marshal_with(TaskSchema(many=True))
|
||||
def get(self):
|
||||
"""
|
||||
List of all session tasks
|
||||
"""
|
||||
return tasks.tasks()
|
||||
|
||||
|
||||
@Tag(["properties", "tasks"])
|
||||
class TaskView(View):
|
||||
@marshal_with(TaskSchema())
|
||||
def get(self, id):
|
||||
"""
|
||||
Show status of a session task
|
||||
|
||||
Includes progress and intermediate data.
|
||||
"""
|
||||
try:
|
||||
task = tasks.dict()[id]
|
||||
except KeyError:
|
||||
return abort(404) # 404 Not Found
|
||||
|
||||
return task
|
||||
|
||||
@marshal_with(TaskSchema())
|
||||
def delete(self, id):
|
||||
"""
|
||||
Terminate a running task.
|
||||
|
||||
If the task is finished, deletes its entry.
|
||||
"""
|
||||
try:
|
||||
task = tasks.dict()[id]
|
||||
except KeyError:
|
||||
return abort(404) # 404 Not Found
|
||||
|
||||
task.terminate()
|
||||
|
||||
return task
|
||||
|
|
@ -1,76 +0,0 @@
|
|||
from threading import RLock
|
||||
|
||||
from openflexure_microscope.exceptions import LockError
|
||||
|
||||
|
||||
class StrictLock(object):
|
||||
"""
|
||||
Class that behaves like a Python RLock, but with stricter timeout conditions and custom exceptions.
|
||||
|
||||
Args:
|
||||
timeout (int): Time, in seconds, lock acquisition will wait before raising an exception
|
||||
|
||||
Attributes:
|
||||
_lock (:py:class:`threading.RLock`): Parent RLock object
|
||||
timeout (int): Time, in seconds, lock acquisition will wait before raising an exception
|
||||
"""
|
||||
|
||||
def __init__(self, timeout=1):
|
||||
self._lock = RLock()
|
||||
self.timeout = timeout
|
||||
|
||||
def locked(self):
|
||||
return self._lock.locked()
|
||||
|
||||
def acquire(self, blocking=True):
|
||||
return self._lock.acquire(blocking, timeout=self.timeout)
|
||||
|
||||
def __enter__(self):
|
||||
result = self._lock.acquire(blocking=True, timeout=self.timeout)
|
||||
if result:
|
||||
return result
|
||||
else:
|
||||
raise LockError("ACQUIRE_ERROR", self)
|
||||
|
||||
def __exit__(self, *args):
|
||||
self._lock.release()
|
||||
|
||||
def release(self):
|
||||
self._lock.release()
|
||||
|
||||
|
||||
class CompositeLock(object):
|
||||
"""
|
||||
Class that behaves like a :py:class:`openflexure_microscope.common.lock.StrictLock`,
|
||||
but allows multiple locks to be acquired and released.
|
||||
|
||||
Args:
|
||||
locks (list): List of parent RLock objects
|
||||
timeout (int): Time, in seconds, lock acquisition will wait before raising an exception
|
||||
|
||||
Attributes:
|
||||
locks (list): List of parent RLock objects
|
||||
timeout (int): Time, in seconds, lock acquisition will wait before raising an exception
|
||||
"""
|
||||
|
||||
def __init__(self, locks, timeout=1):
|
||||
self.locks = locks
|
||||
self.timeout = timeout
|
||||
|
||||
def acquire(self, blocking=True):
|
||||
return (lock.acquire(blocking=blocking) for lock in self.locks)
|
||||
|
||||
def __enter__(self):
|
||||
result = (lock.acquire(blocking=True) for lock in self.locks)
|
||||
if all(result):
|
||||
return result
|
||||
else:
|
||||
raise LockError("ACQUIRE_ERROR", self)
|
||||
|
||||
def __exit__(self, *args):
|
||||
for lock in self.locks:
|
||||
lock.release()
|
||||
|
||||
def release(self):
|
||||
for lock in self.locks:
|
||||
lock.release()
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
__all__ = [
|
||||
"taskify",
|
||||
"tasks",
|
||||
"dict",
|
||||
"states",
|
||||
"current_task",
|
||||
"update_task_progress",
|
||||
"cleanup_tasks",
|
||||
"remove_task",
|
||||
"update_task_data",
|
||||
"ThreadTerminationError",
|
||||
]
|
||||
|
||||
from .pool import (
|
||||
tasks,
|
||||
dict,
|
||||
states,
|
||||
current_task,
|
||||
update_task_progress,
|
||||
cleanup_tasks,
|
||||
remove_task,
|
||||
update_task_data,
|
||||
taskify,
|
||||
)
|
||||
from .thread import ThreadTerminationError
|
||||
|
|
@ -1,145 +0,0 @@
|
|||
import threading
|
||||
import logging
|
||||
from functools import wraps
|
||||
|
||||
from .thread import TaskThread
|
||||
|
||||
from flask import copy_current_request_context
|
||||
|
||||
|
||||
class TaskMaster:
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._tasks = []
|
||||
|
||||
@property
|
||||
def tasks(self):
|
||||
"""
|
||||
Returns:
|
||||
list: List of TaskThread objects.
|
||||
"""
|
||||
return self._tasks
|
||||
|
||||
@property
|
||||
def dict(self):
|
||||
"""
|
||||
Returns:
|
||||
dict: Dictionary of TaskThread objects. Key is TaskThread ID.
|
||||
"""
|
||||
return {str(t.id): t for t in self._tasks}
|
||||
|
||||
@property
|
||||
def states(self):
|
||||
"""
|
||||
Returns:
|
||||
dict: Dictionary of TaskThread.state dictionaries. Key is TaskThread ID.
|
||||
"""
|
||||
return {str(t.id): t.state for t in self._tasks}
|
||||
|
||||
def new(self, f, *args, **kwargs):
|
||||
# copy_current_request_context allows threads to access flask current_app
|
||||
task = TaskThread(
|
||||
target=copy_current_request_context(f), args=args, kwargs=kwargs
|
||||
)
|
||||
self._tasks.append(task)
|
||||
return task
|
||||
|
||||
def remove(self, task_id):
|
||||
for task in self._tasks:
|
||||
if (task.id == task_id) and not task.isAlive():
|
||||
del task
|
||||
|
||||
def cleanup(self):
|
||||
for task in self._tasks:
|
||||
if not task.isAlive():
|
||||
del task
|
||||
|
||||
|
||||
# Task management
|
||||
|
||||
|
||||
def tasks():
|
||||
"""
|
||||
List of tasks in default taskmaster
|
||||
Returns:
|
||||
list: List of tasks in default taskmaster
|
||||
"""
|
||||
global _default_task_master
|
||||
return _default_task_master.tasks
|
||||
|
||||
|
||||
def dict():
|
||||
"""
|
||||
Dictionary of tasks in default taskmaster
|
||||
Returns:
|
||||
dict: Dictionary of tasks in default taskmaster
|
||||
"""
|
||||
global _default_task_master
|
||||
return _default_task_master.dict
|
||||
|
||||
|
||||
def states():
|
||||
"""
|
||||
Dictionary of TaskThread.state dictionaries. Key is TaskThread ID.
|
||||
Returns:
|
||||
dict: Dictionary of task states in default taskmaster
|
||||
"""
|
||||
global _default_task_master
|
||||
return _default_task_master.states
|
||||
|
||||
|
||||
def cleanup_tasks():
|
||||
global _default_task_master
|
||||
return _default_task_master.cleanup()
|
||||
|
||||
|
||||
def remove_task(task_id: str):
|
||||
global _default_task_master
|
||||
return _default_task_master.remove(task_id)
|
||||
|
||||
|
||||
# Operations on the current task
|
||||
|
||||
|
||||
def current_task():
|
||||
current_task_thread = threading.current_thread()
|
||||
if not isinstance(current_task_thread, TaskThread):
|
||||
return None
|
||||
return current_task_thread
|
||||
|
||||
|
||||
def update_task_progress(progress: int):
|
||||
if current_task():
|
||||
current_task().update_progress(progress)
|
||||
else:
|
||||
logging.info("Cannot update task progress of __main__ thread. Skipping.")
|
||||
|
||||
|
||||
def update_task_data(data: dict):
|
||||
if current_task():
|
||||
current_task().update_data(data)
|
||||
else:
|
||||
logging.info("Cannot update task data of __main__ thread. Skipping.")
|
||||
|
||||
|
||||
# Main "taskify" functions
|
||||
|
||||
|
||||
def taskify(f):
|
||||
"""
|
||||
A decorator that wraps the passed in function
|
||||
and surpresses exceptions should one occur
|
||||
"""
|
||||
|
||||
@wraps(f)
|
||||
def wrapped(*args, **kwargs):
|
||||
task = _default_task_master.new(
|
||||
f, *args, **kwargs
|
||||
) # Append to parent object's task list
|
||||
task.start() # Start the function
|
||||
return task
|
||||
|
||||
return wrapped
|
||||
|
||||
|
||||
# Create our default, protected, module-level task pool
|
||||
_default_task_master = TaskMaster()
|
||||
|
|
@ -1,200 +0,0 @@
|
|||
import ctypes
|
||||
import datetime
|
||||
import logging
|
||||
import traceback
|
||||
import uuid
|
||||
|
||||
import threading
|
||||
|
||||
_LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ThreadTerminationError(SystemExit):
|
||||
"""Sibling of SystemExit, but specific to thread termination."""
|
||||
|
||||
|
||||
class TaskThread(threading.Thread):
|
||||
def __init__(self, target=None, name=None, args=(), kwargs={}, daemon=True):
|
||||
threading.Thread.__init__(
|
||||
self,
|
||||
group=None,
|
||||
target=target,
|
||||
name=name,
|
||||
args=args,
|
||||
kwargs=kwargs,
|
||||
daemon=daemon,
|
||||
)
|
||||
|
||||
# A UUID for the TaskThread (not the same as the threading.Thread ident)
|
||||
self._ID = uuid.uuid4() # Task ID
|
||||
|
||||
# Make _target, _args, and _kwargs available to the subclass
|
||||
self._target = target
|
||||
self._args = args
|
||||
self._kwargs = kwargs
|
||||
|
||||
# Nice string representation of target function
|
||||
self.target_string = f"{self._target}(args={self._args}, kwargs={self._kwargs})"
|
||||
|
||||
# Private state properties
|
||||
self._status: str = "idle" # Task status
|
||||
self._return_value = None # Return value
|
||||
self._start_time = None # Task start time
|
||||
self._end_time = None # Task end time
|
||||
|
||||
# Public state properties
|
||||
self.progress: int = None # Percent progress of the task
|
||||
self.data = {} # Dictionary of custom data added during the task
|
||||
|
||||
# Stuff for handling termination
|
||||
self._running_lock = (
|
||||
threading.Lock()
|
||||
) # Lock obtained while self._target is running
|
||||
self._killed = (
|
||||
threading.Event()
|
||||
) # Event triggered when thread is manually terminated
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
"""
|
||||
Return ID of current TaskThread
|
||||
"""
|
||||
return self._ID
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
return {
|
||||
"function": self.target_string,
|
||||
"id": self._ID,
|
||||
"status": self._status,
|
||||
"progress": self.progress,
|
||||
"data": self.data,
|
||||
"return": self._return_value,
|
||||
"start_time": self._start_time,
|
||||
"end_time": self._end_time,
|
||||
}
|
||||
|
||||
def update_progress(self, progress: int):
|
||||
# Update progress of the task
|
||||
self.progress = progress
|
||||
|
||||
def update_data(self, data: dict):
|
||||
# Store data to be used before task finishes (eg for real-time plotting)
|
||||
self.data.update(data)
|
||||
|
||||
def _thread_proc(self, f):
|
||||
"""
|
||||
Wraps the target function to handle recording `status` and `return` to `state`.
|
||||
Happens inside the task thread.
|
||||
"""
|
||||
|
||||
def wrapped(*args, **kwargs):
|
||||
nonlocal self
|
||||
|
||||
self._status = "running"
|
||||
self._start_time = datetime.datetime.now().strftime("%Y-%m-%d %H-%M-%S")
|
||||
try:
|
||||
self._return_value = f(*args, **kwargs)
|
||||
self._status = "success"
|
||||
except Exception as e:
|
||||
logging.error(e)
|
||||
logging.error(traceback.format_exc())
|
||||
self._return_value = str(e)
|
||||
self._status = "error"
|
||||
finally:
|
||||
self._end_time = datetime.datetime.now().strftime("%Y-%m-%d %H-%M-%S")
|
||||
|
||||
return wrapped
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
Overrides default threading.Thread run() method
|
||||
"""
|
||||
logging.debug((self._args, self._kwargs))
|
||||
try:
|
||||
with self._running_lock:
|
||||
if self._killed.is_set():
|
||||
raise ThreadTerminationError()
|
||||
if self._target:
|
||||
self._thread_proc(self._target)(*self._args, **self._kwargs)
|
||||
finally:
|
||||
# Avoid a refcycle if the thread is running a function with
|
||||
# an argument that has a member that points to the thread.
|
||||
del self._target, self._args, self._kwargs
|
||||
|
||||
def wait(self):
|
||||
"""
|
||||
Start waiting for the task to finish before returning
|
||||
"""
|
||||
print("Joining thread {}".format(self))
|
||||
self.join()
|
||||
return self._return_value
|
||||
|
||||
def async_raise(self, exc_type):
|
||||
"""Raise an exception in this thread."""
|
||||
# Should only be called on a started thread, so raise otherwise.
|
||||
assert self.ident is not None, "Only started threads have thread identifier"
|
||||
|
||||
# If the thread has died we don't want to raise an exception so log.
|
||||
if not self.is_alive():
|
||||
_LOG.debug(
|
||||
"Not raising %s because thread %s (%s) is not alive",
|
||||
exc_type,
|
||||
self.name,
|
||||
self.ident,
|
||||
)
|
||||
return
|
||||
|
||||
result = ctypes.pythonapi.PyThreadState_SetAsyncExc(
|
||||
ctypes.c_long(self.ident), ctypes.py_object(exc_type)
|
||||
)
|
||||
if result == 0 and self.is_alive():
|
||||
# Don't raise an exception an error unnecessarily if the thread is dead.
|
||||
raise ValueError("Thread ID was invalid.", self.ident)
|
||||
elif result > 1:
|
||||
# Something bad happened, call with a NULL exception to undo.
|
||||
ctypes.pythonapi.PyThreadState_SetAsyncExc(self.ident, None)
|
||||
raise RuntimeError(
|
||||
"Error: PyThreadState_SetAsyncExc %s %s (%s) %s"
|
||||
% (exc_type, self.name, self.ident, result)
|
||||
)
|
||||
|
||||
def _is_thread_proc_running(self):
|
||||
"""
|
||||
Test if thread funtion (_thread_proc) is running,
|
||||
by attemtping to acquire the lock _thread_proc acquires at runtime.
|
||||
Returns:
|
||||
bool: If _thread_proc is currently running
|
||||
"""
|
||||
could_acquire = self._running_lock.acquire(0)
|
||||
if could_acquire:
|
||||
self._running_lock.release()
|
||||
return False
|
||||
return True
|
||||
|
||||
def terminate(self):
|
||||
"""
|
||||
Raise ThreadTerminatedException in the context of the given thread,
|
||||
which should cause the thread to exit silently.
|
||||
"""
|
||||
_LOG.warning(f"Terminating thread {self}")
|
||||
self._killed.set()
|
||||
if not self.is_alive():
|
||||
logging.debug("Cannot kill thread that is no longer running.")
|
||||
return
|
||||
if not self._is_thread_proc_running():
|
||||
logging.debug(
|
||||
"Thread's _thread_proc function is no longer running, "
|
||||
"will not kill; letting thread exit gracefully."
|
||||
)
|
||||
return
|
||||
self.async_raise(ThreadTerminationError)
|
||||
|
||||
# Wait for the thread for finish closing. If the threaded function has cleanup code in a try-except,
|
||||
# this pause allows it to finish running before the main process can continue.
|
||||
while self._is_thread_proc_running():
|
||||
pass
|
||||
|
||||
# Set state to terminated
|
||||
self._status = "terminated"
|
||||
self.progress = None
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
import collections.abc
|
||||
import re
|
||||
import base64
|
||||
import operator
|
||||
from functools import reduce
|
||||
|
||||
|
||||
def get_docstring(obj):
|
||||
ds = obj.__doc__
|
||||
if ds:
|
||||
return ds.strip()
|
||||
else:
|
||||
return ""
|
||||
|
||||
|
||||
def get_summary(obj):
|
||||
return get_docstring(obj).partition("\n")[0].strip()
|
||||
|
||||
|
||||
def rupdate(d, u):
|
||||
for k, v in u.items():
|
||||
# Merge lists if they're present in both objects
|
||||
if isinstance(v, list):
|
||||
if not k in d:
|
||||
d[k] = []
|
||||
if isinstance(d[k], list):
|
||||
d[k].extend(v)
|
||||
# Recursively merge dictionaries if the element is a dictionary
|
||||
elif isinstance(v, collections.abc.Mapping):
|
||||
if not k in d:
|
||||
d[k] = {}
|
||||
d[k] = rupdate(d.get(k, {}), v)
|
||||
# If not a list or dictionary, overwrite old value with new value
|
||||
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("_", "-")
|
||||
|
|
@ -8,15 +8,13 @@ as well as some Flask imports to simplify API route development
|
|||
from openflexure_microscope.api.utilities import JsonResponse
|
||||
|
||||
# Task management
|
||||
from openflexure_microscope.common.labthings_core.tasks import (
|
||||
from labthings.core.tasks import (
|
||||
current_task,
|
||||
update_task_progress,
|
||||
update_task_data,
|
||||
taskify,
|
||||
)
|
||||
|
||||
# Exceptions
|
||||
from openflexure_microscope.exceptions import TaskDeniedException
|
||||
|
||||
# Flask things
|
||||
from flask import abort, escape, jsonify, Response, request
|
||||
|
|
|
|||
|
|
@ -1,10 +1,6 @@
|
|||
from threading import ThreadError
|
||||
|
||||
|
||||
class TaskDeniedException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class LockError(ThreadError):
|
||||
ERROR_CODES = {
|
||||
"ACQUIRE_ERROR": "Unable to acquire. Lock in use by another thread.",
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ from openflexure_microscope.camera.mock import MockStreamer
|
|||
from openflexure_microscope.utilities import serialise_array_b64
|
||||
from openflexure_microscope.config import user_settings
|
||||
|
||||
from openflexure_microscope.common.labthings_core.lock import CompositeLock
|
||||
from labthings.core.lock import CompositeLock
|
||||
|
||||
|
||||
class Microscope:
|
||||
|
|
@ -33,7 +33,7 @@ class Microscope:
|
|||
self.stage = None #: Currently connected stage object
|
||||
|
||||
# Initialise with an empty composite lock
|
||||
#: :py:class:`openflexure_microscope.common.lock.CompositeLock`: Composite lock for locking both camera and stage
|
||||
#: :py:class:`labthings.lock.CompositeLock`: Composite lock for locking both camera and stage
|
||||
self.lock = CompositeLock([])
|
||||
|
||||
# Apply settings loaded from file
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import numpy as np
|
||||
from abc import ABCMeta, abstractmethod
|
||||
from openflexure_microscope.common.labthings_core.lock import StrictLock
|
||||
from labthings.core.lock import StrictLock
|
||||
|
||||
|
||||
class BaseStage(metaclass=ABCMeta):
|
||||
"""
|
||||
Attributes:
|
||||
lock (:py:class:`openflexure_microscope.common.lock.StrictLock`): Strict lock controlling thread
|
||||
lock (:py:class:`labthings.lock.StrictLock`): Strict lock controlling thread
|
||||
access to camera hardware
|
||||
"""
|
||||
|
||||
|
|
|
|||
530
poetry.lock
generated
530
poetry.lock
generated
|
|
@ -14,6 +14,14 @@ optional = false
|
|||
python-versions = ">=3.5"
|
||||
version = "3.2.0"
|
||||
|
||||
[package.extras]
|
||||
dev = ["PyYAML (>=3.10)", "prance (>=0.11)", "marshmallow (>=2.19.2)", "pytest", "mock", "flake8 (3.7.9)", "flake8-bugbear (19.8.0)", "pre-commit (>=1.20,<2.0)", "tox"]
|
||||
docs = ["marshmallow (>=2.19.2)", "pyyaml (5.2)", "sphinx (2.3.0)", "sphinx-issues (1.2.0)", "sphinx-rtd-theme (0.4.3)"]
|
||||
lint = ["flake8 (3.7.9)", "flake8-bugbear (19.8.0)", "pre-commit (>=1.20,<2.0)"]
|
||||
tests = ["PyYAML (>=3.10)", "prance (>=0.11)", "marshmallow (>=2.19.2)", "pytest", "mock"]
|
||||
validation = ["prance (>=0.11)"]
|
||||
yaml = ["PyYAML (>=3.10)"]
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
|
||||
|
|
@ -47,6 +55,12 @@ optional = false
|
|||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
version = "19.3.0"
|
||||
|
||||
[package.extras]
|
||||
azure-pipelines = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "pytest-azurepipelines"]
|
||||
dev = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "pre-commit"]
|
||||
docs = ["sphinx", "zope.interface"]
|
||||
tests = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"]
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "Internationalization utilities"
|
||||
|
|
@ -72,6 +86,9 @@ attrs = ">=17.4.0"
|
|||
click = ">=6.5"
|
||||
toml = ">=0.9.4"
|
||||
|
||||
[package.extras]
|
||||
d = ["aiohttp (>=3.3.2)"]
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "Python package for providing Mozilla's CA Bundle."
|
||||
|
|
@ -127,6 +144,11 @@ Werkzeug = ">=0.15"
|
|||
click = ">=5.1"
|
||||
itsdangerous = ">=0.24"
|
||||
|
||||
[package.extras]
|
||||
dev = ["pytest", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinxcontrib-log-cabinet", "sphinx-issues"]
|
||||
docs = ["sphinx", "pallets-sphinx-themes", "sphinxcontrib-log-cabinet", "sphinx-issues"]
|
||||
dotenv = ["python-dotenv"]
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "A Flask extension adding a decorator for CORS support"
|
||||
|
|
@ -163,6 +185,12 @@ optional = false
|
|||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
version = "4.3.21"
|
||||
|
||||
[package.extras]
|
||||
pipfile = ["pipreqs", "requirementslib"]
|
||||
pyproject = ["toml"]
|
||||
requirements = ["pipreqs", "pip-api"]
|
||||
xdg_home = ["appdirs (>=1.4.0)"]
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Various helpers to pass data to untrusted environments and back."
|
||||
|
|
@ -182,6 +210,28 @@ version = "2.10.3"
|
|||
[package.dependencies]
|
||||
MarkupSafe = ">=0.23"
|
||||
|
||||
[package.extras]
|
||||
i18n = ["Babel (>=0.8)"]
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = ""
|
||||
name = "labthings"
|
||||
optional = false
|
||||
python-versions = "^3.6"
|
||||
version = "0.1.0"
|
||||
|
||||
[package.dependencies]
|
||||
Flask = "^1.1.1"
|
||||
apispec = "^3.2.0"
|
||||
flask-cors = "^3.0.8"
|
||||
marshmallow = "^3.3.0"
|
||||
webargs = "^5.5.2"
|
||||
|
||||
[package.source]
|
||||
reference = "2854c2ea80fee1053d51edbe34cde8bbdb0364bb"
|
||||
type = "git"
|
||||
url = "https://github.com/labthings/python-labthings.git"
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "A fast and thorough lazy object proxy."
|
||||
|
|
@ -206,6 +256,12 @@ optional = false
|
|||
python-versions = ">=3.5"
|
||||
version = "3.3.0"
|
||||
|
||||
[package.extras]
|
||||
dev = ["pytest", "pytz", "simplejson", "mypy (0.750)", "flake8 (3.7.9)", "flake8-bugbear (19.8.0)", "pre-commit (>=1.20,<2.0)", "tox"]
|
||||
docs = ["sphinx (2.2.2)", "sphinx-issues (1.2.0)", "alabaster (0.7.12)", "sphinx-version-warning (1.1.2)"]
|
||||
lint = ["mypy (0.750)", "flake8 (3.7.9)", "flake8-bugbear (19.8.0)", "pre-commit (>=1.20,<2.0)"]
|
||||
tests = ["pytest", "pytz", "simplejson"]
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "McCabe checker, plugin for flake8"
|
||||
|
|
@ -242,11 +298,15 @@ optional = true
|
|||
python-versions = "*"
|
||||
version = "1.13.1b0"
|
||||
|
||||
[package.extras]
|
||||
array = ["numpy"]
|
||||
doc = ["sphinx"]
|
||||
test = ["coverage", "pytest", "mock", "pillow", "numpy"]
|
||||
|
||||
[package.source]
|
||||
reference = "b39c2b6e42f5f7f57bb46eafcb5c9e2bbdb5d0cb"
|
||||
type = "git"
|
||||
url = "https://github.com/rwb27/picamera.git"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Python Imaging Library (Fork)"
|
||||
|
|
@ -323,6 +383,10 @@ chardet = ">=3.0.2,<3.1.0"
|
|||
idna = ">=2.5,<2.9"
|
||||
urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26"
|
||||
|
||||
[package.extras]
|
||||
security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)"]
|
||||
socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"]
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "a python refactoring library..."
|
||||
|
|
@ -355,8 +419,8 @@ category = "main"
|
|||
description = "Python 2 and 3 compatibility utilities"
|
||||
name = "six"
|
||||
optional = false
|
||||
python-versions = ">=2.6, !=3.0.*, !=3.1.*"
|
||||
version = "1.13.0"
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||
version = "1.14.0"
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
|
|
@ -393,6 +457,10 @@ sphinxcontrib-jsmath = "*"
|
|||
sphinxcontrib-qthelp = "*"
|
||||
sphinxcontrib-serializinghtml = "*"
|
||||
|
||||
[package.extras]
|
||||
docs = ["sphinxcontrib-websupport"]
|
||||
test = ["pytest", "pytest-cov", "html5lib", "flake8 (>=3.5.0)", "flake8-import-order", "mypy (>=0.761)", "docutils-stubs"]
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = ""
|
||||
|
|
@ -401,6 +469,9 @@ optional = false
|
|||
python-versions = "*"
|
||||
version = "1.0.1"
|
||||
|
||||
[package.extras]
|
||||
test = ["pytest", "flake8", "mypy"]
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = ""
|
||||
|
|
@ -409,6 +480,9 @@ optional = false
|
|||
python-versions = "*"
|
||||
version = "1.0.1"
|
||||
|
||||
[package.extras]
|
||||
test = ["pytest", "flake8", "mypy"]
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = ""
|
||||
|
|
@ -417,6 +491,9 @@ optional = false
|
|||
python-versions = "*"
|
||||
version = "1.0.2"
|
||||
|
||||
[package.extras]
|
||||
test = ["pytest", "flake8", "mypy", "html5lib"]
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "Sphinx domain for documenting HTTP APIs"
|
||||
|
|
@ -437,6 +514,9 @@ optional = false
|
|||
python-versions = ">=3.5"
|
||||
version = "1.0.1"
|
||||
|
||||
[package.extras]
|
||||
test = ["pytest", "flake8", "mypy"]
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = ""
|
||||
|
|
@ -445,6 +525,9 @@ optional = false
|
|||
python-versions = "*"
|
||||
version = "1.0.2"
|
||||
|
||||
[package.extras]
|
||||
test = ["pytest", "flake8", "mypy"]
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = ""
|
||||
|
|
@ -453,6 +536,9 @@ optional = false
|
|||
python-versions = "*"
|
||||
version = "1.1.3"
|
||||
|
||||
[package.extras]
|
||||
test = ["pytest", "flake8", "mypy"]
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "Python Library for Tom's Obvious, Minimal Language"
|
||||
|
|
@ -478,6 +564,11 @@ optional = false
|
|||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, <4"
|
||||
version = "1.25.7"
|
||||
|
||||
[package.extras]
|
||||
brotli = ["brotlipy (>=0.6.0)"]
|
||||
secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
|
||||
socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"]
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Declarative parsing and validation of HTTP request objects, with built-in support for popular web frameworks, including Flask, Django, Bottle, Tornado, Pyramid, webapp2, Falcon, and aiohttp."
|
||||
|
|
@ -489,6 +580,13 @@ version = "5.5.2"
|
|||
[package.dependencies]
|
||||
marshmallow = ">=2.15.2"
|
||||
|
||||
[package.extras]
|
||||
dev = ["pytest", "mock", "webtest (2.0.33)", "Flask (>=0.12.2)", "Django (>=1.11.16)", "bottle (>=0.12.13)", "tornado (>=4.5.2)", "pyramid (>=1.9.1)", "webapp2 (>=3.0.0b1)", "falcon (>=1.4.0,<2.0)", "flake8 (3.7.8)", "pre-commit (>=1.17,<2.0)", "tox", "webtest-aiohttp (2.0.0)", "pytest-aiohttp (>=0.3.0)", "aiohttp (>=3.0.0)", "mypy (0.730)", "flake8-bugbear (19.8.0)"]
|
||||
docs = ["Sphinx (2.2.0)", "sphinx-issues (1.2.0)", "sphinx-typlog-theme (0.7.3)", "Flask (>=0.12.2)", "Django (>=1.11.16)", "bottle (>=0.12.13)", "tornado (>=4.5.2)", "pyramid (>=1.9.1)", "webapp2 (>=3.0.0b1)", "falcon (>=1.4.0,<2.0)", "aiohttp (>=3.0.0)"]
|
||||
frameworks = ["Flask (>=0.12.2)", "Django (>=1.11.16)", "bottle (>=0.12.13)", "tornado (>=4.5.2)", "pyramid (>=1.9.1)", "webapp2 (>=3.0.0b1)", "falcon (>=1.4.0,<2.0)", "aiohttp (>=3.0.0)"]
|
||||
lint = ["flake8 (3.7.8)", "pre-commit (>=1.17,<2.0)", "mypy (0.730)", "flake8-bugbear (19.8.0)"]
|
||||
tests = ["pytest", "mock", "webtest (2.0.33)", "Flask (>=0.12.2)", "Django (>=1.11.16)", "bottle (>=0.12.13)", "tornado (>=4.5.2)", "pyramid (>=1.9.1)", "webapp2 (>=3.0.0b1)", "falcon (>=1.4.0,<2.0)", "webtest-aiohttp (2.0.0)", "pytest-aiohttp (>=0.3.0)", "aiohttp (>=3.0.0)"]
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "The comprehensive WSGI web application library."
|
||||
|
|
@ -497,6 +595,11 @@ optional = false
|
|||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
version = "0.16.0"
|
||||
|
||||
[package.extras]
|
||||
dev = ["pytest", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinx-issues"]
|
||||
termcolor = ["termcolor"]
|
||||
watchdog = ["watchdog"]
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "Module for decorators, wrappers and monkey patching."
|
||||
|
|
@ -509,60 +612,373 @@ version = "1.11.2"
|
|||
rpi = ["picamera", "RPi.GPIO"]
|
||||
|
||||
[metadata]
|
||||
content-hash = "26681a870da7f987373db62329a0dee4b39f6be767ef86faf47815f5f35abf8b"
|
||||
content-hash = "53c82daa835ef29f7aac8ac7d36048487a9d989662a58a2d05d9a70465fbc8c6"
|
||||
python-versions = "^3.6"
|
||||
|
||||
[metadata.hashes]
|
||||
alabaster = ["446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359", "a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"]
|
||||
apispec = ["c1625ae910f699c9adb21928693a3245b9faab6843f1b1c94ab2d505549cd78a", "f2ecb3a6534cfb42cf69b5c1222d2c0e695aab5ec3d7edde31b7354670948f05"]
|
||||
appdirs = ["9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92", "d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"]
|
||||
astroid = ["71ea07f44df9568a75d0f354c49143a4575d90645e9fead6dfb52c26a85ed13a", "840947ebfa8b58f318d42301cf8c0a20fd794a33b61cc4638e28e9e61ba32f42"]
|
||||
attrs = ["08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c", "f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"]
|
||||
babel = ["1aac2ae2d0d8ea368fa90906567f5c08463d98ade155c0c4bfedd6a0f7160e38", "d670ea0b10f8b723672d3a6abeb87b565b244da220d76b4dba1b66269ec152d4"]
|
||||
black = ["817243426042db1d36617910df579a54f1afd659adb96fc5032fcf4b36209739", "e030a9a28f542debc08acceb273f228ac422798e5215ba2a791a6ddeaaca22a5"]
|
||||
certifi = ["017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3", "25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f"]
|
||||
chardet = ["84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", "fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"]
|
||||
click = ["2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", "5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"]
|
||||
colorama = ["7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff", "e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"]
|
||||
docutils = ["0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af", "c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc"]
|
||||
flask = ["13f9f196f330c7c2c5d7a5cf91af894110ca0215ac051b5844701f2bfd934d52", "45eb5a6fd193d6cf7e0cf5d8a5b31f83d5faae0293695626f539a823e93b13f6"]
|
||||
flask-cors = ["72170423eb4612f0847318afff8c247b38bd516b7737adfc10d1c2cdbb382d16", "f4d97201660e6bbcff2d89d082b5b6d31abee04b1b3003ee073a6fd25ad1d69a"]
|
||||
idna = ["c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", "ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"]
|
||||
imagesize = ["6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1", "b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"]
|
||||
isort = ["54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1", "6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd"]
|
||||
itsdangerous = ["321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19", "b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"]
|
||||
jinja2 = ["74320bb91f31270f9551d46522e33af46a80c3d619f4a4bf42b3164d30b5911f", "9fe95f19286cfefaa917656583d020be14e7859c6b0252588391e47db34527de"]
|
||||
lazy-object-proxy = ["0c4b206227a8097f05c4dbdd323c50edf81f15db3b8dc064d08c62d37e1a504d", "194d092e6f246b906e8f70884e620e459fc54db3259e60cf69a4d66c3fda3449", "1be7e4c9f96948003609aa6c974ae59830a6baecc5376c25c92d7d697e684c08", "4677f594e474c91da97f489fea5b7daa17b5517190899cf213697e48d3902f5a", "48dab84ebd4831077b150572aec802f303117c8cc5c871e182447281ebf3ac50", "5541cada25cd173702dbd99f8e22434105456314462326f06dba3e180f203dfd", "59f79fef100b09564bc2df42ea2d8d21a64fdcda64979c0fa3db7bdaabaf6239", "8d859b89baf8ef7f8bc6b00aa20316483d67f0b1cbf422f5b4dc56701c8f2ffb", "9254f4358b9b541e3441b007a0ea0764b9d056afdeafc1a5569eee1cc6c1b9ea", "9651375199045a358eb6741df3e02a651e0330be090b3bc79f6d0de31a80ec3e", "97bb5884f6f1cdce0099f86b907aa41c970c3c672ac8b9c8352789e103cf3156", "9b15f3f4c0f35727d3a0fba4b770b3c4ebbb1fa907dbcc046a1d2799f3edd142", "a2238e9d1bb71a56cd710611a1614d1194dc10a175c1e08d75e1a7bcc250d442", "a6ae12d08c0bf9909ce12385803a543bfe99b95fe01e752536a60af2b7797c62", "ca0a928a3ddbc5725be2dd1cf895ec0a254798915fb3a36af0964a0a4149e3db", "cb2c7c57005a6804ab66f106ceb8482da55f5314b7fcb06551db1edae4ad1531", "d74bb8693bf9cf75ac3b47a54d716bbb1a92648d5f781fc799347cfc95952383", "d945239a5639b3ff35b70a88c5f2f491913eb94871780ebfabb2568bd58afc5a", "eba7011090323c1dadf18b3b689845fd96a61ba0a1dfbd7f24b921398affc357", "efa1909120ce98bbb3777e8b6f92237f5d5c8ea6758efea36a473e1d38f7d3e4", "f3900e8a5de27447acbf900b4750b0ddfd7ec1ea7fbaf11dfa911141bc522af0"]
|
||||
markupsafe = ["00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", "09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", "09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", "1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", "24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", "29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", "43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", "46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", "500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", "535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", "62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", "6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", "717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", "79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", "7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", "88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", "8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", "98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", "9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", "9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", "ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", "b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", "b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", "b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", "ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", "c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", "cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", "e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"]
|
||||
marshmallow = ["0ba81b6da4ae69eb229b74b3c741ff13fe04fb899824377b1aff5aaa1a9fd46e", "3e53dd9e9358977a3929e45cdbe4a671f9eff53a7d6a23f33ed3eab8c1890d8f"]
|
||||
mccabe = ["ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", "dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"]
|
||||
numpy = ["1786a08236f2c92ae0e70423c45e1e62788ed33028f94ca99c4df03f5be6b3c6", "17aa7a81fe7599a10f2b7d95856dc5cf84a4eefa45bc96123cbbc3ebc568994e", "20b26aaa5b3da029942cdcce719b363dbe58696ad182aff0e5dcb1687ec946dc", "2d75908ab3ced4223ccba595b48e538afa5ecc37405923d1fea6906d7c3a50bc", "39d2c685af15d3ce682c99ce5925cc66efc824652e10990d2462dfe9b8918c6a", "56bc8ded6fcd9adea90f65377438f9fea8c05fcf7c5ba766bef258d0da1554aa", "590355aeade1a2eaba17617c19edccb7db8d78760175256e3cf94590a1a964f3", "70a840a26f4e61defa7bdf811d7498a284ced303dfbc35acb7be12a39b2aa121", "77c3bfe65d8560487052ad55c6998a04b654c2fbc36d546aef2b2e511e760971", "9537eecf179f566fd1c160a2e912ca0b8e02d773af0a7a1120ad4f7507cd0d26", "9acdf933c1fd263c513a2df3dceecea6f3ff4419d80bf238510976bf9bcb26cd", "ae0975f42ab1f28364dcda3dde3cf6c1ddab3e1d4b2909da0cb0191fa9ca0480", "b3af02ecc999c8003e538e60c89a2b37646b39b688d4e44d7373e11c2debabec", "b6ff59cee96b454516e47e7721098e6ceebef435e3e21ac2d6c3b8b02628eb77", "b765ed3930b92812aa698a455847141869ef755a87e099fddd4ccf9d81fffb57", "c98c5ffd7d41611407a1103ae11c8b634ad6a43606eca3e2a5a269e5d6e8eb07", "cf7eb6b1025d3e169989416b1adcd676624c2dbed9e3bcb7137f51bfc8cc2572", "d92350c22b150c1cae7ebb0ee8b5670cc84848f6359cf6b5d8f86617098a9b73", "e422c3152921cece8b6a2fb6b0b4d73b6579bd20ae075e7d15143e711f3ca2ca", "e840f552a509e3380b0f0ec977e8124d0dc34dc0e68289ca28f4d7c1d0d79474", "f3d0a94ad151870978fb93538e95411c83899c9dc63e6fb65542f769568ecfa5"]
|
||||
packaging = ["aec3fdbb8bc9e4bb65f0634b9f551ced63983a529d6a8931817d52fdd0816ddb", "fe1d8331dfa7cc0a883b49d75fc76380b2ab2734b220fbb87d774e4fd4b851f8"]
|
||||
[metadata.files]
|
||||
alabaster = [
|
||||
{file = "alabaster-0.7.12-py2.py3-none-any.whl", hash = "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359"},
|
||||
{file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"},
|
||||
]
|
||||
apispec = [
|
||||
{file = "apispec-3.2.0-py2.py3-none-any.whl", hash = "sha256:f2ecb3a6534cfb42cf69b5c1222d2c0e695aab5ec3d7edde31b7354670948f05"},
|
||||
{file = "apispec-3.2.0.tar.gz", hash = "sha256:c1625ae910f699c9adb21928693a3245b9faab6843f1b1c94ab2d505549cd78a"},
|
||||
]
|
||||
appdirs = [
|
||||
{file = "appdirs-1.4.3-py2.py3-none-any.whl", hash = "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"},
|
||||
{file = "appdirs-1.4.3.tar.gz", hash = "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92"},
|
||||
]
|
||||
astroid = [
|
||||
{file = "astroid-2.3.3-py3-none-any.whl", hash = "sha256:840947ebfa8b58f318d42301cf8c0a20fd794a33b61cc4638e28e9e61ba32f42"},
|
||||
{file = "astroid-2.3.3.tar.gz", hash = "sha256:71ea07f44df9568a75d0f354c49143a4575d90645e9fead6dfb52c26a85ed13a"},
|
||||
]
|
||||
attrs = [
|
||||
{file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"},
|
||||
{file = "attrs-19.3.0.tar.gz", hash = "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"},
|
||||
]
|
||||
babel = [
|
||||
{file = "Babel-2.8.0-py2.py3-none-any.whl", hash = "sha256:d670ea0b10f8b723672d3a6abeb87b565b244da220d76b4dba1b66269ec152d4"},
|
||||
{file = "Babel-2.8.0.tar.gz", hash = "sha256:1aac2ae2d0d8ea368fa90906567f5c08463d98ade155c0c4bfedd6a0f7160e38"},
|
||||
]
|
||||
black = [
|
||||
{file = "black-18.9b0-py36-none-any.whl", hash = "sha256:817243426042db1d36617910df579a54f1afd659adb96fc5032fcf4b36209739"},
|
||||
{file = "black-18.9b0.tar.gz", hash = "sha256:e030a9a28f542debc08acceb273f228ac422798e5215ba2a791a6ddeaaca22a5"},
|
||||
]
|
||||
certifi = [
|
||||
{file = "certifi-2019.11.28-py2.py3-none-any.whl", hash = "sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3"},
|
||||
{file = "certifi-2019.11.28.tar.gz", hash = "sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f"},
|
||||
]
|
||||
chardet = [
|
||||
{file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"},
|
||||
{file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"},
|
||||
]
|
||||
click = [
|
||||
{file = "Click-7.0-py2.py3-none-any.whl", hash = "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13"},
|
||||
{file = "Click-7.0.tar.gz", hash = "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"},
|
||||
]
|
||||
colorama = [
|
||||
{file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"},
|
||||
{file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"},
|
||||
]
|
||||
docutils = [
|
||||
{file = "docutils-0.16-py2.py3-none-any.whl", hash = "sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af"},
|
||||
{file = "docutils-0.16.tar.gz", hash = "sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc"},
|
||||
]
|
||||
flask = [
|
||||
{file = "Flask-1.1.1-py2.py3-none-any.whl", hash = "sha256:45eb5a6fd193d6cf7e0cf5d8a5b31f83d5faae0293695626f539a823e93b13f6"},
|
||||
{file = "Flask-1.1.1.tar.gz", hash = "sha256:13f9f196f330c7c2c5d7a5cf91af894110ca0215ac051b5844701f2bfd934d52"},
|
||||
]
|
||||
flask-cors = [
|
||||
{file = "Flask-Cors-3.0.8.tar.gz", hash = "sha256:72170423eb4612f0847318afff8c247b38bd516b7737adfc10d1c2cdbb382d16"},
|
||||
{file = "Flask_Cors-3.0.8-py2.py3-none-any.whl", hash = "sha256:f4d97201660e6bbcff2d89d082b5b6d31abee04b1b3003ee073a6fd25ad1d69a"},
|
||||
]
|
||||
idna = [
|
||||
{file = "idna-2.8-py2.py3-none-any.whl", hash = "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"},
|
||||
{file = "idna-2.8.tar.gz", hash = "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407"},
|
||||
]
|
||||
imagesize = [
|
||||
{file = "imagesize-1.2.0-py2.py3-none-any.whl", hash = "sha256:6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1"},
|
||||
{file = "imagesize-1.2.0.tar.gz", hash = "sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"},
|
||||
]
|
||||
isort = [
|
||||
{file = "isort-4.3.21-py2.py3-none-any.whl", hash = "sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd"},
|
||||
{file = "isort-4.3.21.tar.gz", hash = "sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1"},
|
||||
]
|
||||
itsdangerous = [
|
||||
{file = "itsdangerous-1.1.0-py2.py3-none-any.whl", hash = "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"},
|
||||
{file = "itsdangerous-1.1.0.tar.gz", hash = "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19"},
|
||||
]
|
||||
jinja2 = [
|
||||
{file = "Jinja2-2.10.3-py2.py3-none-any.whl", hash = "sha256:74320bb91f31270f9551d46522e33af46a80c3d619f4a4bf42b3164d30b5911f"},
|
||||
{file = "Jinja2-2.10.3.tar.gz", hash = "sha256:9fe95f19286cfefaa917656583d020be14e7859c6b0252588391e47db34527de"},
|
||||
]
|
||||
labthings = []
|
||||
lazy-object-proxy = [
|
||||
{file = "lazy-object-proxy-1.4.3.tar.gz", hash = "sha256:f3900e8a5de27447acbf900b4750b0ddfd7ec1ea7fbaf11dfa911141bc522af0"},
|
||||
{file = "lazy_object_proxy-1.4.3-cp27-cp27m-macosx_10_13_x86_64.whl", hash = "sha256:a2238e9d1bb71a56cd710611a1614d1194dc10a175c1e08d75e1a7bcc250d442"},
|
||||
{file = "lazy_object_proxy-1.4.3-cp27-cp27m-win32.whl", hash = "sha256:efa1909120ce98bbb3777e8b6f92237f5d5c8ea6758efea36a473e1d38f7d3e4"},
|
||||
{file = "lazy_object_proxy-1.4.3-cp27-cp27m-win_amd64.whl", hash = "sha256:4677f594e474c91da97f489fea5b7daa17b5517190899cf213697e48d3902f5a"},
|
||||
{file = "lazy_object_proxy-1.4.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:0c4b206227a8097f05c4dbdd323c50edf81f15db3b8dc064d08c62d37e1a504d"},
|
||||
{file = "lazy_object_proxy-1.4.3-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:d945239a5639b3ff35b70a88c5f2f491913eb94871780ebfabb2568bd58afc5a"},
|
||||
{file = "lazy_object_proxy-1.4.3-cp34-cp34m-win32.whl", hash = "sha256:9651375199045a358eb6741df3e02a651e0330be090b3bc79f6d0de31a80ec3e"},
|
||||
{file = "lazy_object_proxy-1.4.3-cp34-cp34m-win_amd64.whl", hash = "sha256:eba7011090323c1dadf18b3b689845fd96a61ba0a1dfbd7f24b921398affc357"},
|
||||
{file = "lazy_object_proxy-1.4.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:48dab84ebd4831077b150572aec802f303117c8cc5c871e182447281ebf3ac50"},
|
||||
{file = "lazy_object_proxy-1.4.3-cp35-cp35m-win32.whl", hash = "sha256:ca0a928a3ddbc5725be2dd1cf895ec0a254798915fb3a36af0964a0a4149e3db"},
|
||||
{file = "lazy_object_proxy-1.4.3-cp35-cp35m-win_amd64.whl", hash = "sha256:194d092e6f246b906e8f70884e620e459fc54db3259e60cf69a4d66c3fda3449"},
|
||||
{file = "lazy_object_proxy-1.4.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:97bb5884f6f1cdce0099f86b907aa41c970c3c672ac8b9c8352789e103cf3156"},
|
||||
{file = "lazy_object_proxy-1.4.3-cp36-cp36m-win32.whl", hash = "sha256:cb2c7c57005a6804ab66f106ceb8482da55f5314b7fcb06551db1edae4ad1531"},
|
||||
{file = "lazy_object_proxy-1.4.3-cp36-cp36m-win_amd64.whl", hash = "sha256:8d859b89baf8ef7f8bc6b00aa20316483d67f0b1cbf422f5b4dc56701c8f2ffb"},
|
||||
{file = "lazy_object_proxy-1.4.3-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:1be7e4c9f96948003609aa6c974ae59830a6baecc5376c25c92d7d697e684c08"},
|
||||
{file = "lazy_object_proxy-1.4.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d74bb8693bf9cf75ac3b47a54d716bbb1a92648d5f781fc799347cfc95952383"},
|
||||
{file = "lazy_object_proxy-1.4.3-cp37-cp37m-win32.whl", hash = "sha256:9b15f3f4c0f35727d3a0fba4b770b3c4ebbb1fa907dbcc046a1d2799f3edd142"},
|
||||
{file = "lazy_object_proxy-1.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9254f4358b9b541e3441b007a0ea0764b9d056afdeafc1a5569eee1cc6c1b9ea"},
|
||||
{file = "lazy_object_proxy-1.4.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:a6ae12d08c0bf9909ce12385803a543bfe99b95fe01e752536a60af2b7797c62"},
|
||||
{file = "lazy_object_proxy-1.4.3-cp38-cp38-win32.whl", hash = "sha256:5541cada25cd173702dbd99f8e22434105456314462326f06dba3e180f203dfd"},
|
||||
{file = "lazy_object_proxy-1.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:59f79fef100b09564bc2df42ea2d8d21a64fdcda64979c0fa3db7bdaabaf6239"},
|
||||
]
|
||||
markupsafe = [
|
||||
{file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"},
|
||||
{file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"},
|
||||
{file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183"},
|
||||
{file = "MarkupSafe-1.1.1-cp27-cp27m-win32.whl", hash = "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b"},
|
||||
{file = "MarkupSafe-1.1.1-cp27-cp27m-win_amd64.whl", hash = "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e"},
|
||||
{file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f"},
|
||||
{file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1"},
|
||||
{file = "MarkupSafe-1.1.1-cp34-cp34m-macosx_10_6_intel.whl", hash = "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5"},
|
||||
{file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1"},
|
||||
{file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735"},
|
||||
{file = "MarkupSafe-1.1.1-cp34-cp34m-win32.whl", hash = "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21"},
|
||||
{file = "MarkupSafe-1.1.1-cp34-cp34m-win_amd64.whl", hash = "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235"},
|
||||
{file = "MarkupSafe-1.1.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b"},
|
||||
{file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f"},
|
||||
{file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905"},
|
||||
{file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"},
|
||||
{file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"},
|
||||
{file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"},
|
||||
{file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"},
|
||||
{file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"},
|
||||
{file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"},
|
||||
{file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"},
|
||||
{file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"},
|
||||
{file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"},
|
||||
{file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"},
|
||||
{file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"},
|
||||
{file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"},
|
||||
{file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"},
|
||||
]
|
||||
marshmallow = [
|
||||
{file = "marshmallow-3.3.0-py2.py3-none-any.whl", hash = "sha256:3e53dd9e9358977a3929e45cdbe4a671f9eff53a7d6a23f33ed3eab8c1890d8f"},
|
||||
{file = "marshmallow-3.3.0.tar.gz", hash = "sha256:0ba81b6da4ae69eb229b74b3c741ff13fe04fb899824377b1aff5aaa1a9fd46e"},
|
||||
]
|
||||
mccabe = [
|
||||
{file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"},
|
||||
{file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"},
|
||||
]
|
||||
numpy = [
|
||||
{file = "numpy-1.18.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:20b26aaa5b3da029942cdcce719b363dbe58696ad182aff0e5dcb1687ec946dc"},
|
||||
{file = "numpy-1.18.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:70a840a26f4e61defa7bdf811d7498a284ced303dfbc35acb7be12a39b2aa121"},
|
||||
{file = "numpy-1.18.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:17aa7a81fe7599a10f2b7d95856dc5cf84a4eefa45bc96123cbbc3ebc568994e"},
|
||||
{file = "numpy-1.18.1-cp35-cp35m-win32.whl", hash = "sha256:f3d0a94ad151870978fb93538e95411c83899c9dc63e6fb65542f769568ecfa5"},
|
||||
{file = "numpy-1.18.1-cp35-cp35m-win_amd64.whl", hash = "sha256:1786a08236f2c92ae0e70423c45e1e62788ed33028f94ca99c4df03f5be6b3c6"},
|
||||
{file = "numpy-1.18.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:ae0975f42ab1f28364dcda3dde3cf6c1ddab3e1d4b2909da0cb0191fa9ca0480"},
|
||||
{file = "numpy-1.18.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:cf7eb6b1025d3e169989416b1adcd676624c2dbed9e3bcb7137f51bfc8cc2572"},
|
||||
{file = "numpy-1.18.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:b765ed3930b92812aa698a455847141869ef755a87e099fddd4ccf9d81fffb57"},
|
||||
{file = "numpy-1.18.1-cp36-cp36m-win32.whl", hash = "sha256:2d75908ab3ced4223ccba595b48e538afa5ecc37405923d1fea6906d7c3a50bc"},
|
||||
{file = "numpy-1.18.1-cp36-cp36m-win_amd64.whl", hash = "sha256:9acdf933c1fd263c513a2df3dceecea6f3ff4419d80bf238510976bf9bcb26cd"},
|
||||
{file = "numpy-1.18.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:56bc8ded6fcd9adea90f65377438f9fea8c05fcf7c5ba766bef258d0da1554aa"},
|
||||
{file = "numpy-1.18.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:e422c3152921cece8b6a2fb6b0b4d73b6579bd20ae075e7d15143e711f3ca2ca"},
|
||||
{file = "numpy-1.18.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:b3af02ecc999c8003e538e60c89a2b37646b39b688d4e44d7373e11c2debabec"},
|
||||
{file = "numpy-1.18.1-cp37-cp37m-win32.whl", hash = "sha256:d92350c22b150c1cae7ebb0ee8b5670cc84848f6359cf6b5d8f86617098a9b73"},
|
||||
{file = "numpy-1.18.1-cp37-cp37m-win_amd64.whl", hash = "sha256:77c3bfe65d8560487052ad55c6998a04b654c2fbc36d546aef2b2e511e760971"},
|
||||
{file = "numpy-1.18.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c98c5ffd7d41611407a1103ae11c8b634ad6a43606eca3e2a5a269e5d6e8eb07"},
|
||||
{file = "numpy-1.18.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:9537eecf179f566fd1c160a2e912ca0b8e02d773af0a7a1120ad4f7507cd0d26"},
|
||||
{file = "numpy-1.18.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:e840f552a509e3380b0f0ec977e8124d0dc34dc0e68289ca28f4d7c1d0d79474"},
|
||||
{file = "numpy-1.18.1-cp38-cp38-win32.whl", hash = "sha256:590355aeade1a2eaba17617c19edccb7db8d78760175256e3cf94590a1a964f3"},
|
||||
{file = "numpy-1.18.1-cp38-cp38-win_amd64.whl", hash = "sha256:39d2c685af15d3ce682c99ce5925cc66efc824652e10990d2462dfe9b8918c6a"},
|
||||
{file = "numpy-1.18.1.zip", hash = "sha256:b6ff59cee96b454516e47e7721098e6ceebef435e3e21ac2d6c3b8b02628eb77"},
|
||||
]
|
||||
packaging = [
|
||||
{file = "packaging-20.0-py2.py3-none-any.whl", hash = "sha256:aec3fdbb8bc9e4bb65f0634b9f551ced63983a529d6a8931817d52fdd0816ddb"},
|
||||
{file = "packaging-20.0.tar.gz", hash = "sha256:fe1d8331dfa7cc0a883b49d75fc76380b2ab2734b220fbb87d774e4fd4b851f8"},
|
||||
]
|
||||
picamera = []
|
||||
pillow = ["01a501be4ae05fd714d269cb9c9f145518e58e73faa3f140ddb67fae0c2607b1", "051de330a06c99d6f84bcf582960487835bcae3fc99365185dc2d4f65a390c0e", "07c35919f983c2c593498edcc126ad3a94154184899297cc9d27a6587672cbaa", "0ae5289948c5e0a16574750021bd8be921c27d4e3527800dc9c2c1d2abc81bf7", "0b1efce03619cdbf8bcc61cfae81fcda59249a469f31c6735ea59badd4a6f58a", "0cf0208500df8d0c3cad6383cd98a2d038b0678fd4f777a8f7e442c5faeee81d", "163136e09bd1d6c6c6026b0a662976e86c58b932b964f255ff384ecc8c3cefa3", "18e912a6ccddf28defa196bd2021fe33600cbe5da1aa2f2e2c6df15f720b73d1", "24ec3dea52339a610d34401d2d53d0fb3c7fd08e34b20c95d2ad3973193591f1", "267f8e4c0a1d7e36e97c6a604f5b03ef58e2b81c1becb4fccecddcb37e063cc7", "3273a28734175feebbe4d0a4cde04d4ed20f620b9b506d26f44379d3c72304e1", "39fbd5d62167197318a0371b2a9c699ce261b6800bb493eadde2ba30d868fe8c", "4132c78200372045bb348fcad8d52518c8f5cfc077b1089949381ee4a61f1c6d", "4baab2d2da57b0d9d544a2ce0f461374dd90ccbcf723fe46689aff906d43a964", "4c678e23006798fc8b6f4cef2eaad267d53ff4c1779bd1af8725cc11b72a63f3", "4d4bc2e6bb6861103ea4655d6b6f67af8e5336e7216e20fff3e18ffa95d7a055", "505738076350a337c1740a31646e1de09a164c62c07db3b996abdc0f9d2e50cf", "5233664eadfa342c639b9b9977190d64ad7aca4edc51a966394d7e08e7f38a9f", "52e2e56fc3706d8791761a157115dc8391319720ad60cc32992350fda74b6be2", "5337ac3280312aa065ed0a8ec1e4b6142e9f15c31baed36b5cd964745853243f", "5ccd97e0f01f42b7e35907272f0f8ad2c3660a482d799a0c564c7d50e83604d4", "5d95cb9f6cced2628f3e4de7e795e98b2659dfcc7176ab4a01a8b48c2c2f488f", "634209852cc06c0c1243cc74f8fdc8f7444d866221de51125f7b696d775ec5ca", "75d1f20bd8072eff92c5f457c266a61619a02d03ece56544195c56d41a1a0522", "7eda4c737637af74bac4b23aa82ea6fbb19002552be85f0b89bc27e3a762d239", "801ddaa69659b36abf4694fed5aa9f61d1ecf2daaa6c92541bbbbb775d97b9fe", "825aa6d222ce2c2b90d34a0ea31914e141a85edefc07e17342f1d2fdf121c07c", "87fe838f9dac0597f05f2605c0700b1926f9390c95df6af45d83141e0c514bd9", "9c215442ff8249d41ff58700e91ef61d74f47dfd431a50253e1a1ca9436b0697", "a3d90022f2202bbb14da991f26ca7a30b7e4c62bf0f8bf9825603b22d7e87494", "a631fd36a9823638fe700d9225f9698fb59d049c942d322d4c09544dc2115356", "a6523a23a205be0fe664b6b8747a5c86d55da960d9586db039eec9f5c269c0e6", "a756ecf9f4b9b3ed49a680a649af45a8767ad038de39e6c030919c2f443eb000", "ac036b6a6bac7010c58e643d78c234c2f7dc8bb7e591bd8bc3555cf4b1527c28", "b117287a5bdc81f1bac891187275ec7e829e961b8032c9e5ff38b70fd036c78f", "ba04f57d1715ca5ff74bb7f8a818bf929a204b3b3c2c2826d1e1cc3b1c13398c", "ba6ef2bd62671c7fb9cdb3277414e87a5cd38b86721039ada1464f7452ad30b2", "c8939dba1a37960a502b1a030a4465c46dd2c2bca7adf05fa3af6bea594e720e", "cd878195166723f30865e05d87cbaf9421614501a4bd48792c5ed28f90fd36ca", "cee815cc62d136e96cf76771b9d3eb58e0777ec18ea50de5cfcede8a7c429aa8", "d1722b7aa4b40cf93ac3c80d3edd48bf93b9208241d166a14ad8e7a20ee1d4f3", "d7c1c06246b05529f9984435fc4fa5a545ea26606e7f450bdbe00c153f5aeaad", "db418635ea20528f247203bf131b40636f77c8209a045b89fa3badb89e1fcea0", "e1555d4fda1db8005de72acf2ded1af660febad09b4708430091159e8ae1963e", "e9c8066249c040efdda84793a2a669076f92a301ceabe69202446abb4c5c5ef9", "e9f13711780c981d6eadd6042af40e172548c54b06266a1aabda7de192db0838", "f0e3288b92ca5dbb1649bd00e80ef652a72b657dc94989fa9c348253d179054b", "f227d7e574d050ff3996049e086e1f18c7bd2d067ef24131e50a1d3fe5831fbc", "f62b1aeb5c2ced8babd4fbba9c74cbef9de309f5ed106184b12d9778a3971f15", "f71ff657e63a9b24cac254bb8c9bd3c89c7a1b5e00ee4b3997ca1c18100dac28", "fc9a12aad714af36cf3ad0275a96a733526571e52710319855628f476dcb144e"]
|
||||
pygments = ["2a3fe295e54a20164a9df49c75fa58526d3be48e14aceba6d6b1e8ac0bfd6f1b", "98c8aa5a9f778fcd1026a17361ddaf7330d1b7c62ae97c3bb0ae73e0b9b6b0fe"]
|
||||
pylint = ["3db5468ad013380e987410a8d6956226963aed94ecb5f9d3a28acca6d9ac36cd", "886e6afc935ea2590b462664b161ca9a5e40168ea99e5300935f6591ad467df4"]
|
||||
pyparsing = ["4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f", "c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec"]
|
||||
pyserial = ["6e2d401fdee0eab996cf734e67773a0143b932772ca8b42451440cfed942c627", "e0770fadba80c31013896c7e6ef703f72e7834965954a78e71a3049488d4d7d8"]
|
||||
pytz = ["1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d", "b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be"]
|
||||
pyyaml = ["3d7da3009c0f3e783b2c873687652d83b1bbfd5c88e9813fb7e5b03c0dd3108b", "3ef3092145e9b70e3ddd2c7ad59bdd0252a94dfe3949721633e41344de00a6bf", "40c71b8e076d0550b2e6380bada1f1cd1017b882f7e16f09a65be98e017f211a", "558dd60b890ba8fd982e05941927a3911dc409a63dcb8b634feaa0cda69330d3", "a7c28b45d9f99102fa092bb213aa12e0aaf9a6a1f5e395d36166639c1f96c3a1", "aa7dd4a6a427aed7df6fb7f08a580d68d9b118d90310374716ae90b710280af1", "bc558586e6045763782014934bfaf39d48b8ae85a2713117d16c39864085c613", "d46d7982b62e0729ad0175a9bc7e10a566fc07b224d2c79fafb5e032727eaa04", "d5eef459e30b09f5a098b9cea68bebfeb268697f78d647bd255a085371ac7f3f", "e01d3203230e1786cd91ccfdc8f8454c8069c91bee3962ad93b87a4b2860f537", "e170a9e6fcfd19021dd29845af83bb79236068bf5fd4df3327c1be18182b2531"]
|
||||
requests = ["11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", "9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"]
|
||||
rope = ["6b728fdc3e98a83446c27a91fc5d56808a004f8beab7a31ab1d7224cecc7d969", "c5c5a6a87f7b1a2095fb311135e2a3d1f194f5ecb96900fdd0a9100881f48aaf", "f0dcf719b63200d492b85535ebe5ea9b29e0d0b8aebeb87fe03fc1a65924fdaf"]
|
||||
"rpi.gpio" = ["a4210ad63bfe844e43995286de0d3950dfacfa0f3799bb9392770ac54a7d2e47"]
|
||||
scipy = ["03b1e0775edbe6a4c64effb05fff2ce1429b76d29d754aa5ee2d848b60033351", "09d008237baabf52a5d4f5a6fcf9b3c03408f3f61a69c404472a16861a73917e", "10325f0ffac2400b1ec09537b7e403419dcd25d9fee602a44e8a32119af9079e", "1db9f964ed9c52dc5bd6127f0dd90ac89791daa690a5665cc01eae185912e1ba", "409846be9d6bdcbd78b9e5afe2f64b2da5a923dd7c1cd0615ce589489533fdbb", "4907040f62b91c2e170359c3d36c000af783f0fa1516a83d6c1517cde0af5340", "6c0543f2fdd38dee631fb023c0f31c284a532d205590b393d72009c14847f5b1", "826b9f5fbb7f908a13aa1efd4b7321e36992f5868d5d8311c7b40cf9b11ca0e7", "a7695a378c2ce402405ea37b12c7a338a8755e081869bd6b95858893ceb617ae", "a84c31e8409b420c3ca57fd30c7589378d6fdc8d155d866a7f8e6e80dec6fd06", "adadeeae5500de0da2b9e8dd478520d0a9945b577b2198f2462555e68f58e7ef", "b283a76a83fe463c9587a2c88003f800e08c3929dfbeba833b78260f9c209785", "c19a7389ab3cd712058a8c3c9ffd8d27a57f3d84b9c91a931f542682bb3d269d", "c3bb4bd2aca82fb498247deeac12265921fe231502a6bc6edea3ee7fe6c40a7a", "c5ea60ece0c0c1c849025bfc541b60a6751b491b6f11dd9ef37ab5b8c9041921", "db61a640ca20f237317d27bc658c1fc54c7581ff7f6502d112922dc285bdabee"]
|
||||
six = ["1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd", "30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66"]
|
||||
snowballstemmer = ["209f257d7533fdb3cb73bdbd24f436239ca3b2fa67d56f6ff88e86be08cc5ef0", "df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52"]
|
||||
sphinx = ["298537cb3234578b2d954ff18c5608468229e116a9757af3b831c2b2b4819159", "e6e766b74f85f37a5f3e0773a1e1be8db3fcb799deb58ca6d18b70b0b44542a5"]
|
||||
sphinxcontrib-applehelp = ["edaa0ab2b2bc74403149cb0209d6775c96de797dfd5b5e2a71981309efab3897", "fb8dee85af95e5c30c91f10e7eb3c8967308518e0f7488a2828ef7bc191d0d5d"]
|
||||
sphinxcontrib-devhelp = ["6c64b077937330a9128a4da74586e8c2130262f014689b4b89e2d08ee7294a34", "9512ecb00a2b0821a146736b39f7aeb90759834b07e81e8cc23a9c70bacb9981"]
|
||||
sphinxcontrib-htmlhelp = ["4670f99f8951bd78cd4ad2ab962f798f5618b17675c35c5ac3b2132a14ea8422", "d4fd39a65a625c9df86d7fa8a2d9f3cd8299a3a4b15db63b50aac9e161d8eff7"]
|
||||
sphinxcontrib-httpdomain = ["1fb5375007d70bf180cdd1c79e741082be7aa2d37ba99efe561e1c2e3f38191e", "ac40b4fba58c76b073b03931c7b8ead611066a6aebccafb34dc19694f4eb6335"]
|
||||
sphinxcontrib-jsmath = ["2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", "a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"]
|
||||
sphinxcontrib-qthelp = ["513049b93031beb1f57d4daea74068a4feb77aa5630f856fcff2e50de14e9a20", "79465ce11ae5694ff165becda529a600c754f4bc459778778c7017374d4d406f"]
|
||||
sphinxcontrib-serializinghtml = ["c0efb33f8052c04fd7a26c0a07f1678e8512e0faec19f4aa8f2473a8b81d5227", "db6615af393650bf1151a6cd39120c29abaf93cc60db8c48eb2dddbfdc3a9768"]
|
||||
toml = ["229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c", "235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e", "f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"]
|
||||
typed-ast = ["0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355", "0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919", "249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa", "24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652", "269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75", "4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01", "498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d", "4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1", "6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907", "715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c", "73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3", "8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b", "8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614", "aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb", "bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b", "c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41", "d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6", "d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34", "d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe", "fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4", "fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"]
|
||||
urllib3 = ["a8a318824cc77d1fd4b2bec2ded92646630d7fe8619497b142c84a9e6f5a7293", "f3c5fd51747d450d4dcf6f923c81f78f811aab8205fda64b0aba34a4e48b0745"]
|
||||
webargs = ["3beca296598067cec24a0b6f91c0afcc19b6e3c4d84ab026b931669628bb47b4", "3f9dc15de183d356c9a0acc159c100ea0506c0c240c1e6f1d8b308c5fed4dbbd", "fa4ad3ad9b38bedd26c619264fdc50d7ae014b49186736bca851e5b5228f2a1b"]
|
||||
werkzeug = ["7280924747b5733b246fe23972186c6b348f9ae29724135a6dfc1e53cea433e7", "e5f4a1f98b52b18a93da705a7458e55afb26f32bff83ff5d19189f92462d65c4"]
|
||||
wrapt = ["565a021fd19419476b9362b05eeaa094178de64f8361e44468f9e9d7843901e1"]
|
||||
pillow = [
|
||||
{file = "Pillow-5.4.1-cp27-cp27m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:18e912a6ccddf28defa196bd2021fe33600cbe5da1aa2f2e2c6df15f720b73d1"},
|
||||
{file = "Pillow-5.4.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:267f8e4c0a1d7e36e97c6a604f5b03ef58e2b81c1becb4fccecddcb37e063cc7"},
|
||||
{file = "Pillow-5.4.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:051de330a06c99d6f84bcf582960487835bcae3fc99365185dc2d4f65a390c0e"},
|
||||
{file = "Pillow-5.4.1-cp27-cp27m-win32.whl", hash = "sha256:825aa6d222ce2c2b90d34a0ea31914e141a85edefc07e17342f1d2fdf121c07c"},
|
||||
{file = "Pillow-5.4.1-cp27-cp27m-win_amd64.whl", hash = "sha256:5d95cb9f6cced2628f3e4de7e795e98b2659dfcc7176ab4a01a8b48c2c2f488f"},
|
||||
{file = "Pillow-5.4.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:ba04f57d1715ca5ff74bb7f8a818bf929a204b3b3c2c2826d1e1cc3b1c13398c"},
|
||||
{file = "Pillow-5.4.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:f227d7e574d050ff3996049e086e1f18c7bd2d067ef24131e50a1d3fe5831fbc"},
|
||||
{file = "Pillow-5.4.1-cp34-cp34m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:3273a28734175feebbe4d0a4cde04d4ed20f620b9b506d26f44379d3c72304e1"},
|
||||
{file = "Pillow-5.4.1-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:cee815cc62d136e96cf76771b9d3eb58e0777ec18ea50de5cfcede8a7c429aa8"},
|
||||
{file = "Pillow-5.4.1-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:4d4bc2e6bb6861103ea4655d6b6f67af8e5336e7216e20fff3e18ffa95d7a055"},
|
||||
{file = "Pillow-5.4.1-cp34-cp34m-win32.whl", hash = "sha256:a6523a23a205be0fe664b6b8747a5c86d55da960d9586db039eec9f5c269c0e6"},
|
||||
{file = "Pillow-5.4.1-cp34-cp34m-win_amd64.whl", hash = "sha256:505738076350a337c1740a31646e1de09a164c62c07db3b996abdc0f9d2e50cf"},
|
||||
{file = "Pillow-5.4.1-cp35-cp35m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:7eda4c737637af74bac4b23aa82ea6fbb19002552be85f0b89bc27e3a762d239"},
|
||||
{file = "Pillow-5.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:163136e09bd1d6c6c6026b0a662976e86c58b932b964f255ff384ecc8c3cefa3"},
|
||||
{file = "Pillow-5.4.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9c215442ff8249d41ff58700e91ef61d74f47dfd431a50253e1a1ca9436b0697"},
|
||||
{file = "Pillow-5.4.1-cp35-cp35m-win32.whl", hash = "sha256:0ae5289948c5e0a16574750021bd8be921c27d4e3527800dc9c2c1d2abc81bf7"},
|
||||
{file = "Pillow-5.4.1-cp35-cp35m-win_amd64.whl", hash = "sha256:801ddaa69659b36abf4694fed5aa9f61d1ecf2daaa6c92541bbbbb775d97b9fe"},
|
||||
{file = "Pillow-5.4.1-cp36-cp36m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:cd878195166723f30865e05d87cbaf9421614501a4bd48792c5ed28f90fd36ca"},
|
||||
{file = "Pillow-5.4.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:fc9a12aad714af36cf3ad0275a96a733526571e52710319855628f476dcb144e"},
|
||||
{file = "Pillow-5.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d7c1c06246b05529f9984435fc4fa5a545ea26606e7f450bdbe00c153f5aeaad"},
|
||||
{file = "Pillow-5.4.1-cp36-cp36m-win32.whl", hash = "sha256:0b1efce03619cdbf8bcc61cfae81fcda59249a469f31c6735ea59badd4a6f58a"},
|
||||
{file = "Pillow-5.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:a631fd36a9823638fe700d9225f9698fb59d049c942d322d4c09544dc2115356"},
|
||||
{file = "Pillow-5.4.1-cp37-cp37m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:24ec3dea52339a610d34401d2d53d0fb3c7fd08e34b20c95d2ad3973193591f1"},
|
||||
{file = "Pillow-5.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:e9c8066249c040efdda84793a2a669076f92a301ceabe69202446abb4c5c5ef9"},
|
||||
{file = "Pillow-5.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:4c678e23006798fc8b6f4cef2eaad267d53ff4c1779bd1af8725cc11b72a63f3"},
|
||||
{file = "Pillow-5.4.1-cp37-cp37m-win32.whl", hash = "sha256:b117287a5bdc81f1bac891187275ec7e829e961b8032c9e5ff38b70fd036c78f"},
|
||||
{file = "Pillow-5.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:d1722b7aa4b40cf93ac3c80d3edd48bf93b9208241d166a14ad8e7a20ee1d4f3"},
|
||||
{file = "Pillow-5.4.1-pp260-pypy_41-win32.whl", hash = "sha256:a3d90022f2202bbb14da991f26ca7a30b7e4c62bf0f8bf9825603b22d7e87494"},
|
||||
{file = "Pillow-5.4.1-pp360-pp360-win32.whl", hash = "sha256:a756ecf9f4b9b3ed49a680a649af45a8767ad038de39e6c030919c2f443eb000"},
|
||||
{file = "Pillow-5.4.1-py2.7-macosx-10.13-x86_64.egg", hash = "sha256:634209852cc06c0c1243cc74f8fdc8f7444d866221de51125f7b696d775ec5ca"},
|
||||
{file = "Pillow-5.4.1-py2.7-win-amd64.egg", hash = "sha256:0cf0208500df8d0c3cad6383cd98a2d038b0678fd4f777a8f7e442c5faeee81d"},
|
||||
{file = "Pillow-5.4.1-py2.7-win32.egg", hash = "sha256:f71ff657e63a9b24cac254bb8c9bd3c89c7a1b5e00ee4b3997ca1c18100dac28"},
|
||||
{file = "Pillow-5.4.1-py3.4-win-amd64.egg", hash = "sha256:01a501be4ae05fd714d269cb9c9f145518e58e73faa3f140ddb67fae0c2607b1"},
|
||||
{file = "Pillow-5.4.1-py3.4-win32.egg", hash = "sha256:4baab2d2da57b0d9d544a2ce0f461374dd90ccbcf723fe46689aff906d43a964"},
|
||||
{file = "Pillow-5.4.1-py3.5-win-amd64.egg", hash = "sha256:f62b1aeb5c2ced8babd4fbba9c74cbef9de309f5ed106184b12d9778a3971f15"},
|
||||
{file = "Pillow-5.4.1-py3.5-win32.egg", hash = "sha256:e9f13711780c981d6eadd6042af40e172548c54b06266a1aabda7de192db0838"},
|
||||
{file = "Pillow-5.4.1-py3.6-win-amd64.egg", hash = "sha256:07c35919f983c2c593498edcc126ad3a94154184899297cc9d27a6587672cbaa"},
|
||||
{file = "Pillow-5.4.1-py3.6-win32.egg", hash = "sha256:87fe838f9dac0597f05f2605c0700b1926f9390c95df6af45d83141e0c514bd9"},
|
||||
{file = "Pillow-5.4.1-py3.7-win-amd64.egg", hash = "sha256:c8939dba1a37960a502b1a030a4465c46dd2c2bca7adf05fa3af6bea594e720e"},
|
||||
{file = "Pillow-5.4.1-py3.7-win32.egg", hash = "sha256:5337ac3280312aa065ed0a8ec1e4b6142e9f15c31baed36b5cd964745853243f"},
|
||||
{file = "Pillow-5.4.1.tar.gz", hash = "sha256:5233664eadfa342c639b9b9977190d64ad7aca4edc51a966394d7e08e7f38a9f"},
|
||||
{file = "Pillow-5.4.1.win-amd64-py2.7.exe", hash = "sha256:e1555d4fda1db8005de72acf2ded1af660febad09b4708430091159e8ae1963e"},
|
||||
{file = "Pillow-5.4.1.win-amd64-py3.4.exe", hash = "sha256:52e2e56fc3706d8791761a157115dc8391319720ad60cc32992350fda74b6be2"},
|
||||
{file = "Pillow-5.4.1.win-amd64-py3.5.exe", hash = "sha256:ba6ef2bd62671c7fb9cdb3277414e87a5cd38b86721039ada1464f7452ad30b2"},
|
||||
{file = "Pillow-5.4.1.win-amd64-py3.6.exe", hash = "sha256:39fbd5d62167197318a0371b2a9c699ce261b6800bb493eadde2ba30d868fe8c"},
|
||||
{file = "Pillow-5.4.1.win-amd64-py3.7.exe", hash = "sha256:5ccd97e0f01f42b7e35907272f0f8ad2c3660a482d799a0c564c7d50e83604d4"},
|
||||
{file = "Pillow-5.4.1.win32-py2.7.exe", hash = "sha256:db418635ea20528f247203bf131b40636f77c8209a045b89fa3badb89e1fcea0"},
|
||||
{file = "Pillow-5.4.1.win32-py3.4.exe", hash = "sha256:ac036b6a6bac7010c58e643d78c234c2f7dc8bb7e591bd8bc3555cf4b1527c28"},
|
||||
{file = "Pillow-5.4.1.win32-py3.5.exe", hash = "sha256:f0e3288b92ca5dbb1649bd00e80ef652a72b657dc94989fa9c348253d179054b"},
|
||||
{file = "Pillow-5.4.1.win32-py3.6.exe", hash = "sha256:4132c78200372045bb348fcad8d52518c8f5cfc077b1089949381ee4a61f1c6d"},
|
||||
{file = "Pillow-5.4.1.win32-py3.7.exe", hash = "sha256:75d1f20bd8072eff92c5f457c266a61619a02d03ece56544195c56d41a1a0522"},
|
||||
]
|
||||
pygments = [
|
||||
{file = "Pygments-2.5.2-py2.py3-none-any.whl", hash = "sha256:2a3fe295e54a20164a9df49c75fa58526d3be48e14aceba6d6b1e8ac0bfd6f1b"},
|
||||
{file = "Pygments-2.5.2.tar.gz", hash = "sha256:98c8aa5a9f778fcd1026a17361ddaf7330d1b7c62ae97c3bb0ae73e0b9b6b0fe"},
|
||||
]
|
||||
pylint = [
|
||||
{file = "pylint-2.4.4-py3-none-any.whl", hash = "sha256:886e6afc935ea2590b462664b161ca9a5e40168ea99e5300935f6591ad467df4"},
|
||||
{file = "pylint-2.4.4.tar.gz", hash = "sha256:3db5468ad013380e987410a8d6956226963aed94ecb5f9d3a28acca6d9ac36cd"},
|
||||
]
|
||||
pyparsing = [
|
||||
{file = "pyparsing-2.4.6-py2.py3-none-any.whl", hash = "sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec"},
|
||||
{file = "pyparsing-2.4.6.tar.gz", hash = "sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f"},
|
||||
]
|
||||
pyserial = [
|
||||
{file = "pyserial-3.4-py2.py3-none-any.whl", hash = "sha256:e0770fadba80c31013896c7e6ef703f72e7834965954a78e71a3049488d4d7d8"},
|
||||
{file = "pyserial-3.4.tar.gz", hash = "sha256:6e2d401fdee0eab996cf734e67773a0143b932772ca8b42451440cfed942c627"},
|
||||
]
|
||||
pytz = [
|
||||
{file = "pytz-2019.3-py2.py3-none-any.whl", hash = "sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d"},
|
||||
{file = "pytz-2019.3.tar.gz", hash = "sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be"},
|
||||
]
|
||||
pyyaml = [
|
||||
{file = "PyYAML-3.13-cp27-cp27m-win32.whl", hash = "sha256:d5eef459e30b09f5a098b9cea68bebfeb268697f78d647bd255a085371ac7f3f"},
|
||||
{file = "PyYAML-3.13-cp27-cp27m-win_amd64.whl", hash = "sha256:e01d3203230e1786cd91ccfdc8f8454c8069c91bee3962ad93b87a4b2860f537"},
|
||||
{file = "PyYAML-3.13-cp34-cp34m-win32.whl", hash = "sha256:558dd60b890ba8fd982e05941927a3911dc409a63dcb8b634feaa0cda69330d3"},
|
||||
{file = "PyYAML-3.13-cp34-cp34m-win_amd64.whl", hash = "sha256:d46d7982b62e0729ad0175a9bc7e10a566fc07b224d2c79fafb5e032727eaa04"},
|
||||
{file = "PyYAML-3.13-cp35-cp35m-win32.whl", hash = "sha256:a7c28b45d9f99102fa092bb213aa12e0aaf9a6a1f5e395d36166639c1f96c3a1"},
|
||||
{file = "PyYAML-3.13-cp35-cp35m-win_amd64.whl", hash = "sha256:bc558586e6045763782014934bfaf39d48b8ae85a2713117d16c39864085c613"},
|
||||
{file = "PyYAML-3.13-cp36-cp36m-win32.whl", hash = "sha256:40c71b8e076d0550b2e6380bada1f1cd1017b882f7e16f09a65be98e017f211a"},
|
||||
{file = "PyYAML-3.13-cp36-cp36m-win_amd64.whl", hash = "sha256:3d7da3009c0f3e783b2c873687652d83b1bbfd5c88e9813fb7e5b03c0dd3108b"},
|
||||
{file = "PyYAML-3.13-cp37-cp37m-win32.whl", hash = "sha256:e170a9e6fcfd19021dd29845af83bb79236068bf5fd4df3327c1be18182b2531"},
|
||||
{file = "PyYAML-3.13-cp37-cp37m-win_amd64.whl", hash = "sha256:aa7dd4a6a427aed7df6fb7f08a580d68d9b118d90310374716ae90b710280af1"},
|
||||
{file = "PyYAML-3.13.tar.gz", hash = "sha256:3ef3092145e9b70e3ddd2c7ad59bdd0252a94dfe3949721633e41344de00a6bf"},
|
||||
]
|
||||
requests = [
|
||||
{file = "requests-2.22.0-py2.py3-none-any.whl", hash = "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"},
|
||||
{file = "requests-2.22.0.tar.gz", hash = "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4"},
|
||||
]
|
||||
rope = [
|
||||
{file = "rope-0.14.0-py2-none-any.whl", hash = "sha256:6b728fdc3e98a83446c27a91fc5d56808a004f8beab7a31ab1d7224cecc7d969"},
|
||||
{file = "rope-0.14.0-py3-none-any.whl", hash = "sha256:f0dcf719b63200d492b85535ebe5ea9b29e0d0b8aebeb87fe03fc1a65924fdaf"},
|
||||
{file = "rope-0.14.0.tar.gz", hash = "sha256:c5c5a6a87f7b1a2095fb311135e2a3d1f194f5ecb96900fdd0a9100881f48aaf"},
|
||||
]
|
||||
"rpi.gpio" = [
|
||||
{file = "RPi.GPIO-0.6.5.tar.gz", hash = "sha256:a4210ad63bfe844e43995286de0d3950dfacfa0f3799bb9392770ac54a7d2e47"},
|
||||
]
|
||||
scipy = [
|
||||
{file = "scipy-1.3.0-cp35-cp35m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:4907040f62b91c2e170359c3d36c000af783f0fa1516a83d6c1517cde0af5340"},
|
||||
{file = "scipy-1.3.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:1db9f964ed9c52dc5bd6127f0dd90ac89791daa690a5665cc01eae185912e1ba"},
|
||||
{file = "scipy-1.3.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:adadeeae5500de0da2b9e8dd478520d0a9945b577b2198f2462555e68f58e7ef"},
|
||||
{file = "scipy-1.3.0-cp35-cp35m-win32.whl", hash = "sha256:03b1e0775edbe6a4c64effb05fff2ce1429b76d29d754aa5ee2d848b60033351"},
|
||||
{file = "scipy-1.3.0-cp35-cp35m-win_amd64.whl", hash = "sha256:a7695a378c2ce402405ea37b12c7a338a8755e081869bd6b95858893ceb617ae"},
|
||||
{file = "scipy-1.3.0-cp36-cp36m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:826b9f5fbb7f908a13aa1efd4b7321e36992f5868d5d8311c7b40cf9b11ca0e7"},
|
||||
{file = "scipy-1.3.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:b283a76a83fe463c9587a2c88003f800e08c3929dfbeba833b78260f9c209785"},
|
||||
{file = "scipy-1.3.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:db61a640ca20f237317d27bc658c1fc54c7581ff7f6502d112922dc285bdabee"},
|
||||
{file = "scipy-1.3.0-cp36-cp36m-win32.whl", hash = "sha256:409846be9d6bdcbd78b9e5afe2f64b2da5a923dd7c1cd0615ce589489533fdbb"},
|
||||
{file = "scipy-1.3.0-cp36-cp36m-win_amd64.whl", hash = "sha256:c19a7389ab3cd712058a8c3c9ffd8d27a57f3d84b9c91a931f542682bb3d269d"},
|
||||
{file = "scipy-1.3.0-cp37-cp37m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:09d008237baabf52a5d4f5a6fcf9b3c03408f3f61a69c404472a16861a73917e"},
|
||||
{file = "scipy-1.3.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:a84c31e8409b420c3ca57fd30c7589378d6fdc8d155d866a7f8e6e80dec6fd06"},
|
||||
{file = "scipy-1.3.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:c5ea60ece0c0c1c849025bfc541b60a6751b491b6f11dd9ef37ab5b8c9041921"},
|
||||
{file = "scipy-1.3.0-cp37-cp37m-win32.whl", hash = "sha256:6c0543f2fdd38dee631fb023c0f31c284a532d205590b393d72009c14847f5b1"},
|
||||
{file = "scipy-1.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:10325f0ffac2400b1ec09537b7e403419dcd25d9fee602a44e8a32119af9079e"},
|
||||
{file = "scipy-1.3.0.tar.gz", hash = "sha256:c3bb4bd2aca82fb498247deeac12265921fe231502a6bc6edea3ee7fe6c40a7a"},
|
||||
]
|
||||
six = [
|
||||
{file = "six-1.14.0-py2.py3-none-any.whl", hash = "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"},
|
||||
{file = "six-1.14.0.tar.gz", hash = "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a"},
|
||||
]
|
||||
snowballstemmer = [
|
||||
{file = "snowballstemmer-2.0.0-py2.py3-none-any.whl", hash = "sha256:209f257d7533fdb3cb73bdbd24f436239ca3b2fa67d56f6ff88e86be08cc5ef0"},
|
||||
{file = "snowballstemmer-2.0.0.tar.gz", hash = "sha256:df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52"},
|
||||
]
|
||||
sphinx = [
|
||||
{file = "Sphinx-2.3.1-py3-none-any.whl", hash = "sha256:298537cb3234578b2d954ff18c5608468229e116a9757af3b831c2b2b4819159"},
|
||||
{file = "Sphinx-2.3.1.tar.gz", hash = "sha256:e6e766b74f85f37a5f3e0773a1e1be8db3fcb799deb58ca6d18b70b0b44542a5"},
|
||||
]
|
||||
sphinxcontrib-applehelp = [
|
||||
{file = "sphinxcontrib-applehelp-1.0.1.tar.gz", hash = "sha256:edaa0ab2b2bc74403149cb0209d6775c96de797dfd5b5e2a71981309efab3897"},
|
||||
{file = "sphinxcontrib_applehelp-1.0.1-py2.py3-none-any.whl", hash = "sha256:fb8dee85af95e5c30c91f10e7eb3c8967308518e0f7488a2828ef7bc191d0d5d"},
|
||||
]
|
||||
sphinxcontrib-devhelp = [
|
||||
{file = "sphinxcontrib-devhelp-1.0.1.tar.gz", hash = "sha256:6c64b077937330a9128a4da74586e8c2130262f014689b4b89e2d08ee7294a34"},
|
||||
{file = "sphinxcontrib_devhelp-1.0.1-py2.py3-none-any.whl", hash = "sha256:9512ecb00a2b0821a146736b39f7aeb90759834b07e81e8cc23a9c70bacb9981"},
|
||||
]
|
||||
sphinxcontrib-htmlhelp = [
|
||||
{file = "sphinxcontrib-htmlhelp-1.0.2.tar.gz", hash = "sha256:4670f99f8951bd78cd4ad2ab962f798f5618b17675c35c5ac3b2132a14ea8422"},
|
||||
{file = "sphinxcontrib_htmlhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:d4fd39a65a625c9df86d7fa8a2d9f3cd8299a3a4b15db63b50aac9e161d8eff7"},
|
||||
]
|
||||
sphinxcontrib-httpdomain = [
|
||||
{file = "sphinxcontrib-httpdomain-1.7.0.tar.gz", hash = "sha256:ac40b4fba58c76b073b03931c7b8ead611066a6aebccafb34dc19694f4eb6335"},
|
||||
{file = "sphinxcontrib_httpdomain-1.7.0-py2.py3-none-any.whl", hash = "sha256:1fb5375007d70bf180cdd1c79e741082be7aa2d37ba99efe561e1c2e3f38191e"},
|
||||
]
|
||||
sphinxcontrib-jsmath = [
|
||||
{file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"},
|
||||
{file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"},
|
||||
]
|
||||
sphinxcontrib-qthelp = [
|
||||
{file = "sphinxcontrib-qthelp-1.0.2.tar.gz", hash = "sha256:79465ce11ae5694ff165becda529a600c754f4bc459778778c7017374d4d406f"},
|
||||
{file = "sphinxcontrib_qthelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:513049b93031beb1f57d4daea74068a4feb77aa5630f856fcff2e50de14e9a20"},
|
||||
]
|
||||
sphinxcontrib-serializinghtml = [
|
||||
{file = "sphinxcontrib-serializinghtml-1.1.3.tar.gz", hash = "sha256:c0efb33f8052c04fd7a26c0a07f1678e8512e0faec19f4aa8f2473a8b81d5227"},
|
||||
{file = "sphinxcontrib_serializinghtml-1.1.3-py2.py3-none-any.whl", hash = "sha256:db6615af393650bf1151a6cd39120c29abaf93cc60db8c48eb2dddbfdc3a9768"},
|
||||
]
|
||||
toml = [
|
||||
{file = "toml-0.10.0-py2.7.egg", hash = "sha256:f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"},
|
||||
{file = "toml-0.10.0-py2.py3-none-any.whl", hash = "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e"},
|
||||
{file = "toml-0.10.0.tar.gz", hash = "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c"},
|
||||
]
|
||||
typed-ast = [
|
||||
{file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3"},
|
||||
{file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb"},
|
||||
{file = "typed_ast-1.4.1-cp35-cp35m-win32.whl", hash = "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919"},
|
||||
{file = "typed_ast-1.4.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01"},
|
||||
{file = "typed_ast-1.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75"},
|
||||
{file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652"},
|
||||
{file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"},
|
||||
{file = "typed_ast-1.4.1-cp36-cp36m-win32.whl", hash = "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1"},
|
||||
{file = "typed_ast-1.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa"},
|
||||
{file = "typed_ast-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614"},
|
||||
{file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41"},
|
||||
{file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b"},
|
||||
{file = "typed_ast-1.4.1-cp37-cp37m-win32.whl", hash = "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe"},
|
||||
{file = "typed_ast-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355"},
|
||||
{file = "typed_ast-1.4.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6"},
|
||||
{file = "typed_ast-1.4.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907"},
|
||||
{file = "typed_ast-1.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d"},
|
||||
{file = "typed_ast-1.4.1-cp38-cp38-win32.whl", hash = "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c"},
|
||||
{file = "typed_ast-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4"},
|
||||
{file = "typed_ast-1.4.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34"},
|
||||
{file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"},
|
||||
]
|
||||
urllib3 = [
|
||||
{file = "urllib3-1.25.7-py2.py3-none-any.whl", hash = "sha256:a8a318824cc77d1fd4b2bec2ded92646630d7fe8619497b142c84a9e6f5a7293"},
|
||||
{file = "urllib3-1.25.7.tar.gz", hash = "sha256:f3c5fd51747d450d4dcf6f923c81f78f811aab8205fda64b0aba34a4e48b0745"},
|
||||
]
|
||||
webargs = [
|
||||
{file = "webargs-5.5.2-py2-none-any.whl", hash = "sha256:3f9dc15de183d356c9a0acc159c100ea0506c0c240c1e6f1d8b308c5fed4dbbd"},
|
||||
{file = "webargs-5.5.2-py3-none-any.whl", hash = "sha256:fa4ad3ad9b38bedd26c619264fdc50d7ae014b49186736bca851e5b5228f2a1b"},
|
||||
{file = "webargs-5.5.2.tar.gz", hash = "sha256:3beca296598067cec24a0b6f91c0afcc19b6e3c4d84ab026b931669628bb47b4"},
|
||||
]
|
||||
werkzeug = [
|
||||
{file = "Werkzeug-0.16.0-py2.py3-none-any.whl", hash = "sha256:e5f4a1f98b52b18a93da705a7458e55afb26f32bff83ff5d19189f92462d65c4"},
|
||||
{file = "Werkzeug-0.16.0.tar.gz", hash = "sha256:7280924747b5733b246fe23972186c6b348f9ae29724135a6dfc1e53cea433e7"},
|
||||
]
|
||||
wrapt = [
|
||||
{file = "wrapt-1.11.2.tar.gz", hash = "sha256:565a021fd19419476b9362b05eeaa094178de64f8361e44468f9e9d7843901e1"},
|
||||
]
|
||||
|
|
|
|||
|
|
@ -28,15 +28,13 @@ Flask = "^1.0"
|
|||
pyyaml = "^3.13"
|
||||
numpy = "^1.17.0"
|
||||
Pillow = "^5.4"
|
||||
flask-cors = "^3.0"
|
||||
scipy = "1.3.0" # Exact version until we can guarantee a wheel
|
||||
|
||||
picamera = { git = "https://github.com/rwb27/picamera.git", branch = "lens-shading", optional = true } # Lens shading requires >1.14 or RWB fork, lens-shading branch.
|
||||
"RPi.GPIO" = { version = "^0.6.5", optional = true }
|
||||
marshmallow = "^3.3" # LabThings
|
||||
webargs = "^5.5.2" # LabThings
|
||||
pyserial = "^3.4" # Used for sangaboard (basic_serial_instrument) until we move to sangaboard pip library
|
||||
apispec = "^3.2" # LabThings
|
||||
|
||||
labthings = { git = "https://github.com/labthings/python-labthings.git", branch = "master"}
|
||||
|
||||
[tool.poetry.extras]
|
||||
rpi = ["picamera", "RPi.GPIO"]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue