From e51502325e0979bbe81cc27519f2b33edd4deed7 Mon Sep 17 00:00:00 2001 From: Nate Gay Date: Sun, 31 Aug 2025 12:32:38 -0500 Subject: [PATCH] Use typeshed --- .gitignore | 1 + .pre-commit-config.yaml | 12 + Makefile | 12 +- docs/design-guide.md | 14 +- mocks/circuitpython/busio.py | 4 +- mocks/circuitpython/digitalio.py | 4 +- pyproject.toml | 12 +- pysquared/beacon.py | 5 +- pysquared/logger.py | 4 +- pysquared/rtc/manager/microcontroller.py | 2 +- .../imu/manager/test_lsm6dsox_manager.py | 39 +- .../manager/test_veml7700_manager.py | 2 + .../manager/test_lis2mdl_manager.py | 19 +- .../manager/test_ina219_manager.py | 2 + .../radio/manager/test_rfm9x_manager.py | 137 +++---- .../radio/manager/test_sx126x_manager.py | 96 ++--- .../radio/manager/test_sx1280_manager.py | 168 +++++---- .../manager/test_mcp9808_manager.py | 5 +- tests/unit/hardware/test_burnwire.py | 2 + tests/unit/hardware/test_digitalio.py | 13 +- .../manager/test_microcontroller_manager.py | 3 + tests/unit/rtc/manager/test_rv3028_manager.py | 20 +- tests/unit/test_beacon.py | 4 +- tests/unit/test_cdh.py | 22 +- tests/unit/test_config.py | 2 + tests/unit/test_detumble.py | 26 +- tests/unit/test_logger.py | 6 +- tests/unit/test_sleep_helper.py | 20 +- tests/unit/test_watchdog.py | 10 +- typings/micropython.pyi | 354 ------------------ uv.lock | 10 +- 31 files changed, 366 insertions(+), 664 deletions(-) delete mode 100644 typings/micropython.pyi diff --git a/.gitignore b/.gitignore index 1b98f3f2..57bae1b0 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ build/ pysquared.egg-info/ **/*.mpy site/ +typeshed/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 045412ed..cb3fbf5c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -21,6 +21,18 @@ repos: entry: '# type:? *ignore' language: pygrep types: [python] + files: ^pysquared/ + exclude: ^pysquared/beacon\.py|^pysquared/logger\.py|^pysquared/rtc/manager/microcontroller\.py + + - repo: local + hooks: + - id: prevent-pyright-ignore + name: prevent pyright ignore annotations + description: 'Enforce that no `# type: ignore` annotations exist in the codebase.' + entry: '# pyright:? *.*false' + language: pygrep + types: [python] + files: ^pysquared/ - repo: https://github.com/codespell-project/codespell rev: v2.4.1 diff --git a/Makefile b/Makefile index db93797d..9692feab 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ .PHONY: all -all: .venv pre-commit-install +all: .venv typeshed pre-commit-install .PHONY: help help: ## Display this help. @@ -13,6 +13,11 @@ help: ## Display this help. @$(UV) venv @$(UV) pip install --requirement pyproject.toml +typeshed: ## Install CircuitPython typeshed stubs + @echo "Installing CircuitPython typeshed stubs..." + @$(MAKE) uv + @$(UV) pip install circuitpython-typeshed==0.1.0 --target typeshed + .PHONY: pre-commit-install pre-commit-install: uv @echo "Installing pre-commit hooks..." @@ -22,7 +27,8 @@ pre-commit-install: uv fmt: pre-commit-install ## Lint and format files $(UVX) pre-commit run --all-files -typecheck: .venv ## Run type check +.PHONY: typecheck +typecheck: .venv typeshed ## Run type check @$(UV) run -m pyright . .PHONY: test @@ -68,7 +74,7 @@ $(TOOLS_DIR): mkdir -p $(TOOLS_DIR) ### Tool Versions -UV_VERSION ?= 0.7.13 +UV_VERSION ?= 0.8.14 MPY_CROSS_VERSION ?= 9.0.5 UV_DIR ?= $(TOOLS_DIR)/uv-$(UV_VERSION) diff --git a/docs/design-guide.md b/docs/design-guide.md index 0bce48fd..36189be9 100644 --- a/docs/design-guide.md +++ b/docs/design-guide.md @@ -19,7 +19,19 @@ PySquared is built on top of CircuitPython, which is a version of Python designe We use type hints throughout the PySquared codebase to ensure that our code is clear and maintainable. Type hints help us catch errors early and make it easier to understand the expected types of variables and function parameters. -We do not accept changes with lines that are ignored the type checker i.e. `# type: ignore`. If you run into an issue where you think you need to ignore a type, it is likely a problem with the design of your component. Please take a moment to think about how you can fix the type error instead. If you need help, please reach out for assistance. +We use [typeshed](https://peps.python.org/pep-0561/) stubs to provide more accurate type hints for CircuitPython, replacing the default Python standard library type hints. These CircuitPython-specific stubs are located in the `typeshed/` directory. This helps the typechecker catch compatibility issues with CircuitPython code before running it on a device. + +However, using these stubs means that type hints in test files also reference CircuitPython types, not the standard Python types available in the test environment. As a workaround, we add pyright ignore comments (e.g., `# pyright: reportOptionalMemberAccess=false`) when necessary at the top of test files to suppress related errors. This workaround isn’t ideal. If you have suggestions for handling this issue more effectively, please share your feedback. + +We do not accept changes to files in the `pysquared/` directory that include lines ignoring the type checker (e.g., `# type: ignore`). The only exceptions are: + +- **Upstream Fix in Progress:** If a type error is caused by a bug or limitation in an external dependency, you may ignore the line only when leaving a comment with a link to the issue or PR where it is fixed or a fix is in progress. A valid type hint might look like this: + + ```python + some_variable = some_function() # type: ignore # PR https://github.com/adafruit/circuitpython/pull/10603 + ``` + +If you encounter a type error, first consider if it can be resolved by improving your code's design. If you believe an exception is necessary, please reach out for assistance before proceeding. ??? note "Using the Typing Module" For more advanced type hinting we can use the Python standard library's `typing` module which was introduced in Python 3.5. This module provides a variety of type hints that can be used to specify more complex types, such as `List`, `Dict`, and `Optional`. CircuitPython does not support the `typing` module so we must wrap the import in a try/except block to avoid import errors. For example: diff --git a/mocks/circuitpython/busio.py b/mocks/circuitpython/busio.py index fe13538b..f751f5c4 100644 --- a/mocks/circuitpython/busio.py +++ b/mocks/circuitpython/busio.py @@ -5,11 +5,9 @@ the need for actual hardware. """ -from __future__ import annotations - from typing import Optional -import mocks.circuitpython.microcontroller as microcontroller +import microcontroller class SPI: diff --git a/mocks/circuitpython/digitalio.py b/mocks/circuitpython/digitalio.py index 27b33e94..630fe118 100644 --- a/mocks/circuitpython/digitalio.py +++ b/mocks/circuitpython/digitalio.py @@ -5,9 +5,7 @@ the need for actual hardware. """ -from __future__ import annotations - -import mocks.circuitpython.microcontroller as microcontroller +import microcontroller class DriveMode: diff --git a/pyproject.toml b/pyproject.toml index 8bb063e5..25ab6489 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,7 +29,7 @@ dev = [ "coverage==7.9.1", "freezegun>=1.5.2", "pre-commit==4.2.0", - "pyright[nodejs]==1.1.402", + "pyright[nodejs]==1.1.404", "pytest==8.4.1", "hypothesis==6.136.7", ] @@ -92,15 +92,18 @@ directory = ".coverage-reports/html" [tool.coverage.xml] output = ".coverage-reports/coverage.xml" +[tool.ty.environment] +typeshed = "./typeshed" + [tool.pyright] include = ["pysquared"] exclude = [ "**/__pycache__", ".venv", ".git", - "typings", + "typeshed", ] -stubPath = "./typings" +typeshedPath = "./typeshed" reportMissingModuleSource = false [tool.interrogate] @@ -109,3 +112,6 @@ omit-covered-files = true fail-under = 100 verbose = 2 color = true +exclude = [ + "typeshed", +] diff --git a/pysquared/beacon.py b/pysquared/beacon.py index 81236214..2caaafc9 100644 --- a/pysquared/beacon.py +++ b/pysquared/beacon.py @@ -109,10 +109,9 @@ def _add_system_info(self, state: OrderedDict[str, object]) -> None: """ state["name"] = self._name - # Warning: CircuitPython does not support time.gmtime(), when testing this code it will use your local timezone - now = time.localtime() + now = time.localtime() # type: ignore # PR: https://github.com/adafruit/circuitpython/pull/10603 state["time"] = ( - f"{now.tm_year}-{now.tm_mon:02d}-{now.tm_mday:02d} {now.tm_hour:02d}:{now.tm_min:02d}:{now.tm_sec:02d}" + f"{now.tm_year}-{now.tm_mon:02d}-{now.tm_mday:02d} {now.tm_hour:02d}:{now.tm_min:02d}:{now.tm_sec:02d}" # type: ignore # PR: https://github.com/adafruit/circuitpython/pull/10603 ) state["uptime"] = time.time() - self._boot_time diff --git a/pysquared/logger.py b/pysquared/logger.py index 3c9f65ce..8b5ebd7e 100644 --- a/pysquared/logger.py +++ b/pysquared/logger.py @@ -129,8 +129,8 @@ def _log(self, level: str, level_value: int, message: str, **kwargs) -> None: message (str): The log message. **kwargs: Additional key/value pairs to include in the log. """ - now = time.localtime() - asctime = f"{now.tm_year}-{now.tm_mon:02d}-{now.tm_mday:02d} {now.tm_hour:02d}:{now.tm_min:02d}:{now.tm_sec:02d}" + now = time.localtime() # type: ignore # PR: https://github.com/adafruit/circuitpython/pull/10603 + asctime = f"{now.tm_year}-{now.tm_mon:02d}-{now.tm_mday:02d} {now.tm_hour:02d}:{now.tm_min:02d}:{now.tm_sec:02d}" # type: ignore # PR: https://github.com/adafruit/circuitpython/pull/10603 # case where someone used debug, info, or warning yet also provides an 'err' kwarg with an Exception if ( diff --git a/pysquared/rtc/manager/microcontroller.py b/pysquared/rtc/manager/microcontroller.py index 0c5dfcef..bac5ddc8 100644 --- a/pysquared/rtc/manager/microcontroller.py +++ b/pysquared/rtc/manager/microcontroller.py @@ -30,7 +30,7 @@ def __init__(self) -> None: This method is required on every boot to ensure the RTC is ready for use. """ microcontroller_rtc = rtc.RTC() - microcontroller_rtc.datetime = time.localtime() + microcontroller_rtc.datetime = time.localtime() # type: ignore # PR: https://github.com/adafruit/circuitpython/pull/10603 def set_time( self, diff --git a/tests/unit/hardware/imu/manager/test_lsm6dsox_manager.py b/tests/unit/hardware/imu/manager/test_lsm6dsox_manager.py index fc77cf33..2bd47a2d 100644 --- a/tests/unit/hardware/imu/manager/test_lsm6dsox_manager.py +++ b/tests/unit/hardware/imu/manager/test_lsm6dsox_manager.py @@ -5,7 +5,8 @@ and error handling for acceleration, angular_velocity, and temperature readings. """ -import math +# pyright: reportAttributeAccessIssue=false, reportOptionalMemberAccess=false, reportReturnType=false + from typing import Generator from unittest.mock import MagicMock, PropertyMock, patch @@ -37,7 +38,7 @@ def mock_logger() -> MagicMock: @pytest.fixture -def mock_lsm6dsox(mock_i2c: MagicMock) -> Generator[MagicMock, None, None]: +def mock_lsm6dsox(mock_i2c: I2C) -> Generator[MagicMock, None, None]: """Mocks the LSM6DSOX class. Args: @@ -53,8 +54,8 @@ def mock_lsm6dsox(mock_i2c: MagicMock) -> Generator[MagicMock, None, None]: def test_create_imu( mock_lsm6dsox: MagicMock, - mock_i2c: MagicMock, - mock_logger: MagicMock, + mock_i2c: I2C, + mock_logger, ) -> None: """Tests successful creation of an LSM6DSOX IMU instance. @@ -71,8 +72,8 @@ def test_create_imu( def test_create_imu_failed( mock_lsm6dsox: MagicMock, - mock_i2c: MagicMock, - mock_logger: MagicMock, + mock_i2c: I2C, + mock_logger, ) -> None: """Tests that initialization is retried when it fails. @@ -92,8 +93,8 @@ def test_create_imu_failed( def test_get_acceleration_success( mock_lsm6dsox: MagicMock, - mock_i2c: MagicMock, - mock_logger: MagicMock, + mock_i2c: I2C, + mock_logger: Logger, ) -> None: """Tests successful retrieval of the acceleration vector. @@ -117,8 +118,8 @@ def test_get_acceleration_success( def test_get_acceleration_failure( mock_lsm6dsox: MagicMock, - mock_i2c: MagicMock, - mock_logger: MagicMock, + mock_i2c: I2C, + mock_logger, ) -> None: """Tests handling of exceptions when retrieving the acceleration vector. @@ -143,8 +144,8 @@ def test_get_acceleration_failure( def test_get_angular_velocity_success( mock_lsm6dsox: MagicMock, - mock_i2c: MagicMock, - mock_logger: MagicMock, + mock_i2c: I2C, + mock_logger: Logger, ) -> None: """Tests successful retrieval of the angular_velocity vector. @@ -167,8 +168,8 @@ def test_get_angular_velocity_success( def test_get_angular_velocity_failure( mock_lsm6dsox: MagicMock, - mock_i2c: MagicMock, - mock_logger: MagicMock, + mock_i2c: I2C, + mock_logger, ) -> None: """Tests handling of exceptions when retrieving the angular_velocity vector. @@ -192,8 +193,8 @@ def test_get_angular_velocity_failure( def test_get_temperature_success( mock_lsm6dsox: MagicMock, - mock_i2c: MagicMock, - mock_logger: MagicMock, + mock_i2c: I2C, + mock_logger: Logger, ) -> None: """Tests successful retrieval of the temperature. @@ -209,13 +210,13 @@ def test_get_temperature_success( temp = imu_manager.get_temperature() assert isinstance(temp, Temperature) - assert math.isclose(temp.value, expected_temp, rel_tol=1e-9) + assert pytest.approx(expected_temp, rel=1e-9) == temp.value def test_get_temperature_failure( mock_lsm6dsox: MagicMock, - mock_i2c: MagicMock, - mock_logger: MagicMock, + mock_i2c: I2C, + mock_logger, ) -> None: """Tests handling of exceptions when retrieving the temperature. diff --git a/tests/unit/hardware/light_sensor/manager/test_veml7700_manager.py b/tests/unit/hardware/light_sensor/manager/test_veml7700_manager.py index f0b8b863..8af8cd11 100644 --- a/tests/unit/hardware/light_sensor/manager/test_veml7700_manager.py +++ b/tests/unit/hardware/light_sensor/manager/test_veml7700_manager.py @@ -1,5 +1,7 @@ """Test the VEML7700Manager class.""" +# pyright: reportAttributeAccessIssue=false, reportOptionalMemberAccess=false, reportReturnType=false + from typing import Generator from unittest.mock import MagicMock, PropertyMock, patch diff --git a/tests/unit/hardware/magnetometer/manager/test_lis2mdl_manager.py b/tests/unit/hardware/magnetometer/manager/test_lis2mdl_manager.py index b5cb0a5f..3ac8510c 100644 --- a/tests/unit/hardware/magnetometer/manager/test_lis2mdl_manager.py +++ b/tests/unit/hardware/magnetometer/manager/test_lis2mdl_manager.py @@ -5,10 +5,13 @@ retrieval, and error handling for magnetic field vector readings. """ +# pyright: reportAttributeAccessIssue=false, reportOptionalMemberAccess=false, reportReturnType=false + from typing import Generator from unittest.mock import MagicMock, patch import pytest +from busio import I2C from mocks.adafruit_lis2mdl.lis2mdl import LIS2MDL from pysquared.hardware.exception import HardwareInitializationError @@ -48,8 +51,8 @@ def mock_lis2mdl(mock_i2c: MagicMock) -> Generator[MagicMock, None, None]: def test_create_magnetometer( mock_lis2mdl: MagicMock, - mock_i2c: MagicMock, - mock_logger: MagicMock, + mock_i2c: I2C, + mock_logger, ) -> None: """Tests successful creation of a LIS2MDL magnetometer instance. @@ -66,8 +69,8 @@ def test_create_magnetometer( def test_create_magnetometer_failed( mock_lis2mdl: MagicMock, - mock_i2c: MagicMock, - mock_logger: MagicMock, + mock_i2c: I2C, + mock_logger, ) -> None: """Tests that initialization is retried when it fails. @@ -91,8 +94,8 @@ def test_create_magnetometer_failed( def test_get_magnetic_field_success( mock_lis2mdl: MagicMock, - mock_i2c: MagicMock, - mock_logger: MagicMock, + mock_i2c: I2C, + mock_logger, ) -> None: """Tests successful retrieval of the magnetic field vector. @@ -122,8 +125,8 @@ def mock_magnetic(): def test_get_magnetic_field_unknown_error( mock_lis2mdl: MagicMock, - mock_i2c: MagicMock, - mock_logger: MagicMock, + mock_i2c: I2C, + mock_logger, ) -> None: """Tests handling of unknown errors when retrieving the magnetic field vector. diff --git a/tests/unit/hardware/power_monitor/manager/test_ina219_manager.py b/tests/unit/hardware/power_monitor/manager/test_ina219_manager.py index 72f3f959..a984749c 100644 --- a/tests/unit/hardware/power_monitor/manager/test_ina219_manager.py +++ b/tests/unit/hardware/power_monitor/manager/test_ina219_manager.py @@ -5,6 +5,8 @@ retrieval, and error handling for bus voltage, shunt voltage, and current readings. """ +# pyright: reportAttributeAccessIssue=false, reportOptionalMemberAccess=false, reportReturnType=false + from typing import Generator from unittest.mock import MagicMock, PropertyMock, patch diff --git a/tests/unit/hardware/radio/manager/test_rfm9x_manager.py b/tests/unit/hardware/radio/manager/test_rfm9x_manager.py index 5246ecb9..6c79f2ec 100644 --- a/tests/unit/hardware/radio/manager/test_rfm9x_manager.py +++ b/tests/unit/hardware/radio/manager/test_rfm9x_manager.py @@ -6,7 +6,8 @@ and RSSI. """ -import math +# pyright: reportAttributeAccessIssue=false, reportOptionalMemberAccess=false, reportReturnType=false + from typing import Generator from unittest.mock import MagicMock, patch @@ -72,7 +73,7 @@ def mock_radio_config() -> RadioConfig: @pytest.fixture def mock_rfm9x( - mock_spi: MagicMock, mock_chip_select: MagicMock, mock_reset: MagicMock + mock_spi: SPI, mock_chip_select: DigitalInOut, mock_reset: DigitalInOut ) -> Generator[MagicMock, None, None]: """Mocks the RFM9x class. @@ -111,10 +112,10 @@ def mock_rfm9xfsk( def test_init_fsk_success( mock_rfm9x: MagicMock, mock_rfm9xfsk: MagicMock, - mock_logger: MagicMock, - mock_spi: MagicMock, - mock_chip_select: MagicMock, - mock_reset: MagicMock, + mock_logger, + mock_spi: SPI, + mock_chip_select: DigitalInOut, + mock_reset: DigitalInOut, mock_radio_config: RadioConfig, ): """Tests successful initialization when radio_config.modulation is FSK. @@ -161,10 +162,10 @@ def test_init_fsk_success( def test_init_lora_success( mock_rfm9x: MagicMock, mock_rfm9xfsk: MagicMock, - mock_logger: MagicMock, - mock_spi: MagicMock, - mock_chip_select: MagicMock, - mock_reset: MagicMock, + mock_logger, + mock_spi: SPI, + mock_chip_select: DigitalInOut, + mock_reset: DigitalInOut, mock_radio_config: RadioConfig, ): """Tests successful initialization when radio_config.modulation is LoRa. @@ -219,10 +220,10 @@ def test_init_lora_success( def test_init_lora_high_sf_success( mock_rfm9x: MagicMock, mock_rfm9xfsk: MagicMock, - mock_logger: MagicMock, - mock_spi: MagicMock, - mock_chip_select: MagicMock, - mock_reset: MagicMock, + mock_logger, + mock_spi: SPI, + mock_chip_select: DigitalInOut, + mock_reset: DigitalInOut, mock_radio_config: RadioConfig, # Use base config ): """Tests LoRa initialization with high spreading factor. @@ -260,10 +261,10 @@ def test_init_lora_high_sf_success( def test_init_failed_fsk( mock_rfm9xfsk: MagicMock, - mock_logger: MagicMock, - mock_spi: MagicMock, - mock_chip_select: MagicMock, - mock_reset: MagicMock, + mock_logger, + mock_spi: SPI, + mock_chip_select: DigitalInOut, + mock_reset: DigitalInOut, mock_radio_config: RadioConfig, ): """Tests __init__ retries on FSK initialization failure. @@ -295,10 +296,10 @@ def test_init_failed_fsk( def test_init_failed_lora( mock_rfm9x: MagicMock, - mock_logger: MagicMock, - mock_spi: MagicMock, - mock_chip_select: MagicMock, - mock_reset: MagicMock, + mock_logger, + mock_spi: SPI, + mock_chip_select: DigitalInOut, + mock_reset: DigitalInOut, mock_radio_config: RadioConfig, ): """Tests __init__ retries on LoRa initialization failure. @@ -331,10 +332,10 @@ def test_init_failed_lora( def test_send_success_bytes( mock_rfm9x: MagicMock, - mock_logger: MagicMock, - mock_spi: MagicMock, - mock_chip_select: MagicMock, - mock_reset: MagicMock, + mock_logger, + mock_spi: SPI, + mock_chip_select: DigitalInOut, + mock_reset: DigitalInOut, mock_radio_config: RadioConfig, ): """Tests successful sending of bytes. @@ -368,10 +369,10 @@ def test_send_success_bytes( def test_send_unlicensed( mock_rfm9x: MagicMock, - mock_logger: MagicMock, - mock_spi: MagicMock, - mock_chip_select: MagicMock, - mock_reset: MagicMock, + mock_logger, + mock_spi: SPI, + mock_chip_select: DigitalInOut, + mock_reset: DigitalInOut, mock_radio_config: RadioConfig, ): """Tests send attempt when not licensed. @@ -410,10 +411,10 @@ def test_send_unlicensed( def test_send_exception( mock_rfm9x: MagicMock, - mock_logger: MagicMock, - mock_spi: MagicMock, - mock_chip_select: MagicMock, - mock_reset: MagicMock, + mock_logger, + mock_spi: SPI, + mock_chip_select: DigitalInOut, + mock_reset: DigitalInOut, mock_radio_config: RadioConfig, ): """Tests handling of exception during radio.send(). @@ -450,10 +451,10 @@ def test_send_exception( def test_get_modulation_initialized( mock_rfm9x: MagicMock, mock_rfm9xfsk: MagicMock, - mock_logger: MagicMock, - mock_spi: MagicMock, - mock_chip_select: MagicMock, - mock_reset: MagicMock, + mock_logger, + mock_spi: SPI, + mock_chip_select: DigitalInOut, + mock_reset: DigitalInOut, mock_radio_config: RadioConfig, ): """Tests get_modulation when radio is initialized. @@ -502,10 +503,10 @@ def test_get_modulation_initialized( def test_get_temperature_success( mock_rfm9x: MagicMock, mock_rfm9xfsk: MagicMock, - mock_logger: MagicMock, - mock_spi: MagicMock, - mock_chip_select: MagicMock, - mock_reset: MagicMock, + mock_logger, + mock_spi: SPI, + mock_chip_select: DigitalInOut, + mock_reset: DigitalInOut, mock_radio_config: RadioConfig, raw_value: int, expected_temperature: float, @@ -539,7 +540,7 @@ def test_get_temperature_success( temp = manager.get_temperature() assert isinstance(temp, Temperature) - assert math.isclose(temp.value, expected_temperature, rel_tol=1e-9) + assert temp.value == pytest.approx(expected_temperature, rel=1e-9) mock_radio_instance.read_u8.assert_called_once_with(0x5B) mock_logger.debug.assert_called_with("Radio temperature read", temp=temp.value) @@ -547,10 +548,10 @@ def test_get_temperature_success( def test_get_temperature_read_exception( mock_rfm9x: MagicMock, mock_rfm9xfsk: MagicMock, - mock_logger: MagicMock, - mock_spi: MagicMock, - mock_chip_select: MagicMock, - mock_reset: MagicMock, + mock_logger, + mock_spi: SPI, + mock_chip_select: DigitalInOut, + mock_reset: DigitalInOut, mock_radio_config: RadioConfig, ): """Tests handling exception during radio.read_u8(). @@ -585,10 +586,10 @@ def test_get_temperature_read_exception( def test_receive_success( mock_rfm9x: MagicMock, - mock_logger: MagicMock, - mock_spi: MagicMock, - mock_chip_select: MagicMock, - mock_reset: MagicMock, + mock_logger, + mock_spi: SPI, + mock_chip_select: DigitalInOut, + mock_reset: DigitalInOut, mock_radio_config: RadioConfig, ): """Tests successful reception of a message. @@ -625,10 +626,10 @@ def test_receive_success( def test_receive_no_message( mock_rfm9x: MagicMock, - mock_logger: MagicMock, - mock_spi: MagicMock, - mock_chip_select: MagicMock, - mock_reset: MagicMock, + mock_logger, + mock_spi: SPI, + mock_chip_select: DigitalInOut, + mock_reset: DigitalInOut, mock_radio_config: RadioConfig, ): """Tests receiving when no message is available (timeout). @@ -665,10 +666,10 @@ def test_receive_no_message( def test_receive_exception( mock_rfm9x: MagicMock, - mock_logger: MagicMock, - mock_spi: MagicMock, - mock_chip_select: MagicMock, - mock_reset: MagicMock, + mock_logger, + mock_spi: SPI, + mock_chip_select: DigitalInOut, + mock_reset: DigitalInOut, mock_radio_config: RadioConfig, ): """Tests handling of exception during radio.receive(). @@ -704,7 +705,7 @@ def test_receive_exception( def test_modify_lora_config( - mock_logger: MagicMock, + mock_logger: Logger, mock_spi: MagicMock, mock_chip_select: MagicMock, mock_reset: MagicMock, @@ -750,7 +751,7 @@ def test_modify_lora_config( def test_modify_lora_config_high_sf_success( - mock_logger: MagicMock, + mock_logger: Logger, mock_spi: MagicMock, mock_chip_select: MagicMock, mock_reset: MagicMock, @@ -789,7 +790,7 @@ def test_modify_lora_config_high_sf_success( def test_modify_fsk_config( - mock_logger: MagicMock, + mock_logger: Logger, mock_spi: MagicMock, mock_chip_select: MagicMock, mock_reset: MagicMock, @@ -831,7 +832,7 @@ def test_modify_fsk_config( def test_get_max_packet_size_lora( - mock_logger: MagicMock, + mock_logger: Logger, mock_spi: MagicMock, mock_chip_select: MagicMock, mock_reset: MagicMock, @@ -862,7 +863,7 @@ def test_get_max_packet_size_lora( def test_get_max_packet_size_fsk( - mock_logger: MagicMock, + mock_logger: Logger, mock_spi: MagicMock, mock_chip_select: MagicMock, mock_reset: MagicMock, @@ -894,10 +895,10 @@ def test_get_max_packet_size_fsk( def test_get_rssi( mock_rfm9x: MagicMock, - mock_logger: MagicMock, - mock_spi: MagicMock, - mock_chip_select: MagicMock, - mock_reset: MagicMock, + mock_logger, + mock_spi: SPI, + mock_chip_select: DigitalInOut, + mock_reset: DigitalInOut, mock_radio_config: RadioConfig, ): """Tests getting the RSSI value from the radio. diff --git a/tests/unit/hardware/radio/manager/test_sx126x_manager.py b/tests/unit/hardware/radio/manager/test_sx126x_manager.py index 627ae49f..9c5b0da8 100644 --- a/tests/unit/hardware/radio/manager/test_sx126x_manager.py +++ b/tests/unit/hardware/radio/manager/test_sx126x_manager.py @@ -5,6 +5,8 @@ and retrieving the current modulation. """ +# pyright: reportAttributeAccessIssue=false, reportOptionalMemberAccess=false, reportReturnType=false + from typing import Generator from unittest.mock import MagicMock, call, patch @@ -85,11 +87,11 @@ def mock_radio_config() -> RadioConfig: @pytest.fixture def mock_sx1262( - mock_spi: MagicMock, - mock_chip_select: MagicMock, - mock_reset: MagicMock, - mock_irq: MagicMock, - mock_gpio: MagicMock, + mock_spi: SPI, + mock_chip_select: DigitalInOut, + mock_reset: DigitalInOut, + mock_irq: DigitalInOut, + mock_gpio: DigitalInOut, ) -> Generator[MagicMock, None, None]: """Mocks the SX1262 class. @@ -112,12 +114,12 @@ def mock_sx1262( def test_init_fsk_success( mock_sx1262: MagicMock, - mock_logger: MagicMock, - mock_spi: MagicMock, - mock_chip_select: MagicMock, - mock_reset: MagicMock, - mock_irq: MagicMock, - mock_gpio: MagicMock, + mock_logger, + mock_spi: SPI, + mock_chip_select: DigitalInOut, + mock_reset: DigitalInOut, + mock_irq: DigitalInOut, + mock_gpio: DigitalInOut, mock_radio_config: RadioConfig, ): """Tests successful initialization when radio_config.modulation is FSK. @@ -163,12 +165,12 @@ def test_init_fsk_success( def test_init_lora_success( mock_sx1262: MagicMock, - mock_logger: MagicMock, - mock_spi: MagicMock, - mock_chip_select: MagicMock, - mock_reset: MagicMock, - mock_irq: MagicMock, - mock_gpio: MagicMock, + mock_logger, + mock_spi: SPI, + mock_chip_select: DigitalInOut, + mock_reset: DigitalInOut, + mock_irq: DigitalInOut, + mock_gpio: DigitalInOut, mock_radio_config: RadioConfig, ): """Tests successful initialization when radio_config.modulation is LoRa. @@ -219,12 +221,12 @@ def test_init_lora_success( def test_init_failed_fsk( mock_sx1262: MagicMock, - mock_logger: MagicMock, - mock_spi: MagicMock, - mock_chip_select: MagicMock, - mock_reset: MagicMock, - mock_irq: MagicMock, - mock_gpio: MagicMock, + mock_logger, + mock_spi: SPI, + mock_chip_select: DigitalInOut, + mock_reset: DigitalInOut, + mock_irq: DigitalInOut, + mock_gpio: DigitalInOut, mock_radio_config: RadioConfig, ): """Tests __init__ retries on FSK initialization failure. @@ -262,12 +264,12 @@ def test_init_failed_fsk( def test_init_failed_lora( mock_sx1262: MagicMock, - mock_logger: MagicMock, - mock_spi: MagicMock, - mock_chip_select: MagicMock, - mock_reset: MagicMock, - mock_irq: MagicMock, - mock_gpio: MagicMock, + mock_logger, + mock_spi: SPI, + mock_chip_select: DigitalInOut, + mock_reset: DigitalInOut, + mock_irq: DigitalInOut, + mock_gpio: DigitalInOut, mock_radio_config: RadioConfig, ): """Tests __init__ retries on FSK initialization failure. @@ -309,12 +311,12 @@ def test_init_failed_lora( @pytest.fixture def initialized_manager( mock_sx1262: MagicMock, - mock_logger: MagicMock, - mock_spi: MagicMock, - mock_chip_select: MagicMock, - mock_reset: MagicMock, - mock_irq: MagicMock, - mock_gpio: MagicMock, + mock_logger, + mock_spi: SPI, + mock_chip_select: DigitalInOut, + mock_reset: DigitalInOut, + mock_irq: DigitalInOut, + mock_gpio: DigitalInOut, mock_radio_config: RadioConfig, ) -> SX126xManager: """Provides an initialized SX126xManager instance with a mock radio. @@ -364,12 +366,12 @@ def test_send_success_bytes( def test_send_unlicensed( mock_sx1262: MagicMock, - mock_logger: MagicMock, - mock_spi: MagicMock, - mock_chip_select: MagicMock, - mock_reset: MagicMock, - mock_irq: MagicMock, - mock_gpio: MagicMock, + mock_logger, + mock_spi: SPI, + mock_chip_select: DigitalInOut, + mock_reset: DigitalInOut, + mock_irq: DigitalInOut, + mock_gpio: DigitalInOut, mock_radio_config: RadioConfig, ): """Tests send attempt when not licensed. @@ -576,12 +578,12 @@ def test_receive_exception( def test_get_modulation_initialized( mock_sx1262: MagicMock, - mock_logger: MagicMock, - mock_spi: MagicMock, - mock_chip_select: MagicMock, - mock_reset: MagicMock, - mock_irq: MagicMock, - mock_gpio: MagicMock, + mock_logger: Logger, + mock_spi: SPI, + mock_chip_select: DigitalInOut, + mock_reset: DigitalInOut, + mock_irq: DigitalInOut, + mock_gpio: DigitalInOut, mock_radio_config: RadioConfig, ): """Tests get_modulation when radio is initialized. diff --git a/tests/unit/hardware/radio/manager/test_sx1280_manager.py b/tests/unit/hardware/radio/manager/test_sx1280_manager.py index 4d2521eb..3741fa51 100644 --- a/tests/unit/hardware/radio/manager/test_sx1280_manager.py +++ b/tests/unit/hardware/radio/manager/test_sx1280_manager.py @@ -5,6 +5,8 @@ and retrieving the current modulation. """ +# pyright: reportAttributeAccessIssue=false, reportOptionalMemberAccess=false, reportReturnType=false + from typing import Generator from unittest.mock import MagicMock, patch @@ -86,12 +88,12 @@ def mock_radio_config() -> RadioConfig: @pytest.fixture def mock_sx1280( - mock_spi: MagicMock, - mock_chip_select: MagicMock, - mock_reset: MagicMock, - mock_busy: MagicMock, - mock_txen: MagicMock, - mock_rxen: MagicMock, + mock_spi: SPI, + mock_chip_select: DigitalInOut, + mock_reset: DigitalInOut, + mock_busy: DigitalInOut, + mock_txen: DigitalInOut, + mock_rxen: DigitalInOut, ) -> Generator[MagicMock, None, None]: """Mocks the SX1280 class. @@ -121,13 +123,13 @@ def mock_sx1280( def test_init_fsk_success( mock_sx1280: MagicMock, - mock_logger: MagicMock, - mock_spi: MagicMock, - mock_chip_select: MagicMock, - mock_reset: MagicMock, - mock_busy: MagicMock, - mock_txen: MagicMock, - mock_rxen: MagicMock, + mock_logger, + mock_spi: SPI, + mock_chip_select: DigitalInOut, + mock_reset: DigitalInOut, + mock_busy: DigitalInOut, + mock_txen: DigitalInOut, + mock_rxen: DigitalInOut, mock_radio_config: RadioConfig, ): """Tests successful initialization when radio_config.modulation is set to FSK. @@ -175,13 +177,13 @@ def test_init_fsk_success( def test_init_lora_success( mock_sx1280: MagicMock, - mock_logger: MagicMock, - mock_spi: MagicMock, - mock_chip_select: MagicMock, - mock_reset: MagicMock, - mock_busy: MagicMock, - mock_txen: MagicMock, - mock_rxen: MagicMock, + mock_logger, + mock_spi: SPI, + mock_chip_select: DigitalInOut, + mock_reset: DigitalInOut, + mock_busy: DigitalInOut, + mock_txen: DigitalInOut, + mock_rxen: DigitalInOut, mock_radio_config: RadioConfig, ): """Tests successful initialization when radio_config.modulation is set to LoRa. @@ -230,13 +232,13 @@ def test_init_lora_success( def test_init_failed_fsk( mock_sx1280: MagicMock, - mock_logger: MagicMock, - mock_spi: MagicMock, - mock_chip_select: MagicMock, - mock_reset: MagicMock, - mock_busy: MagicMock, - mock_txen: MagicMock, - mock_rxen: MagicMock, + mock_logger, + mock_spi: SPI, + mock_chip_select: DigitalInOut, + mock_reset: DigitalInOut, + mock_busy: DigitalInOut, + mock_txen: DigitalInOut, + mock_rxen: DigitalInOut, mock_radio_config: RadioConfig, ): """Tests initialization retries on FSK initialization failure. @@ -275,13 +277,13 @@ def test_init_failed_fsk( def test_init_failed_lora( mock_sx1280: MagicMock, - mock_logger: MagicMock, - mock_spi: MagicMock, - mock_chip_select: MagicMock, - mock_reset: MagicMock, - mock_busy: MagicMock, - mock_txen: MagicMock, - mock_rxen: MagicMock, + mock_logger, + mock_spi: SPI, + mock_chip_select: DigitalInOut, + mock_reset: DigitalInOut, + mock_busy: DigitalInOut, + mock_txen: DigitalInOut, + mock_rxen: DigitalInOut, mock_radio_config: RadioConfig, ): """Tests initialization retries on LoRa initialization failure. @@ -320,13 +322,13 @@ def test_init_failed_lora( def test_send_success_bytes( mock_sx1280: MagicMock, - mock_logger: MagicMock, - mock_spi: MagicMock, - mock_chip_select: MagicMock, - mock_reset: MagicMock, - mock_busy: MagicMock, - mock_txen: MagicMock, - mock_rxen: MagicMock, + mock_logger: Logger, + mock_spi: SPI, + mock_chip_select: DigitalInOut, + mock_reset: DigitalInOut, + mock_busy: DigitalInOut, + mock_txen: DigitalInOut, + mock_rxen: DigitalInOut, mock_radio_config: RadioConfig, ): """Tests successful sending of bytes. @@ -367,13 +369,13 @@ def test_send_success_bytes( def test_send_unlicensed( mock_sx1280: MagicMock, - mock_logger: MagicMock, - mock_spi: MagicMock, - mock_chip_select: MagicMock, - mock_reset: MagicMock, - mock_busy: MagicMock, - mock_txen: MagicMock, - mock_rxen: MagicMock, + mock_logger, + mock_spi: SPI, + mock_chip_select: DigitalInOut, + mock_reset: DigitalInOut, + mock_busy: DigitalInOut, + mock_txen: DigitalInOut, + mock_rxen: DigitalInOut, mock_radio_config: RadioConfig, ): """Tests send attempt when not licensed. @@ -419,13 +421,13 @@ def test_send_unlicensed( def test_send_exception( mock_sx1280: MagicMock, - mock_logger: MagicMock, - mock_spi: MagicMock, - mock_chip_select: MagicMock, - mock_reset: MagicMock, - mock_busy: MagicMock, - mock_txen: MagicMock, - mock_rxen: MagicMock, + mock_logger, + mock_spi: SPI, + mock_chip_select: DigitalInOut, + mock_reset: DigitalInOut, + mock_busy: DigitalInOut, + mock_txen: DigitalInOut, + mock_rxen: DigitalInOut, mock_radio_config: RadioConfig, ): """Tests handling of exception during radio.send(). @@ -468,13 +470,13 @@ def test_send_exception( def test_get_modulation_initialized( mock_sx1280: MagicMock, - mock_logger: MagicMock, - mock_spi: MagicMock, - mock_chip_select: MagicMock, - mock_reset: MagicMock, - mock_busy: MagicMock, - mock_txen: MagicMock, - mock_rxen: MagicMock, + mock_logger, + mock_spi: SPI, + mock_chip_select: DigitalInOut, + mock_reset: DigitalInOut, + mock_busy: DigitalInOut, + mock_txen: DigitalInOut, + mock_rxen: DigitalInOut, mock_radio_config: RadioConfig, ): """Tests get_modulation when radio is initialized. @@ -522,13 +524,13 @@ def test_get_modulation_initialized( def test_receive_success( mock_sx1280: MagicMock, - mock_logger: MagicMock, - mock_spi: MagicMock, - mock_chip_select: MagicMock, - mock_reset: MagicMock, - mock_busy: MagicMock, - mock_txen: MagicMock, - mock_rxen: MagicMock, + mock_logger, + mock_spi: SPI, + mock_chip_select: DigitalInOut, + mock_reset: DigitalInOut, + mock_busy: DigitalInOut, + mock_txen: DigitalInOut, + mock_rxen: DigitalInOut, mock_radio_config: RadioConfig, ): """Tests successful reception of a message. @@ -572,13 +574,13 @@ def test_receive_success( def test_receive_no_message( mock_sx1280: MagicMock, - mock_logger: MagicMock, - mock_spi: MagicMock, - mock_chip_select: MagicMock, - mock_reset: MagicMock, - mock_busy: MagicMock, - mock_txen: MagicMock, - mock_rxen: MagicMock, + mock_logger, + mock_spi: SPI, + mock_chip_select: DigitalInOut, + mock_reset: DigitalInOut, + mock_busy: DigitalInOut, + mock_txen: DigitalInOut, + mock_rxen: DigitalInOut, mock_radio_config: RadioConfig, ): """Tests receiving when no message is available (timeout). @@ -622,13 +624,13 @@ def test_receive_no_message( def test_receive_exception( mock_sx1280: MagicMock, - mock_logger: MagicMock, - mock_spi: MagicMock, - mock_chip_select: MagicMock, - mock_reset: MagicMock, - mock_busy: MagicMock, - mock_txen: MagicMock, - mock_rxen: MagicMock, + mock_logger, + mock_spi: SPI, + mock_chip_select: DigitalInOut, + mock_reset: DigitalInOut, + mock_busy: DigitalInOut, + mock_txen: DigitalInOut, + mock_rxen: DigitalInOut, mock_radio_config: RadioConfig, ): """Tests handling of exception during radio.receive(). diff --git a/tests/unit/hardware/temperature_sensor/manager/test_mcp9808_manager.py b/tests/unit/hardware/temperature_sensor/manager/test_mcp9808_manager.py index 92dda5f5..3316039f 100644 --- a/tests/unit/hardware/temperature_sensor/manager/test_mcp9808_manager.py +++ b/tests/unit/hardware/temperature_sensor/manager/test_mcp9808_manager.py @@ -1,9 +1,12 @@ """Tests for the MCP9808Manager class.""" +# pyright: reportAttributeAccessIssue=false, reportOptionalMemberAccess=false, reportReturnType=false + from typing import Generator from unittest.mock import MagicMock, PropertyMock, patch import pytest +from busio import I2C from mocks.adafruit_mcp9808.mcp9808 import MCP9808 from pysquared.hardware.exception import HardwareInitializationError @@ -35,7 +38,7 @@ def mock_i2c(): @pytest.fixture -def mock_mcp9808(mock_i2c: MagicMock) -> Generator[MagicMock, None, None]: +def mock_mcp9808(mock_i2c: I2C) -> Generator[MagicMock, None, None]: """Mocks the MCP9808 class. Args: diff --git a/tests/unit/hardware/test_burnwire.py b/tests/unit/hardware/test_burnwire.py index 7077eeb1..89d6c0e8 100644 --- a/tests/unit/hardware/test_burnwire.py +++ b/tests/unit/hardware/test_burnwire.py @@ -5,6 +5,8 @@ operations, error handling, and cleanup procedures. """ +# pyright: reportAttributeAccessIssue=false, reportOptionalMemberAccess=false, reportFunctionMemberAccess=false + from unittest.mock import ANY, MagicMock, patch import pytest diff --git a/tests/unit/hardware/test_digitalio.py b/tests/unit/hardware/test_digitalio.py index 35403ea7..4d7b080d 100644 --- a/tests/unit/hardware/test_digitalio.py +++ b/tests/unit/hardware/test_digitalio.py @@ -9,6 +9,7 @@ import pytest from digitalio import Direction +from microcontroller import Pin from pysquared.hardware.digitalio import initialize_pin from pysquared.hardware.exception import HardwareInitializationError @@ -17,7 +18,7 @@ @patch("pysquared.hardware.digitalio.DigitalInOut") @patch("pysquared.hardware.digitalio.Pin") -def test_initialize_pin_success(mock_pin: MagicMock, mock_digital_in_out: MagicMock): +def test_initialize_pin_success(mock_pin: Pin, mock_digital_in_out: MagicMock): """Tests successful initialization of a digital pin. Args: @@ -28,7 +29,6 @@ def test_initialize_pin_success(mock_pin: MagicMock, mock_digital_in_out: MagicM mock_logger = MagicMock(spec=Logger) # Mock pin and direction - mock_pin = mock_pin() mock_direction = Direction.OUTPUT initial_value = True @@ -47,7 +47,7 @@ def test_initialize_pin_success(mock_pin: MagicMock, mock_digital_in_out: MagicM @patch("pysquared.hardware.digitalio.DigitalInOut") @patch("pysquared.hardware.digitalio.Pin") -def test_initialize_pin_failure(mock_pin: MagicMock, mock_digital_in_out: MagicMock): +def test_initialize_pin_failure(mock_pin: Pin, mock_digital_in_out: MagicMock): """Tests digital pin initialization failure with retries. Args: @@ -57,17 +57,12 @@ def test_initialize_pin_failure(mock_pin: MagicMock, mock_digital_in_out: MagicM # Mock the logger mock_logger = MagicMock(spec=Logger) - # Mock pin and direction - mock_pin = mock_pin() - mock_direction = Direction.OUTPUT - initial_value = True - # Mock DigitalInOut to raise an exception mock_digital_in_out.side_effect = Exception("Simulated failure") # Call the function and assert exception with pytest.raises(HardwareInitializationError): - initialize_pin(mock_logger, mock_pin, mock_direction, initial_value) + initialize_pin(mock_logger, mock_pin, Direction.OUTPUT, True) # Assertions mock_digital_in_out.assert_called_once_with(mock_pin) diff --git a/tests/unit/rtc/manager/test_microcontroller_manager.py b/tests/unit/rtc/manager/test_microcontroller_manager.py index ba768ef8..9885f568 100644 --- a/tests/unit/rtc/manager/test_microcontroller_manager.py +++ b/tests/unit/rtc/manager/test_microcontroller_manager.py @@ -5,6 +5,9 @@ initialization and setting the time. """ +# PR: https://github.com/adafruit/circuitpython/pull/10603 +# pyright: reportAttributeAccessIssue=false + import time import pytest diff --git a/tests/unit/rtc/manager/test_rv3028_manager.py b/tests/unit/rtc/manager/test_rv3028_manager.py index bf54764d..9a356c9c 100644 --- a/tests/unit/rtc/manager/test_rv3028_manager.py +++ b/tests/unit/rtc/manager/test_rv3028_manager.py @@ -4,6 +4,7 @@ the RV3028 Real-Time Clock (RTC). The tests cover initialization, successful time setting, and error handling during time setting operations. """ +# pyright: reportAttributeAccessIssue=false, reportOptionalMemberAccess=false, reportReturnType=false from typing import Generator from unittest.mock import MagicMock, patch @@ -30,7 +31,7 @@ def mock_logger() -> MagicMock: @pytest.fixture -def mock_rv3028(mock_i2c: MagicMock) -> Generator[MagicMock, None, None]: +def mock_rv3028(mock_i2c: I2C) -> Generator[MagicMock, None, None]: """Mocks the RV3028 class. Args: @@ -40,11 +41,12 @@ def mock_rv3028(mock_i2c: MagicMock) -> Generator[MagicMock, None, None]: A MagicMock instance of RV3028. """ with patch("pysquared.rtc.manager.rv3028.RV3028") as mock_class: - mock_class.return_value = RV3028(mock_i2c) + mock_instance = MagicMock(spec=RV3028) + mock_class.return_value = mock_instance yield mock_class -def test_create_rtc(mock_rv3028, mock_i2c: MagicMock, mock_logger: MagicMock) -> None: +def test_create_rtc(mock_rv3028, mock_i2c: I2C, mock_logger: Logger) -> None: """Tests successful creation of an RV3028 RTC instance. Args: @@ -60,8 +62,8 @@ def test_create_rtc(mock_rv3028, mock_i2c: MagicMock, mock_logger: MagicMock) -> def test_create_rtc_failed( mock_rv3028: MagicMock, - mock_i2c: MagicMock, - mock_logger: MagicMock, + mock_i2c: I2C, + mock_logger: Logger, ) -> None: """Tests that initialization is retried when it fails. @@ -80,9 +82,7 @@ def test_create_rtc_failed( assert mock_rv3028.call_count <= 3 -def test_set_time_success( - mock_rv3028, mock_i2c: MagicMock, mock_logger: MagicMock -) -> None: +def test_set_time_success(mock_rv3028, mock_i2c: I2C, mock_logger: Logger) -> None: """Tests successful setting of the time. Args: @@ -106,7 +106,7 @@ def test_set_time_success( def test_set_time_failure_set_date( - mock_rv3028, mock_i2c: MagicMock, mock_logger: MagicMock + mock_rv3028, mock_i2c: I2C, mock_logger: Logger ) -> None: """Tests handling of exceptions during set_date. @@ -133,7 +133,7 @@ def test_set_time_failure_set_date( def test_set_time_failure_set_time( - mock_rv3028, mock_i2c: MagicMock, mock_logger: MagicMock + mock_rv3028, mock_i2c: I2C, mock_logger: Logger ) -> None: """Tests handling of exceptions during set_time. diff --git a/tests/unit/test_beacon.py b/tests/unit/test_beacon.py index 98099eed..1a197597 100644 --- a/tests/unit/test_beacon.py +++ b/tests/unit/test_beacon.py @@ -5,6 +5,8 @@ sending functionality, and sending with various sensor types. """ +# pyright: reportAttributeAccessIssue=false + import json import time from typing import Optional, Type @@ -162,7 +164,7 @@ def test_beacon_send_basic(mock_time, mock_logger, mock_packet_manager): send_args = mock_packet_manager.send.call_args[0][0] d = json.loads(send_args) assert d["name"] == "test_beacon" - assert d["time"] == time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) + assert d["time"] == time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) # type: ignore # PR: https://github.com/adafruit/circuitpython/pull/10603 assert d["uptime"] == 60.0 diff --git a/tests/unit/test_cdh.py b/tests/unit/test_cdh.py index d935969e..389d311e 100644 --- a/tests/unit/test_cdh.py +++ b/tests/unit/test_cdh.py @@ -17,19 +17,19 @@ @pytest.fixture -def mock_logger() -> MagicMock: +def mock_logger() -> Logger: """Mocks the Logger class.""" return MagicMock(spec=Logger) @pytest.fixture -def mock_packet_manager() -> MagicMock: +def mock_packet_manager() -> PacketManager: """Mocks the PacketManager class.""" return MagicMock(spec=PacketManager) @pytest.fixture -def mock_config() -> MagicMock: +def mock_config() -> Config: """Mocks the Config class.""" config = MagicMock(spec=Config) config.super_secret_code = "test_password" @@ -281,8 +281,11 @@ def test_reset(mock_microcontroller, cdh, mock_logger): mock_logger.info.assert_called_once() +@patch("time.sleep") @patch("pysquared.cdh.microcontroller") -def test_listen_for_commands_reset(mock_microcontroller, cdh, mock_packet_manager): +def test_listen_for_commands_reset( + mock_microcontroller, mock_sleep, cdh, mock_packet_manager +): """Tests listen_for_commands with reset command. Args: @@ -310,9 +313,10 @@ def test_listen_for_commands_reset(mock_microcontroller, cdh, mock_packet_manage mock_microcontroller.reset.assert_called_once() +@patch("time.sleep") @patch("random.choice") def test_listen_for_commands_send_joke( - mock_random_choice, cdh, mock_packet_manager, mock_config + mock_random_choice, mock_sleep, cdh, mock_packet_manager, mock_config ): """Tests listen_for_commands with send_joke command. @@ -338,8 +342,9 @@ def test_listen_for_commands_send_joke( ) +@patch("time.sleep") def test_listen_for_commands_change_radio_modulation( - cdh, mock_packet_manager, mock_config + mock_sleep, cdh, mock_packet_manager, mock_config ): """Tests listen_for_commands with change_radio_modulation command. @@ -363,7 +368,10 @@ def test_listen_for_commands_change_radio_modulation( ) -def test_listen_for_commands_unknown_command(cdh, mock_packet_manager, mock_logger): +@patch("time.sleep") +def test_listen_for_commands_unknown_command( + mock_sleep, cdh, mock_packet_manager, mock_logger +): """Tests listen_for_commands with an unknown command. Args: diff --git a/tests/unit/test_config.py b/tests/unit/test_config.py index 2670bf74..e36cca0d 100644 --- a/tests/unit/test_config.py +++ b/tests/unit/test_config.py @@ -5,6 +5,8 @@ data types, validation rules, and update scenarios. """ +# pyright: reportAttributeAccessIssue=false + import json import os import tempfile diff --git a/tests/unit/test_detumble.py b/tests/unit/test_detumble.py index f6d1bfe5..bdfe9022 100644 --- a/tests/unit/test_detumble.py +++ b/tests/unit/test_detumble.py @@ -7,14 +7,14 @@ import pytest -import pysquared.detumble as detumble +from pysquared.detumble import dot_product, magnetorquer_dipole, x_product def test_dot_product(): """Tests the dot_product function with positive values.""" # dot_product is only ever called to give the square of mag_field mag_field_vector = (30.0, 45.0, 60.0) - result = detumble.dot_product(mag_field_vector, mag_field_vector) + result = dot_product(mag_field_vector, mag_field_vector) assert result == 6525.0 # 30.0*30.0 + 45.0*45.0 + 60.0*60.0 = 6525.0 @@ -22,7 +22,7 @@ def test_dot_product_negatives(): """Tests the dot_product function with negative vectors.""" vector1 = (-1, -2, -3) vector2 = (-4, -5, -6) - result = detumble.dot_product(vector1, vector2) + result = dot_product(vector1, vector2) assert result == 32 # -1*-4 + -2*-5 + -3*-6 @@ -30,14 +30,14 @@ def test_dot_product_large_val(): """Tests the dot_product function with large value vectors.""" vector1 = (1e6, 1e6, 1e6) vector2 = (1e6, 1e6, 1e6) - result = detumble.dot_product(vector1, vector2) + result = dot_product(vector1, vector2) assert result == 3e12 # 1e6*1e6 + 1e6*1e6 + 1e6*1e6 = 3e12 def test_dot_product_zero(): """Tests the dot_product function with zero values.""" vector = (0.0, 0.0, 0.0) - result = detumble.dot_product(vector, vector) + result = dot_product(vector, vector) assert result == 0.0 @@ -47,9 +47,7 @@ def test_x_product(): ang_vel_vector = (0.0, 0.02, 0.015) expected_result = [-0.525, 0.45, 0.6] # x_product takes in tuple arguments and returns a list value - actual_result = detumble.x_product( - mag_field_vector, ang_vel_vector - ) # cross product + actual_result = x_product(mag_field_vector, ang_vel_vector) # cross product assert pytest.approx(actual_result[0], 0.001) == expected_result[0] assert pytest.approx(actual_result[1], 0.001) == expected_result[1] assert pytest.approx(actual_result[2], 0.001) == expected_result[2] @@ -61,7 +59,7 @@ def test_x_product_negatives(): mag_field_vector = (-30.0, -45.0, -60.0) ang_vel_vector = (-0.02, -0.02, -0.015) expected_result = [-0.525, -0.75, -0.3] - actual_result = detumble.x_product(mag_field_vector, ang_vel_vector) + actual_result = x_product(mag_field_vector, ang_vel_vector) assert pytest.approx(actual_result[0], 0.001) == expected_result[0] assert pytest.approx(actual_result[1], 0.001) == expected_result[1] assert pytest.approx(actual_result[2], 0.001) == expected_result[2] @@ -71,7 +69,7 @@ def test_x_product_large_val(): """Tests the x_product function with large values.""" mag_field_vector = (1e6, 1e6, 1e6) ang_vel_vector = (1e6, 1e6, 1e6) # cross product of parallel vector equals 0 - result = detumble.x_product(mag_field_vector, ang_vel_vector) + result = x_product(mag_field_vector, ang_vel_vector) assert result == [0.0, 0.0, 0.0] @@ -79,7 +77,7 @@ def test_x_product_zero(): """Tests the x_product function with zero values.""" mag_field_vector = (0.0, 0.0, 0.0) ang_vel_vector = (0.0, 0.02, 0.015) - result = detumble.x_product(mag_field_vector, ang_vel_vector) + result = x_product(mag_field_vector, ang_vel_vector) assert result == [0.0, 0.0, 0.0] @@ -91,7 +89,7 @@ def test_magnetorquer_dipole(): mag_field = (30.0, -45.0, 60.0) ang_vel = (0.0, 0.02, 0.015) expected_result = [0.023211, -0.00557, -0.007426] - actual_result = detumble.magnetorquer_dipole(mag_field, ang_vel) + actual_result = magnetorquer_dipole(mag_field, ang_vel) assert pytest.approx(actual_result[0], 0.001) == expected_result[0] assert pytest.approx(actual_result[1], 0.001) == expected_result[1] assert pytest.approx(actual_result[2], 0.001) == expected_result[2] @@ -102,12 +100,12 @@ def test_magnetorquer_dipole_zero_mag_field(): mag_field = (0.0, 0.0, 0.0) ang_vel = (0.0, 0.02, 0.015) with pytest.raises(ZeroDivisionError): - detumble.magnetorquer_dipole(mag_field, ang_vel) + magnetorquer_dipole(mag_field, ang_vel) def test_magnetorquer_dipole_zero_ang_vel(): """Tests magnetorquer_dipole with zero angular velocity.""" mag_field = (30.0, -45.0, 60.0) ang_vel = (0.0, 0.0, 0.0) - result = detumble.magnetorquer_dipole(mag_field, ang_vel) + result = magnetorquer_dipole(mag_field, ang_vel) assert result == [0.0, 0.0, 0.0] diff --git a/tests/unit/test_logger.py b/tests/unit/test_logger.py index 87502680..152f15d6 100644 --- a/tests/unit/test_logger.py +++ b/tests/unit/test_logger.py @@ -9,21 +9,21 @@ import pytest from microcontroller import Pin -import pysquared.nvm.counter as counter from pysquared.logger import Logger, _color +from pysquared.nvm.counter import Counter @pytest.fixture def logger(): """Provides a Logger instance for testing without colorization.""" - count = MagicMock(spec=counter.Counter) + count = MagicMock(spec=Counter) return Logger(count) @pytest.fixture def logger_color(): """Provides a Logger instance for testing with colorization enabled.""" - count = MagicMock(spec=counter.Counter) + count = MagicMock(spec=Counter) return Logger(error_counter=count, colorized=True) diff --git a/tests/unit/test_sleep_helper.py b/tests/unit/test_sleep_helper.py index 71cef8ed..04c95129 100644 --- a/tests/unit/test_sleep_helper.py +++ b/tests/unit/test_sleep_helper.py @@ -26,13 +26,13 @@ @pytest.fixture -def mock_logger() -> MagicMock: +def mock_logger() -> Logger: """Mocks the Logger class.""" return MagicMock(spec=Logger) @pytest.fixture -def mock_config() -> MagicMock: +def mock_config() -> Config: """Mocks the Config class with a predefined longest allowable sleep time.""" config = MagicMock(spec=Config) config.longest_allowable_sleep_time = 100 @@ -40,25 +40,25 @@ def mock_config() -> MagicMock: @pytest.fixture -def mock_watchdog() -> MagicMock: +def mock_watchdog() -> Watchdog: """Mocks the Watchdog class.""" return MagicMock(spec=Watchdog) @pytest.fixture def sleep_helper( - mock_logger: MagicMock, - mock_config: MagicMock, - mock_watchdog: MagicMock, + mock_logger: Logger, + mock_config: Config, + mock_watchdog: Watchdog, ) -> SleepHelper: """Provides a SleepHelper instance for testing.""" return SleepHelper(mock_logger, mock_config, mock_watchdog) def test_init( - mock_logger: MagicMock, - mock_config: MagicMock, - mock_watchdog: MagicMock, + mock_logger: Logger, + mock_config: Config, + mock_watchdog: Watchdog, ) -> None: """Tests SleepHelper initialization. @@ -120,7 +120,6 @@ def test_safe_sleep_exceeds_limit( mock_time: MagicMock, sleep_helper: SleepHelper, mock_logger: MagicMock, - mock_config: MagicMock, mock_watchdog: MagicMock, ) -> None: """Tests safe_sleep with duration exceeding the allowable limit. @@ -129,7 +128,6 @@ def test_safe_sleep_exceeds_limit( mock_time: Mocked time module. sleep_helper: SleepHelper instance for testing. mock_logger: Mocked Logger instance. - mock_config: Mocked Config instance. mock_watchdog: Mocked Watchdog instance. """ # Reset mocks diff --git a/tests/unit/test_watchdog.py b/tests/unit/test_watchdog.py index 07f2669a..57d980b8 100644 --- a/tests/unit/test_watchdog.py +++ b/tests/unit/test_watchdog.py @@ -15,20 +15,20 @@ @pytest.fixture -def mock_pin() -> MagicMock: +def mock_pin() -> Pin: """Mocks a microcontroller Pin.""" return MagicMock(spec=Pin) @pytest.fixture -def mock_logger() -> MagicMock: +def mock_logger() -> Logger: """Mocks the Logger class.""" return MagicMock(spec=Logger) @patch("pysquared.watchdog.initialize_pin") def test_watchdog_init( - mock_initialize_pin: MagicMock, mock_logger: MagicMock, mock_pin: MagicMock + mock_initialize_pin: MagicMock, mock_logger: Logger, mock_pin: Pin ) -> None: """Tests Watchdog initialization. @@ -56,8 +56,8 @@ def test_watchdog_init( def test_watchdog_pet( mock_initialize_pin: MagicMock, mock_sleep: MagicMock, - mock_logger: MagicMock, - mock_pin: MagicMock, + mock_logger: Logger, + mock_pin: Pin, ) -> None: """Tests Watchdog pet method using side_effect on sleep. diff --git a/typings/micropython.pyi b/typings/micropython.pyi deleted file mode 100644 index 250c2d0f..00000000 --- a/typings/micropython.pyi +++ /dev/null @@ -1,354 +0,0 @@ -""" -Access and control MicroPython internals. - -MicroPython module: https://docs.micropython.org/en/v1.25.0/library/micropython.html - ---- -Module: 'micropython' on micropython-v1.25.0-rp2-RPI_PICO ---- -pysquared: Borrowed from https://github.com/Josverl/micropython-stubs -https://pypi.org/project/micropython-rp2-stubs/#files -""" - -# MCU: {'build': '', 'ver': '1.25.0', 'version': '1.25.0', 'port': 'rp2', 'board': 'RPI_PICO', 'mpy': 'v6.3', 'family': 'micropython', 'cpu': 'RP2040', 'arch': 'armv6m'} -# Stubber: v1.24.0 -from __future__ import annotations - -from typing import Any, Callable, Optional, Tuple, overload - -from _typeshed import Incomplete -from typing_extensions import ParamSpec, TypeVar - -_T = TypeVar("_T") -_F = TypeVar("_F", bound=Callable[..., Any]) -Const_T = TypeVar("Const_T", int, float, str, bytes, Tuple) -_Param = ParamSpec("_Param") -_Ret = TypeVar("_Ret") - -@overload -def opt_level() -> int: - """ - If *level* is given then this function sets the optimisation level for subsequent - compilation of scripts, and returns ``None``. Otherwise it returns the current - optimisation level. - - The optimisation level controls the following compilation features: - - - Assertions: at level 0 assertion statements are enabled and compiled into the - bytecode; at levels 1 and higher assertions are not compiled. - - Built-in ``__debug__`` variable: at level 0 this variable expands to ``True``; - at levels 1 and higher it expands to ``False``. - - Source-code line numbers: at levels 0, 1 and 2 source-code line number are - stored along with the bytecode so that exceptions can report the line number - they occurred at; at levels 3 and higher line numbers are not stored. - - The default optimisation level is usually level 0. - """ - -@overload -def opt_level(level: int, /) -> None: - """ - If *level* is given then this function sets the optimisation level for subsequent - compilation of scripts, and returns ``None``. Otherwise it returns the current - optimisation level. - - The optimisation level controls the following compilation features: - - - Assertions: at level 0 assertion statements are enabled and compiled into the - bytecode; at levels 1 and higher assertions are not compiled. - - Built-in ``__debug__`` variable: at level 0 this variable expands to ``True``; - at levels 1 and higher it expands to ``False``. - - Source-code line numbers: at levels 0, 1 and 2 source-code line number are - stored along with the bytecode so that exceptions can report the line number - they occurred at; at levels 3 and higher line numbers are not stored. - - The default optimisation level is usually level 0. - """ - -@overload -def mem_info() -> None: - """ - Print information about currently used memory. If the *verbose* argument - is given then extra information is printed. - - The information that is printed is implementation dependent, but currently - includes the amount of stack and heap used. In verbose mode it prints out - the entire heap indicating which blocks are used and which are free. - """ - -@overload -def mem_info(verbose: int, /) -> None: - """ - Print information about currently used memory. If the *verbose* argument - is given then extra information is printed. - - The information that is printed is implementation dependent, but currently - includes the amount of stack and heap used. In verbose mode it prints out - the entire heap indicating which blocks are used and which are free. - """ - -def kbd_intr(chr: int) -> None: - """ - Set the character that will raise a `KeyboardInterrupt` exception. By - default this is set to 3 during script execution, corresponding to Ctrl-C. - Passing -1 to this function will disable capture of Ctrl-C, and passing 3 - will restore it. - - This function can be used to prevent the capturing of Ctrl-C on the - incoming stream of characters that is usually used for the REPL, in case - that stream is used for other purposes. - """ - ... - -@overload -def qstr_info() -> None: - """ - Print information about currently interned strings. If the *verbose* - argument is given then extra information is printed. - - The information that is printed is implementation dependent, but currently - includes the number of interned strings and the amount of RAM they use. In - verbose mode it prints out the names of all RAM-interned strings. - """ - -@overload -def qstr_info(verbose: bool, /) -> None: - """ - Print information about currently interned strings. If the *verbose* - argument is given then extra information is printed. - - The information that is printed is implementation dependent, but currently - includes the number of interned strings and the amount of RAM they use. In - verbose mode it prints out the names of all RAM-interned strings. - """ - -def schedule(func: Callable[[_T], None], arg: _T, /) -> None: - """ - Schedule the function *func* to be executed "very soon". The function - is passed the value *arg* as its single argument. "Very soon" means that - the MicroPython runtime will do its best to execute the function at the - earliest possible time, given that it is also trying to be efficient, and - that the following conditions hold: - - - A scheduled function will never preempt another scheduled function. - - Scheduled functions are always executed "between opcodes" which means - that all fundamental Python operations (such as appending to a list) - are guaranteed to be atomic. - - A given port may define "critical regions" within which scheduled - functions will never be executed. Functions may be scheduled within - a critical region but they will not be executed until that region - is exited. An example of a critical region is a preempting interrupt - handler (an IRQ). - - A use for this function is to schedule a callback from a preempting IRQ. - Such an IRQ puts restrictions on the code that runs in the IRQ (for example - the heap may be locked) and scheduling a function to call later will lift - those restrictions. - - On multi-threaded ports, the scheduled function's behaviour depends on - whether the Global Interpreter Lock (GIL) is enabled for the specific port: - - - If GIL is enabled, the function can preempt any thread and run in its - context. - - If GIL is disabled, the function will only preempt the main thread and run - in its context. - - Note: If `schedule()` is called from a preempting IRQ, when memory - allocation is not allowed and the callback to be passed to `schedule()` is - a bound method, passing this directly will fail. This is because creating a - reference to a bound method causes memory allocation. A solution is to - create a reference to the method in the class constructor and to pass that - reference to `schedule()`. This is discussed in detail here - :ref:`reference documentation ` under "Creation of Python - objects". - - There is a finite queue to hold the scheduled functions and `schedule()` - will raise a `RuntimeError` if the queue is full. - """ - ... - -def stack_use() -> int: - """ - Return an integer representing the current amount of stack that is being - used. The absolute value of this is not particularly useful, rather it - should be used to compute differences in stack usage at different points. - """ - ... - -def heap_unlock() -> int: - """ - Lock or unlock the heap. When locked no memory allocation can occur and a - `MemoryError` will be raised if any heap allocation is attempted. - `heap_locked()` returns a true value if the heap is currently locked. - - These functions can be nested, ie `heap_lock()` can be called multiple times - in a row and the lock-depth will increase, and then `heap_unlock()` must be - called the same number of times to make the heap available again. - - Both `heap_unlock()` and `heap_locked()` return the current lock depth - (after unlocking for the former) as a non-negative integer, with 0 meaning - the heap is not locked. - - If the REPL becomes active with the heap locked then it will be forcefully - unlocked. - - Note: `heap_locked()` is not enabled on most ports by default, - requires ``MICROPY_PY_MICROPYTHON_HEAP_LOCKED``. - """ - -def const(expr: Const_T, /) -> Const_T: - """ - Used to declare that the expression is a constant so that the compiler can - optimise it. The use of this function should be as follows:: - - from micropython import const - - CONST_X = const(123) - CONST_Y = const(2 * CONST_X + 1) - - Constants declared this way are still accessible as global variables from - outside the module they are declared in. On the other hand, if a constant - begins with an underscore then it is hidden, it is not available as a global - variable, and does not take up any memory during execution. - - This `const` function is recognised directly by the MicroPython parser and is - provided as part of the :mod:`micropython` module mainly so that scripts can be - written which run under both CPython and MicroPython, by following the above - pattern. - """ - ... - -def heap_lock() -> int: - """ - Lock or unlock the heap. When locked no memory allocation can occur and a - `MemoryError` will be raised if any heap allocation is attempted. - `heap_locked()` returns a true value if the heap is currently locked. - - These functions can be nested, ie `heap_lock()` can be called multiple times - in a row and the lock-depth will increase, and then `heap_unlock()` must be - called the same number of times to make the heap available again. - - Both `heap_unlock()` and `heap_locked()` return the current lock depth - (after unlocking for the former) as a non-negative integer, with 0 meaning - the heap is not locked. - - If the REPL becomes active with the heap locked then it will be forcefully - unlocked. - - Note: `heap_locked()` is not enabled on most ports by default, - requires ``MICROPY_PY_MICROPYTHON_HEAP_LOCKED``. - """ - -def alloc_emergency_exception_buf(size: int, /) -> None: - """ - Allocate *size* bytes of RAM for the emergency exception buffer (a good - size is around 100 bytes). The buffer is used to create exceptions in cases - when normal RAM allocation would fail (eg within an interrupt handler) and - therefore give useful traceback information in these situations. - - A good way to use this function is to put it at the start of your main script - (eg ``boot.py`` or ``main.py``) and then the emergency exception buffer will be active - for all the code following it. - """ - ... - -class RingIO: - def readinto(self, buf, nbytes: Optional[Any] = None) -> int: - """ - Read available bytes into the provided ``buf``. If ``nbytes`` is - specified then read at most that many bytes. Otherwise, read at - most ``len(buf)`` bytes. - - Return value: Integer count of the number of bytes read into ``buf``. - """ - ... - - def write(self, buf) -> int: - """ - Non-blocking write of bytes from ``buf`` into the ringbuffer, limited - by the available space in the ringbuffer. - - Return value: Integer count of bytes written. - """ - ... - - def readline(self, nbytes: Optional[Any] = None) -> bytes: - """ - Read a line, ending in a newline character or return if one exists in - the buffer, else return available bytes in buffer. If ``nbytes`` is - specified then read at most that many bytes. - - Return value: a bytes object containing the line read. - """ - ... - - def any(self) -> int: - """ - Returns an integer counting the number of characters that can be read. - """ - ... - - def read(self, nbytes: Optional[Any] = None) -> bytes: - """ - Read available characters. This is a non-blocking function. If ``nbytes`` - is specified then read at most that many bytes, otherwise read as much - data as possible. - - Return value: a bytes object containing the bytes read. Will be - zero-length bytes object if no data is available. - """ - ... - - def close(self) -> Incomplete: - """ - No-op provided as part of standard `stream` interface. Has no effect - on data in the ringbuffer. - """ - ... - - def __init__(self, size) -> None: ... - -# decorators -@overload # force merge -def viper(_func: Callable[_Param, _Ret], /) -> Callable[_Param, _Ret]: - """ - The Viper code emitter is not fully compliant. It supports special Viper native data types in pursuit of performance. - Integer processing is non-compliant because it uses machine words: arithmetic on 32 bit hardware is performed modulo 2**32. - Like the Native emitter Viper produces machine instructions but further optimisations are performed, substantially increasing - performance especially for integer arithmetic and bit manipulations. - See: https://docs.micropython.org/en/latest/reference/speed_python.html?highlight=viper#the-native-code-emitter - """ - ... - -@overload # force merge -def native(_func: Callable[_Param, _Ret], /) -> Callable[_Param, _Ret]: - """ - This causes the MicroPython compiler to emit native CPU opcodes rather than bytecode. - It covers the bulk of the MicroPython functionality, so most functions will require no adaptation. - See: https://docs.micropython.org/en/latest/reference/speed_python.html#the-native-code-emitter - """ - ... - -@overload # force merge -def asm_thumb(_func: Callable[_Param, _Ret], /) -> Callable[_Param, _Ret]: - """ - This decorator is used to mark a function as containing inline assembler code. - The assembler code is written is a subset of the ARM Thumb-2 instruction set, and is executed on the target CPU. - - Availability: Only on specific boards where MICROPY_EMIT_INLINE_THUMB is defined. - See: https://docs.micropython.org/en/latest/reference/asm_thumb2_index.html - """ - ... - -@overload # force merge -def asm_xtensa(_func: Callable[_Param, _Ret], /) -> Callable[_Param, _Ret]: - """ - This decorator is used to mark a function as containing inline assembler code for the esp8266. - The assembler code is written in the Xtensa instruction set, and is executed on the target CPU. - - Availability: Only on eps8266 boards. - """ - ... - # See : - # - https://github.com/orgs/micropython/discussions/12965 - # - https://github.com/micropython/micropython/pull/16731 diff --git a/uv.lock b/uv.lock index b08576f3..764d046e 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 2 +revision = 3 requires-python = ">=3.13" [[package]] @@ -982,15 +982,15 @@ wheels = [ [[package]] name = "pyright" -version = "1.1.402" +version = "1.1.404" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "nodeenv" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/aa/04/ce0c132d00e20f2d2fb3b3e7c125264ca8b909e693841210534b1ea1752f/pyright-1.1.402.tar.gz", hash = "sha256:85a33c2d40cd4439c66aa946fd4ce71ab2f3f5b8c22ce36a623f59ac22937683", size = 3888207, upload-time = "2025-06-11T08:48:35.759Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/6e/026be64c43af681d5632722acd100b06d3d39f383ec382ff50a71a6d5bce/pyright-1.1.404.tar.gz", hash = "sha256:455e881a558ca6be9ecca0b30ce08aa78343ecc031d37a198ffa9a7a1abeb63e", size = 4065679, upload-time = "2025-08-20T18:46:14.029Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/37/1a1c62d955e82adae588be8e374c7f77b165b6cb4203f7d581269959abbc/pyright-1.1.402-py3-none-any.whl", hash = "sha256:2c721f11869baac1884e846232800fe021c33f1b4acb3929cff321f7ea4e2982", size = 5624004, upload-time = "2025-06-11T08:48:33.998Z" }, + { url = "https://files.pythonhosted.org/packages/84/30/89aa7f7d7a875bbb9a577d4b1dc5a3e404e3d2ae2657354808e905e358e0/pyright-1.1.404-py3-none-any.whl", hash = "sha256:c7b7ff1fdb7219c643079e4c3e7d4125f0dafcc19d253b47e898d130ea426419", size = 5902951, upload-time = "2025-08-20T18:46:12.096Z" }, ] [package.optional-dependencies] @@ -1076,7 +1076,7 @@ dev = [ { name = "freezegun", specifier = ">=1.5.2" }, { name = "hypothesis", specifier = "==6.136.7" }, { name = "pre-commit", specifier = "==4.2.0" }, - { name = "pyright", extras = ["nodejs"], specifier = "==1.1.402" }, + { name = "pyright", extras = ["nodejs"], specifier = "==1.1.404" }, { name = "pytest", specifier = "==8.4.1" }, ] docs = [