Merge branch 'optimised-capture-threadpools' into 'master'

Optimised capture IO

See merge request openflexure/openflexure-microscope-server!62
This commit is contained in:
Joel Collins 2020-06-15 16:28:25 +00:00
commit aa9f621476
12 changed files with 220 additions and 175 deletions

View file

@ -3,13 +3,20 @@ from gevent import monkey
monkey.patch_all() monkey.patch_all()
import sys
import time import time
import atexit import atexit
import logging, logging.handlers import logging, logging.handlers
# Look for debug flag
if "-d" in sys.argv or "--debug" in sys.argv:
log_level = logging.DEBUG
else:
log_level = logging.INFO
# Set root logger level # Set root logger level
logger = logging.getLogger() logger = logging.getLogger()
logger.setLevel(logging.INFO) logger.setLevel(log_level)
import os import os
import pkg_resources import pkg_resources

View file

@ -25,7 +25,7 @@ class MjpegStream(PropertyView):
frame from the camera object, passed to the Flask response, and then repeats until frame from the camera object, passed to the Flask response, and then repeats until
the connection is closed. the connection is closed.
Without monkey patching with gevent, or using a native threaded server, the stream Without monkey patching, or using a native threaded server, the stream
will block all proceeding requests. will block all proceeding requests.
""" """
microscope = find_component("org.openflexure.microscope") microscope = find_component("org.openflexure.microscope")

View file

@ -2,12 +2,10 @@
import time import time
import os import os
import shutil import shutil
import threading
import datetime import datetime
import logging import logging
import threading import threading
import gevent
from abc import ABCMeta, abstractmethod from abc import ABCMeta, abstractmethod
@ -25,7 +23,7 @@ class BaseCamera(metaclass=ABCMeta):
self.thread = None self.thread = None
self.camera = None self.camera = None
self.lock = StrictLock(timeout=1, name="Camera") self.lock = StrictLock(name="Camera")
self.frame = None self.frame = None
self.last_access = 0 self.last_access = 0
@ -93,9 +91,10 @@ class BaseCamera(metaclass=ABCMeta):
self.stop = False self.stop = False
if not self.stream_active: if not self.stream_active:
# Spawn a greenlet to handle stream # Start background frame thread
# start background frame thread self.thread = threading.Thread(target=self._thread)
self.thread = gevent.spawn(self._thread) self.thread.daemon = True
self.thread.start()
# wait until frames are available # wait until frames are available
logging.info("Waiting for frames") logging.info("Waiting for frames")

View file

@ -197,19 +197,14 @@ class MissingCamera(BaseCamera):
bayer (bool): Store raw bayer data in capture bayer (bool): Store raw bayer data in capture
""" """
if isinstance(output, CaptureObject):
target = output.file
else:
target = output
with self.lock: with self.lock:
if isinstance(target, str): if isinstance(output, str):
target = open(target, "wb") output = open(output, "wb")
target.write(self.stream.getvalue()) output.write(self.stream.getvalue())
if isinstance(target, str): if isinstance(output, str):
target.close() output.close()
# HANDLE STREAM FRAMES # HANDLE STREAM FRAMES

View file

@ -108,18 +108,14 @@ class PiCameraStreamer(BaseCamera):
1312, 1312,
976, 976,
) #: tuple: Resolution for numpy array captures ) #: tuple: Resolution for numpy array captures
self.jpeg_quality = 75 #: int: JPEG quality self.jpeg_quality = 100 #: int: JPEG quality
self.mjpeg_quality = 75 #: int: MJPEG quality
# Set default lens shading table path # Set default lens shading table path
self.picamera_lst_path = settings_file_path( self.picamera_lst_path = settings_file_path(
"picamera_lst.npy" "picamera_lst.npy"
) #: str: Path of .npy lens shading table file ) #: str: Path of .npy lens shading table file
# Create an empty stream
self.stream = io.BytesIO()
# Start streaming
self.start_worker()
@property @property
def configuration(self): def configuration(self):
@ -159,6 +155,7 @@ class PiCameraStreamer(BaseCamera):
"image_resolution": self.image_resolution, "image_resolution": self.image_resolution,
"numpy_resolution": self.numpy_resolution, "numpy_resolution": self.numpy_resolution,
"jpeg_quality": self.jpeg_quality, "jpeg_quality": self.jpeg_quality,
"mjpeg_quality": self.mjpeg_quality,
"picamera": {}, "picamera": {},
} }
) )
@ -479,7 +476,7 @@ class PiCameraStreamer(BaseCamera):
self.camera.start_recording( self.camera.start_recording(
self.stream, self.stream,
format="mjpeg", format="mjpeg",
quality=self.jpeg_quality, quality=self.mjpeg_quality,
bitrate=-1, # RWB: disable bitrate control bitrate=-1, # RWB: disable bitrate control
# (bitrate control makes JPEG size less good as a focus # (bitrate control makes JPEG size less good as a focus
# metric) # metric)
@ -520,12 +517,6 @@ class PiCameraStreamer(BaseCamera):
Returns: Returns:
output_object (str/BytesIO): Target object. output_object (str/BytesIO): Target object.
""" """
if isinstance(output, CaptureObject):
target = output.file
else:
target = output
with self.lock: with self.lock:
logging.info("Capturing to {}".format(output)) logging.info("Capturing to {}".format(output))
@ -535,20 +526,20 @@ class PiCameraStreamer(BaseCamera):
time.sleep(0.1) time.sleep(0.1)
self.camera.capture( self.camera.capture(
target, output,
format=fmt, format=fmt,
quality=100, quality=self.jpeg_quality,
resize=resize, resize=resize,
bayer=(not use_video_port) and bayer, bayer=(not use_video_port) and bayer,
use_video_port=use_video_port, use_video_port=use_video_port,
) )
time.sleep(0.1) #time.sleep(0.1)
# Set resolution and start stream recording if necessary # Set resolution and start stream recording if necessary
if not use_video_port: if not use_video_port:
self.start_stream_recording() self.start_stream_recording()
return target return output
def yuv( def yuv(
self, use_video_port: bool = True, resize: Tuple[int, int] = None self, use_video_port: bool = True, resize: Tuple[int, int] = None

View file

@ -10,6 +10,9 @@ from PIL import Image
import dateutil.parser import dateutil.parser
import atexit import atexit
from gevent.fileobject import FileObjectThread
import gevent
from collections import OrderedDict from collections import OrderedDict
from openflexure_microscope.camera import piexif from openflexure_microscope.camera import piexif
@ -118,12 +121,18 @@ def capture_from_exif(path, exif_dict):
class CaptureObject(object): class CaptureObject(object):
""" """
StreamObject used to store and process on-disk capture data, and metadata. File-like object used to store and process on-disk capture data, and metadata.
Serves to simplify modifying properties of on-disk capture data. Serves to simplify modifying properties of on-disk capture data.
""" """
def __init__(self, filepath) -> None: def __init__(self, filepath) -> None:
"""Create a new StreamObject, to manage capture data.""" """Create a new StreamObject, to manage capture data."""
# Stream for buffering capture data
self.stream = io.BytesIO()
# Event to notify when the stream has finished writing to disk
self.file_ready = gevent.event.Event()
# Access lock for the disk file
self.lock = gevent.lock.BoundedSemaphore()
# Store a nice ID # Store a nice ID
self.id = uuid.uuid4() #: str: Unique capture ID self.id = uuid.uuid4() #: str: Unique capture ID
@ -148,6 +157,23 @@ class CaptureObject(object):
# Thumbnail (populated only for PIL captures) # Thumbnail (populated only for PIL captures)
self.thumb_bytes = None self.thumb_bytes = None
def write(self, s):
self.stream.write(s)
def _stream_to_file(self):
with self.lock:
logging.info(f"Writing to disk {self.file}")
with FileObjectThread(open(self.file, "wb"), 'wb') as outfile:
outfile.write(self.stream.getbuffer())
self.stream.close()
self.file_ready.set()
logging.info(f"Finished writing to disk {self.file}")
def flush(self):
logging.debug(f"Flushing {self.file}")
gevent.spawn(self._stream_to_file)
logging.debug(f"Returning flushing {self.file}")
def open(self, mode): def open(self, mode):
return open(self.file, mode) return open(self.file, mode)
@ -227,18 +253,20 @@ class CaptureObject(object):
global EXIF_FORMATS global EXIF_FORMATS
if self.format.upper() in EXIF_FORMATS and self.exists: if self.format.upper() in EXIF_FORMATS and self.exists:
logging.debug("Writing exif data to capture file") self.file_ready.wait()
# Extract current Exif data with self.lock:
exif_dict = piexif.load(self.file) logging.debug("Writing exif data to capture file")
# Serialize metadata # Extract current Exif data
metadata_string = json.dumps(self.metadata, cls=JSONEncoder) exif_dict = piexif.load(self.file)
logging.debug(f"Saving metadata string to file: {metadata_string}") # Serialize metadata
# Insert metadata into exif_dict metadata_string = json.dumps(self.metadata, cls=JSONEncoder)
exif_dict["Exif"][piexif.ExifIFD.UserComment] = metadata_string.encode() logging.debug(f"Saving metadata string to file: {metadata_string}")
# Convert new exif dict to exif bytes # Insert metadata into exif_dict
exif_bytes = piexif.dump(exif_dict) exif_dict["Exif"][piexif.ExifIFD.UserComment] = metadata_string.encode()
# Insert exif into file # Convert new exif dict to exif bytes
piexif.insert(exif_bytes, self.file) exif_bytes = piexif.dump(exif_dict)
# Insert exif into file
piexif.insert(exif_bytes, self.file)
@property @property
def metadata(self) -> dict: def metadata(self) -> dict:

View file

@ -7,6 +7,8 @@ import pkg_resources
import uuid import uuid
from typing import Tuple from typing import Tuple
import gevent
from openflexure_microscope.captures import CaptureManager from openflexure_microscope.captures import CaptureManager
from openflexure_microscope.stage.mock import MissingStage from openflexure_microscope.stage.mock import MissingStage
@ -151,39 +153,41 @@ class Microscope:
Return: Return:
dict: Dictionary containing complete microscope state dict: Dictionary containing complete microscope state
""" """
state = {"camera": self.camera.state, "stage": self.stage.state} with self.lock:
return state state = {"camera": self.camera.state, "stage": self.stage.state}
return state
def update_settings(self, settings: dict): def update_settings(self, settings: dict):
""" """
Applies a settings dictionary to the microscope. Missing parameters will be left untouched. Applies a settings dictionary to the microscope. Missing parameters will be left untouched.
""" """
logging.debug("Microscope: Applying settings: {}".format(settings)) with self.lock:
logging.debug("Microscope: Applying settings: {}".format(settings))
# If attached to a camera # If attached to a camera
if ("camera" in settings) and self.camera: if ("camera" in settings) and self.camera:
self.camera.update_settings(settings.get("camera", {})) self.camera.update_settings(settings.get("camera", {}))
# If attached to a stage # If attached to a stage
if ("stage" in settings) and self.stage: if ("stage" in settings) and self.stage:
self.stage.update_settings(settings.get("stage", {})) self.stage.update_settings(settings.get("stage", {}))
# Capture manager # Capture manager
self.captures.update_settings(settings.get("captures", {})) self.captures.update_settings(settings.get("captures", {}))
# Microscope settings # Microscope settings
if "id" in settings: if "id" in settings:
self.id = settings["id"] self.id = settings["id"]
if "name" in settings: if "name" in settings:
self.name = settings["name"] self.name = settings["name"]
if "fov" in settings: if "fov" in settings:
self.fov = settings["fov"] self.fov = settings["fov"]
# Extension settings # Extension settings
if "extensions" in settings: if "extensions" in settings:
self.extension_settings.update(settings["extensions"]) self.extension_settings.update(settings["extensions"])
# TODO: warn if there are settings that we silently ignore # TODO: warn if there are settings that we silently ignore
def read_settings(self, full: bool = True): def read_settings(self, full: bool = True):
""" """
@ -195,49 +199,50 @@ class Microscope:
This is to ensure that settings for currently disconnected hardware This is to ensure that settings for currently disconnected hardware
don't get removed from the settings file. don't get removed from the settings file.
""" """
with self.lock:
settings_current = { settings_current = {
"id": self.id, "id": self.id,
"name": self.name, "name": self.name,
"fov": self.fov, "fov": self.fov,
"extensions": self.extension_settings, "extensions": self.extension_settings,
} }
# If attached to a camera # If attached to a camera
if self.camera: if self.camera:
settings_current_camera = self.camera.read_settings() settings_current_camera = self.camera.read_settings()
settings_current["camera"] = settings_current_camera settings_current["camera"] = settings_current_camera
# Store an encoded copy of the PiCamera lens shading table, if it exists # Store an encoded copy of the PiCamera lens shading table, if it exists
if hasattr(self.camera, "read_lens_shading_table"): if hasattr(self.camera, "read_lens_shading_table"):
# Read LST. Returns None if no LST is active # Read LST. Returns None if no LST is active
lst_arr = self.camera.read_lens_shading_table() lst_arr = self.camera.read_lens_shading_table()
if lst_arr is not None: if lst_arr is not None:
b64_string, dtype, shape = serialise_array_b64(lst_arr) b64_string, dtype, shape = serialise_array_b64(lst_arr)
settings_current["camera"]["lens_shading_table"] = { settings_current["camera"]["lens_shading_table"] = {
"@type": "ndarray", "@type": "ndarray",
"b64_string": b64_string, "b64_string": b64_string,
"dtype": dtype, "dtype": dtype,
"shape": shape, "shape": shape,
} }
# If attached to a stage # If attached to a stage
if self.stage: if self.stage:
settings_current_stage = self.stage.read_settings() settings_current_stage = self.stage.read_settings()
settings_current["stage"] = settings_current_stage settings_current["stage"] = settings_current_stage
# Capture manager # Capture manager
settings_current_captures = self.captures.read_settings() settings_current_captures = self.captures.read_settings()
settings_current["captures"] = settings_current_captures settings_current["captures"] = settings_current_captures
settings_full = self.settings_file.merge(settings_current) settings_full = self.settings_file.merge(settings_current)
if full: if full:
return settings_full return settings_full
else: else:
return settings_current return settings_current
def save_settings(self): def save_settings(self):
""" """
@ -286,6 +291,21 @@ class Microscope:
return system_metadata return system_metadata
def add_metadata_to_capture(self, output, metadata, annotations, tags):
logging.debug(f"Waiting for {output.file}")
# Wait for the file to be written to disk
output.file_ready.wait()
logging.info(f"Asynchronously injecting EXIF data into {output.file}")
# Inject system metadata
output.put_metadata({"instrument": self.metadata})
# Insert custom metadata
output.put_metadata(metadata)
# Insert custom metadata
output.put_annotations(annotations)
# Insert custom tags
output.put_tags(tags)
logging.info(f"Finished injecting EXIF data into {output.file}")
def capture( def capture(
self, self,
filename: str = None, filename: str = None,
@ -299,6 +319,7 @@ class Microscope:
tags: list = None, tags: list = None,
metadata: dict = None, metadata: dict = None,
): ):
logging.debug(f"Microscope capturing to {filename}")
if not annotations: if not annotations:
annotations = {} annotations = {}
if not metadata: if not metadata:
@ -313,21 +334,18 @@ class Microscope:
) )
# Capture to output object # Capture to output object
logging.info("Starting microscope capture...")
self.camera.capture( self.camera.capture(
output.file, output,
use_video_port=use_video_port, use_video_port=use_video_port,
resize=resize, resize=resize,
bayer=bayer, bayer=bayer,
fmt=fmt, fmt=fmt,
) )
# Inject system metadata # Gether metadata from hardware in a greenlet
output.put_metadata({"instrument": self.metadata}) gevent.get_hub().threadpool.spawn(self.add_metadata_to_capture, output, metadata, annotations, tags)
# Insert custom metadata
output.put_metadata(metadata) logging.info(f"Finished capture to {output.file}")
# Insert custom metadata
output.put_annotations(annotations)
# Insert custom tags
output.put_tags(tags)
return output return output

View file

@ -11,7 +11,7 @@ class BaseStage(metaclass=ABCMeta):
""" """
def __init__(self): def __init__(self):
self.lock = StrictLock(timeout=5) self.lock = StrictLock(name="Stage")
@abstractmethod @abstractmethod
def update_settings(self, config: dict): def update_settings(self, config: dict):

View file

@ -51,7 +51,8 @@ class SangaStage(BaseStage):
@property @property
def position(self): def position(self):
return self.board.position with self.lock:
return self.board.position
@property @property
def backlash(self): def backlash(self):
@ -108,6 +109,7 @@ class SangaStage(BaseStage):
backlash: (default: True) whether to correct for backlash. backlash: (default: True) whether to correct for backlash.
""" """
with self.lock: with self.lock:
logging.info(f"Moving sangaboard by {displacement}")
if not backlash or self.backlash is None: if not backlash or self.backlash is None:
return self.board.move_rel(displacement, axis=axis) return self.board.move_rel(displacement, axis=axis)
@ -145,6 +147,7 @@ class SangaStage(BaseStage):
"""Make an absolute move to a position """Make an absolute move to a position
""" """
with self.lock: with self.lock:
logging.info(f"Moving sangaboard to {final}")
self.board.move_abs(final, **kwargs) self.board.move_abs(final, **kwargs)
def zero_position(self): def zero_position(self):

115
poetry.lock generated
View file

@ -12,12 +12,12 @@ description = "A pluggable API specification generator. Currently supports the O
name = "apispec" name = "apispec"
optional = false optional = false
python-versions = ">=3.5" python-versions = ">=3.5"
version = "3.3.0" version = "3.3.1"
[package.extras] [package.extras]
dev = ["PyYAML (>=3.10)", "prance (>=0.11)", "marshmallow (>=2.19.2)", "pytest", "mock", "flake8 (3.7.9)", "flake8-bugbear (20.1.4)", "pre-commit (>=1.20,<3.0)", "tox"] dev = ["PyYAML (>=3.10)", "prance (>=0.11)", "marshmallow (>=2.19.2)", "pytest", "mock", "flake8 (3.8.2)", "flake8-bugbear (20.1.4)", "pre-commit (>=2.4,<3.0)", "tox"]
docs = ["marshmallow (>=2.19.2)", "pyyaml (5.3)", "sphinx (2.4.1)", "sphinx-issues (1.2.0)", "sphinx-rtd-theme (0.4.3)"] docs = ["marshmallow (>=2.19.2)", "pyyaml (5.3.1)", "sphinx (3.0.4)", "sphinx-issues (1.2.0)", "sphinx-rtd-theme (0.4.3)"]
lint = ["flake8 (3.7.9)", "flake8-bugbear (20.1.4)", "pre-commit (>=1.20,<3.0)"] lint = ["flake8 (3.8.2)", "flake8-bugbear (20.1.4)", "pre-commit (>=2.4,<3.0)"]
tests = ["PyYAML (>=3.10)", "prance (>=0.11)", "marshmallow (>=2.19.2)", "pytest", "mock"] tests = ["PyYAML (>=3.10)", "prance (>=0.11)", "marshmallow (>=2.19.2)", "pytest", "mock"]
validation = ["prance (>=0.11)"] validation = ["prance (>=0.11)"]
yaml = ["PyYAML (>=3.10)"] yaml = ["PyYAML (>=3.10)"]
@ -36,7 +36,7 @@ description = "An abstract syntax tree for Python with inference support."
name = "astroid" name = "astroid"
optional = false optional = false
python-versions = ">=3.5" python-versions = ">=3.5"
version = "2.4.1" version = "2.4.2"
[package.dependencies] [package.dependencies]
lazy-object-proxy = ">=1.4.0,<1.5.0" lazy-object-proxy = ">=1.4.0,<1.5.0"
@ -110,6 +110,7 @@ ofmclient = []
reference = "f1803f5f655b405a6a902f6c697c9f720f6a18da" reference = "f1803f5f655b405a6a902f6c697c9f720f6a18da"
type = "git" type = "git"
url = "https://gitlab.com/openflexure/microscope-extensions/camera-stage-mapping.git" url = "https://gitlab.com/openflexure/microscope-extensions/camera-stage-mapping.git"
[[package]] [[package]]
category = "main" category = "main"
description = "Pure Python CBOR (de)serializer with extensive tag support" description = "Pure Python CBOR (de)serializer with extensive tag support"
@ -127,7 +128,7 @@ description = "Python package for providing Mozilla's CA Bundle."
name = "certifi" name = "certifi"
optional = false optional = false
python-versions = "*" python-versions = "*"
version = "2020.4.5.1" version = "2020.4.5.2"
[[package]] [[package]]
category = "main" category = "main"
@ -219,11 +220,11 @@ description = "Coroutine-based network library"
name = "gevent" name = "gevent"
optional = false optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*"
version = "20.5.2" version = "20.6.0"
[package.dependencies] [package.dependencies]
cffi = ">=1.12.2" cffi = ">=1.12.2"
greenlet = ">=0.4.14" greenlet = ">=0.4.16"
setuptools = "*" setuptools = "*"
"zope.event" = "*" "zope.event" = "*"
"zope.interface" = "*" "zope.interface" = "*"
@ -232,8 +233,8 @@ setuptools = "*"
dnspython = ["dnspython (>=1.16.0)", "idna"] dnspython = ["dnspython (>=1.16.0)", "idna"]
docs = ["repoze.sphinx.autointerface", "sphinxcontrib-programoutput"] docs = ["repoze.sphinx.autointerface", "sphinxcontrib-programoutput"]
monitor = ["psutil (>=5.6.1)", "psutil (5.6.3)"] monitor = ["psutil (>=5.6.1)", "psutil (5.6.3)"]
recommended = ["dnspython (>=1.16.0)", "idna", "cffi (>=1.12.2)", "psutil (>=5.6.1)", "psutil (5.6.3)"] recommended = ["dnspython (>=1.16.0)", "idna", "cffi (>=1.12.2)", "psutil (>=5.6.1)", "psutil (5.6.3)", "selectors2", "backports.socketpair"]
test = ["dnspython (>=1.16.0)", "idna", "requests", "objgraph", "cffi (>=1.12.2)", "psutil (>=5.6.1)", "psutil (5.6.3)", "futures", "mock", "contextvars (2.4)", "coverage (<5.0)", "coveralls (>=1.7.0)"] test = ["dnspython (>=1.16.0)", "idna", "requests", "objgraph", "cffi (>=1.12.2)", "psutil (>=5.6.1)", "psutil (5.6.3)", "selectors2", "futures", "mock", "backports.socketpair", "contextvars (2.4)", "coverage (<5.0)", "coveralls (>=1.7.0)"]
[[package]] [[package]]
category = "main" category = "main"
@ -265,11 +266,11 @@ version = "2.9"
[[package]] [[package]]
category = "main" category = "main"
description = "Enumerates all IP addresses on all network adapters of the system." description = "Cross-platform network interface and IP address enumeration library"
name = "ifaddr" name = "ifaddr"
optional = false optional = false
python-versions = "*" python-versions = "*"
version = "0.1.6" version = "0.1.7"
[[package]] [[package]]
category = "dev" category = "dev"
@ -321,7 +322,7 @@ description = "Python implementation of LabThings, based on the Flask microframe
name = "labthings" name = "labthings"
optional = false optional = false
python-versions = ">=3.6,<4.0" python-versions = ">=3.6,<4.0"
version = "0.6.4" version = "0.6.5"
[package.dependencies] [package.dependencies]
Flask = ">=1.1.1,<2.0.0" Flask = ">=1.1.1,<2.0.0"
@ -332,7 +333,7 @@ gevent = ">=1.4,<21.0"
gevent-websocket = ">=0.10.1,<0.11.0" gevent-websocket = ">=0.10.1,<0.11.0"
marshmallow = ">=3.4.0,<4.0.0" marshmallow = ">=3.4.0,<4.0.0"
webargs = ">=6.0.0,<7.0.0" webargs = ">=6.0.0,<7.0.0"
zeroconf = ">=0.24.5,<0.27.0" zeroconf = ">=0.24.5,<0.28.0"
[[package]] [[package]]
category = "dev" category = "dev"
@ -433,6 +434,7 @@ test = ["coverage", "pytest", "mock", "pillow", "numpy"]
reference = "" reference = ""
type = "url" type = "url"
url = "https://github.com/rwb27/picamera/releases/download/v1.13.2b0/picamera-1.13.2b0-py3-none-any.whl" url = "https://github.com/rwb27/picamera/releases/download/v1.13.2b0/picamera-1.13.2b0-py3-none-any.whl"
[[package]] [[package]]
category = "main" category = "main"
description = "Python Imaging Library (Fork)" description = "Python Imaging Library (Fork)"
@ -475,7 +477,7 @@ description = "python code static checker"
name = "pylint" name = "pylint"
optional = false optional = false
python-versions = ">=3.5.*" python-versions = ">=3.5.*"
version = "2.5.2" version = "2.5.3"
[package.dependencies] [package.dependencies]
astroid = ">=2.4.0,<=2.5" astroid = ">=2.4.0,<=2.5"
@ -572,7 +574,7 @@ description = "Communication to the Sangaboard unipolar motor driver"
name = "sangaboard" name = "sangaboard"
optional = false optional = false
python-versions = "*" python-versions = "*"
version = "0.2.0" version = "0.2.3"
[package.dependencies] [package.dependencies]
future = "*" future = "*"
@ -612,7 +614,7 @@ description = "Python documentation generator"
name = "sphinx" name = "sphinx"
optional = false optional = false
python-versions = ">=3.5" python-versions = ">=3.5"
version = "3.0.4" version = "3.1.1"
[package.dependencies] [package.dependencies]
Jinja2 = ">=2.3" Jinja2 = ">=2.3"
@ -635,7 +637,7 @@ sphinxcontrib-serializinghtml = "*"
[package.extras] [package.extras]
docs = ["sphinxcontrib-websupport"] docs = ["sphinxcontrib-websupport"]
lint = ["flake8 (>=3.5.0)", "flake8-import-order", "mypy (>=0.770)", "docutils-stubs"] lint = ["flake8 (>=3.5.0)", "flake8-import-order", "mypy (>=0.780)", "docutils-stubs"]
test = ["pytest", "pytest-cov", "html5lib", "typed-ast", "cython"] test = ["pytest", "pytest-cov", "html5lib", "typed-ast", "cython"]
[[package]] [[package]]
@ -835,7 +837,7 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"]
rpi = ["RPi.GPIO"] rpi = ["RPi.GPIO"]
[metadata] [metadata]
content-hash = "1c577dec4c5e870bacd76d24991f49cb0c7fed212ace179bea075e2849c91e00" content-hash = "46a88e0322847fc9c13602f72b7de7f10be81bbddc5c0f87e174a540fb427a7c"
python-versions = "^3.6" python-versions = "^3.6"
[metadata.files] [metadata.files]
@ -844,16 +846,16 @@ alabaster = [
{file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"}, {file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"},
] ]
apispec = [ apispec = [
{file = "apispec-3.3.0-py2.py3-none-any.whl", hash = "sha256:9bf4e51d56c9067c60668b78210ae213894f060f85593dc2ad8805eb7d875a2a"}, {file = "apispec-3.3.1-py2.py3-none-any.whl", hash = "sha256:b65063e11968a8c26dbbe9b4100ee24026a41cc358dd204df279bdedd7d8dea2"},
{file = "apispec-3.3.0.tar.gz", hash = "sha256:419d0564b899e182c2af50483ea074db8cb05fee60838be58bb4542095d5c08d"}, {file = "apispec-3.3.1.tar.gz", hash = "sha256:f5244ccca33f7a81309f6b3c9d458e33e869050c2d861b9f8cee24b3ad739d2b"},
] ]
appdirs = [ appdirs = [
{file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"},
{file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"},
] ]
astroid = [ astroid = [
{file = "astroid-2.4.1-py3-none-any.whl", hash = "sha256:d8506842a3faf734b81599c8b98dcc423de863adcc1999248480b18bd31a0f38"}, {file = "astroid-2.4.2-py3-none-any.whl", hash = "sha256:bc58d83eb610252fd8de6363e39d4f1d0619c894b0ed24603b881c02e64c7386"},
{file = "astroid-2.4.1.tar.gz", hash = "sha256:4c17cea3e592c21b6e222f673868961bad77e1f985cb1694ed077475a89229c1"}, {file = "astroid-2.4.2.tar.gz", hash = "sha256:2f4078c2a41bf377eea06d71c9d2ba4eb8f6b1af2135bec27bbbb7d8f12bb703"},
] ]
attrs = [ attrs = [
{file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"}, {file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"},
@ -872,8 +874,8 @@ cbor2 = [
{file = "cbor2-5.1.0.tar.gz", hash = "sha256:43ce11e8c2fe4971d386d1a60cf83bfa0a4a667b97668ba76acbf5e6398821aa"}, {file = "cbor2-5.1.0.tar.gz", hash = "sha256:43ce11e8c2fe4971d386d1a60cf83bfa0a4a667b97668ba76acbf5e6398821aa"},
] ]
certifi = [ certifi = [
{file = "certifi-2020.4.5.1-py2.py3-none-any.whl", hash = "sha256:1d987a998c75633c40847cc966fcf5904906c920a7f17ef374f5aa4282abd304"}, {file = "certifi-2020.4.5.2-py2.py3-none-any.whl", hash = "sha256:9cd41137dc19af6a5e03b630eefe7d1f458d964d406342dd3edf625839b944cc"},
{file = "certifi-2020.4.5.1.tar.gz", hash = "sha256:51fcb31174be6e6664c5f69e3e1691a2d72a1a12e90f872cbdb1567eb47b6519"}, {file = "certifi-2020.4.5.2.tar.gz", hash = "sha256:5ad7e9a056d25ffa5082862e36f119f7f7cec6457fa07ee2f8c339814b80c9b1"},
] ]
cffi = [ cffi = [
{file = "cffi-1.14.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1cae98a7054b5c9391eb3249b86e0e99ab1e02bb0cc0575da191aedadbdf4384"}, {file = "cffi-1.14.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1cae98a7054b5c9391eb3249b86e0e99ab1e02bb0cc0575da191aedadbdf4384"},
@ -933,28 +935,28 @@ future = [
{file = "future-0.18.2.tar.gz", hash = "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"}, {file = "future-0.18.2.tar.gz", hash = "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"},
] ]
gevent = [ gevent = [
{file = "gevent-20.5.2-cp27-cp27m-macosx_10_15_x86_64.whl", hash = "sha256:4a69373a07a0d4ff74401274d3b30aa47430e76d7e314f23bb9f399da1bbed6e"}, {file = "gevent-20.6.0-cp27-cp27m-macosx_10_15_x86_64.whl", hash = "sha256:ccac571d5bb7a85be736c0c5e241a5263a36904b358edc2dcceaa3b54a18eb25"},
{file = "gevent-20.5.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:a55ee5c388511362b1f886be57f06062334bc44b91ef215b997435d21901def7"}, {file = "gevent-20.6.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:eb74efa2486b8c2576eaf2ef6a3a782b1a38fe414390a4a3ab411efca91e54f5"},
{file = "gevent-20.5.2-cp27-cp27m-win32.whl", hash = "sha256:2436dfbc1f0cfa2c2fc8ec9e824dcc13f8501085c29605b4488c981456f12ed6"}, {file = "gevent-20.6.0-cp27-cp27m-win32.whl", hash = "sha256:2701ac60993fb74f1f548348641c78160215aa9707b08e37caf10fe63712f5d9"},
{file = "gevent-20.5.2-cp27-cp27m-win_amd64.whl", hash = "sha256:c2c2e0e275b375941be80a174cd47b3ee20ccf457fbec31863808448ec4fcf59"}, {file = "gevent-20.6.0-cp27-cp27m-win_amd64.whl", hash = "sha256:1a449ab1390fd48cc509d846a62d867e7c548f8f9d5632ca012af2741c97e6d6"},
{file = "gevent-20.5.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:1bb8330f1f86460b845b589eeea73b3e2dd52f84bee30178a8eaa0ddd61622a6"}, {file = "gevent-20.6.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:e963584dd93b8268764297b13d6d99e24d9f1d63d921d5260286b3cde92f2851"},
{file = "gevent-20.5.2-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:2863c2899f0a379322cb07724c67eb608652e99ab5bdf5bfce936c9bf89f4a87"}, {file = "gevent-20.6.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:56f2949b068e922bce3695e80bbf484115536901ad77e833b0e1ac7e4d996654"},
{file = "gevent-20.5.2-cp35-cp35m-win32.whl", hash = "sha256:de44cb7bf1f74043e7976f2dbf9780da7a99a2b58799675817d1df77ccfde519"}, {file = "gevent-20.6.0-cp35-cp35m-win32.whl", hash = "sha256:d4376e9d8f6ecf68de182fed99eb7d7f255b15170edb0bc7d06720552f541c6f"},
{file = "gevent-20.5.2-cp35-cp35m-win_amd64.whl", hash = "sha256:9cec7379b540cd7a8cdffec9ebe799943c07d0f575e6f222bf3515e7655544d7"}, {file = "gevent-20.6.0-cp35-cp35m-win_amd64.whl", hash = "sha256:6d172744705aa401b0014142343e866f98668bd86fc5ba070ace22f4e98f76dd"},
{file = "gevent-20.5.2-cp36-cp36m-macosx_10_15_x86_64.whl", hash = "sha256:9c4601f67f26e534237d1cbaded3157b2a70a89e7760c819e8afb20e32878553"}, {file = "gevent-20.6.0-cp36-cp36m-macosx_10_15_x86_64.whl", hash = "sha256:664f061993e1104f901801dc7b305824389f5c05936e7daeabee56bf76837726"},
{file = "gevent-20.5.2-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:f2932f05b8cfef3d7c285ddbd77008d642bd843c88625e1da4e96a643d4de885"}, {file = "gevent-20.6.0-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:f9521f4721f50f7e1ade81bf61c17387d2cd901de65701038e74c5e87e2785cc"},
{file = "gevent-20.5.2-cp36-cp36m-win32.whl", hash = "sha256:08de7d46923ab04529e70ff0601522bc7897b7bcef8ac40e8a9f906168518875"}, {file = "gevent-20.6.0-cp36-cp36m-win32.whl", hash = "sha256:779ab592efc30550879546bfe83ee6cb42538080fcd495817868ed7b7b17c3ff"},
{file = "gevent-20.5.2-cp36-cp36m-win_amd64.whl", hash = "sha256:ae6787acd68bd2f8d40e10fd8d031a5da2ac9dae6ddd35390cf4c18261a8bd38"}, {file = "gevent-20.6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:94241c8e962b5eab49d5459b319454161bdb1a0c53ffe883a94a9952ab916345"},
{file = "gevent-20.5.2-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:12276c30ce4d6da84eba674dade04b0cd80fb5808dbfdef01ba49b75d50b3816"}, {file = "gevent-20.6.0-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:77d9237d17a64054e1a03883d3265d27fe98e49f41cf1dd22b42ee3206f99b53"},
{file = "gevent-20.5.2-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:4a3ee667456ccec0c1d215aeec8d150449d0a6da5d58ba2c6be741249af1f139"}, {file = "gevent-20.6.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:5520e72522906483559bd12eacabf72f130972dabe8e05d159e227eca97cbcf9"},
{file = "gevent-20.5.2-cp37-cp37m-win32.whl", hash = "sha256:2f138b82527c520fff432e3aa9f606335d23eebfa1e8aeb0e36822e34a91afdd"}, {file = "gevent-20.6.0-cp37-cp37m-win32.whl", hash = "sha256:8625256ec3acc9b095050d15c256ad9ce3f8bac833982bd712af14a83991b067"},
{file = "gevent-20.5.2-cp37-cp37m-win_amd64.whl", hash = "sha256:899e1cdbfaa7aa0d53a430f2f4cb8d03c00db0ee94872ce2b4bb918817a719f0"}, {file = "gevent-20.6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e5e1f2024fbf753549703cde287f6cc940248d860f617d657c3b3ea0638035f0"},
{file = "gevent-20.5.2-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:bd261c12724e3e4be81ce4bd978c3ba47d31bd56444ba9f30332b8e7d4f09b81"}, {file = "gevent-20.6.0-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:3165510ed37f8ea38345a8a2464d5c700f7cc8828194612889f6c583e2889ff1"},
{file = "gevent-20.5.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:1886197bcf8aea097c066e212c32a0aed9ad6a0ed245c124f3ff6b7dce7db354"}, {file = "gevent-20.6.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:b5f73ef6442e72774359afcf892c47cbdcb0811a4577fc1b4b11191aa362e353"},
{file = "gevent-20.5.2-cp38-cp38-win32.whl", hash = "sha256:9146860dd8ac0d0b675abc64025ea8dcd3d399de9290a8eec23bbed6d3993be4"}, {file = "gevent-20.6.0-cp38-cp38-win32.whl", hash = "sha256:6e232e1870dfd2e32aaa26083488c116492e9378d288dffca0e38c1521ba502d"},
{file = "gevent-20.5.2-cp38-cp38-win_amd64.whl", hash = "sha256:67820fd68ba14a38a1645261fc8f3530c4cfad6c45e29f40cc1dda8d5dcc17fb"}, {file = "gevent-20.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:dffc2d2bb815a6ab0370e3e478094e0c43f610a3466e241bc150af21293763db"},
{file = "gevent-20.5.2-pp27-pypy_73-win32.whl", hash = "sha256:91bfe6c3a2ec4d0027b30eb1aa678ab1f828c7d8961e64ac000d8c4a80fc8806"}, {file = "gevent-20.6.0-pp27-pypy_73-win32.whl", hash = "sha256:ea79e58f7927939ac60d3c73aa9e5d6186c3fea77e4414d768d658e1f58ef7a3"},
{file = "gevent-20.5.2.tar.gz", hash = "sha256:2756de36f56b33c46f6cc7146a74ba65afcd1471922c95b6771ce87b279d689c"}, {file = "gevent-20.6.0.tar.gz", hash = "sha256:51cab8f792961a75da39452586bb3474eaa1607ab2c83398745ef1b9ba09cb92"},
] ]
gevent-websocket = [ gevent-websocket = [
{file = "gevent-websocket-0.10.1.tar.gz", hash = "sha256:7eaef32968290c9121f7c35b973e2cc302ffb076d018c9068d2f5ca8b2d85fb0"}, {file = "gevent-websocket-0.10.1.tar.gz", hash = "sha256:7eaef32968290c9121f7c35b973e2cc302ffb076d018c9068d2f5ca8b2d85fb0"},
@ -984,7 +986,8 @@ idna = [
{file = "idna-2.9.tar.gz", hash = "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb"}, {file = "idna-2.9.tar.gz", hash = "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb"},
] ]
ifaddr = [ ifaddr = [
{file = "ifaddr-0.1.6.tar.gz", hash = "sha256:c19c64882a7ad51a394451dabcbbed72e98b5625ec1e79789924d5ea3e3ecb93"}, {file = "ifaddr-0.1.7-py2.py3-none-any.whl", hash = "sha256:d1f603952f0a71c9ab4e705754511e4e03b02565bc4cec7188ad6415ff534cd3"},
{file = "ifaddr-0.1.7.tar.gz", hash = "sha256:1f9e8a6ca6f16db5a37d3356f07b6e52344f6f9f7e806d618537731669eb1a94"},
] ]
imagesize = [ imagesize = [
{file = "imagesize-1.2.0-py2.py3-none-any.whl", hash = "sha256:6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1"}, {file = "imagesize-1.2.0-py2.py3-none-any.whl", hash = "sha256:6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1"},
@ -1003,8 +1006,8 @@ jinja2 = [
{file = "Jinja2-2.11.2.tar.gz", hash = "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0"}, {file = "Jinja2-2.11.2.tar.gz", hash = "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0"},
] ]
labthings = [ labthings = [
{file = "labthings-0.6.4-py3-none-any.whl", hash = "sha256:61e540e4635278008b8f851a1b8104199cae92affd043f229f0ffa9f78c0479e"}, {file = "labthings-0.6.5-py3-none-any.whl", hash = "sha256:f28fb8b6fa05eabc105c5dc987835ebed8784f9357ccd1f6ef9eb32b6280216c"},
{file = "labthings-0.6.4.tar.gz", hash = "sha256:d734e74716b2348350d31b48f67cbf96c01c88b6b3185bace287e81fd3aa5826"}, {file = "labthings-0.6.5.tar.gz", hash = "sha256:adadcf9a1588d1d38c12d36939fe902b30e8f56d50c196fc79161ad58e0331a0"},
] ]
lazy-object-proxy = [ lazy-object-proxy = [
{file = "lazy-object-proxy-1.4.3.tar.gz", hash = "sha256:f3900e8a5de27447acbf900b4750b0ddfd7ec1ea7fbaf11dfa911141bc522af0"}, {file = "lazy-object-proxy-1.4.3.tar.gz", hash = "sha256:f3900e8a5de27447acbf900b4750b0ddfd7ec1ea7fbaf11dfa911141bc522af0"},
@ -1219,8 +1222,8 @@ pygments = [
{file = "Pygments-2.6.1.tar.gz", hash = "sha256:647344a061c249a3b74e230c739f434d7ea4d8b1d5f3721bc0f3558049b38f44"}, {file = "Pygments-2.6.1.tar.gz", hash = "sha256:647344a061c249a3b74e230c739f434d7ea4d8b1d5f3721bc0f3558049b38f44"},
] ]
pylint = [ pylint = [
{file = "pylint-2.5.2-py3-none-any.whl", hash = "sha256:dd506acce0427e9e08fb87274bcaa953d38b50a58207170dbf5b36cf3e16957b"}, {file = "pylint-2.5.3-py3-none-any.whl", hash = "sha256:d0ece7d223fe422088b0e8f13fa0a1e8eb745ebffcb8ed53d3e95394b6101a1c"},
{file = "pylint-2.5.2.tar.gz", hash = "sha256:b95e31850f3af163c2283ed40432f053acbc8fc6eba6a069cb518d9dbf71848c"}, {file = "pylint-2.5.3.tar.gz", hash = "sha256:7dd78437f2d8d019717dbf287772d0b2dbdfd13fc016aa7faa08d67bccc46adc"},
] ]
pynpm = [ pynpm = [
{file = "pynpm-0.1.2-py2.py3-none-any.whl", hash = "sha256:3f03fbf667549f8b8b7e0419eef88d1b21833ce288f96de66fbb761b9f4c4061"}, {file = "pynpm-0.1.2-py2.py3-none-any.whl", hash = "sha256:3f03fbf667549f8b8b7e0419eef88d1b21833ce288f96de66fbb761b9f4c4061"},
@ -1255,8 +1258,8 @@ rope = [
{file = "RPi.GPIO-0.6.5.tar.gz", hash = "sha256:a4210ad63bfe844e43995286de0d3950dfacfa0f3799bb9392770ac54a7d2e47"}, {file = "RPi.GPIO-0.6.5.tar.gz", hash = "sha256:a4210ad63bfe844e43995286de0d3950dfacfa0f3799bb9392770ac54a7d2e47"},
] ]
sangaboard = [ sangaboard = [
{file = "sangaboard-0.2.0-py3-none-any.whl", hash = "sha256:510cb95a56e070ef7a9214f6a0673a92f2269e71e125c54b8bb1c16b1abfb095"}, {file = "sangaboard-0.2.3-py3-none-any.whl", hash = "sha256:3791159d57a749571f89acdce70aa9c1de82afef71f5186d5e2845c7be44698c"},
{file = "sangaboard-0.2.0.tar.gz", hash = "sha256:3636203179e9748cd27a1856d44de2b1c7adf398d033a8d5d382748903a098f0"}, {file = "sangaboard-0.2.3.tar.gz", hash = "sha256:ed0c5a9864f5901d11bed0ed2387da3b5555f75fef72a9f35997304447f9b08c"},
] ]
scipy = [ scipy = [
{file = "scipy-1.4.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:c5cac0c0387272ee0e789e94a570ac51deb01c796b37fb2aad1fb13f85e2f97d"}, {file = "scipy-1.4.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:c5cac0c0387272ee0e789e94a570ac51deb01c796b37fb2aad1fb13f85e2f97d"},
@ -1290,8 +1293,8 @@ snowballstemmer = [
{file = "snowballstemmer-2.0.0.tar.gz", hash = "sha256:df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52"}, {file = "snowballstemmer-2.0.0.tar.gz", hash = "sha256:df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52"},
] ]
sphinx = [ sphinx = [
{file = "Sphinx-3.0.4-py3-none-any.whl", hash = "sha256:779a519adbd3a70fc7c468af08c5e74829868b0a5b34587b33340e010291856c"}, {file = "Sphinx-3.1.1-py3-none-any.whl", hash = "sha256:97c9e3bcce2f61d9f5edf131299ee9d1219630598d9f9a8791459a4d9e815be5"},
{file = "Sphinx-3.0.4.tar.gz", hash = "sha256:ea64df287958ee5aac46be7ac2b7277305b0381d213728c3a49d8bb9b8415807"}, {file = "Sphinx-3.1.1.tar.gz", hash = "sha256:74fbead182a611ce1444f50218a1c5fc70b6cc547f64948f5182fb30a2a20258"},
] ]
sphinxcontrib-applehelp = [ sphinxcontrib-applehelp = [
{file = "sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"}, {file = "sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"},

View file

@ -4,7 +4,7 @@ build-backend = "poetry.masonry.api"
[tool.poetry] [tool.poetry]
name = "openflexure-microscope-server" name = "openflexure-microscope-server"
version = "2.1.3" version = "2.2.0-beta.0"
description = "Python module, and Flask-based web API, to run the OpenFlexure Microscope." description = "Python module, and Flask-based web API, to run the OpenFlexure Microscope."
authors = [ authors = [
@ -42,10 +42,11 @@ opencv-python-headless = [
{version = "4.1.0.25", python = "~3.7"}, # PiWheels build for RPi running Py37 {version = "4.1.0.25", python = "~3.7"}, # PiWheels build for RPi running Py37
{version = "4.2.0.34", python = "^3.8"} # Latest for Py38 systems {version = "4.2.0.34", python = "^3.8"} # Latest for Py38 systems
] ]
labthings = "0.6.4" labthings = "0.6.5"
pynpm = "^0.1.2" pynpm = "^0.1.2"
camera-stage-mapping = {git = "https://gitlab.com/openflexure/microscope-extensions/camera-stage-mapping.git"} camera-stage-mapping = {git = "https://gitlab.com/openflexure/microscope-extensions/camera-stage-mapping.git"}
sangaboard = "^0.2.0" sangaboard = "^0.2"
gevent = "20.6.0" # Pinned version to guarantee piwheels wheel
[tool.poetry.extras] [tool.poetry.extras]
rpi = ["RPi.GPIO"] rpi = ["RPi.GPIO"]

View file

@ -31,7 +31,7 @@ class TestCaptureMethods(unittest.TestCase):
with camera.new_image() as output: with camera.new_image() as output:
camera.capture( camera.capture(
output.file, use_video_port=use_video_port, resize=resize output, use_video_port=use_video_port, resize=resize
) )
# Ensure file deletion fails and returns False # Ensure file deletion fails and returns False