Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

All notable changes to this project will be documented in this file.

## [0.4.2] - 2025-08-17

### Changed

- Aligned quality targets either improved or tagged

## [0.4.1] - 2025-08-17

### Changed
Expand Down
5 changes: 2 additions & 3 deletions airos/airos8.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ async def _request_json(
raise AirOSConnectionAuthenticationError from err
raise AirOSConnectionSetupError from err
except (TimeoutError, aiohttp.ClientError) as err:
_LOGGER.exception("Error during API call to %s: %s", url, err)
_LOGGER.exception("Error during API call to %s", url)
raise AirOSDeviceConnectionError from err
except json.JSONDecodeError as err:
_LOGGER.error("Failed to decode JSON from %s", url)
Expand All @@ -242,8 +242,7 @@ async def status(self) -> AirOSData:

try:
adjusted_json = self.derived_data(response)
airos_data = AirOSData.from_dict(adjusted_json)
return airos_data
return AirOSData.from_dict(adjusted_json)
except InvalidFieldValue as err:
# Log with .error() as this is a specific, known type of issue
redacted_data = redact_data_smart(response)
Expand Down
8 changes: 1 addition & 7 deletions airos/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ def is_ip_address(value: str) -> bool:
"""Check if a string is a valid IPv4 or IPv6 address."""
try:
ipaddress.ip_address(value)
return True
except ValueError:
return False
return True


def redact_data_smart(data: dict[str, Any]) -> dict[str, Any]:
Expand Down Expand Up @@ -107,8 +107,6 @@ def _redact(d: dict[str, Any]) -> dict[str, Any]:
class AirOSDataClass(DataClassDictMixin):
"""A base class for all mashumaro dataclasses."""

pass


def _check_and_log_unknown_enum_value(
data_dict: dict[str, Any],
Expand Down Expand Up @@ -534,15 +532,11 @@ class Interface(AirOSDataClass):
class ProvisioningMode(AirOSDataClass):
"""Leaf definition."""

pass


@dataclass
class NtpClient(AirOSDataClass):
"""Leaf definition."""

pass


@dataclass
class GPSMain(AirOSDataClass):
Expand Down
16 changes: 10 additions & 6 deletions airos/discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ def parse_airos_packet(self, data: bytes, host_ip: str) -> dict[str, Any] | None
log = f"Truncated MAC address TLV (Type 0x06). Expected {expected_length}, got {len(data) - offset} bytes. Remaining: {data[offset:].hex()}"
_LOGGER.warning(log)
log = f"Malformed packet: {log}"
raise AirOSEndpointError(log)
raise AirOSEndpointError(log) from None # noqa: TRY301

elif tlv_type in [
0x02,
Expand All @@ -169,7 +169,7 @@ def parse_airos_packet(self, data: bytes, host_ip: str) -> dict[str, Any] | None
log = f"Truncated TLV (Type {tlv_type:#x}), no 2-byte length field. Remaining: {data[offset:].hex()}"
_LOGGER.warning(log)
log = f"Malformed packet: {log}"
raise AirOSEndpointError(log)
raise AirOSEndpointError(log) from None # noqa: TRY301

tlv_length: int = struct.unpack_from(">H", data, offset)[0]
offset += 2
Expand All @@ -181,7 +181,7 @@ def parse_airos_packet(self, data: bytes, host_ip: str) -> dict[str, Any] | None
f"Data from TLV start: {data[offset - 3 :].hex()}"
)
_LOGGER.warning(log)
raise AirOSEndpointError(f"Malformed packet: {log}")
raise AirOSEndpointError(f"Malformed packet: {log}") from None # noqa: TRY301

tlv_value: bytes = data[offset : offset + tlv_length]

Expand All @@ -194,7 +194,9 @@ def parse_airos_packet(self, data: bytes, host_ip: str) -> dict[str, Any] | None
else:
log = f"Unexpected length for 0x02 TLV (MAC+IP). Expected 10, got {tlv_length}. Value: {tlv_value.hex()}"
_LOGGER.warning(log)
raise AirOSEndpointError(f"Malformed packet: {log}")
raise AirOSEndpointError( # noqa: TRY301
f"Malformed packet: {log}"
) from None

elif tlv_type == 0x03:
parsed_info["firmware_version"] = tlv_value.decode(
Expand All @@ -213,7 +215,9 @@ def parse_airos_packet(self, data: bytes, host_ip: str) -> dict[str, Any] | None
else:
log = f"Unexpected length for Uptime (Type 0x0A): {tlv_length}. Value: {tlv_value.hex()}"
_LOGGER.warning(log)
raise AirOSEndpointError(f"Malformed packet: {log}")
raise AirOSEndpointError( # noqa: TRY301
f"Malformed packet: {log}"
) from None

elif tlv_type == 0x0B:
parsed_info["hostname"] = tlv_value.decode(
Expand Down Expand Up @@ -260,7 +264,7 @@ def parse_airos_packet(self, data: bytes, host_ip: str) -> dict[str, Any] | None
log += f"Cannot determine length, stopping parsing. Remaining: {data[offset - 1 :].hex()}"
_LOGGER.warning(log)
log = f"Malformed packet: {log}"
raise AirOSEndpointError(log)
raise AirOSEndpointError(log) from None # noqa: TRY301

except (struct.error, IndexError) as err:
log = f"Parsing error (struct/index) in AirOSDiscoveryProtocol: {err} at offset {offset}. Remaining data: {data[offset:].hex()}"
Expand Down
44 changes: 44 additions & 0 deletions mypy.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
[mypy]
python_version = 3.13
platform = linux
plugins = pydantic.mypy, pydantic.v1.mypy
show_error_codes = true
follow_imports = normal
local_partial_types = true
strict_equality = true
strict_bytes = true
no_implicit_optional = true
warn_incomplete_stub = true
warn_redundant_casts = true
warn_unused_configs = true
warn_unused_ignores = true
enable_error_code = deprecated, ignore-without-code, redundant-self, truthy-iterable
disable_error_code = annotation-unchecked, import-not-found, import-untyped
extra_checks = false
check_untyped_defs = true
disallow_incomplete_defs = true
disallow_subclassing_any = true
disallow_untyped_calls = true
disallow_untyped_decorators = true
disallow_untyped_defs = true
warn_return_any = true
warn_unreachable = true

[pydantic-mypy]
init_forbid_extra = true
init_typed = true
warn_required_dynamic_aliases = true
warn_untyped_fields = true

[mypy-airos.*]
no_implicit_reexport = true

[mypy-tests.*]
check_untyped_defs = false
disallow_incomplete_defs = false
disallow_subclassing_any = false
disallow_untyped_calls = false
disallow_untyped_decorators = false
disallow_untyped_defs = false
warn_return_any = false
warn_unreachable = false
Loading
Loading