diff --git a/aio_ld2410/__init__.py b/aio_ld2410/__init__.py index 9439939..fbecf27 100644 --- a/aio_ld2410/__init__.py +++ b/aio_ld2410/__init__.py @@ -28,6 +28,7 @@ __version__ = version __all__ = [ + 'LD2410', 'AioLd2410Error', 'BaudRateIndex', 'CommandContextError', @@ -39,7 +40,6 @@ 'ConnectionClosedError', 'FirmwareVersion', 'GateSensitivityConfig', - 'LD2410', 'LightControl', 'LightControlConfig', 'LightControlStatus', diff --git a/aio_ld2410/ld2410.py b/aio_ld2410/ld2410.py index 34bbafe..6c254a2 100644 --- a/aio_ld2410/ld2410.py +++ b/aio_ld2410/ld2410.py @@ -73,7 +73,8 @@ def configuration( """ if not asyncio.iscoroutinefunction(func): - raise RuntimeError('@configuration decorator is only suitable for async methods.') + msg = '@configuration decorator is only suitable for async methods.' + raise RuntimeError(msg) @functools.wraps(func) async def _check_config_context( @@ -82,7 +83,8 @@ async def _check_config_context( **kwargs: _ParamSpec.kwargs, ) -> _T: if not self.configuring: - raise CommandContextError('This method requires a configuration context') + msg = 'This method requires a configuration context' + raise CommandContextError(msg) return await func(self, *args, **kwargs) return _check_config_context @@ -167,8 +169,8 @@ async def __aenter__(self) -> Self: """ if self.entered: - raise RuntimeError("LD2410's instance is already entered!") - + msg = "LD2410's instance is already entered!" + raise RuntimeError(msg) context = await AsyncExitStack().__aenter__() try: reader, writer = await self._open_serial_connection() @@ -285,7 +287,8 @@ async def _request( command = Command.build({'code': code, 'data': args}) async with self._request_lock: if not self.connected: - raise ConnectionClosedError('We are not connected to the device anymore!') + msg = 'We are not connected to the device anymore!' + raise ConnectionClosedError(msg) async with timeout(self._command_timeout): frame = CommandFrame.build({'data': command}) @@ -302,7 +305,8 @@ async def _request( reply = await replies.get() replies.task_done() if reply is None: - raise ConnectionClosedError('Device has disconnected') + msg = 'Device has disconnected' + raise ConnectionClosedError(msg) valid_reply = bool(code == int(reply.code)) if not valid_reply: @@ -311,7 +315,8 @@ async def _request( # MyPy does not see that reply cannot be None on here. reply = cast(ConstructReply, reply) if int(reply.status) != ReplyStatus.SUCCESS: - raise CommandStatusError(f'Command {code} received bad status: {reply.status}') + msg = f'Command {code} received bad status: {reply.status}' + raise CommandStatusError(msg) return reply @@ -398,7 +403,8 @@ async def get_distance_resolution(self) -> int: return 20 if index == ResolutionIndex.RESOLUTION_75CM: return 75 - raise CommandReplyError(f'Unhandled distance resolution index {index}') + msg = f'Unhandled distance resolution index {index}' + raise CommandReplyError(msg) @configuration async def get_firmware_version(self) -> FirmwareVersion: @@ -585,7 +591,8 @@ async def restart_module(self, *, close_config_context: bool = False) -> None: self._restarted = True if close_config_context: - raise ModuleRestartedError("Module is being restarted from user's request.") + msg = "Module is being restarted from user's request." + raise ModuleRestartedError(msg) @configuration async def set_baud_rate(self, baud_rate: int) -> None: @@ -610,7 +617,8 @@ async def set_baud_rate(self, baud_rate: int) -> None: try: index = BaudRateIndex.from_integer(baud_rate) except KeyError: - raise CommandParamError(f'Unknown index for baud rate {baud_rate}') from None + msg = f'Unknown index for baud rate {baud_rate}' + raise CommandParamError(msg) from None await self._request(CommandCode.BAUD_RATE_SET, {'index': int(index)}) @@ -650,9 +658,8 @@ async def set_bluetooth_password(self, password: str) -> None: """ if len(password) > 6 or not password.isascii(): - raise CommandParamError( - 'Bluetooth password must have less than 7 ASCII characters.' - ) + msg = 'Bluetooth password must have less than 7 ASCII characters.' + raise CommandParamError(msg) await self._request(CommandCode.BLUETOOTH_PASSWORD_SET, {'password': password}) @configuration @@ -682,7 +689,8 @@ async def set_distance_resolution(self, resolution: int) -> None: if resolution == 20: index = ResolutionIndex.RESOLUTION_20CM elif resolution != 75: - raise CommandParamError(f'Unknown index for distance resolution {resolution}') + msg = f'Unknown index for distance resolution {resolution}' + raise CommandParamError(msg) await self._request(CommandCode.DISTANCE_RESOLUTION_SET, {'resolution': index}) @configuration @@ -743,7 +751,8 @@ async def set_light_control(self, **kwargs: Unpack[LightControlConfig]) -> None: data = LightControlConfig(**kwargs) missing = LightControlConfig.__required_keys__.difference(data.keys()) if missing: - raise CommandParamError(f'Missing parameters: {set(missing)}') + msg = f'Missing parameters: {set(missing)}' + raise CommandParamError(msg) await self._request(CommandCode.LIGHT_CONTROL_SET, data) @configuration @@ -783,7 +792,8 @@ async def set_parameters(self, **kwargs: Unpack[ParametersConfig]) -> None: data = ParametersConfig(**kwargs) missing = ParametersConfig.__required_keys__.difference(data.keys()) if missing: - raise CommandParamError(f'Missing parameters: {set(missing)}') + msg = f'Missing parameters: {set(missing)}' + raise CommandParamError(msg) await self._request(CommandCode.PARAMETERS_WRITE, data) @configuration @@ -823,5 +833,6 @@ async def set_gate_sensitivity(self, **kwargs: Unpack[GateSensitivityConfig]) -> data = GateSensitivityConfig(**kwargs) missing = GateSensitivityConfig.__required_keys__.difference(data.keys()) if missing: - raise CommandParamError(f'Missing parameters: {set(missing)}') + msg = f'Missing parameters: {set(missing)}' + raise CommandParamError(msg) await self._request(CommandCode.GATE_SENSITIVITY_SET, data) diff --git a/aio_ld2410/protocol/command.py b/aio_ld2410/protocol/command.py index 1266b5e..7b9d322 100644 --- a/aio_ld2410/protocol/command.py +++ b/aio_ld2410/protocol/command.py @@ -160,7 +160,7 @@ class ResolutionIndex(IntEnum): CommandCode.CONFIG_DISABLE: Pass, # The following configuration is lost on restart. CommandCode.CONFIG_ENABLE: Struct('value' / Const(1, Int16ul)), - ## The following commands are only available on LD2410C. + # All the following commands are only available on LD2410C. # The following command is only available through bluetooth. CommandCode.BLUETOOTH_AUTHENTICATE: Struct( 'password' / PaddedString(6, 'ascii'), @@ -225,7 +225,7 @@ class ResolutionIndex(IntEnum): 'protocol_version' / Int16ul, 'buffer_size' / Int16ul, ), - ## The following replies can only be received on LD2410C. + # All the following replies can only be received on LD2410C. CommandCode.BLUETOOTH_AUTHENTICATE: Pass, CommandCode.BLUETOOTH_PASSWORD_SET: Pass, CommandCode.DISTANCE_RESOLUTION_SET: Pass, diff --git a/docs/requirements.txt b/docs/requirements.txt index ce4ed70..ca8f0f8 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,5 +1,5 @@ furo==2024.8.6 -sphinx==8.1.2 +sphinx==8.1.3 sphinx_autodoc_typehints==2.5.0 sphinx_copybutton==0.5.2 sphinx_inline_tabs==2023.4.21 diff --git a/pyproject.toml b/pyproject.toml index 6478189..940ee27 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -108,9 +108,11 @@ select = [ 'D', # pydocstyle 'DTZ', # flake8-datetimez 'E', # pycodestyle errors + 'ERA', # eradicate 'F', # Pyflakes 'FA', # flake8-future-annotations 'I', # isort + 'INP', # flake8-no-pep420 'LOG', # flake8-logging 'N', # pep8-naming 'PIE', # flake8-pie @@ -123,7 +125,9 @@ select = [ 'S', # flake8-bandit 'SIM', # flake8-simplify 'SLF', # flake8-self + 'T20', # flake8-print 'TCH', # flake8-type-checking + 'TRY', # tryceratops 'UP', # pyupgrade 'W', # pycodestyle warnings ] @@ -158,9 +162,11 @@ max-complexity = 10 [tool.ruff.lint.per-file-ignores] 'docs/conf.py' = [ - 'A001', # Variable is shadowing a Python builtin - 'E402', # Module level import not at top of file - 'F401', # aio_ld2410.version imported but unused + 'A001', # Variable is shadowing a Python builtin + 'E402', # Module level import not at top of file + 'E266', # Too many leading `#` before block comment + 'INP001', # File is part of an implicit namespace package + 'F401', # aio_ld2410.version imported but unused ] 'tests/*.py' = [ 'D', # pydocstyle diff --git a/tests/protocol/test_commands.py b/tests/protocol/test_commands.py index d9e6d55..5ffffa2 100644 --- a/tests/protocol/test_commands.py +++ b/tests/protocol/test_commands.py @@ -94,8 +94,6 @@ def test_commands(code, trace): frame_rebuild = CommandFrame.build({'data': command_rebuild}) assert frame_rebuild == raw - print(command) - @pytest.mark.parametrize(('code', 'trace'), _REPLY_TRACES.items()) def test_replies(code, trace): @@ -111,5 +109,3 @@ def test_replies(code, trace): # Rebuild the frame around the command and check it. frame_rebuild = CommandFrame.build({'data': reply_rebuild}) assert frame_rebuild == raw - - print(reply) diff --git a/tests/protocol/test_reports.py b/tests/protocol/test_reports.py index e078e22..ea49c09 100644 --- a/tests/protocol/test_reports.py +++ b/tests/protocol/test_reports.py @@ -16,6 +16,9 @@ @pytest.mark.parametrize(('type_', 'trace'), _TRACES.items()) def test_good_reports(type_, trace): - frame = ReportFrame.parse(bytes.fromhex(trace)) + raw = bytes.fromhex(trace) + frame = ReportFrame.parse(raw) report = Report.parse(frame.data) - print(report) + report_rebuild = Report.build(report) + frame_rebuild = ReportFrame.build({'data': report_rebuild}) + assert frame_rebuild == raw diff --git a/tests/requirements-linting.txt b/tests/requirements-linting.txt index 2c32847..ffe6e45 100644 --- a/tests/requirements-linting.txt +++ b/tests/requirements-linting.txt @@ -1,4 +1,4 @@ -mypy==1.11.2 -ruff==0.6.9 +mypy==1.13.0 +ruff==0.7.1 construct-typing==0.6.2 typing_extensions==4.12.2 diff --git a/tests/requirements-testing.txt b/tests/requirements-testing.txt index 1f0d696..1b3cd0b 100644 --- a/tests/requirements-testing.txt +++ b/tests/requirements-testing.txt @@ -1,5 +1,5 @@ -anyio==4.6.0 -coverage==7.6.2 +anyio==4.6.2.post1 +coverage==7.6.4 pytest-cov==5.0.0 pytest-timeout==2.3.1 pytest==8.3.3 diff --git a/tests/test_ld2410.py b/tests/test_ld2410.py index efa1a99..32529e9 100644 --- a/tests/test_ld2410.py +++ b/tests/test_ld2410.py @@ -5,7 +5,11 @@ import logging # Python 3.9 and lower have a distinct class for asyncio.TimeoutError. -from asyncio import StreamReader, StreamWriter, TimeoutError +from asyncio import ( + StreamReader, + StreamWriter, + TimeoutError as AsyncTimeoutError, +) from dataclasses import asdict import pytest @@ -359,7 +363,7 @@ async def test_good_engineering_report(self, device): async def test_no_report_while_config(self, device): """Check that we get no report while configuring.""" async with device.configure(): - with pytest.raises(TimeoutError): + with pytest.raises(AsyncTimeoutError): # 500ms is enough since we generate a report every 100ms. await asyncio.wait_for(device.get_next_report(), timeout=0.5)