Sorted remaining content_type issues and blackened
This commit is contained in:
parent
38fbe530d2
commit
b7c9d3b73b
13 changed files with 114 additions and 92 deletions
|
|
@ -44,7 +44,7 @@ project = "OpenFlexure Microscope Software"
|
||||||
copyright = "2018, Bath Open Instrumentation Group" # pylint: disable=redefined-builtin
|
copyright = "2018, Bath Open Instrumentation Group" # pylint: disable=redefined-builtin
|
||||||
author = "Bath Open Instrumentation Group"
|
author = "Bath Open Instrumentation Group"
|
||||||
|
|
||||||
#TODO: extract version from ../setup.py
|
# TODO: extract version from ../setup.py
|
||||||
# The short X.Y version
|
# The short X.Y version
|
||||||
version = ""
|
version = ""
|
||||||
# The full version, including alpha/beta/rc tags
|
# The full version, including alpha/beta/rc tags
|
||||||
|
|
@ -68,11 +68,11 @@ extensions = [
|
||||||
"sphinx.ext.viewcode",
|
"sphinx.ext.viewcode",
|
||||||
"sphinx.ext.githubpages",
|
"sphinx.ext.githubpages",
|
||||||
"sphinx.ext.ifconfig",
|
"sphinx.ext.ifconfig",
|
||||||
#"sphinxcontrib.httpdomain",
|
# "sphinxcontrib.httpdomain",
|
||||||
#"sphinxcontrib.autohttp.flask",
|
# "sphinxcontrib.autohttp.flask",
|
||||||
#"sphinxcontrib.autohttp.flaskqref",
|
# "sphinxcontrib.autohttp.flaskqref",
|
||||||
"sphinxcontrib.openapi",
|
"sphinxcontrib.openapi",
|
||||||
"sphinxcontrib.redoc"
|
"sphinxcontrib.redoc",
|
||||||
]
|
]
|
||||||
|
|
||||||
# Override ordering
|
# Override ordering
|
||||||
|
|
@ -228,7 +228,7 @@ epub_exclude_files = ["search.html"]
|
||||||
# -- Options for intersphinx extension ---------------------------------------
|
# -- Options for intersphinx extension ---------------------------------------
|
||||||
|
|
||||||
# Example configuration for intersphinx: refer to the Python standard library.
|
# Example configuration for intersphinx: refer to the Python standard library.
|
||||||
#TODO: update with pysangaboard?
|
# TODO: update with pysangaboard?
|
||||||
intersphinx_mapping = {
|
intersphinx_mapping = {
|
||||||
"openflexure_stage": ("https://openflexure-stage.readthedocs.io/en/latest/", None),
|
"openflexure_stage": ("https://openflexure-stage.readthedocs.io/en/latest/", None),
|
||||||
"picamerax": ("https://picamerax.readthedocs.io/en/latest//", None),
|
"picamerax": ("https://picamerax.readthedocs.io/en/latest//", None),
|
||||||
|
|
@ -244,9 +244,9 @@ todo_include_todos = True
|
||||||
# -- Options for redoc extension ---------------------------------------------
|
# -- Options for redoc extension ---------------------------------------------
|
||||||
redoc = [
|
redoc = [
|
||||||
{
|
{
|
||||||
'name': 'OpenFlexure Microscope HTTP API',
|
"name": "OpenFlexure Microscope HTTP API",
|
||||||
'page': 'api_redoc',
|
"page": "api_redoc",
|
||||||
'spec': '../build/swagger.yaml',
|
"spec": "../build/swagger.yaml",
|
||||||
'embed': True,
|
"embed": True,
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -94,7 +94,7 @@ class QuickCaptureAPI(ActionView):
|
||||||
args = {"use_video_port": fields.Boolean(missing=True)}
|
args = {"use_video_port": fields.Boolean(missing=True)}
|
||||||
|
|
||||||
# Our success response (200) returns an image (image/jpeg mimetype)
|
# Our success response (200) returns an image (image/jpeg mimetype)
|
||||||
responses = {200: {"content_type": "image/jpeg"}}
|
responses = {200: {"content": {"image/jpeg": {}}}}
|
||||||
|
|
||||||
def post(self, args):
|
def post(self, args):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
with open("./build/swagger.yaml", "r") as f:
|
with open("./build/swagger.yaml", "r") as f:
|
||||||
api = yaml.load(f)
|
api = yaml.load(f)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -210,6 +210,7 @@ def routes():
|
||||||
def api_v1_catch_all(path): # pylint: disable=W0613
|
def api_v1_catch_all(path): # pylint: disable=W0613
|
||||||
abort(410, "API v1 is no longer in use. Please upgrade your client.")
|
abort(410, "API v1 is no longer in use. Please upgrade your client.")
|
||||||
|
|
||||||
|
|
||||||
add_spec_extras(labthing.spec)
|
add_spec_extras(labthing.spec)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -247,36 +248,40 @@ def ofm_serve():
|
||||||
server: Server = Server(app)
|
server: Server = Server(app)
|
||||||
server.run(host="0.0.0.0", port=5000, debug=debug_app, zeroconf=True)
|
server.run(host="0.0.0.0", port=5000, debug=debug_app, zeroconf=True)
|
||||||
|
|
||||||
|
|
||||||
def generate_openapi():
|
def generate_openapi():
|
||||||
parser = argparse.ArgumentParser("Generate an OpenAPI specification document")
|
parser = argparse.ArgumentParser("Generate an OpenAPI specification document")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-o',
|
"-o",
|
||||||
dest='output',
|
dest="output",
|
||||||
default="openapi.yaml",
|
default="openapi.yaml",
|
||||||
help=(
|
help=(
|
||||||
'Specify the output filename. If it ends in .json, we output JSON.'
|
"Specify the output filename. If it ends in .json, we output JSON."
|
||||||
'Use .yml or .yaml for YAML (which is the default'
|
"Use .yml or .yaml for YAML (which is the default"
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--validate",
|
"--validate",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
help="Validate the API spec, returning an error code if it does not pass."
|
help="Validate the API spec, returning an error code if it does not pass.",
|
||||||
)
|
)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
if args.validate:
|
if args.validate:
|
||||||
import apispec.utils
|
import apispec.utils
|
||||||
|
|
||||||
if apispec.utils.validate_spec(labthing.spec):
|
if apispec.utils.validate_spec(labthing.spec):
|
||||||
print("OpenAPI specification validated OK.")
|
print("OpenAPI specification validated OK.")
|
||||||
fname = args.output
|
fname = args.output
|
||||||
if fname.endswith(".json"):
|
if fname.endswith(".json"):
|
||||||
import json
|
import json
|
||||||
with open(fname, 'w') as fd:
|
|
||||||
|
with open(fname, "w") as fd:
|
||||||
json.dump(labthing.spec.to_dict(), fd)
|
json.dump(labthing.spec.to_dict(), fd)
|
||||||
else:
|
else:
|
||||||
with open(fname, 'w') as fd:
|
with open(fname, "w") as fd:
|
||||||
fd.write(labthing.spec.to_yaml())
|
fd.write(labthing.spec.to_yaml())
|
||||||
|
|
||||||
|
|
||||||
# Start the app if the module is run directly
|
# Start the app if the module is run directly
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
ofm_serve()
|
ofm_serve()
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ class JPEGSharpnessMonitor:
|
||||||
"""Monitor JPEG frame size in a background thread
|
"""Monitor JPEG frame size in a background thread
|
||||||
|
|
||||||
This class starts a background thread """
|
This class starts a background thread """
|
||||||
|
|
||||||
def __init__(self, microscope: Microscope):
|
def __init__(self, microscope: Microscope):
|
||||||
self.microscope: Microscope = microscope
|
self.microscope: Microscope = microscope
|
||||||
self.camera: BaseCamera = microscope.camera
|
self.camera: BaseCamera = microscope.camera
|
||||||
|
|
@ -164,6 +165,7 @@ def find_microscope() -> Microscope:
|
||||||
|
|
||||||
return microscope
|
return microscope
|
||||||
|
|
||||||
|
|
||||||
def find_microscope_with_real_stage() -> Microscope:
|
def find_microscope_with_real_stage() -> Microscope:
|
||||||
"""Find the microscope and ensure it has a real stage.
|
"""Find the microscope and ensure it has a real stage.
|
||||||
|
|
||||||
|
|
@ -199,6 +201,7 @@ def extension_action(args=None):
|
||||||
responsible for collating and adding the views in its `__init__` method.
|
responsible for collating and adding the views in its `__init__` method.
|
||||||
"""
|
"""
|
||||||
supplied_args = args
|
supplied_args = args
|
||||||
|
|
||||||
def decorator(func):
|
def decorator(func):
|
||||||
class_docstring = f"""Manage actions for {func.__name__}.
|
class_docstring = f"""Manage actions for {func.__name__}.
|
||||||
|
|
||||||
|
|
@ -206,6 +209,7 @@ def extension_action(args=None):
|
||||||
each time {func.__name__} has been run in response to a `GET` request,
|
each time {func.__name__} has been run in response to a `GET` request,
|
||||||
and will start a new `Action` in response to a `POST` request.
|
and will start a new `Action` in response to a `POST` request.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class ActionViewWrapper(ActionView):
|
class ActionViewWrapper(ActionView):
|
||||||
__doc__ = class_docstring
|
__doc__ = class_docstring
|
||||||
args = supplied_args
|
args = supplied_args
|
||||||
|
|
@ -222,9 +226,9 @@ def extension_action(args=None):
|
||||||
# aren't guaranteed to have the same leading whitespace, we just make sure
|
# aren't guaranteed to have the same leading whitespace, we just make sure
|
||||||
# both docstrings are stripped of leading indent, using `inspect.cleandoc()`
|
# both docstrings are stripped of leading indent, using `inspect.cleandoc()`
|
||||||
ActionViewWrapper.post.description = (
|
ActionViewWrapper.post.description = (
|
||||||
get_docstring(func, remove_newlines=False) +
|
get_docstring(func, remove_newlines=False)
|
||||||
"\n\n" +
|
+ "\n\n"
|
||||||
inspect.cleandoc(
|
+ inspect.cleandoc(
|
||||||
"""
|
"""
|
||||||
This `POST` request starts an Action, i.e. the hardware will do something
|
This `POST` request starts an Action, i.e. the hardware will do something
|
||||||
that may continue after the HTTP request has been responded to. The
|
that may continue after the HTTP request has been responded to. The
|
||||||
|
|
@ -242,11 +246,13 @@ def extension_action(args=None):
|
||||||
ActionViewWrapper.post.summary = get_summary(func)
|
ActionViewWrapper.post.summary = get_summary(func)
|
||||||
ActionViewWrapper.post.__doc__ = ActionViewWrapper.post.description
|
ActionViewWrapper.post.__doc__ = ActionViewWrapper.post.description
|
||||||
ActionViewWrapper.__name__ = func.__name__
|
ActionViewWrapper.__name__ = func.__name__
|
||||||
ActionViewWrapper.get.summary = f"List running and completed `{func.__name__}` actions."
|
ActionViewWrapper.get.summary = (
|
||||||
|
f"List running and completed `{func.__name__}` actions."
|
||||||
|
)
|
||||||
ActionViewWrapper.get.description = (
|
ActionViewWrapper.get.description = (
|
||||||
ActionViewWrapper.get.summary +
|
ActionViewWrapper.get.summary
|
||||||
"\n\n" +
|
+ "\n\n"
|
||||||
inspect.cleandoc(
|
+ inspect.cleandoc(
|
||||||
f"""
|
f"""
|
||||||
This `GET` request will return a list of `Action` objects corresponding
|
This `GET` request will return a list of `Action` objects corresponding
|
||||||
to the `{func.__name__}` action. It will include all the times it has
|
to the `{func.__name__}` action. It will include all the times it has
|
||||||
|
|
@ -258,8 +264,8 @@ def extension_action(args=None):
|
||||||
|
|
||||||
func.flask_view = ActionViewWrapper
|
func.flask_view = ActionViewWrapper
|
||||||
return func
|
return func
|
||||||
return decorator
|
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
### Autofocus extension
|
### Autofocus extension
|
||||||
|
|
@ -291,7 +297,9 @@ class AutofocusExtension(BaseExtension):
|
||||||
self.add_view(obj.flask_view, f"/{name}", name)
|
self.add_view(obj.flask_view, f"/{name}", name)
|
||||||
|
|
||||||
def measure_sharpness(
|
def measure_sharpness(
|
||||||
self, microscope: Optional[Microscope] = None, metric_fn: Callable = sharpness_sum_lap2
|
self,
|
||||||
|
microscope: Optional[Microscope] = None,
|
||||||
|
metric_fn: Callable = sharpness_sum_lap2,
|
||||||
) -> float:
|
) -> float:
|
||||||
"""Measure the sharpness from the MJPEG stream
|
"""Measure the sharpness from the MJPEG stream
|
||||||
|
|
||||||
|
|
@ -309,13 +317,13 @@ class AutofocusExtension(BaseExtension):
|
||||||
raise RuntimeError(f"Object {microscope.camera} has no method `array`")
|
raise RuntimeError(f"Object {microscope.camera} has no method `array`")
|
||||||
|
|
||||||
@extension_action(
|
@extension_action(
|
||||||
args = {
|
args={
|
||||||
"dz": fields.List(
|
"dz": fields.List(
|
||||||
fields.Int(),
|
fields.Int(),
|
||||||
description="An ascending list of relative z positions",
|
description="An ascending list of relative z positions",
|
||||||
example = [int(x) for x in np.linspace(-300, 300, 7)],
|
example=[int(x) for x in np.linspace(-300, 300, 7)],
|
||||||
)
|
)
|
||||||
},
|
}
|
||||||
)
|
)
|
||||||
def autofocus(
|
def autofocus(
|
||||||
self,
|
self,
|
||||||
|
|
@ -360,16 +368,20 @@ class AutofocusExtension(BaseExtension):
|
||||||
|
|
||||||
return positions, sharpnesses
|
return positions, sharpnesses
|
||||||
|
|
||||||
def move_and_find_focus(self, microscope: Optional[Microscope] = None, dz: int = 0) -> int:
|
def move_and_find_focus(
|
||||||
|
self, microscope: Optional[Microscope] = None, dz: int = 0
|
||||||
|
) -> int:
|
||||||
"""Make a relative Z move and return the peak sharpness position"""
|
"""Make a relative Z move and return the peak sharpness position"""
|
||||||
if not microscope:
|
if not microscope:
|
||||||
microscope = find_microscope_with_real_stage()
|
microscope = find_microscope_with_real_stage()
|
||||||
with monitor_sharpness(microscope) as m:
|
with monitor_sharpness(microscope) as m:
|
||||||
m.focus_rel(dz)
|
m.focus_rel(dz)
|
||||||
return m.sharpest_z_on_move(0)
|
return m.sharpest_z_on_move(0)
|
||||||
|
|
||||||
@extension_action(
|
@extension_action(
|
||||||
args={"dz": fields.Int(required=True, description="The relative Z move to make")},
|
args={
|
||||||
|
"dz": fields.Int(required=True, description="The relative Z move to make")
|
||||||
|
}
|
||||||
)
|
)
|
||||||
def move_and_measure(
|
def move_and_measure(
|
||||||
self, microscope: Optional[Microscope] = None, dz: int = 0
|
self, microscope: Optional[Microscope] = None, dz: int = 0
|
||||||
|
|
@ -390,11 +402,11 @@ class AutofocusExtension(BaseExtension):
|
||||||
@extension_action(
|
@extension_action(
|
||||||
args={
|
args={
|
||||||
"dz": fields.Int(
|
"dz": fields.Int(
|
||||||
missing=2000,
|
missing=2000,
|
||||||
example=2000,
|
example=2000,
|
||||||
description="Total Z range to search over (in stage steps)"
|
description="Total Z range to search over (in stage steps)",
|
||||||
)
|
)
|
||||||
},
|
}
|
||||||
)
|
)
|
||||||
def fast_autofocus(
|
def fast_autofocus(
|
||||||
self, microscope: Optional[Microscope] = None, dz: int = 2000
|
self, microscope: Optional[Microscope] = None, dz: int = 2000
|
||||||
|
|
@ -484,27 +496,27 @@ class AutofocusExtension(BaseExtension):
|
||||||
return m.data_dict()
|
return m.data_dict()
|
||||||
|
|
||||||
@extension_action(
|
@extension_action(
|
||||||
args = {
|
args={
|
||||||
"dz": fields.Int(
|
"dz": fields.Int(
|
||||||
missing = 2000,
|
missing=2000,
|
||||||
example = 2000,
|
example=2000,
|
||||||
description = "Total Z range to search over (in stage steps)"
|
description="Total Z range to search over (in stage steps)",
|
||||||
),
|
),
|
||||||
"target_z": fields.Int(
|
"target_z": fields.Int(
|
||||||
missing = 0,
|
missing=0,
|
||||||
example = -100,
|
example=-100,
|
||||||
description = "Target finishing position, relative to the focus."
|
description="Target finishing position, relative to the focus.",
|
||||||
),
|
),
|
||||||
"initial_move_up": fields.Bool(
|
"initial_move_up": fields.Bool(
|
||||||
missing = True,
|
missing=True,
|
||||||
description = "Set to Flase to disable the initial move upwards"
|
description="Set to Flase to disable the initial move upwards",
|
||||||
),
|
),
|
||||||
"backlash": fields.Int(
|
"backlash": fields.Int(
|
||||||
missing=25,
|
missing=25,
|
||||||
minimum=0,
|
minimum=0,
|
||||||
description="Distance to undershoot, before correction move."
|
description="Distance to undershoot, before correction move.",
|
||||||
),
|
),
|
||||||
},
|
}
|
||||||
)
|
)
|
||||||
def fast_up_down_up_autofocus(
|
def fast_up_down_up_autofocus(
|
||||||
self,
|
self,
|
||||||
|
|
@ -563,7 +575,7 @@ class AutofocusExtension(BaseExtension):
|
||||||
"""
|
"""
|
||||||
if not microscope:
|
if not microscope:
|
||||||
microscope = find_microscope_with_real_stage()
|
microscope = find_microscope_with_real_stage()
|
||||||
if not mini_backlash: # I renamed the argument,
|
if not mini_backlash: # I renamed the argument,
|
||||||
mini_backlash = backlash or 25
|
mini_backlash = backlash or 25
|
||||||
with microscope.lock(timeout=1), microscope.camera.lock, microscope.stage.lock:
|
with microscope.lock(timeout=1), microscope.camera.lock, microscope.stage.lock:
|
||||||
with monitor_sharpness(microscope) as m:
|
with monitor_sharpness(microscope) as m:
|
||||||
|
|
@ -617,5 +629,6 @@ class AutofocusExtension(BaseExtension):
|
||||||
|
|
||||||
class MeasureSharpnessAPI(View):
|
class MeasureSharpnessAPI(View):
|
||||||
__doc__ = AutofocusExtension.measure_sharpness.__doc__
|
__doc__ = AutofocusExtension.measure_sharpness.__doc__
|
||||||
|
|
||||||
def post(self):
|
def post(self):
|
||||||
return {"sharpness": self.extension.measure_sharpness()}
|
return {"sharpness": self.extension.measure_sharpness()}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
|
|
||||||
|
|
||||||
API_TAGS = [
|
API_TAGS = [
|
||||||
{
|
{
|
||||||
"name": "actions",
|
"name": "actions",
|
||||||
|
|
@ -11,8 +9,8 @@ API_TAGS = [
|
||||||
),
|
),
|
||||||
"externalDocs": {
|
"externalDocs": {
|
||||||
"url": "https://iot.mozilla.org/wot/#action-resource",
|
"url": "https://iot.mozilla.org/wot/#action-resource",
|
||||||
"description": "Mozilla's description of Web of Things 'Action' resources."
|
"description": "Mozilla's description of Web of Things 'Action' resources.",
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "properties",
|
"name": "properties",
|
||||||
|
|
@ -22,23 +20,15 @@ API_TAGS = [
|
||||||
),
|
),
|
||||||
"externalDocs": {
|
"externalDocs": {
|
||||||
"url": "https://iot.mozilla.org/wot/#property-resource",
|
"url": "https://iot.mozilla.org/wot/#property-resource",
|
||||||
"description": "Mozilla's description of Web of Things 'Property' resources."
|
"description": "Mozilla's description of Web of Things 'Property' resources.",
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{"name": "captures", "description": ""},
|
||||||
"name": "captures",
|
{"name": "extensions", "description": ""},
|
||||||
"description": "",
|
{"name": "events", "description": ""},
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "extensions",
|
|
||||||
"description": "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "events",
|
|
||||||
"description": "",
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def add_spec_extras(spec):
|
def add_spec_extras(spec):
|
||||||
"""Add extra documentation and features to the OpenAPI spec"""
|
"""Add extra documentation and features to the OpenAPI spec"""
|
||||||
# Add a list of tags, so we can control ordering and add descriptions
|
# Add a list of tags, so we can control ordering and add descriptions
|
||||||
|
|
|
||||||
|
|
@ -4,4 +4,4 @@ from .captures import *
|
||||||
from .instrument import *
|
from .instrument import *
|
||||||
from .stage import *
|
from .stage import *
|
||||||
from .streams import *
|
from .streams import *
|
||||||
from .docs import *
|
from .docs import *
|
||||||
|
|
|
||||||
|
|
@ -71,8 +71,8 @@ class RAMCaptureAPI(ActionView):
|
||||||
args = BasicCaptureArgs()
|
args = BasicCaptureArgs()
|
||||||
responses = {
|
responses = {
|
||||||
200: {
|
200: {
|
||||||
"content": { "image/jpeg": {} },
|
"content": {"image/jpeg": {}},
|
||||||
"description": "A JPEG image, representing the capture"
|
"description": "A JPEG image, representing the capture",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ class LSTImageProperty(PropertyView):
|
||||||
|
|
||||||
responses = {
|
responses = {
|
||||||
200: {
|
200: {
|
||||||
"content": { "image/jpeg": {} },
|
"content": {"image/jpeg": {}},
|
||||||
"description": "Lens-shading table in RGB format",
|
"description": "Lens-shading table in RGB format",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -146,8 +146,19 @@ class CaptureList(PropertyView):
|
||||||
return image_list
|
return image_list
|
||||||
|
|
||||||
|
|
||||||
|
CAPTURE_ID_PARAMETER = {
|
||||||
|
"name": "id_",
|
||||||
|
"in": "path",
|
||||||
|
"description": "The unique ID of the capture",
|
||||||
|
"required": True,
|
||||||
|
"schema": {"type": "string"},
|
||||||
|
"example": "eeae7ae9-0c0d-45a4-9ef2-7b84bb67a1d1",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class CaptureView(View):
|
class CaptureView(View):
|
||||||
tags = ["captures"]
|
tags = ["captures"]
|
||||||
|
parameters = [CAPTURE_ID_PARAMETER]
|
||||||
|
|
||||||
@marshal_with(FullCaptureSchema())
|
@marshal_with(FullCaptureSchema())
|
||||||
def get(self, id_):
|
def get(self, id_):
|
||||||
|
|
@ -162,6 +173,8 @@ class CaptureView(View):
|
||||||
|
|
||||||
return capture_obj
|
return capture_obj
|
||||||
|
|
||||||
|
get.responses = {404: {"description": "Capture object was not found"}}
|
||||||
|
|
||||||
def delete(self, id_):
|
def delete(self, id_):
|
||||||
"""
|
"""
|
||||||
Delete a single image capture
|
Delete a single image capture
|
||||||
|
|
@ -182,7 +195,10 @@ class CaptureView(View):
|
||||||
|
|
||||||
class CaptureDownload(View):
|
class CaptureDownload(View):
|
||||||
tags = ["captures"]
|
tags = ["captures"]
|
||||||
responses = {200: {"content_type": "image/jpeg"}}
|
responses = {
|
||||||
|
200: {"content": {"image/jpeg": {}}, "description": "Image data in JPEG format"}
|
||||||
|
}
|
||||||
|
parameters = [CAPTURE_ID_PARAMETER]
|
||||||
|
|
||||||
def get(self, id_, filename: Optional[str]):
|
def get(self, id_, filename: Optional[str]):
|
||||||
"""
|
"""
|
||||||
|
|
@ -223,6 +239,7 @@ class CaptureDownload(View):
|
||||||
|
|
||||||
class CaptureTags(View):
|
class CaptureTags(View):
|
||||||
tags = ["captures"]
|
tags = ["captures"]
|
||||||
|
parameters = [CAPTURE_ID_PARAMETER]
|
||||||
|
|
||||||
def get(self, id_):
|
def get(self, id_):
|
||||||
"""
|
"""
|
||||||
|
|
@ -270,6 +287,7 @@ class CaptureTags(View):
|
||||||
|
|
||||||
class CaptureAnnotations(View):
|
class CaptureAnnotations(View):
|
||||||
tags = ["captures"]
|
tags = ["captures"]
|
||||||
|
parameters = [CAPTURE_ID_PARAMETER]
|
||||||
|
|
||||||
def get(self, id_):
|
def get(self, id_):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ from labthings.views import View
|
||||||
from labthings import current_labthing
|
from labthings import current_labthing
|
||||||
from flask import Response
|
from flask import Response
|
||||||
|
|
||||||
|
|
||||||
class APISpecJSONView(View):
|
class APISpecJSONView(View):
|
||||||
"""OpenAPI v3 documentation
|
"""OpenAPI v3 documentation
|
||||||
|
|
||||||
|
|
@ -11,6 +12,7 @@ class APISpecJSONView(View):
|
||||||
def get(self):
|
def get(self):
|
||||||
return current_labthing().spec.to_dict()
|
return current_labthing().spec.to_dict()
|
||||||
|
|
||||||
|
|
||||||
class APISpecYAMLView(View):
|
class APISpecYAMLView(View):
|
||||||
"""OpenAPI v3 documentation
|
"""OpenAPI v3 documentation
|
||||||
|
|
||||||
|
|
@ -18,4 +20,4 @@ class APISpecYAMLView(View):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def get(self):
|
def get(self):
|
||||||
return Response(current_labthing().spec.to_yaml(), mimetype='text/yaml')
|
return Response(current_labthing().spec.to_yaml(), mimetype="text/yaml")
|
||||||
|
|
|
||||||
|
|
@ -19,9 +19,7 @@ class MjpegStream(PropertyView):
|
||||||
|
|
||||||
responses = {
|
responses = {
|
||||||
200: {
|
200: {
|
||||||
"content": {
|
"content": {"multipart/x-mixed-replace": {}},
|
||||||
"multipart/x-mixed-replace": {}
|
|
||||||
},
|
|
||||||
"description": (
|
"description": (
|
||||||
"An MJPEG stream of camera images.\n\n"
|
"An MJPEG stream of camera images.\n\n"
|
||||||
"This endpoint will serve JPEG images sequentially, \n"
|
"This endpoint will serve JPEG images sequentially, \n"
|
||||||
|
|
@ -62,12 +60,7 @@ class SnapshotStream(PropertyView):
|
||||||
Single JPEG snapshot from the camera stream
|
Single JPEG snapshot from the camera stream
|
||||||
"""
|
"""
|
||||||
|
|
||||||
responses = {
|
responses = {200: {"content": {"image/jpeg": {}}, "description": "Snapshot taken"}}
|
||||||
200: {
|
|
||||||
"content": { "image/jpeg": {} },
|
|
||||||
"description": "Snapshot taken"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def get(self):
|
def get(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
6
setup.py
6
setup.py
|
|
@ -44,7 +44,7 @@ setup(
|
||||||
# set up the project for development, you use those specific packages, rather than
|
# set up the project for development, you use those specific packages, rather than
|
||||||
# the looser specifications given here.
|
# the looser specifications given here.
|
||||||
install_requires=[
|
install_requires=[
|
||||||
"apispec[validation]", # We need the extra to validate the spec
|
"apispec[validation]", # We need the extra to validate the spec
|
||||||
"Flask ~= 1.0",
|
"Flask ~= 1.0",
|
||||||
"Pillow ~= 7.2.0",
|
"Pillow ~= 7.2.0",
|
||||||
"numpy ~= 1.20",
|
"numpy ~= 1.20",
|
||||||
|
|
@ -68,7 +68,7 @@ setup(
|
||||||
# them to specific versions to enable consistent builds and testing.
|
# them to specific versions to enable consistent builds and testing.
|
||||||
extras_require={
|
extras_require={
|
||||||
"dev": [
|
"dev": [
|
||||||
"sphinx < 4.0", # Currently httpdomain isn't ready for 4.0
|
"sphinx < 4.0", # Currently httpdomain isn't ready for 4.0
|
||||||
"sphinxcontrib-openapi ~= 0.7",
|
"sphinxcontrib-openapi ~= 0.7",
|
||||||
"sphinx_rtd_theme ~=0.5.2",
|
"sphinx_rtd_theme ~=0.5.2",
|
||||||
"rope ~= 0.14.0",
|
"rope ~= 0.14.0",
|
||||||
|
|
@ -90,7 +90,7 @@ setup(
|
||||||
"console_scripts": [
|
"console_scripts": [
|
||||||
"ofm-serve=openflexure_microscope.api.app:ofm_serve",
|
"ofm-serve=openflexure_microscope.api.app:ofm_serve",
|
||||||
"ofm-rescue=openflexure_microscope.rescue.auto:main",
|
"ofm-rescue=openflexure_microscope.rescue.auto:main",
|
||||||
"ofm-generate-openapi=openflexure_microscope.api.app:generate_openapi"
|
"ofm-generate-openapi=openflexure_microscope.api.app:generate_openapi",
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
project_urls={
|
project_urls={
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue