From 6f762022d6bc2888aef61a299c1204df967535a8 Mon Sep 17 00:00:00 2001 From: Maxim Pertsov Date: Wed, 19 Jul 2023 10:32:29 -0400 Subject: [PATCH 1/9] clone from existing service --- src/viam/services/navigation/__init__.py | 14 +++++ src/viam/services/navigation/client.py | 52 ++++++++++++++++ src/viam/services/navigation/navigation.py | 50 +++++++++++++++ src/viam/services/navigation/service.py | 71 ++++++++++++++++++++++ 4 files changed, 187 insertions(+) create mode 100644 src/viam/services/navigation/__init__.py create mode 100644 src/viam/services/navigation/client.py create mode 100644 src/viam/services/navigation/navigation.py create mode 100644 src/viam/services/navigation/service.py diff --git a/src/viam/services/navigation/__init__.py b/src/viam/services/navigation/__init__.py new file mode 100644 index 000000000..3be446bce --- /dev/null +++ b/src/viam/services/navigation/__init__.py @@ -0,0 +1,14 @@ +from viam.proto.common import Pose +from viam.resource.registry import Registry, ResourceRegistration + +from .client import NavigationClient +from .navigation import Navigation +from .service import NavigationRPCService + +__all__ = [ + "Pose", + "NavigationClient", + "Navigation", +] + +Registry.register_subtype(ResourceRegistration(Navigation, NavigationRPCService, lambda name, channel: NavigationClient(name, channel))) diff --git a/src/viam/services/navigation/client.py b/src/viam/services/navigation/client.py new file mode 100644 index 000000000..1ac7e8c1e --- /dev/null +++ b/src/viam/services/navigation/client.py @@ -0,0 +1,52 @@ +from typing import List, Mapping, Optional + +from grpclib.client import Channel + +from viam.proto.common import DoCommandRequest, DoCommandResponse +from viam.proto.service.navigation import ( + GetInternalStateRequest, + GetInternalStateResponse, + GetPointCloudMapRequest, + GetPointCloudMapResponse, + GetPositionRequest, + GetPositionResponse, + NavigationServiceStub, +) +from viam.resource.rpc_client_base import ReconfigurableResourceRPCClientBase +from viam.utils import ValueTypes, dict_to_struct, struct_to_dict + +from . import Pose +from .navigation import Navigation + + +class NavigationClient(Navigation, ReconfigurableResourceRPCClientBase): + """ + Connect to the NavigationService, which allows the robot to create a map of its surroundings and find its location in that map. + """ + + client: NavigationServiceStub + + def __init__(self, name: str, channel: Channel): + self.channel = channel + self.client = NavigationServiceStub(channel) + super().__init__(name) + + async def get_position(self, *, timeout: Optional[float] = None) -> Pose: + request = GetPositionRequest(name=self.name) + response: GetPositionResponse = await self.client.GetPosition(request, timeout=timeout) + return response.pose + + async def get_point_cloud_map(self, *, timeout: Optional[float] = None) -> List[GetPointCloudMapResponse]: + request = GetPointCloudMapRequest(name=self.name) + response: List[GetPointCloudMapResponse] = await self.client.GetPointCloudMap(request, timeout=timeout) + return response + + async def get_internal_state(self, *, timeout: Optional[float] = None) -> List[GetInternalStateResponse]: + request = GetInternalStateRequest(name=self.name) + response: List[GetInternalStateResponse] = await self.client.GetInternalState(request, timeout=timeout) + return response + + async def do_command(self, command: Mapping[str, ValueTypes], *, timeout: Optional[float] = None) -> Mapping[str, ValueTypes]: + request = DoCommandRequest(name=self.name, command=dict_to_struct(command)) + response: DoCommandResponse = await self.client.DoCommand(request, timeout=timeout) + return struct_to_dict(response.result) diff --git a/src/viam/services/navigation/navigation.py b/src/viam/services/navigation/navigation.py new file mode 100644 index 000000000..e9554d767 --- /dev/null +++ b/src/viam/services/navigation/navigation.py @@ -0,0 +1,50 @@ +import abc +from typing import Final, List, Optional + +from viam.resource.types import RESOURCE_NAMESPACE_RDK, RESOURCE_TYPE_SERVICE, Subtype + +from ..service_base import ServiceBase +from . import Pose + + +class Navigation(ServiceBase): + """ + Navigation represents a Navigation service. + + This acts as an abstract base class for any drivers representing specific + arm implementations. This cannot be used on its own. If the ``__init__()`` function is + overridden, it must call the ``super().__init__()`` function. + """ + + SUBTYPE: Final = Subtype(RESOURCE_NAMESPACE_RDK, RESOURCE_TYPE_SERVICE, "navigation") + + @abc.abstractmethod + async def get_internal_state(self, *, timeout: Optional[float]) -> List[bytes]: + """Get the internal state of the Navigation algorithm required to continue mapping/localization. + + Returns: + List[GetInternalStateResponse]: Chunks of the internal state of the Navigation algorithm + + """ + ... + + @abc.abstractmethod + async def get_point_cloud_map(self, *, timeout: Optional[float]) -> List[bytes]: + """ + Get the point cloud map. + + Returns: + List[GetPointCloudMapResponse]: Complete pointcloud in standard PCD format. Chunks of the PointCloud, concatenating all + GetPointCloudMapResponse.point_cloud_pcd_chunk values + """ + ... + + @abc.abstractmethod + async def get_position(self, *, timeout: Optional[float]) -> Pose: + """ + Get current position of the specified component in the Navigation Map. + + Returns: + Pose: The current position of the specified component + """ + ... diff --git a/src/viam/services/navigation/service.py b/src/viam/services/navigation/service.py new file mode 100644 index 000000000..72553d10b --- /dev/null +++ b/src/viam/services/navigation/service.py @@ -0,0 +1,71 @@ +from grpclib.server import Stream + +from viam.errors import MethodNotImplementedError +from viam.proto.common import DoCommandRequest, DoCommandResponse +from viam.proto.service.navigation import ( + GetInternalStateRequest, + GetInternalStateResponse, + GetLatestMapInfoRequest, + GetLatestMapInfoResponse, + GetPointCloudMapRequest, + GetPointCloudMapResponse, + GetPositionRequest, + GetPositionResponse, + NavigationServiceBase, +) +from viam.resource.rpc_service_base import ResourceRPCServiceBase +from viam.utils import dict_to_struct, struct_to_dict + +from .navigation import Navigation + + +class NavigationRPCService(NavigationServiceBase, ResourceRPCServiceBase): + """ + gRPC Service for a Navigation service + """ + + RESOURCE_TYPE = Navigation + + async def GetInternalState(self, stream: Stream[GetInternalStateRequest, GetInternalStateResponse]) -> None: + request = await stream.recv_message() + assert request is not None + name = request.name + navigation = self.get_resource(name) + timeout = stream.deadline.time_remaining() if stream.deadline else None + chunks = await navigation.get_internal_state(timeout=timeout) + for chunk in chunks: + response = GetInternalStateResponse(internal_state_chunk=chunk) + await stream.send_message(response) + + async def GetPointCloudMap(self, stream: Stream[GetPointCloudMapRequest, GetPointCloudMapResponse]) -> None: + request = await stream.recv_message() + assert request is not None + name = request.name + navigation = self.get_resource(name) + timeout = stream.deadline.time_remaining() if stream.deadline else None + chunks = await navigation.get_point_cloud_map(timeout=timeout) + for chunk in chunks: + response = GetPointCloudMapResponse(point_cloud_pcd_chunk=chunk) + await stream.send_message(response) + + async def GetPosition(self, stream: Stream[GetPositionRequest, GetPositionResponse]) -> None: + request = await stream.recv_message() + assert request is not None + name = request.name + navigation = self.get_resource(name) + timeout = stream.deadline.time_remaining() if stream.deadline else None + position = await navigation.get_position(timeout=timeout) + response = GetPositionResponse(pose=position) + await stream.send_message(response) + + async def GetLatestMapInfo(self, stream: Stream[GetLatestMapInfoRequest, GetLatestMapInfoResponse]) -> None: + raise MethodNotImplementedError("GetLatestMapInfo").grpc_error + + async def DoCommand(self, stream: Stream[DoCommandRequest, DoCommandResponse]) -> None: + request = await stream.recv_message() + assert request is not None + navigation = self.get_resource(request.name) + timeout = stream.deadline.time_remaining() if stream.deadline else None + result = await navigation.do_command(command=struct_to_dict(request.command), timeout=timeout, metadata=stream.metadata) + response = DoCommandResponse(result=dict_to_struct(result)) + await stream.send_message(response) From 44013b016a80045082500c3a79cd0390c8508be1 Mon Sep 17 00:00:00 2001 From: Maxim Pertsov Date: Wed, 19 Jul 2023 11:10:01 -0400 Subject: [PATCH 2/9] define ABC --- src/viam/services/navigation/__init__.py | 9 ++---- src/viam/services/navigation/navigation.py | 37 ++++++++++------------ 2 files changed, 20 insertions(+), 26 deletions(-) diff --git a/src/viam/services/navigation/__init__.py b/src/viam/services/navigation/__init__.py index 3be446bce..6d07f7973 100644 --- a/src/viam/services/navigation/__init__.py +++ b/src/viam/services/navigation/__init__.py @@ -1,14 +1,11 @@ -from viam.proto.common import Pose +from viam.proto.common import GeoObstacle, GeoPoint +from viam.proto.service.navigation import Mode, Waypoint from viam.resource.registry import Registry, ResourceRegistration from .client import NavigationClient from .navigation import Navigation from .service import NavigationRPCService -__all__ = [ - "Pose", - "NavigationClient", - "Navigation", -] +__all__ = ["GeoPoint", "GeoObstacle", "NavigationClient", "Navigation", "Waypoint", "Mode"] Registry.register_subtype(ResourceRegistration(Navigation, NavigationRPCService, lambda name, channel: NavigationClient(name, channel))) diff --git a/src/viam/services/navigation/navigation.py b/src/viam/services/navigation/navigation.py index e9554d767..fff234e01 100644 --- a/src/viam/services/navigation/navigation.py +++ b/src/viam/services/navigation/navigation.py @@ -4,7 +4,7 @@ from viam.resource.types import RESOURCE_NAMESPACE_RDK, RESOURCE_TYPE_SERVICE, Subtype from ..service_base import ServiceBase -from . import Pose +from . import GeoObstacle, GeoPoint, Mode, Waypoint class Navigation(ServiceBase): @@ -19,32 +19,29 @@ class Navigation(ServiceBase): SUBTYPE: Final = Subtype(RESOURCE_NAMESPACE_RDK, RESOURCE_TYPE_SERVICE, "navigation") @abc.abstractmethod - async def get_internal_state(self, *, timeout: Optional[float]) -> List[bytes]: - """Get the internal state of the Navigation algorithm required to continue mapping/localization. + async def get_location(self, *, timeout: Optional[float]) -> GeoPoint: + ... - Returns: - List[GetInternalStateResponse]: Chunks of the internal state of the Navigation algorithm + @abc.abstractmethod + async def get_obstacles(self, *, timeout: Optional[float]) -> List[GeoObstacle]: + ... - """ + @abc.abstractmethod + async def get_waypoints(self, *, timeout: Optional[float]) -> List[Waypoint]: ... @abc.abstractmethod - async def get_point_cloud_map(self, *, timeout: Optional[float]) -> List[bytes]: - """ - Get the point cloud map. - - Returns: - List[GetPointCloudMapResponse]: Complete pointcloud in standard PCD format. Chunks of the PointCloud, concatenating all - GetPointCloudMapResponse.point_cloud_pcd_chunk values - """ + async def add_waypoint(self, point: GeoPoint, *, timeout: Optional[float]): ... @abc.abstractmethod - async def get_position(self, *, timeout: Optional[float]) -> Pose: - """ - Get current position of the specified component in the Navigation Map. + async def remove_waypoint(self, id: str, *, timeout: Optional[float]): + ... - Returns: - Pose: The current position of the specified component - """ + @abc.abstractmethod + async def get_mode(self, id: str, *, timeout: Optional[float]) -> Mode: + ... + + @abc.abstractmethod + async def set_mode(self, id: str, *, timeout: Optional[float]): ... From 74b04795b6296960ca05e5b34d69437d58f3ad8a Mon Sep 17 00:00:00 2001 From: Maxim Pertsov Date: Wed, 19 Jul 2023 11:34:07 -0400 Subject: [PATCH 3/9] client --- src/viam/services/navigation/client.py | 63 +++++++++++++++------- src/viam/services/navigation/navigation.py | 4 +- 2 files changed, 46 insertions(+), 21 deletions(-) diff --git a/src/viam/services/navigation/client.py b/src/viam/services/navigation/client.py index 1ac7e8c1e..692569916 100644 --- a/src/viam/services/navigation/client.py +++ b/src/viam/services/navigation/client.py @@ -4,18 +4,26 @@ from viam.proto.common import DoCommandRequest, DoCommandResponse from viam.proto.service.navigation import ( - GetInternalStateRequest, - GetInternalStateResponse, - GetPointCloudMapRequest, - GetPointCloudMapResponse, - GetPositionRequest, - GetPositionResponse, + AddWaypointRequest, + AddWaypointResponse, + GetLocationRequest, + GetLocationResponse, + GetModeRequest, + GetModeResponse, + GetObstaclesRequest, + GetObstaclesResponse, + GetWaypointsRequest, + GetWaypointsResponse, NavigationServiceStub, + RemoveWaypointRequest, + RemoveWaypointResponse, + SetModeRequest, + SetModeResponse, ) from viam.resource.rpc_client_base import ReconfigurableResourceRPCClientBase from viam.utils import ValueTypes, dict_to_struct, struct_to_dict -from . import Pose +from . import GeoObstacle, GeoPoint, Mode, Waypoint from .navigation import Navigation @@ -31,20 +39,37 @@ def __init__(self, name: str, channel: Channel): self.client = NavigationServiceStub(channel) super().__init__(name) - async def get_position(self, *, timeout: Optional[float] = None) -> Pose: - request = GetPositionRequest(name=self.name) - response: GetPositionResponse = await self.client.GetPosition(request, timeout=timeout) - return response.pose + async def get_location(self, *, timeout: Optional[float]) -> GeoPoint: + request = GetLocationRequest(name=self.name) + response: GetLocationResponse = await self.client.GetLocation(request, timeout=timeout) + return response.location - async def get_point_cloud_map(self, *, timeout: Optional[float] = None) -> List[GetPointCloudMapResponse]: - request = GetPointCloudMapRequest(name=self.name) - response: List[GetPointCloudMapResponse] = await self.client.GetPointCloudMap(request, timeout=timeout) - return response + async def get_obstacles(self, *, timeout: Optional[float]) -> List[GeoObstacle]: + request = GetObstaclesRequest(name=self.name) + response: GetObstaclesResponse = await self.client.GetObstacles(request, timeout=timeout) + return list(response.obstacles) - async def get_internal_state(self, *, timeout: Optional[float] = None) -> List[GetInternalStateResponse]: - request = GetInternalStateRequest(name=self.name) - response: List[GetInternalStateResponse] = await self.client.GetInternalState(request, timeout=timeout) - return response + async def get_waypoints(self, *, timeout: Optional[float]) -> List[Waypoint]: + request = GetWaypointsRequest(name=self.name) + response: GetWaypointsResponse = await self.client.GetWaypoints(request, timeout=timeout) + return list(response.waypoints) + + async def add_waypoint(self, point: GeoPoint, *, timeout: Optional[float]): + request = AddWaypointRequest(name=self.name, location=point) + await self.client.AddWaypoint(request, timeout=timeout) + + async def remove_waypoint(self, id: str, *, timeout: Optional[float]): + request = RemoveWaypointRequest(name=self.name, id=id) + await self.client.RemoveWaypoint(request, timeout=timeout) + + async def get_mode(self, *, timeout: Optional[float]) -> Mode.ValueType: + request = GetModeRequest(name=self.name) + response: GetModeResponse = await self.client.GetMode(request, timeout=timeout) + return response.mode + + async def set_mode(self, mode: Mode.ValueType, *, timeout: Optional[float]): + request = SetModeRequest(name=self.name, mode=mode) + await self.client.SetMode(request, timeout=timeout) async def do_command(self, command: Mapping[str, ValueTypes], *, timeout: Optional[float] = None) -> Mapping[str, ValueTypes]: request = DoCommandRequest(name=self.name, command=dict_to_struct(command)) diff --git a/src/viam/services/navigation/navigation.py b/src/viam/services/navigation/navigation.py index fff234e01..74e59e767 100644 --- a/src/viam/services/navigation/navigation.py +++ b/src/viam/services/navigation/navigation.py @@ -39,9 +39,9 @@ async def remove_waypoint(self, id: str, *, timeout: Optional[float]): ... @abc.abstractmethod - async def get_mode(self, id: str, *, timeout: Optional[float]) -> Mode: + async def get_mode(self, *, timeout: Optional[float]) -> Mode.ValueType: ... @abc.abstractmethod - async def set_mode(self, id: str, *, timeout: Optional[float]): + async def set_mode(self, mode: Mode.ValueType, *, timeout: Optional[float]): ... From 7fb8cc5eadb7ddd8799805763fe9e97161f3fc3d Mon Sep 17 00:00:00 2001 From: Maxim Pertsov Date: Wed, 19 Jul 2023 14:26:22 -0400 Subject: [PATCH 4/9] better imports for nav --- src/viam/proto/service/navigation/__init__.py | 11 +++++++---- src/viam/services/navigation/service.py | 8 -------- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/src/viam/proto/service/navigation/__init__.py b/src/viam/proto/service/navigation/__init__.py index 815e1d3c2..1e84d920a 100644 --- a/src/viam/proto/service/navigation/__init__.py +++ b/src/viam/proto/service/navigation/__init__.py @@ -1,8 +1,11 @@ -""" +''' @generated by Viam. Do not edit manually! -""" -from ....gen.service.navigation.v1.navigation_grpc import NavigationServiceBase, NavigationServiceStub +''' +from ....gen.service.navigation.v1.navigation_grpc import ( + NavigationServiceBase, + NavigationServiceStub +) from ....gen.service.navigation.v1.navigation_pb2 import ( AddWaypointRequest, AddWaypointResponse, @@ -19,7 +22,7 @@ RemoveWaypointResponse, SetModeRequest, SetModeResponse, - Waypoint, + Waypoint ) __all__ = [ diff --git a/src/viam/services/navigation/service.py b/src/viam/services/navigation/service.py index 72553d10b..2b994fa02 100644 --- a/src/viam/services/navigation/service.py +++ b/src/viam/services/navigation/service.py @@ -3,14 +3,6 @@ from viam.errors import MethodNotImplementedError from viam.proto.common import DoCommandRequest, DoCommandResponse from viam.proto.service.navigation import ( - GetInternalStateRequest, - GetInternalStateResponse, - GetLatestMapInfoRequest, - GetLatestMapInfoResponse, - GetPointCloudMapRequest, - GetPointCloudMapResponse, - GetPositionRequest, - GetPositionResponse, NavigationServiceBase, ) from viam.resource.rpc_service_base import ResourceRPCServiceBase From 98a2872b2fbad8e2c859ee5c9893875e5771de9c Mon Sep 17 00:00:00 2001 From: Maxim Pertsov Date: Wed, 19 Jul 2023 16:38:51 -0400 Subject: [PATCH 5/9] service --- src/viam/services/navigation/service.py | 82 ++++++++++++++++++++----- 1 file changed, 67 insertions(+), 15 deletions(-) diff --git a/src/viam/services/navigation/service.py b/src/viam/services/navigation/service.py index 2b994fa02..8c7fe9778 100644 --- a/src/viam/services/navigation/service.py +++ b/src/viam/services/navigation/service.py @@ -3,7 +3,21 @@ from viam.errors import MethodNotImplementedError from viam.proto.common import DoCommandRequest, DoCommandResponse from viam.proto.service.navigation import ( + AddWaypointRequest, + AddWaypointResponse, + GetLocationRequest, + GetLocationResponse, + GetModeRequest, + GetModeResponse, + GetObstaclesRequest, + GetObstaclesResponse, + GetWaypointsRequest, + GetWaypointsResponse, NavigationServiceBase, + RemoveWaypointRequest, + RemoveWaypointResponse, + SetModeRequest, + SetModeResponse, ) from viam.resource.rpc_service_base import ResourceRPCServiceBase from viam.utils import dict_to_struct, struct_to_dict @@ -18,40 +32,78 @@ class NavigationRPCService(NavigationServiceBase, ResourceRPCServiceBase): RESOURCE_TYPE = Navigation - async def GetInternalState(self, stream: Stream[GetInternalStateRequest, GetInternalStateResponse]) -> None: + async def GetLocation(self, stream: Stream[GetLocationRequest, GetLocationResponse]) -> None: request = await stream.recv_message() assert request is not None name = request.name navigation = self.get_resource(name) timeout = stream.deadline.time_remaining() if stream.deadline else None - chunks = await navigation.get_internal_state(timeout=timeout) - for chunk in chunks: - response = GetInternalStateResponse(internal_state_chunk=chunk) - await stream.send_message(response) + location = await navigation.get_location(timeout=timeout) + response = GetLocationResponse(location=location) + await stream.send_message(response) - async def GetPointCloudMap(self, stream: Stream[GetPointCloudMapRequest, GetPointCloudMapResponse]) -> None: + async def GetObstacles(self, stream: Stream[GetObstaclesRequest, GetObstaclesResponse]) -> None: request = await stream.recv_message() assert request is not None name = request.name navigation = self.get_resource(name) timeout = stream.deadline.time_remaining() if stream.deadline else None - chunks = await navigation.get_point_cloud_map(timeout=timeout) - for chunk in chunks: - response = GetPointCloudMapResponse(point_cloud_pcd_chunk=chunk) - await stream.send_message(response) + obstacles = await navigation.get_obstacles(timeout=timeout) + response = GetObstaclesResponse(obstacles=obstacles) + await stream.send_message(response) - async def GetPosition(self, stream: Stream[GetPositionRequest, GetPositionResponse]) -> None: + async def GetWaypoints(self, stream: Stream[GetWaypointsRequest, GetWaypointsResponse]) -> None: request = await stream.recv_message() assert request is not None name = request.name navigation = self.get_resource(name) timeout = stream.deadline.time_remaining() if stream.deadline else None - position = await navigation.get_position(timeout=timeout) - response = GetPositionResponse(pose=position) + waypoints = await navigation.get_waypoints(timeout=timeout) + response = GetWaypointsResponse(waypoints=waypoints) await stream.send_message(response) - async def GetLatestMapInfo(self, stream: Stream[GetLatestMapInfoRequest, GetLatestMapInfoResponse]) -> None: - raise MethodNotImplementedError("GetLatestMapInfo").grpc_error + async def AddWaypoint(self, stream: Stream[AddWaypointRequest, AddWaypointResponse]) -> None: + request = await stream.recv_message() + assert request is not None + name = request.name + point = request.location + navigation = self.get_resource(name) + timeout = stream.deadline.time_remaining() if stream.deadline else None + await navigation.add_waypoint(point, timeout=timeout) + response = AddWaypointResponse() + await stream.send_message(response) + + async def RemoveWaypoint(self, stream: Stream[RemoveWaypointRequest, RemoveWaypointResponse]) -> None: + request = await stream.recv_message() + assert request is not None + name = request.name + id = request.id + navigation = self.get_resource(name) + timeout = stream.deadline.time_remaining() if stream.deadline else None + await navigation.remove_waypoint(id=id, timeout=timeout) + response = RemoveWaypointResponse() + await stream.send_message(response) + + async def GetMode(self, stream: Stream[GetModeRequest, GetModeResponse]) -> None: + request = await stream.recv_message() + assert request is not None + name = request.name + navigation = self.get_resource(name) + timeout = stream.deadline.time_remaining() if stream.deadline else None + mode = await navigation.get_mode(timeout=timeout) + response = GetModeResponse(mode=mode) + await stream.send_message(response) + + async def SetMode(self, stream: Stream[SetModeRequest, SetModeResponse]) -> None: + request = await stream.recv_message() + assert request is not None + name = request.name + mode = request.mode + navigation = self.get_resource(name) + timeout = stream.deadline.time_remaining() if stream.deadline else None + await navigation.set_mode(mode, timeout=timeout) + response = SetModeResponse() + await stream.send_message(response) async def DoCommand(self, stream: Stream[DoCommandRequest, DoCommandResponse]) -> None: request = await stream.recv_message() From 88c3ec31bdd90eb3624d703014839277ab630457 Mon Sep 17 00:00:00 2001 From: Maxim Pertsov Date: Wed, 19 Jul 2023 18:47:01 -0400 Subject: [PATCH 6/9] QoL: pytest watch --- Makefile | 6 ++ poetry.lock | 244 +++++++++++++++++++++++++++++++++++++++++++++++-- pyproject.toml | 1 + 3 files changed, 243 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index 36a0a9730..4cef14e21 100644 --- a/Makefile +++ b/Makefile @@ -32,6 +32,12 @@ _better_imports: better_imports: poetry run $(MAKE) _better_imports +_test_watch: + poetry run ptw . + +test_watch: + poetry run $(MAKE) _test_watch + _test: coverage run -m pytest && coverage html diff --git a/poetry.lock b/poetry.lock index 3270c13db..f33e90615 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,9 +1,10 @@ -# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand. [[package]] name = "alabaster" version = "0.7.13" description = "A configurable sidebar-enabled Sphinx theme" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -15,6 +16,7 @@ files = [ name = "anyascii" version = "0.3.2" description = "Unicode to ASCII transliteration" +category = "dev" optional = false python-versions = ">=3.3" files = [ @@ -26,6 +28,7 @@ files = [ name = "anyio" version = "3.7.1" description = "High level compatibility layer for multiple asynchronous event loop implementations" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -47,6 +50,7 @@ trio = ["trio (<0.22)"] name = "appnope" version = "0.1.3" description = "Disable App Nap on macOS >= 10.9" +category = "dev" optional = false python-versions = "*" files = [ @@ -58,6 +62,7 @@ files = [ name = "argon2-cffi" version = "21.3.0" description = "The secure Argon2 password hashing algorithm." +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -77,6 +82,7 @@ tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pytest"] name = "argon2-cffi-bindings" version = "21.2.0" description = "Low-level CFFI bindings for Argon2" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -114,6 +120,7 @@ tests = ["pytest"] name = "arrow" version = "1.2.3" description = "Better dates & times for Python" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -128,6 +135,7 @@ python-dateutil = ">=2.7.0" name = "astroid" version = "2.15.6" description = "An abstract syntax tree for Python with inference support." +category = "dev" optional = false python-versions = ">=3.7.2" files = [ @@ -147,6 +155,7 @@ wrapt = [ name = "asttokens" version = "2.2.1" description = "Annotate AST trees with source code positions" +category = "dev" optional = false python-versions = "*" files = [ @@ -164,6 +173,7 @@ test = ["astroid", "pytest"] name = "astunparse" version = "1.6.3" description = "An AST unparser for Python" +category = "dev" optional = false python-versions = "*" files = [ @@ -179,6 +189,7 @@ wheel = ">=0.23.0,<1.0" name = "async-lru" version = "2.0.3" description = "Simple LRU cache for asyncio" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -193,6 +204,7 @@ typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.11\""} name = "attrs" version = "23.1.0" description = "Classes Without Boilerplate" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -211,6 +223,7 @@ tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pyte name = "autopep8" version = "2.0.2" description = "A tool that automatically formats Python code to conform to the PEP 8 style guide" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -226,6 +239,7 @@ tomli = {version = "*", markers = "python_version < \"3.11\""} name = "babel" version = "2.12.1" description = "Internationalization utilities" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -240,6 +254,7 @@ pytz = {version = ">=2015.7", markers = "python_version < \"3.9\""} name = "backcall" version = "0.2.0" description = "Specifications for callback functions passed in to an API" +category = "dev" optional = false python-versions = "*" files = [ @@ -251,6 +266,7 @@ files = [ name = "beautifulsoup4" version = "4.12.2" description = "Screen-scraping library" +category = "dev" optional = false python-versions = ">=3.6.0" files = [ @@ -269,6 +285,7 @@ lxml = ["lxml"] name = "black" version = "23.7.0" description = "The uncompromising code formatter." +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -315,6 +332,7 @@ uvloop = ["uvloop (>=0.15.2)"] name = "bleach" version = "6.0.0" description = "An easy safelist-based HTML-sanitizing tool." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -333,6 +351,7 @@ css = ["tinycss2 (>=1.1.0,<1.2)"] name = "cachetools" version = "5.3.1" description = "Extensible memoizing collections and decorators" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -344,6 +363,7 @@ files = [ name = "certifi" version = "2023.5.7" description = "Python package for providing Mozilla's CA Bundle." +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -355,6 +375,7 @@ files = [ name = "cffi" version = "1.15.1" description = "Foreign Function Interface for Python calling C code." +category = "dev" optional = false python-versions = "*" files = [ @@ -431,6 +452,7 @@ pycparser = "*" name = "chardet" version = "5.1.0" description = "Universal encoding detector for Python 3" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -442,6 +464,7 @@ files = [ name = "charset-normalizer" version = "3.2.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "dev" optional = false python-versions = ">=3.7.0" files = [ @@ -526,6 +549,7 @@ files = [ name = "click" version = "8.1.6" description = "Composable command line interface toolkit" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -540,6 +564,7 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""} name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." +category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ @@ -551,6 +576,7 @@ files = [ name = "comm" version = "0.1.3" description = "Jupyter Python Comm implementation, for usage in ipykernel, xeus-python etc." +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -570,6 +596,7 @@ typing = ["mypy (>=0.990)"] name = "coverage" version = "7.2.7" description = "Code coverage measurement for Python" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -642,6 +669,7 @@ toml = ["tomli"] name = "debugpy" version = "1.6.7" description = "An implementation of the Debug Adapter Protocol for Python" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -669,6 +697,7 @@ files = [ name = "decorator" version = "5.1.1" description = "Decorators for Humans" +category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -680,6 +709,7 @@ files = [ name = "defusedxml" version = "0.7.1" description = "XML bomb protection for Python stdlib modules" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -691,6 +721,7 @@ files = [ name = "distlib" version = "0.3.7" description = "Distribution utilities" +category = "dev" optional = false python-versions = "*" files = [ @@ -702,6 +733,7 @@ files = [ name = "docutils" version = "0.18.1" description = "Docutils -- Python Documentation Utilities" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -713,6 +745,7 @@ files = [ name = "exceptiongroup" version = "1.1.2" description = "Backport of PEP 654 (exception groups)" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -727,6 +760,7 @@ test = ["pytest (>=6)"] name = "executing" version = "1.2.0" description = "Get the currently executing AST node of a frame, and other information" +category = "dev" optional = false python-versions = "*" files = [ @@ -741,6 +775,7 @@ tests = ["asttokens", "littleutils", "pytest", "rich"] name = "fastjsonschema" version = "2.17.1" description = "Fastest Python implementation of JSON schema" +category = "dev" optional = false python-versions = "*" files = [ @@ -755,6 +790,7 @@ devel = ["colorama", "json-spec", "jsonschema", "pylint", "pytest", "pytest-benc name = "filelock" version = "3.12.2" description = "A platform independent file lock." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -770,6 +806,7 @@ testing = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "diff-cover (>=7.5)", "p name = "flake8" version = "6.0.0" description = "the modular source code checker: pep8 pyflakes and co" +category = "dev" optional = false python-versions = ">=3.8.1" files = [ @@ -786,6 +823,7 @@ pyflakes = ">=3.0.0,<3.1.0" name = "fqdn" version = "1.5.1" description = "Validates fully-qualified domain names against RFC 1123, so that they are acceptable to modern bowsers" +category = "dev" optional = false python-versions = ">=2.7, !=3.0, !=3.1, !=3.2, !=3.3, !=3.4, <4" files = [ @@ -797,6 +835,7 @@ files = [ name = "googleapis-common-protos" version = "1.59.1" description = "Common protobufs used in Google APIs" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -814,6 +853,7 @@ grpc = ["grpcio (>=1.44.0,<2.0.0.dev0)"] name = "greenlet" version = "2.0.2" description = "Lightweight in-process concurrent programming" +category = "dev" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" files = [ @@ -887,6 +927,7 @@ test = ["objgraph", "psutil"] name = "grpclib" version = "0.4.5" description = "Pure-Python gRPC implementation for asyncio" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -904,6 +945,7 @@ protobuf = ["protobuf (>=3.15.0)"] name = "h2" version = "4.1.0" description = "HTTP/2 State-Machine based protocol implementation" +category = "main" optional = false python-versions = ">=3.6.1" files = [ @@ -919,6 +961,7 @@ hyperframe = ">=6.0,<7" name = "hpack" version = "4.0.0" description = "Pure-Python HPACK header compression" +category = "main" optional = false python-versions = ">=3.6.1" files = [ @@ -930,6 +973,7 @@ files = [ name = "hyperframe" version = "6.0.1" description = "HTTP/2 framing layer for Python" +category = "main" optional = false python-versions = ">=3.6.1" files = [ @@ -941,6 +985,7 @@ files = [ name = "idna" version = "3.4" description = "Internationalized Domain Names in Applications (IDNA)" +category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -952,6 +997,7 @@ files = [ name = "imagesize" version = "1.4.1" description = "Getting image size from png/jpeg/jpeg2000/gif file" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -963,6 +1009,7 @@ files = [ name = "importlib-metadata" version = "6.8.0" description = "Read metadata from Python packages" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -982,6 +1029,7 @@ testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs name = "importlib-resources" version = "6.0.0" description = "Read resources from Python packages" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1000,6 +1048,7 @@ testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1011,6 +1060,7 @@ files = [ name = "ipykernel" version = "6.24.0" description = "IPython Kernel for Jupyter" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1024,7 +1074,7 @@ comm = ">=0.1.1" debugpy = ">=1.6.5" ipython = ">=7.23.1" jupyter-client = ">=6.1.12" -jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" +jupyter-core = ">=4.12,<5.0.0 || >=5.1.0" matplotlib-inline = ">=0.1" nest-asyncio = "*" packaging = "*" @@ -1044,6 +1094,7 @@ test = ["flaky", "ipyparallel", "pre-commit", "pytest (>=7.0)", "pytest-asyncio" name = "ipython" version = "8.12.2" description = "IPython: Productive Interactive Computing" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1083,6 +1134,7 @@ test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.21)", "pa name = "ipython-genutils" version = "0.2.0" description = "Vestigial utilities from IPython" +category = "dev" optional = false python-versions = "*" files = [ @@ -1094,6 +1146,7 @@ files = [ name = "ipywidgets" version = "8.0.7" description = "Jupyter interactive widgets" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1115,6 +1168,7 @@ test = ["ipykernel", "jsonschema", "pytest (>=3.6.0)", "pytest-cov", "pytz"] name = "isoduration" version = "20.11.0" description = "Operations with ISO 8601 durations" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1129,6 +1183,7 @@ arrow = ">=0.15.0" name = "isort" version = "5.12.0" description = "A Python utility / library to sort Python imports." +category = "dev" optional = false python-versions = ">=3.8.0" files = [ @@ -1146,6 +1201,7 @@ requirements-deprecated-finder = ["pip-api", "pipreqs"] name = "jedi" version = "0.18.2" description = "An autocompletion tool for Python that can be used for text editors." +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1165,6 +1221,7 @@ testing = ["Django (<3.1)", "attrs", "colorama", "docopt", "pytest (<7.0.0)"] name = "jinja2" version = "3.1.2" description = "A very fast and expressive template engine." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1182,6 +1239,7 @@ i18n = ["Babel (>=2.7)"] name = "json5" version = "0.9.14" description = "A Python implementation of the JSON5 data format." +category = "dev" optional = false python-versions = "*" files = [ @@ -1196,6 +1254,7 @@ dev = ["hypothesis"] name = "jsonpointer" version = "2.4" description = "Identify specific nodes in a JSON document (RFC 6901)" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" files = [ @@ -1207,6 +1266,7 @@ files = [ name = "jsonschema" version = "4.18.4" description = "An implementation of JSON Schema validation for Python" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1238,6 +1298,7 @@ format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339- name = "jsonschema-specifications" version = "2023.7.1" description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1253,6 +1314,7 @@ referencing = ">=0.28.0" name = "jupyter" version = "1.0.0" description = "Jupyter metapackage. Install all the Jupyter components in one go." +category = "dev" optional = false python-versions = "*" files = [ @@ -1273,6 +1335,7 @@ qtconsole = "*" name = "jupyter-cache" version = "0.6.1" description = "A defined interface for working with a cache of jupyter notebooks." +category = "dev" optional = false python-versions = "~=3.8" files = [ @@ -1300,6 +1363,7 @@ testing = ["coverage", "ipykernel", "jupytext", "matplotlib", "nbdime", "nbforma name = "jupyter-client" version = "8.3.0" description = "Jupyter protocol implementation and client libraries" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1309,7 +1373,7 @@ files = [ [package.dependencies] importlib-metadata = {version = ">=4.8.3", markers = "python_version < \"3.10\""} -jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" +jupyter-core = ">=4.12,<5.0.0 || >=5.1.0" python-dateutil = ">=2.8.2" pyzmq = ">=23.0" tornado = ">=6.2" @@ -1323,6 +1387,7 @@ test = ["coverage", "ipykernel (>=6.14)", "mypy", "paramiko", "pre-commit", "pyt name = "jupyter-console" version = "6.6.3" description = "Jupyter terminal console" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1334,7 +1399,7 @@ files = [ ipykernel = ">=6.14" ipython = "*" jupyter-client = ">=7.0.0" -jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" +jupyter-core = ">=4.12,<5.0.0 || >=5.1.0" prompt-toolkit = ">=3.0.30" pygments = "*" pyzmq = ">=17" @@ -1347,6 +1412,7 @@ test = ["flaky", "pexpect", "pytest"] name = "jupyter-core" version = "5.3.1" description = "Jupyter core package. A base package on which Jupyter projects rely." +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1367,6 +1433,7 @@ test = ["ipykernel", "pre-commit", "pytest", "pytest-cov", "pytest-timeout"] name = "jupyter-events" version = "0.6.3" description = "Jupyter Event System library" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1391,6 +1458,7 @@ test = ["click", "coverage", "pre-commit", "pytest (>=7.0)", "pytest-asyncio (>= name = "jupyter-lsp" version = "2.2.0" description = "Multi-Language Server WebSocket proxy for Jupyter Notebook/Lab server" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1406,6 +1474,7 @@ jupyter-server = ">=1.1.2" name = "jupyter-server" version = "2.7.0" description = "The backend—i.e. core services, APIs, and REST endpoints—to Jupyter web applications." +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1418,7 +1487,7 @@ anyio = ">=3.1.0" argon2-cffi = "*" jinja2 = "*" jupyter-client = ">=7.4.4" -jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" +jupyter-core = ">=4.12,<5.0.0 || >=5.1.0" jupyter-events = ">=0.6.0" jupyter-server-terminals = "*" nbconvert = ">=6.4.4" @@ -1442,6 +1511,7 @@ test = ["flaky", "ipykernel", "pre-commit", "pytest (>=7.0)", "pytest-console-sc name = "jupyter-server-terminals" version = "0.4.4" description = "A Jupyter Server Extension Providing Terminals." +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1461,6 +1531,7 @@ test = ["coverage", "jupyter-server (>=2.0.0)", "pytest (>=7.0)", "pytest-cov", name = "jupyterlab" version = "4.0.3" description = "JupyterLab computational environment" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1494,6 +1565,7 @@ test = ["coverage", "pytest (>=7.0)", "pytest-check-links (>=0.7)", "pytest-cons name = "jupyterlab-pygments" version = "0.2.2" description = "Pygments theme using JupyterLab CSS variables" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1505,6 +1577,7 @@ files = [ name = "jupyterlab-server" version = "2.23.0" description = "A set of server components for JupyterLab and JupyterLab like applications." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1531,6 +1604,7 @@ test = ["hatch", "ipykernel", "jupyterlab-server[openapi]", "openapi-spec-valida name = "jupyterlab-widgets" version = "3.0.8" description = "Jupyter interactive widgets for JupyterLab" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1542,6 +1616,7 @@ files = [ name = "lazy-object-proxy" version = "1.9.0" description = "A fast and thorough lazy object proxy." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1587,6 +1662,7 @@ files = [ name = "markdown-it-py" version = "2.2.0" description = "Python port of markdown-it. Markdown parsing, done right!" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1611,6 +1687,7 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] name = "markupsafe" version = "2.1.3" description = "Safely add untrusted strings to HTML/XML markup." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1670,6 +1747,7 @@ files = [ name = "matplotlib-inline" version = "0.1.6" description = "Inline Matplotlib backend for Jupyter" +category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -1684,6 +1762,7 @@ traitlets = "*" name = "mccabe" version = "0.7.0" description = "McCabe checker, plugin for flake8" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1695,6 +1774,7 @@ files = [ name = "mdit-py-plugins" version = "0.3.5" description = "Collection of plugins for markdown-it-py" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1714,6 +1794,7 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] name = "mdurl" version = "0.1.2" description = "Markdown URL utilities" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1725,6 +1806,7 @@ files = [ name = "mistune" version = "3.0.1" description = "A sane and fast Markdown parser with useful plugins and renderers" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1736,6 +1818,7 @@ files = [ name = "multidict" version = "6.0.4" description = "multidict implementation" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1819,6 +1902,7 @@ files = [ name = "mypy-extensions" version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." +category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -1830,6 +1914,7 @@ files = [ name = "mypy-protobuf" version = "3.4.0" description = "Generate mypy stub files from protobuf specs" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1845,6 +1930,7 @@ types-protobuf = ">=3.20.4" name = "myst-nb" version = "0.17.2" description = "A Jupyter Notebook Sphinx reader built on top of the MyST markdown parser." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1873,6 +1959,7 @@ testing = ["beautifulsoup4", "coverage (>=6.4,<8.0)", "ipykernel (>=5.5,<6.0)", name = "myst-parser" version = "0.18.1" description = "An extended commonmark compliant parser, with bridges to docutils & sphinx." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1899,6 +1986,7 @@ testing = ["beautifulsoup4", "coverage[toml]", "pytest (>=6,<7)", "pytest-cov", name = "nbclient" version = "0.6.8" description = "A client library for executing notebooks. Formerly nbconvert's ExecutePreprocessor." +category = "dev" optional = false python-versions = ">=3.7.0" files = [ @@ -1920,6 +2008,7 @@ test = ["black", "check-manifest", "flake8", "ipykernel", "ipython", "ipywidgets name = "nbconvert" version = "7.7.2" description = "Converting Jupyter Notebooks" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1958,6 +2047,7 @@ webpdf = ["playwright"] name = "nbformat" version = "5.9.1" description = "The Jupyter Notebook format" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1979,6 +2069,7 @@ test = ["pep440", "pre-commit", "pytest", "testpath"] name = "nbmake" version = "1.4.1" description = "Pytest plugin for testing notebooks" +category = "dev" optional = false python-versions = ">=3.7.0,<4.0.0" files = [ @@ -1998,6 +2089,7 @@ pytest = ">=6.1.0" name = "nest-asyncio" version = "1.5.6" description = "Patch asyncio to allow nested event loops" +category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -2009,6 +2101,7 @@ files = [ name = "notebook" version = "7.0.0" description = "Jupyter Notebook - A web-based notebook environment for interactive computing" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2033,6 +2126,7 @@ test = ["ipykernel", "jupyter-server[test] (>=2.4.0,<3)", "jupyterlab-server[tes name = "notebook-shim" version = "0.2.3" description = "A shim layer for notebook traits and config" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2050,6 +2144,7 @@ test = ["pytest", "pytest-console-scripts", "pytest-jupyter", "pytest-tornasync" name = "overrides" version = "7.3.1" description = "A decorator to automatically detect mismatch when overriding a method." +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -2061,6 +2156,7 @@ files = [ name = "packaging" version = "23.1" description = "Core utilities for Python packages" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2072,6 +2168,7 @@ files = [ name = "pandocfilters" version = "1.5.0" description = "Utilities for writing pandoc filters in python" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -2083,6 +2180,7 @@ files = [ name = "parso" version = "0.8.3" description = "A Python Parser" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -2098,6 +2196,7 @@ testing = ["docopt", "pytest (<6.0.0)"] name = "pathspec" version = "0.11.1" description = "Utility library for gitignore style pattern matching of file paths." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2109,6 +2208,7 @@ files = [ name = "pexpect" version = "4.8.0" description = "Pexpect allows easy control of interactive console applications." +category = "dev" optional = false python-versions = "*" files = [ @@ -2123,6 +2223,7 @@ ptyprocess = ">=0.5" name = "pickleshare" version = "0.7.5" description = "Tiny 'shelve'-like database with concurrency support" +category = "dev" optional = false python-versions = "*" files = [ @@ -2134,6 +2235,7 @@ files = [ name = "pillow" version = "10.0.0" description = "Python Imaging Library (Fork)" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -2203,6 +2305,7 @@ tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "pa name = "pkgutil-resolve-name" version = "1.3.10" description = "Resolve a name to an object." +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -2214,6 +2317,7 @@ files = [ name = "platformdirs" version = "3.9.1" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2229,6 +2333,7 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.3.1)", "pytest- name = "pluggy" version = "1.2.0" description = "plugin and hook calling mechanisms for python" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2244,6 +2349,7 @@ testing = ["pytest", "pytest-benchmark"] name = "prometheus-client" version = "0.17.1" description = "Python client for the Prometheus monitoring system." +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -2258,6 +2364,7 @@ twisted = ["twisted"] name = "prompt-toolkit" version = "3.0.39" description = "Library for building powerful interactive command lines in Python" +category = "dev" optional = false python-versions = ">=3.7.0" files = [ @@ -2272,6 +2379,7 @@ wcwidth = "*" name = "protobuf" version = "4.23.4" description = "" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2294,6 +2402,7 @@ files = [ name = "protoletariat" version = "3.2.13" description = "Python protocol buffers for the rest of us" +category = "dev" optional = false python-versions = ">=3.8,<4.0" files = [ @@ -2313,6 +2422,7 @@ grpcio-tools = ["grpcio-tools (>=1.42.0,<2)"] name = "psutil" version = "5.9.5" description = "Cross-platform lib for process and system monitoring in Python." +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -2339,6 +2449,7 @@ test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] name = "ptyprocess" version = "0.7.0" description = "Run a subprocess in a pseudo terminal" +category = "dev" optional = false python-versions = "*" files = [ @@ -2350,6 +2461,7 @@ files = [ name = "pure-eval" version = "0.2.2" description = "Safely evaluate AST nodes without side effects" +category = "dev" optional = false python-versions = "*" files = [ @@ -2364,6 +2476,7 @@ tests = ["pytest"] name = "pycodestyle" version = "2.10.0" description = "Python style guide checker" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -2375,6 +2488,7 @@ files = [ name = "pycparser" version = "2.21" description = "C parser in Python" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -2386,6 +2500,7 @@ files = [ name = "pydantic" version = "1.10.11" description = "Data validation and settings management using python type hints" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2438,6 +2553,7 @@ email = ["email-validator (>=1.0.3)"] name = "pyflakes" version = "3.0.1" description = "passive checker of Python programs" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -2449,6 +2565,7 @@ files = [ name = "pygments" version = "2.15.1" description = "Pygments is a syntax highlighting package written in Python." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2463,6 +2580,7 @@ plugins = ["importlib-metadata"] name = "pyproject-api" version = "1.5.3" description = "API to interact with the python pyproject.toml based projects" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2482,6 +2600,7 @@ testing = ["covdefaults (>=2.3)", "importlib-metadata (>=6.6)", "pytest (>=7.3.1 name = "pytest" version = "7.4.0" description = "pytest: simple powerful testing with Python" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2504,6 +2623,7 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no name = "pytest-asyncio" version = "0.21.1" description = "Pytest support for asyncio" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2518,10 +2638,27 @@ pytest = ">=7.0.0" docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy (>=0.931)", "pytest-trio (>=0.7.0)"] +[[package]] +name = "pytest-watcher" +version = "0.3.4" +description = "Automatically rerun your tests on file modifications" +category = "dev" +optional = false +python-versions = ">=3.7.0,<4.0.0" +files = [ + {file = "pytest_watcher-0.3.4-py3-none-any.whl", hash = "sha256:edd2bd9c8a1fb14d48c9f4947234065eb9b4c1acedc0bf213b1f12501dfcffd3"}, + {file = "pytest_watcher-0.3.4.tar.gz", hash = "sha256:d39491ba15b589221bb9a78ef4bed3d5d1503aed08209b1a138aeb95b9117a18"}, +] + +[package.dependencies] +tomli = {version = ">=2.0.1,<3.0.0", markers = "python_version < \"3.11\""} +watchdog = ">=2.0.0" + [[package]] name = "python-dateutil" version = "2.8.2" description = "Extensions to the standard Python datetime module" +category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ @@ -2536,6 +2673,7 @@ six = ">=1.5" name = "python-json-logger" version = "2.0.7" description = "A python library adding a json log formatter" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -2547,6 +2685,7 @@ files = [ name = "pytz" version = "2023.3" description = "World timezone definitions, modern and historical" +category = "dev" optional = false python-versions = "*" files = [ @@ -2558,6 +2697,7 @@ files = [ name = "pywin32" version = "306" description = "Python for Window Extensions" +category = "dev" optional = false python-versions = "*" files = [ @@ -2581,6 +2721,7 @@ files = [ name = "pywinpty" version = "2.0.11" description = "Pseudo terminal support for Windows from Python." +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2595,6 +2736,7 @@ files = [ name = "pyyaml" version = "6.0.1" description = "YAML parser and emitter for Python" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -2644,6 +2786,7 @@ files = [ name = "pyzmq" version = "25.1.0" description = "Python bindings for 0MQ" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -2733,6 +2876,7 @@ cffi = {version = "*", markers = "implementation_name == \"pypy\""} name = "qtconsole" version = "5.4.3" description = "Jupyter Qt console" +category = "dev" optional = false python-versions = ">= 3.7" files = [ @@ -2759,6 +2903,7 @@ test = ["flaky", "pytest", "pytest-qt"] name = "qtpy" version = "2.3.1" description = "Provides an abstraction layer on top of the various Qt bindings (PyQt5/6 and PySide2/6)." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2776,6 +2921,7 @@ test = ["pytest (>=6,!=7.0.0,!=7.0.1)", "pytest-cov (>=3.0.0)", "pytest-qt"] name = "referencing" version = "0.30.0" description = "JSON Referencing + Python" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2791,6 +2937,7 @@ rpds-py = ">=0.7.0" name = "requests" version = "2.31.0" description = "Python HTTP for Humans." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2812,6 +2959,7 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] name = "rfc3339-validator" version = "0.1.4" description = "A pure python RFC3339 validator" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -2826,6 +2974,7 @@ six = "*" name = "rfc3986-validator" version = "0.1.1" description = "Pure python rfc3986 validator" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -2837,6 +2986,7 @@ files = [ name = "rpds-py" version = "0.9.2" description = "Python bindings to Rust's persistent data structures (rpds)" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2943,6 +3093,7 @@ files = [ name = "send2trash" version = "1.8.2" description = "Send file to trash natively under Mac OS X, Windows and Linux" +category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" files = [ @@ -2959,6 +3110,7 @@ win32 = ["pywin32"] name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -2970,6 +3122,7 @@ files = [ name = "sniffio" version = "1.3.0" description = "Sniff out which async library your code is running under" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2981,6 +3134,7 @@ files = [ name = "snowballstemmer" version = "2.2.0" description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." +category = "dev" optional = false python-versions = "*" files = [ @@ -2992,6 +3146,7 @@ files = [ name = "soupsieve" version = "2.4.1" description = "A modern CSS selector implementation for Beautiful Soup." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3003,6 +3158,7 @@ files = [ name = "sphinx" version = "5.3.0" description = "Python documentation generator" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -3038,6 +3194,7 @@ test = ["cython", "html5lib", "pytest (>=4.6)", "typed_ast"] name = "sphinx-autoapi" version = "2.1.1" description = "Sphinx API documentation generator" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3061,6 +3218,7 @@ go = ["sphinxcontrib-golangdomain"] name = "sphinx-rtd-theme" version = "1.2.2" description = "Read the Docs theme for Sphinx" +category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ @@ -3080,6 +3238,7 @@ dev = ["bump2version", "sphinxcontrib-httpdomain", "transifex-client", "wheel"] name = "sphinxcontrib-applehelp" version = "1.0.4" description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -3095,6 +3254,7 @@ test = ["pytest"] name = "sphinxcontrib-devhelp" version = "1.0.2" description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." +category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -3110,6 +3270,7 @@ test = ["pytest"] name = "sphinxcontrib-htmlhelp" version = "2.0.1" description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -3125,6 +3286,7 @@ test = ["html5lib", "pytest"] name = "sphinxcontrib-jquery" version = "4.1" description = "Extension to include jQuery on newer Sphinx releases" +category = "dev" optional = false python-versions = ">=2.7" files = [ @@ -3139,6 +3301,7 @@ Sphinx = ">=1.8" name = "sphinxcontrib-jsmath" version = "1.0.1" description = "A sphinx extension which renders display math in HTML via JavaScript" +category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -3153,6 +3316,7 @@ test = ["flake8", "mypy", "pytest"] name = "sphinxcontrib-qthelp" version = "1.0.3" description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." +category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -3168,6 +3332,7 @@ test = ["pytest"] name = "sphinxcontrib-serializinghtml" version = "1.1.5" description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." +category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -3183,6 +3348,7 @@ test = ["pytest"] name = "sqlalchemy" version = "2.0.19" description = "Database Abstraction Library" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3230,7 +3396,7 @@ files = [ ] [package.dependencies] -greenlet = {version = "!=0.4.17", markers = "platform_machine == \"win32\" or platform_machine == \"WIN32\" or platform_machine == \"AMD64\" or platform_machine == \"amd64\" or platform_machine == \"x86_64\" or platform_machine == \"ppc64le\" or platform_machine == \"aarch64\""} +greenlet = {version = "!=0.4.17", markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\""} typing-extensions = ">=4.2.0" [package.extras] @@ -3261,6 +3427,7 @@ sqlcipher = ["sqlcipher3-binary"] name = "stack-data" version = "0.6.2" description = "Extract data from python stack frames and tracebacks for informative displays" +category = "dev" optional = false python-versions = "*" files = [ @@ -3280,6 +3447,7 @@ tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] name = "tabulate" version = "0.9.0" description = "Pretty-print tabular data" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3294,6 +3462,7 @@ widechars = ["wcwidth"] name = "terminado" version = "0.17.1" description = "Tornado websocket backend for the Xterm.js Javascript terminal emulator library." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3314,6 +3483,7 @@ test = ["pre-commit", "pytest (>=7.0)", "pytest-timeout"] name = "tinycss2" version = "1.2.1" description = "A tiny CSS parser" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3332,6 +3502,7 @@ test = ["flake8", "isort", "pytest"] name = "tomli" version = "2.0.1" description = "A lil' TOML parser" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3343,6 +3514,7 @@ files = [ name = "tornado" version = "6.3.2" description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." +category = "dev" optional = false python-versions = ">= 3.8" files = [ @@ -3363,6 +3535,7 @@ files = [ name = "tox" version = "4.6.4" description = "tox is a generic virtualenv management and test command line tool" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3390,6 +3563,7 @@ testing = ["build[virtualenv] (>=0.10)", "covdefaults (>=2.3)", "detect-test-pol name = "traitlets" version = "5.9.0" description = "Traitlets Python configuration system" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3405,6 +3579,7 @@ test = ["argcomplete (>=2.0)", "pre-commit", "pytest", "pytest-mock"] name = "types-pillow" version = "10.0.0.1" description = "Typing stubs for Pillow" +category = "dev" optional = false python-versions = "*" files = [ @@ -3416,6 +3591,7 @@ files = [ name = "types-protobuf" version = "4.23.0.1" description = "Typing stubs for protobuf" +category = "dev" optional = false python-versions = "*" files = [ @@ -3427,6 +3603,7 @@ files = [ name = "typing-extensions" version = "4.7.1" description = "Backported and Experimental Type Hints for Python 3.7+" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3438,6 +3615,7 @@ files = [ name = "uri-template" version = "1.3.0" description = "RFC 6570 URI Template Processor" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3452,6 +3630,7 @@ dev = ["flake8", "flake8-annotations", "flake8-bandit", "flake8-bugbear", "flake name = "urllib3" version = "2.0.4" description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3469,6 +3648,7 @@ zstd = ["zstandard (>=0.18.0)"] name = "virtualenv" version = "20.24.0" description = "Virtual Python Environment builder" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3485,10 +3665,51 @@ platformdirs = ">=3.5.1,<4" docs = ["furo (>=2023.5.20)", "proselint (>=0.13)", "sphinx (>=7.0.1)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.3.1)", "pytest-env (>=0.8.1)", "pytest-freezer (>=0.4.6)", "pytest-mock (>=3.10)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=67.8)", "time-machine (>=2.9)"] +[[package]] +name = "watchdog" +version = "3.0.0" +description = "Filesystem events monitoring" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "watchdog-3.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:336adfc6f5cc4e037d52db31194f7581ff744b67382eb6021c868322e32eef41"}, + {file = "watchdog-3.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a70a8dcde91be523c35b2bf96196edc5730edb347e374c7de7cd20c43ed95397"}, + {file = "watchdog-3.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:adfdeab2da79ea2f76f87eb42a3ab1966a5313e5a69a0213a3cc06ef692b0e96"}, + {file = "watchdog-3.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2b57a1e730af3156d13b7fdddfc23dea6487fceca29fc75c5a868beed29177ae"}, + {file = "watchdog-3.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7ade88d0d778b1b222adebcc0927428f883db07017618a5e684fd03b83342bd9"}, + {file = "watchdog-3.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7e447d172af52ad204d19982739aa2346245cc5ba6f579d16dac4bfec226d2e7"}, + {file = "watchdog-3.0.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9fac43a7466eb73e64a9940ac9ed6369baa39b3bf221ae23493a9ec4d0022674"}, + {file = "watchdog-3.0.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8ae9cda41fa114e28faf86cb137d751a17ffd0316d1c34ccf2235e8a84365c7f"}, + {file = "watchdog-3.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:25f70b4aa53bd743729c7475d7ec41093a580528b100e9a8c5b5efe8899592fc"}, + {file = "watchdog-3.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4f94069eb16657d2c6faada4624c39464f65c05606af50bb7902e036e3219be3"}, + {file = "watchdog-3.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7c5f84b5194c24dd573fa6472685b2a27cc5a17fe5f7b6fd40345378ca6812e3"}, + {file = "watchdog-3.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3aa7f6a12e831ddfe78cdd4f8996af9cf334fd6346531b16cec61c3b3c0d8da0"}, + {file = "watchdog-3.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:233b5817932685d39a7896b1090353fc8efc1ef99c9c054e46c8002561252fb8"}, + {file = "watchdog-3.0.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:13bbbb462ee42ec3c5723e1205be8ced776f05b100e4737518c67c8325cf6100"}, + {file = "watchdog-3.0.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8f3ceecd20d71067c7fd4c9e832d4e22584318983cabc013dbf3f70ea95de346"}, + {file = "watchdog-3.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c9d8c8ec7efb887333cf71e328e39cffbf771d8f8f95d308ea4125bf5f90ba64"}, + {file = "watchdog-3.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:0e06ab8858a76e1219e68c7573dfeba9dd1c0219476c5a44d5333b01d7e1743a"}, + {file = "watchdog-3.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:d00e6be486affb5781468457b21a6cbe848c33ef43f9ea4a73b4882e5f188a44"}, + {file = "watchdog-3.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:c07253088265c363d1ddf4b3cdb808d59a0468ecd017770ed716991620b8f77a"}, + {file = "watchdog-3.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:5113334cf8cf0ac8cd45e1f8309a603291b614191c9add34d33075727a967709"}, + {file = "watchdog-3.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:51f90f73b4697bac9c9a78394c3acbbd331ccd3655c11be1a15ae6fe289a8c83"}, + {file = "watchdog-3.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:ba07e92756c97e3aca0912b5cbc4e5ad802f4557212788e72a72a47ff376950d"}, + {file = "watchdog-3.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:d429c2430c93b7903914e4db9a966c7f2b068dd2ebdd2fa9b9ce094c7d459f33"}, + {file = "watchdog-3.0.0-py3-none-win32.whl", hash = "sha256:3ed7c71a9dccfe838c2f0b6314ed0d9b22e77d268c67e015450a29036a81f60f"}, + {file = "watchdog-3.0.0-py3-none-win_amd64.whl", hash = "sha256:4c9956d27be0bb08fc5f30d9d0179a855436e655f046d288e2bcc11adfae893c"}, + {file = "watchdog-3.0.0-py3-none-win_ia64.whl", hash = "sha256:5d9f3a10e02d7371cd929b5d8f11e87d4bad890212ed3901f9b4d68767bee759"}, + {file = "watchdog-3.0.0.tar.gz", hash = "sha256:4d98a320595da7a7c5a18fc48cb633c2e73cda78f93cac2ef42d42bf609a33f9"}, +] + +[package.extras] +watchmedo = ["PyYAML (>=3.10)"] + [[package]] name = "wcwidth" version = "0.2.6" description = "Measures the displayed width of unicode strings in a terminal" +category = "dev" optional = false python-versions = "*" files = [ @@ -3500,6 +3721,7 @@ files = [ name = "webcolors" version = "1.13" description = "A library for working with the color formats defined by HTML and CSS." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3515,6 +3737,7 @@ tests = ["pytest", "pytest-cov"] name = "webencodings" version = "0.5.1" description = "Character encoding aliases for legacy web content" +category = "dev" optional = false python-versions = "*" files = [ @@ -3526,6 +3749,7 @@ files = [ name = "websocket-client" version = "1.6.1" description = "WebSocket client for Python with low level API options" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3542,6 +3766,7 @@ test = ["websockets"] name = "wheel" version = "0.40.0" description = "A built-package format for Python" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3556,6 +3781,7 @@ test = ["pytest (>=6.0.0)"] name = "widgetsnbextension" version = "4.0.8" description = "Jupyter interactive widgets for Jupyter Notebook" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -3567,6 +3793,7 @@ files = [ name = "wrapt" version = "1.15.0" description = "Module for decorators, wrappers and monkey patching." +category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" files = [ @@ -3651,6 +3878,7 @@ files = [ name = "zipp" version = "3.16.2" description = "Backport of pathlib-compatible object wrapper for zip files" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -3664,5 +3892,5 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" -python-versions = ">=3.8.1,<3.12" -content-hash = "263b8b77aef499c8babd6e5e267e9c9da6d4dffe5430a67ce42623f0f39c87f2" + python-versions = ">=3.8.1,<3.12" +content-hash = "a6960394b176df439233616f7e53e019dcc8a206c664694614e1b2976dede778" diff --git a/pyproject.toml b/pyproject.toml index c83deb9f3..58fdfd30b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,6 +35,7 @@ include = ["LICENSE", "src/viam/rpc/libviam_rust_utils.*"] mypy-protobuf = "^3.4.0" tox = "^4.6.4" isort = "^5.12.0" + pytest-watcher = "^0.3.4" [tool.pytest.ini_options] addopts = "-ra" From 06621902e1d893814251570104bad5f20ab14321 Mon Sep 17 00:00:00 2001 From: Maxim Pertsov Date: Wed, 19 Jul 2023 18:06:41 -0400 Subject: [PATCH 7/9] test --- src/viam/services/navigation/client.py | 14 +- tests/mocks/services.py | 49 ++++- tests/test_navigation.py | 239 +++++++++++++++++++++++++ 3 files changed, 294 insertions(+), 8 deletions(-) create mode 100644 tests/test_navigation.py diff --git a/src/viam/services/navigation/client.py b/src/viam/services/navigation/client.py index 692569916..e8b9f11ed 100644 --- a/src/viam/services/navigation/client.py +++ b/src/viam/services/navigation/client.py @@ -39,35 +39,35 @@ def __init__(self, name: str, channel: Channel): self.client = NavigationServiceStub(channel) super().__init__(name) - async def get_location(self, *, timeout: Optional[float]) -> GeoPoint: + async def get_location(self, *, timeout: Optional[float] = None) -> GeoPoint: request = GetLocationRequest(name=self.name) response: GetLocationResponse = await self.client.GetLocation(request, timeout=timeout) return response.location - async def get_obstacles(self, *, timeout: Optional[float]) -> List[GeoObstacle]: + async def get_obstacles(self, *, timeout: Optional[float] = None) -> List[GeoObstacle]: request = GetObstaclesRequest(name=self.name) response: GetObstaclesResponse = await self.client.GetObstacles(request, timeout=timeout) return list(response.obstacles) - async def get_waypoints(self, *, timeout: Optional[float]) -> List[Waypoint]: + async def get_waypoints(self, *, timeout: Optional[float] = None) -> List[Waypoint]: request = GetWaypointsRequest(name=self.name) response: GetWaypointsResponse = await self.client.GetWaypoints(request, timeout=timeout) return list(response.waypoints) - async def add_waypoint(self, point: GeoPoint, *, timeout: Optional[float]): + async def add_waypoint(self, point: GeoPoint, *, timeout: Optional[float] = None): request = AddWaypointRequest(name=self.name, location=point) await self.client.AddWaypoint(request, timeout=timeout) - async def remove_waypoint(self, id: str, *, timeout: Optional[float]): + async def remove_waypoint(self, id: str, *, timeout: Optional[float] = None): request = RemoveWaypointRequest(name=self.name, id=id) await self.client.RemoveWaypoint(request, timeout=timeout) - async def get_mode(self, *, timeout: Optional[float]) -> Mode.ValueType: + async def get_mode(self, *, timeout: Optional[float] = None) -> Mode.ValueType: request = GetModeRequest(name=self.name) response: GetModeResponse = await self.client.GetMode(request, timeout=timeout) return response.mode - async def set_mode(self, mode: Mode.ValueType, *, timeout: Optional[float]): + async def set_mode(self, mode: Mode.ValueType, *, timeout: Optional[float] = None): request = SetModeRequest(name=self.name, mode=mode) await self.client.SetMode(request, timeout=timeout) diff --git a/tests/mocks/services.py b/tests/mocks/services.py index f3380d18c..6a49dbd69 100644 --- a/tests/mocks/services.py +++ b/tests/mocks/services.py @@ -4,7 +4,7 @@ from PIL import Image from viam.media.video import RawImage -from viam.proto.common import DoCommandRequest, DoCommandResponse, PointCloudObject, Pose, PoseInFrame, ResourceName +from viam.proto.common import DoCommandRequest, DoCommandResponse, GeoObstacle, GeoPoint, PointCloudObject, Pose, PoseInFrame, ResourceName from viam.proto.service.motion import ( Constraints, GetPoseRequest, @@ -19,6 +19,7 @@ MoveSingleComponentRequest, MoveSingleComponentResponse, ) +from viam.proto.service.navigation import Mode, Waypoint from viam.proto.service.sensors import ( GetReadingsRequest, GetReadingsResponse, @@ -29,6 +30,7 @@ ) from viam.proto.service.vision import Classification, Detection from viam.services.mlmodel import File, LabelType, Metadata, MLModel, TensorInfo +from viam.services.navigation import Navigation from viam.services.slam import SLAM from viam.services.vision import Vision from viam.utils import ValueTypes, struct_to_dict @@ -269,3 +271,48 @@ async def get_position(self, *, timeout: Optional[float] = None) -> Pose: async def do_command(self, command: Mapping[str, ValueTypes], *, timeout: Optional[float] = None, **kwargs) -> Mapping[str, ValueTypes]: return {"command": command} + + +class MockNavigation(Navigation): + LOCATION = GeoPoint(latitude=100.0, longitude=150.0) + OBSTACLES = [GeoObstacle(location=GeoPoint(latitude=200.0, longitude=250.0))] + WAYPOINTS = [Waypoint(location=GeoPoint(latitude=300.0, longitude=350.0))] + + def __init__(self, name: str): + self.name = name + self.add_waypoints: list[GeoPoint] = [] + self.remove_waypoints: list[str] = [] + self.mode = Mode.MODE_UNSPECIFIED + self.timeout: Optional[float] = None + super().__init__(name) + + async def get_location(self, *, timeout: Optional[float] = None) -> GeoPoint: + self.timeout = timeout + return self.LOCATION + + async def get_obstacles(self, *, timeout: Optional[float] = None) -> List[GeoObstacle]: + self.timeout = timeout + return self.OBSTACLES + + async def get_waypoints(self, *, timeout: Optional[float] = None) -> List[Waypoint]: + self.timeout = timeout + return self.WAYPOINTS + + async def add_waypoint(self, point: GeoPoint, *, timeout: Optional[float] = None): + self.timeout = timeout + self.add_waypoints.append(point) + + async def remove_waypoint(self, id: str, *, timeout: Optional[float] = None): + self.timeout = timeout + self.remove_waypoints.append(id) + + async def get_mode(self, *, timeout: Optional[float] = None) -> Mode.ValueType: + self.timeout = timeout + return self.mode + + async def set_mode(self, mode: Mode.ValueType, *, timeout: Optional[float] = None): + self.timeout = timeout + self.mode = mode + + async def do_command(self, command: Mapping[str, ValueTypes], *, timeout: Optional[float] = None, **kwargs) -> Mapping[str, ValueTypes]: + return {"command": command} diff --git a/tests/test_navigation.py b/tests/test_navigation.py new file mode 100644 index 000000000..eaaab3028 --- /dev/null +++ b/tests/test_navigation.py @@ -0,0 +1,239 @@ +from typing import List + +import pytest +from grpclib.testing import ChannelFor + +from viam.proto.common import DoCommandRequest, DoCommandResponse, GeoPoint +from viam.proto.service.navigation import ( + AddWaypointRequest, + AddWaypointResponse, + GetLocationRequest, + GetLocationResponse, + GetModeRequest, + GetModeResponse, + GetObstaclesRequest, + GetObstaclesResponse, + GetWaypointsRequest, + GetWaypointsResponse, + Mode, + NavigationServiceStub, + RemoveWaypointRequest, + RemoveWaypointResponse, + SetModeRequest, + SetModeResponse, + Waypoint, +) +from viam.resource.manager import ResourceManager +from viam.services.navigation import NavigationClient, NavigationRPCService +from viam.utils import dict_to_struct, struct_to_dict + +from .mocks.services import MockNavigation + + +class TestNavigationService: + name = "navigation" + navigation = MockNavigation(name="navigation") + + @pytest.mark.asyncio + async def test_get_location(self): + result = await self.navigation.get_location() + assert result == MockNavigation.LOCATION + + @pytest.mark.asyncio + async def test_get_obstacles(self): + result = await self.navigation.get_obstacles() + assert result == MockNavigation.OBSTACLES + + @pytest.mark.asyncio + async def test_get_waypoints(self): + result = await self.navigation.get_waypoints() + assert result == self.navigation.WAYPOINTS + + @pytest.mark.asyncio + async def test_add_waypoint(self): + assert self.navigation.add_waypoints == [] + point = GeoPoint(latitude=100.0, longitude=200.0) + await self.navigation.add_waypoint(point) + assert self.navigation.add_waypoints == [point] + + @pytest.mark.asyncio + async def test_remove_waypoint(self): + assert self.navigation.remove_waypoints == [] + id = "xyz" + await self.navigation.remove_waypoint(id) + assert self.navigation.remove_waypoints == [id] + + @pytest.mark.asyncio + async def test_get_mode(self): + result = await self.navigation.get_mode() + assert result == Mode.MODE_UNSPECIFIED + + @pytest.mark.asyncio + async def test_set_mode(self): + assert self.navigation.mode == Mode.MODE_UNSPECIFIED + mode = Mode.MODE_MANUAL + await self.navigation.set_mode(mode) + assert self.navigation.mode == mode + + @pytest.mark.asyncio + async def test_do(self): + command = {"command": "args"} + result = await self.navigation.do_command(command) + assert result == {"command": command} + + +class TestService: + @classmethod + def setup_class(cls): + cls.name = "navigation" + cls.navigation = MockNavigation(name=cls.name) + cls.manager = ResourceManager([cls.navigation]) + cls.service = NavigationRPCService(cls.manager) + + @pytest.mark.asyncio + async def test_get_location(self): + async with ChannelFor([self.service]) as channel: + client = NavigationServiceStub(channel) + request = GetLocationRequest(name=self.name) + response: GetLocationResponse = await client.GetLocation(request) + result = response.location + assert result == self.navigation.LOCATION + + @pytest.mark.asyncio + async def test_get_obstacles(self): + async with ChannelFor([self.service]) as channel: + client = NavigationServiceStub(channel) + request = GetObstaclesRequest(name=self.name) + response: GetObstaclesResponse = await client.GetObstacles(request) + result = response.obstacles + assert result == self.navigation.OBSTACLES + + @pytest.mark.asyncio + async def test_get_waypoints(self): + async with ChannelFor([self.service]) as channel: + client = NavigationServiceStub(channel) + request = GetWaypointsRequest(name=self.name) + response: GetWaypointsResponse = await client.GetWaypoints(request) + result = response.waypoints + assert result == self.navigation.WAYPOINTS + + @pytest.mark.asyncio + async def test_add_waypoint(self): + async with ChannelFor([self.service]) as channel: + assert self.navigation.add_waypoints == [] + client = NavigationServiceStub(channel) + point = GeoPoint(latitude=100.0, longitude=200.0) + request = AddWaypointRequest(name=self.name, location=point) + await client.AddWaypoint(request) + assert self.navigation.add_waypoints == [point] + + @pytest.mark.asyncio + async def test_remove_waypoint(self): + async with ChannelFor([self.service]) as channel: + assert self.navigation.remove_waypoints == [] + client = NavigationServiceStub(channel) + id = "xyz" + request = RemoveWaypointRequest(name=self.name, id=id) + await client.RemoveWaypoint(request) + assert self.navigation.remove_waypoints == [id] + + @pytest.mark.asyncio + async def test_get_mode(self): + async with ChannelFor([self.service]) as channel: + client = NavigationServiceStub(channel) + request = GetModeRequest(name=self.name) + response: GetModeResponse = await client.GetMode(request) + result = response.mode + assert result == Mode.MODE_UNSPECIFIED + + @pytest.mark.asyncio + async def test_set_mode(self): + async with ChannelFor([self.service]) as channel: + assert self.navigation.mode == Mode.MODE_UNSPECIFIED + client = NavigationServiceStub(channel) + mode = Mode.MODE_MANUAL + request = SetModeRequest(name=self.name, mode=mode) + await client.SetMode(request) + assert self.navigation.mode == Mode.MODE_MANUAL + + @pytest.mark.asyncio + async def test_do(self): + async with ChannelFor([self.service]) as channel: + client = NavigationServiceStub(channel) + command = {"command": "args"} + request = DoCommandRequest(name=self.name, command=dict_to_struct(command)) + response: DoCommandResponse = await client.DoCommand(request) + result = struct_to_dict(response.result) + assert result == {"command": command} + + +class TestClient: + @classmethod + def setup_class(cls): + cls.name = "navigation" + cls.navigation = MockNavigation(name=cls.name) + cls.manager = ResourceManager([cls.navigation]) + cls.service = NavigationRPCService(cls.manager) + + @pytest.mark.asyncio + async def test_get_location(self): + async with ChannelFor([self.service]) as channel: + client = NavigationClient(self.name, channel) + result = await client.get_location() + assert result == self.navigation.LOCATION + + @pytest.mark.asyncio + async def test_get_obstacles(self): + async with ChannelFor([self.service]) as channel: + client = NavigationClient(self.name, channel) + result = await client.get_obstacles() + assert result == self.navigation.OBSTACLES + + @pytest.mark.asyncio + async def test_get_waypoints(self): + async with ChannelFor([self.service]) as channel: + client = NavigationClient(self.name, channel) + result = await client.get_waypoints() + assert result == self.navigation.WAYPOINTS + + @pytest.mark.asyncio + async def test_add_waypoint(self): + async with ChannelFor([self.service]) as channel: + assert self.navigation.add_waypoints == [] + client = NavigationClient(self.name, channel) + point = GeoPoint(latitude=100.0, longitude=200.0) + await client.add_waypoint(point) + assert self.navigation.add_waypoints == [point] + + @pytest.mark.asyncio + async def test_remove_waypoint(self): + async with ChannelFor([self.service]) as channel: + assert self.navigation.remove_waypoints == [] + client = NavigationClient(self.name, channel) + id = "xyz" + await client.remove_waypoint(id) + assert self.navigation.remove_waypoints == [id] + + @pytest.mark.asyncio + async def test_get_mode(self): + async with ChannelFor([self.service]) as channel: + client = NavigationClient(self.name, channel) + result = await client.get_mode() + assert result == Mode.MODE_UNSPECIFIED + + @pytest.mark.asyncio + async def test_set_mode(self): + async with ChannelFor([self.service]) as channel: + assert self.navigation.mode == Mode.MODE_UNSPECIFIED + client = NavigationClient(self.name, channel) + mode = Mode.MODE_MANUAL + await client.set_mode(mode) + assert self.navigation.mode == Mode.MODE_MANUAL + + @pytest.mark.asyncio + async def test_do(self): + async with ChannelFor([self.service]) as channel: + client = NavigationClient(self.name, channel) + command = {"command": "args"} + response = await client.do_command(command) + assert response == {"command": command} From 5903f3f508a1654863f099576d38700b13d217a1 Mon Sep 17 00:00:00 2001 From: Maxim Pertsov Date: Wed, 19 Jul 2023 18:59:29 -0400 Subject: [PATCH 8/9] lint --- src/viam/services/navigation/client.py | 3 --- src/viam/services/navigation/service.py | 1 - tests/test_navigation.py | 6 ------ 3 files changed, 10 deletions(-) diff --git a/src/viam/services/navigation/client.py b/src/viam/services/navigation/client.py index e8b9f11ed..4ddc59d35 100644 --- a/src/viam/services/navigation/client.py +++ b/src/viam/services/navigation/client.py @@ -5,7 +5,6 @@ from viam.proto.common import DoCommandRequest, DoCommandResponse from viam.proto.service.navigation import ( AddWaypointRequest, - AddWaypointResponse, GetLocationRequest, GetLocationResponse, GetModeRequest, @@ -16,9 +15,7 @@ GetWaypointsResponse, NavigationServiceStub, RemoveWaypointRequest, - RemoveWaypointResponse, SetModeRequest, - SetModeResponse, ) from viam.resource.rpc_client_base import ReconfigurableResourceRPCClientBase from viam.utils import ValueTypes, dict_to_struct, struct_to_dict diff --git a/src/viam/services/navigation/service.py b/src/viam/services/navigation/service.py index 8c7fe9778..0e9198ea3 100644 --- a/src/viam/services/navigation/service.py +++ b/src/viam/services/navigation/service.py @@ -1,6 +1,5 @@ from grpclib.server import Stream -from viam.errors import MethodNotImplementedError from viam.proto.common import DoCommandRequest, DoCommandResponse from viam.proto.service.navigation import ( AddWaypointRequest, diff --git a/tests/test_navigation.py b/tests/test_navigation.py index eaaab3028..9de67baa6 100644 --- a/tests/test_navigation.py +++ b/tests/test_navigation.py @@ -1,12 +1,9 @@ -from typing import List - import pytest from grpclib.testing import ChannelFor from viam.proto.common import DoCommandRequest, DoCommandResponse, GeoPoint from viam.proto.service.navigation import ( AddWaypointRequest, - AddWaypointResponse, GetLocationRequest, GetLocationResponse, GetModeRequest, @@ -18,10 +15,7 @@ Mode, NavigationServiceStub, RemoveWaypointRequest, - RemoveWaypointResponse, SetModeRequest, - SetModeResponse, - Waypoint, ) from viam.resource.manager import ResourceManager from viam.services.navigation import NavigationClient, NavigationRPCService From 09230477084f5d5426b415c8a4ef0a7ea9d04fcf Mon Sep 17 00:00:00 2001 From: Maxim Pertsov Date: Thu, 20 Jul 2023 10:38:19 -0400 Subject: [PATCH 9/9] CR@njooma: fixup watch command --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 4cef14e21..155433809 100644 --- a/Makefile +++ b/Makefile @@ -33,7 +33,7 @@ better_imports: poetry run $(MAKE) _better_imports _test_watch: - poetry run ptw . + ptw . test_watch: poetry run $(MAKE) _test_watch