From a250f08909683e8926e08cb6744f4088a3e04e16 Mon Sep 17 00:00:00 2001 From: WT-MM Date: Fri, 15 Nov 2024 11:30:08 -0800 Subject: [PATCH 1/9] use right directory --- .github/workflows/publish.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 50046bf..7b19937 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -27,9 +27,6 @@ jobs: - name: Checkout code uses: actions/checkout@v3 - - name: Change directory - run: cd pykos - - name: Set up Python uses: actions/setup-python@v4 with: @@ -41,7 +38,9 @@ jobs: pip install build wheel - name: Build package + working-directory: ./pykos run: python -m build --sdist --wheel --outdir dist/ . - name: Publish package + working-directory: ./pykos uses: pypa/gh-action-pypi-publish@release/v1 From ab4fd337e969ccf2fb0811a2973ddcad7a85310e Mon Sep 17 00:00:00 2001 From: WT-MM Date: Fri, 15 Nov 2024 11:33:26 -0800 Subject: [PATCH 2/9] whoops --- .github/workflows/test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e56596c..2520ee8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,9 +25,6 @@ jobs: - name: Check out repository uses: actions/checkout@v3 - - name: Change directory - run: cd pykos - - name: Set up Python uses: actions/setup-python@v4 with: @@ -46,15 +43,18 @@ jobs: python-requirements- - name: Install package + working-directory: ./pykos run: | pip install --upgrade --upgrade-strategy eager --extra-index-url https://download.pytorch.org/whl/cpu -e '.[dev]' - name: Run static checks + working-directory: ./pykos run: | mkdir -p .mypy_cache make static-checks - name: Run unit tests + working-directory: ./pykos run: | make test From 7862d865a0ebc8206914fae19e1f6b2b3e6d855c Mon Sep 17 00:00:00 2001 From: WT-MM Date: Fri, 15 Nov 2024 11:36:11 -0800 Subject: [PATCH 3/9] add reqs --- pykos/pykos/requirements-dev.txt | 7 +++++++ pykos/setup.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/pykos/pykos/requirements-dev.txt b/pykos/pykos/requirements-dev.txt index b591669..8b31de3 100644 --- a/pykos/pykos/requirements-dev.txt +++ b/pykos/pykos/requirements-dev.txt @@ -1,3 +1,10 @@ # requirements-dev.txt grpcio-tools + +# python checks +black +darglint +mypy +pytest +ruff diff --git a/pykos/setup.py b/pykos/setup.py index 4da8b5c..20da267 100644 --- a/pykos/setup.py +++ b/pykos/setup.py @@ -42,4 +42,4 @@ "pykos=pykos.cli:cli", ], }, -) \ No newline at end of file +) From 74e1f2686a2b4a1f69fb359842a0a83c0429fdee Mon Sep 17 00:00:00 2001 From: WT-MM Date: Fri, 15 Nov 2024 11:42:49 -0800 Subject: [PATCH 4/9] lint --- pykos/pykos/__init__.py | 2 +- pykos/pykos/client.py | 18 ++++++----- pykos/pykos/services/actuator.py | 52 +++++++++++++++++--------------- pykos/pykos/services/imu.py | 32 ++++++++++++++------ 4 files changed, 60 insertions(+), 44 deletions(-) diff --git a/pykos/pykos/__init__.py b/pykos/pykos/__init__.py index 135f791..b134d49 100644 --- a/pykos/pykos/__init__.py +++ b/pykos/pykos/__init__.py @@ -1,3 +1,3 @@ __version__ = "0.1.1" -from pykos.client import KOS \ No newline at end of file +from pykos.client import KOS diff --git a/pykos/pykos/client.py b/pykos/pykos/client.py index c0c2b10..5a4449d 100644 --- a/pykos/pykos/client.py +++ b/pykos/pykos/client.py @@ -1,10 +1,13 @@ +"""KOS client.""" + import grpc -from pykos.services.imu import IMUServiceClient from pykos.services.actuator import ActuatorServiceClient +from pykos.services.imu import IMUServiceClient + + class KOS: - """ - KOS client + """KOS client. Args: ip (str, optional): IP address of the robot running KOS. Defaults to localhost. @@ -13,15 +16,14 @@ class KOS: Attributes: imu (IMUServiceClient): Client for the IMU service. """ - def __init__(self, ip: str = "localhost", port: int = 50051): + + def __init__(self, ip: str = "localhost", port: int = 50051) -> None: self.ip = ip self.port = port self.channel = grpc.insecure_channel(f"{self.ip}:{self.port}") self.imu = IMUServiceClient(self.channel) self.actuator = ActuatorServiceClient(self.channel) - def close(self): - """ - Close the gRPC channel. - """ + def close(self) -> None: + """Close the gRPC channel.""" self.channel.close() diff --git a/pykos/pykos/services/actuator.py b/pykos/pykos/services/actuator.py index b1be475..8dbb7ff 100644 --- a/pykos/pykos/services/actuator.py +++ b/pykos/pykos/services/actuator.py @@ -1,41 +1,46 @@ -from kos import actuator_pb2_grpc, actuator_pb2 -from google.protobuf.empty_pb2 import Empty -from google.protobuf.any_pb2 import Any +"""Actuator service client.""" + +import grpc from google.longrunning import operations_pb2, operations_pb2_grpc +from google.protobuf.any_pb2 import Any + +from kos import actuator_pb2, actuator_pb2_grpc from kos.actuator_pb2 import CalibrateActuatorMetadata + class CalibrationStatus: Calibrating = "calibrating" Calibrated = "calibrated" Timeout = "timeout" + class CalibrationMetadata: - def __init__(self, metadata_any: Any): + def __init__(self, metadata_any: Any) -> None: self.actuator_id = None self.status = None self.decode_metadata(metadata_any) - def decode_metadata(self, metadata_any: Any): + def decode_metadata(self, metadata_any: Any) -> None: metadata = CalibrateActuatorMetadata() if metadata_any.Is(CalibrateActuatorMetadata.DESCRIPTOR): metadata_any.Unpack(metadata) self.actuator_id = metadata.actuator_id self.status = metadata.status - def __str__(self): + def __str__(self) -> str: return f"CalibrationMetadata(actuator_id={self.actuator_id}, status={self.status})" - - def __repr__(self): + + def __repr__(self) -> str: return self.__str__() + class ActuatorServiceClient: - def __init__(self, channel): + def __init__(self, channel: grpc.Channel) -> None: self.stub = actuator_pb2_grpc.ActuatorServiceStub(channel) self.operations_stub = operations_pb2_grpc.OperationsStub(channel) - def calibrate(self, actuator_id: int): - """ - Calibrate an actuator. + def calibrate(self, actuator_id: int) -> CalibrationMetadata: + """Calibrate an actuator. Returns: Operation: The operation for the calibration. @@ -44,14 +49,15 @@ def calibrate(self, actuator_id: int): metadata = CalibrationMetadata(response.metadata) return metadata - def get_calibration_status(self, actuator_id: int): - response = self.operations_stub.GetOperation(operations_pb2.GetOperationRequest(name=f"operations/calibrate_actuator/{actuator_id}")) + def get_calibration_status(self, actuator_id: int) -> str: + response = self.operations_stub.GetOperation( + operations_pb2.GetOperationRequest(name=f"operations/calibrate_actuator/{actuator_id}") + ) metadata = CalibrationMetadata(response.metadata) return metadata.status - def command_actuators(self, commands: list[dict]): - """ - Command multiple actuators at once. + def command_actuators(self, commands: list[dict]) -> list[actuator_pb2.ActionResult]: + """Command multiple actuators at once. Args: commands: List of dictionaries containing actuator commands. @@ -66,9 +72,8 @@ def command_actuators(self, commands: list[dict]): response = self.stub.CommandActuators(request) return response.results - def configure_actuator(self, actuator_id: int, **kwargs): - """ - Configure an actuator's parameters. + def configure_actuator(self, actuator_id: int, **kwargs: Any) -> actuator_pb2.ActionResponse: + """Configure an actuator's parameters. Args: actuator_id: ID of the actuator to configure @@ -83,9 +88,8 @@ def configure_actuator(self, actuator_id: int, **kwargs): request = actuator_pb2.ConfigureActuatorRequest(**config) return self.stub.ConfigureActuator(request) - def get_actuators_state(self, actuator_ids: list[int]): - """ - Get the state of multiple actuators. + def get_actuators_state(self, actuator_ids: list[int]) -> list[actuator_pb2.ActuatorStateResponse]: + """Get the state of multiple actuators. Args: actuator_ids: List of actuator IDs to query @@ -96,5 +100,3 @@ def get_actuators_state(self, actuator_ids: list[int]): request = actuator_pb2.GetActuatorsStateRequest(actuator_ids=actuator_ids) response = self.stub.GetActuatorsState(request) return response.states - - \ No newline at end of file diff --git a/pykos/pykos/services/imu.py b/pykos/pykos/services/imu.py index 961d500..2eed4a5 100644 --- a/pykos/pykos/services/imu.py +++ b/pykos/pykos/services/imu.py @@ -1,8 +1,13 @@ -from kos import imu_pb2_grpc, imu_pb2 +"""IMU service client.""" + +import grpc from google.protobuf.empty_pb2 import Empty +from kos import imu_pb2, imu_pb2_grpc + + class ImuValues: - def __init__(self, response: imu_pb2.IMUValuesResponse): + def __init__(self, response: imu_pb2.IMUValuesResponse) -> None: self.accel_x = response.accel_x self.accel_y = response.accel_y self.accel_z = response.accel_z @@ -13,22 +18,29 @@ def __init__(self, response: imu_pb2.IMUValuesResponse): self.mag_y = response.mag_y if response.HasField("mag_y") else None self.mag_z = response.mag_z if response.HasField("mag_z") else None self.error = response.error if response.HasField("error") else None - def __str__(self): - return f"ImuValues(accel_x={self.accel_x}, accel_y={self.accel_y}, accel_z={self.accel_z}, gyro_x={self.gyro_x}, gyro_y={self.gyro_y}, gyro_z={self.gyro_z}, mag_x={self.mag_x}, mag_y={self.mag_y}, mag_z={self.mag_z}, error={self.error})" - def __repr__(self): + + def __str__(self) -> str: + return ( + f"ImuValues(" + f"accel_x={self.accel_x}, accel_y={self.accel_y}, accel_z={self.accel_z}, " + f"gyro_x={self.gyro_x}, gyro_y={self.gyro_y}, gyro_z={self.gyro_z}, " + f"mag_x={self.mag_x}, mag_y={self.mag_y}, mag_z={self.mag_z}, " + f"error={self.error})" + ) + + def __repr__(self) -> str: return self.__str__() + class IMUServiceClient: - def __init__(self, channel): + def __init__(self, channel: grpc.Channel) -> None: self.stub = imu_pb2_grpc.IMUServiceStub(channel) - def get_imu_values(self): - """ - Get the latest IMU sensor values. + def get_imu_values(self) -> ImuValues: + """Get the latest IMU sensor values. Returns: ImuValuesResponse: The latest IMU sensor values. """ response = self.stub.GetValues(Empty()) return ImuValues(response) - \ No newline at end of file From 1a857d9c71a35efc05034261daff969baf7c3c16 Mon Sep 17 00:00:00 2001 From: WT-MM Date: Fri, 15 Nov 2024 11:54:02 -0800 Subject: [PATCH 5/9] lint --- pykos/pykos/requirements-dev.txt | 2 ++ pykos/pykos/services/actuator.py | 20 +++++++++++--------- pykos/setup.py | 5 +++-- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/pykos/pykos/requirements-dev.txt b/pykos/pykos/requirements-dev.txt index 8b31de3..538f1c9 100644 --- a/pykos/pykos/requirements-dev.txt +++ b/pykos/pykos/requirements-dev.txt @@ -1,6 +1,8 @@ # requirements-dev.txt grpcio-tools +types-grpcio +types-protobuf # python checks black diff --git a/pykos/pykos/services/actuator.py b/pykos/pykos/services/actuator.py index 8dbb7ff..1ee2ed7 100644 --- a/pykos/pykos/services/actuator.py +++ b/pykos/pykos/services/actuator.py @@ -1,8 +1,10 @@ """Actuator service client.""" +from typing import Any, Dict, List, Optional + import grpc from google.longrunning import operations_pb2, operations_pb2_grpc -from google.protobuf.any_pb2 import Any +from google.protobuf.any_pb2 import Any as AnyPb2 from kos import actuator_pb2, actuator_pb2_grpc from kos.actuator_pb2 import CalibrateActuatorMetadata @@ -15,17 +17,17 @@ class CalibrationStatus: class CalibrationMetadata: - def __init__(self, metadata_any: Any) -> None: - self.actuator_id = None - self.status = None + def __init__(self, metadata_any: AnyPb2) -> None: + self.actuator_id: Optional[int] = None + self.status: Optional[str] = None self.decode_metadata(metadata_any) - def decode_metadata(self, metadata_any: Any) -> None: + def decode_metadata(self, metadata_any: AnyPb2) -> None: metadata = CalibrateActuatorMetadata() if metadata_any.Is(CalibrateActuatorMetadata.DESCRIPTOR): metadata_any.Unpack(metadata) self.actuator_id = metadata.actuator_id - self.status = metadata.status + self.status = metadata.status if metadata.HasField("status") else None def __str__(self) -> str: return f"CalibrationMetadata(actuator_id={self.actuator_id}, status={self.status})" @@ -56,7 +58,7 @@ def get_calibration_status(self, actuator_id: int) -> str: metadata = CalibrationMetadata(response.metadata) return metadata.status - def command_actuators(self, commands: list[dict]) -> list[actuator_pb2.ActionResult]: + def command_actuators(self, commands: List[Dict[str, Any]]) -> List[actuator_pb2.ActionResult]: """Command multiple actuators at once. Args: @@ -72,7 +74,7 @@ def command_actuators(self, commands: list[dict]) -> list[actuator_pb2.ActionRes response = self.stub.CommandActuators(request) return response.results - def configure_actuator(self, actuator_id: int, **kwargs: Any) -> actuator_pb2.ActionResponse: + def configure_actuator(self, actuator_id: int, **kwargs: dict[str, Any]) -> actuator_pb2.ActionResponse: """Configure an actuator's parameters. Args: @@ -88,7 +90,7 @@ def configure_actuator(self, actuator_id: int, **kwargs: Any) -> actuator_pb2.Ac request = actuator_pb2.ConfigureActuatorRequest(**config) return self.stub.ConfigureActuator(request) - def get_actuators_state(self, actuator_ids: list[int]) -> list[actuator_pb2.ActuatorStateResponse]: + def get_actuators_state(self, actuator_ids: List[int]) -> List[actuator_pb2.ActuatorStateResponse]: """Get the state of multiple actuators. Args: diff --git a/pykos/setup.py b/pykos/setup.py index 20da267..3b213d8 100644 --- a/pykos/setup.py +++ b/pykos/setup.py @@ -3,6 +3,7 @@ """Setup script for the project.""" import re +from typing import List from setuptools import setup @@ -11,11 +12,11 @@ with open("pykos/requirements.txt", "r", encoding="utf-8") as f: - requirements: list[str] = f.read().splitlines() + requirements: List[str] = f.read().splitlines() with open("pykos/requirements-dev.txt", "r", encoding="utf-8") as f: - requirements_dev: list[str] = f.read().splitlines() + requirements_dev: List[str] = f.read().splitlines() with open("pykos/__init__.py", "r", encoding="utf-8") as fh: From 1e832d83442d4bd61efa5583c39b28b4f2590521 Mon Sep 17 00:00:00 2001 From: WT-MM Date: Fri, 15 Nov 2024 11:55:39 -0800 Subject: [PATCH 6/9] remove --- pykos/pykos/requirements-dev.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/pykos/pykos/requirements-dev.txt b/pykos/pykos/requirements-dev.txt index 538f1c9..26fc6dc 100644 --- a/pykos/pykos/requirements-dev.txt +++ b/pykos/pykos/requirements-dev.txt @@ -1,7 +1,6 @@ # requirements-dev.txt grpcio-tools -types-grpcio types-protobuf # python checks From 05f9b0eeec16244c037d4af9d9e74f50bee7c018 Mon Sep 17 00:00:00 2001 From: WT-MM Date: Fri, 15 Nov 2024 12:22:13 -0800 Subject: [PATCH 7/9] pass checks --- pykos/Makefile | 11 ++++++++--- pykos/pykos/services/actuator.py | 4 ++-- pykos/pyproject.toml | 13 +++++++++++++ pykos/setup.py | 4 ++++ 4 files changed, 27 insertions(+), 5 deletions(-) diff --git a/pykos/Makefile b/pykos/Makefile index bf29758..b4178b2 100644 --- a/pykos/Makefile +++ b/pykos/Makefile @@ -27,7 +27,12 @@ all: # ------------------------ # generate-proto: - python -m grpc_tools.protoc --python_out=. --grpc_python_out=. --proto_path=../kos_core/proto --proto_path=../proto/googleapis ../proto/kos/* + python -m grpc_tools.protoc \ + --python_out=. \ + --grpc_python_out=. \ + --proto_path=../kos_core/proto/ \ + --proto_path=../kos_core/proto/googleapis \ + ../kos_core/proto/kos/*.proto .PHONY: generate-proto @@ -57,7 +62,7 @@ push-to-pypi: build-for-pypi # Static Checks # # ------------------------ # -py-files := $(shell find . -name '*.py') +py-files := $(shell find . -name '*.py' -not -path './kos/*' -not -path './pykos/kos/*') format: @black $(py-files) @@ -68,7 +73,7 @@ static-checks: @black --diff --check $(py-files) @ruff check $(py-files) @mypy --install-types --non-interactive $(py-files) -.PHONY: lint +.PHONY: static-checks # ------------------------ # # Unit tests # diff --git a/pykos/pykos/services/actuator.py b/pykos/pykos/services/actuator.py index 1ee2ed7..52fe0d1 100644 --- a/pykos/pykos/services/actuator.py +++ b/pykos/pykos/services/actuator.py @@ -51,7 +51,7 @@ def calibrate(self, actuator_id: int) -> CalibrationMetadata: metadata = CalibrationMetadata(response.metadata) return metadata - def get_calibration_status(self, actuator_id: int) -> str: + def get_calibration_status(self, actuator_id: int) -> Optional[str]: response = self.operations_stub.GetOperation( operations_pb2.GetOperationRequest(name=f"operations/calibrate_actuator/{actuator_id}") ) @@ -74,7 +74,7 @@ def command_actuators(self, commands: List[Dict[str, Any]]) -> List[actuator_pb2 response = self.stub.CommandActuators(request) return response.results - def configure_actuator(self, actuator_id: int, **kwargs: dict[str, Any]) -> actuator_pb2.ActionResponse: + def configure_actuator(self, actuator_id: int, **kwargs: Dict[str, Any]) -> actuator_pb2.ActionResponse: """Configure an actuator's parameters. Args: diff --git a/pykos/pyproject.toml b/pykos/pyproject.toml index f68982b..4c8a3ef 100644 --- a/pykos/pyproject.toml +++ b/pykos/pyproject.toml @@ -29,14 +29,27 @@ warn_redundant_casts = true incremental = true namespace_packages = false +exclude = ["kos/.*\\.py$"] [[tool.mypy.overrides]] module = [ "setuptools", "setuptools_rust", + "grpc", + "grpc.*", + "google.protobuf.*", + "google.longrunning.*", + "kos.*", + "pykos.*", + "kos.actuator_pb2_grpc", + "kos.imu_pb2_grpc", + "kos.actuator_pb2", + "kos.imu_pb2" ] ignore_missing_imports = true +follow_imports = "skip" +disallow_untyped_defs = false [tool.isort] diff --git a/pykos/setup.py b/pykos/setup.py index 3b213d8..9c20b21 100644 --- a/pykos/setup.py +++ b/pykos/setup.py @@ -38,6 +38,10 @@ tests_require=requirements_dev, extras_require={"dev": requirements_dev}, packages=["pykos"], + package_data={ + "pykos": ["py.typed"], + }, + include_package_data=True, entry_points={ "console_scripts": [ "pykos=pykos.cli:cli", From babede3d4d9280ceaf5b92b6251db20261a9896f Mon Sep 17 00:00:00 2001 From: WT-MM Date: Fri, 15 Nov 2024 12:23:24 -0800 Subject: [PATCH 8/9] move tests --- MANIFEST.in | 2 +- {tests => pykos/tests}/conftest.py | 0 {tests => pykos/tests}/test_dummy.py | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename {tests => pykos/tests}/conftest.py (100%) rename {tests => pykos/tests}/test_dummy.py (100%) diff --git a/MANIFEST.in b/MANIFEST.in index aefc580..2ede204 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1 +1 @@ -recursive-include kos/ *.py *.txt py.typed MANIFEST.in +recursive-include kos/ *.py *.txt py.typed MANIFEST.in py.typed diff --git a/tests/conftest.py b/pykos/tests/conftest.py similarity index 100% rename from tests/conftest.py rename to pykos/tests/conftest.py diff --git a/tests/test_dummy.py b/pykos/tests/test_dummy.py similarity index 100% rename from tests/test_dummy.py rename to pykos/tests/test_dummy.py From 3e751354e8d0a92499bc2397855ea266af65ff94 Mon Sep 17 00:00:00 2001 From: WT-MM Date: Fri, 15 Nov 2024 12:24:28 -0800 Subject: [PATCH 9/9] fix typing --- pykos/tests/conftest.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pykos/tests/conftest.py b/pykos/tests/conftest.py index fdc62be..26b37ca 100644 --- a/pykos/tests/conftest.py +++ b/pykos/tests/conftest.py @@ -1,6 +1,7 @@ """Defines PyTest configuration for the project.""" import random +from typing import List import pytest from _pytest.python import Function @@ -11,5 +12,5 @@ def set_random_seed() -> None: random.seed(1337) -def pytest_collection_modifyitems(items: list[Function]) -> None: +def pytest_collection_modifyitems(items: List[Function]) -> None: items.sort(key=lambda x: x.get_closest_marker("slow") is not None)