Skip to content

Conversation

@CoMPaTech
Copy link
Owner

@CoMPaTech CoMPaTech commented Aug 17, 2025

Summary by CodeRabbit

  • Changed

    • Shorter/cleaner error logging and suppressed exception chaining in discovery paths for clearer output.
  • Documentation

    • Added changelog entry for 0.4.2 noting alignment of quality targets.
  • Tests

    • Reordered and tightened tests, improved expectations and parsing coverage, and adjusted test imports.
  • Style

    • Added lint suppressions and reorganized imports across scripts and tests.
  • Chores

    • Version bumped to 0.4.2a0; overhauled lint/coverage/pytest/mypy configs; added a test-only dependency.

@coderabbitai
Copy link

coderabbitai bot commented Aug 17, 2025

Warning

Rate limit exceeded

@CoMPaTech has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 4 minutes and 54 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 3c795ff and 604fa3f.

📒 Files selected for processing (3)
  • .github/workflows/verify.yml (1 hunks)
  • .pre-commit-config.yaml (1 hunks)
  • pyproject.toml (4 hunks)

Walkthrough

Documentation, tests, and tooling configs updated; minor cosmetic and logging changes to core modules. Discovery parser raises now suppress exception chaining. New mypy/ruff/pytest/coverage configurations and test dependency added. Scripts receive lint suppressions; tests reorder imports and tighten some assertions. No public API signature changes.

Changes

Cohort / File(s) Summary
Changelog
CHANGELOG.md
Added 0.4.2 entry with a single “Changed” bullet; prior entries unchanged.
Core: Request & Status
airos/airos8.py
Shortened error log message on TimeoutError/ClientError in _request_json; status() now returns AirOSData.from_dict(...) directly (removed intermediate variable).
Core: Discovery Parser
airos/discovery.py
Multiple raise AirOSEndpointError(...) sites changed to suppress exception chaining (from None); messages unchanged.
Core: Data Models (cosmetic)
airos/data.py
Reorganized control flow in is_ip_address and removed no-op pass statements from several classes; no behavior/signature changes.
Tooling & Config
pyproject.toml, mypy.ini, requirements-test.txt
Added new mypy.ini; large Ruff/Pylint/PyTest/Coverage rework in pyproject.toml; bumped project version to 0.4.2a0; added pydantic==2.11.7 to requirements-test.txt.
Scripts (lint suppressions & init)
script/__init__.py, script/generate_discovery_fixture.py, script/generate_ha_fixture.py, script/mashumaro-step-debug.py
Added module docstring and numerous # noqa lint suppressions; no functional changes.
Tests: imports, parametrization, assertions
tests/conftest.py, tests/test_airos8.py, tests/test_airos_request.py, tests/test_data.py, tests/test_discovery.py, tests/test_stations.py
Reordered/deduplicated imports (added aiohttp/pytest where needed), added inline noqa, narrowed exception handling, refactored some parametrizations and assertion blocks; no API changes.
CI / Hooks
.github/workflows/verify.yml, .pre-commit-config.yaml
Narrowed pytest coverage path to airos/; updated pytest invocations and pre-commit pytest hook to include log-level flag.

Sequence Diagram(s)

sequenceDiagram
    participant Caller as caller (status / _request_json)
    participant AIROS as airos8._request_json
    participant HTTP as aiohttp
    participant Parser as AirOSData.from_dict

    Caller->>AIROS: call status()
    AIROS->>HTTP: perform HTTP request
    alt request raises TimeoutError / ClientError
        HTTP-->>AIROS: exception
        AIROS->>Caller: log "Error during API call to <URL>" and raise AirOSDeviceConnectionError
    else request returns JSON
        AIROS->>Parser: AirOSData.from_dict(json)
        Parser-->>Caller: AirOSData
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

I nibbled at logs and trimmed each long line,
Hid causes in shadows, kept messages fine.
Linters hummed softly while tests found their place,
Packets parsed neatly, no trace left to chase.
A tiny bump of version — carrots for grace. 🥕🐇

✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch upd

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai or @coderabbitai title anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (29)
requirements-test.txt (1)

14-14: Pydantic v2 compliance confirmed
Ran a code-wide scan for Pydantic v1–era imports and APIs; no instances of pydantic.v1, BaseModel, BaseSettings, validator, root_validator, etc., were found.

• Optional: dedupe duplicate entries for aiofiles and aioresponses in requirements-test.txt to avoid confusion.

CHANGELOG.md (1)

9-9: Tighten the phrasing for clarity.

The current bullet reads a bit awkwardly. Suggest a minor wording tweak.

-- Aligned quality targets either improved or tagged
+- Aligned quality targets: improved or tagged.
script/generate_discovery_fixture.py (2)

11-13: Prefer pathlib over os.path; drop the noqa suppressions.

Using pathlib simplifies path handling and removes the need for PTH nox-qa.

Apply this diff to the selected lines:

-fixture_dir = os.path.join(os.path.dirname(__file__), "../fixtures")  # noqa: PTH118, PTH120
-os.makedirs(fixture_dir, exist_ok=True)  # Ensure the directory exists  # noqa: PTH103
-fixture_path = os.path.join(fixture_dir, "airos_sta_discovery_packet.bin")  # noqa: PTH118
+fixture_dir = Path(__file__).resolve().parent.parent / "fixtures"
+fixture_dir.mkdir(parents=True, exist_ok=True)  # Ensure the directory exists
+fixture_path = fixture_dir / "airos_sta_discovery_packet.bin"

And update imports (outside this hunk) to add pathlib and optionally drop os:

from pathlib import Path
# import os  # can be removed if unused elsewhere

97-97: Use Path.open to remove the PTH123 suppression.

This keeps behavior identical and removes the noqa.

-with open(fixture_path, "wb") as f:  # noqa: PTH123
+with fixture_path.open("wb") as f:
airos/data.py (1)

33-40: Harden is_ip_address against non-str inputs and stray whitespace.

Callers may pass non-strs; ipaddress.ip_address(None) raises TypeError. Trimming whitespace avoids false negatives.

-def is_ip_address(value: str) -> bool:
+def is_ip_address(value: str) -> bool:
     """Check if a string is a valid IPv4 or IPv6 address."""
     try:
-        ipaddress.ip_address(value)
-    except ValueError:
+        ipaddress.ip_address(value.strip() if isinstance(value, str) else value)
+    except (ValueError, TypeError):
         return False
     return True
script/mashumaro-step-debug.py (5)

26-26: Prefer SystemExit (or argparse) over raising bare Exception

Raising a broad Exception for CLI argument errors is noisy and flagged by linters. Use SystemExit or argparse to provide a clean exit code/message.

-        raise Exception("File to check not provided.")  # noqa: TRY002
+        raise SystemExit("File to check not provided.")

28-33: Remove redundant sys.path setup inside main()

current_script_dir/project_root_dir and sys.path mutation are already done at module import (Lines 9-13). This duplication is unnecessary.

-    current_script_dir = os.path.dirname(os.path.abspath(__file__))  # noqa: PTH100, PTH120
-    project_root_dir = os.path.abspath(os.path.join(current_script_dir, os.pardir))  # noqa: PTH100, PTH118
-
-    if project_root_dir not in sys.path:
-        sys.path.append(project_root_dir)
+    # Project path already added at module import; no need to repeat here.

35-36: Use json.load to read file directly

Slight simplification and marginally better memory behavior.

-    with open(sys.argv[1], encoding="utf-8") as f:  # noqa: PTH123
-        data = json.loads(f.read())
+    with open(sys.argv[1], encoding="utf-8") as f:  # noqa: PTH123
+        data = json.load(f)

49-61: Avoid building an unused list

station_obj_list is populated but never used. Iterate without accumulating to reduce noise.

-        station_list_data = wireless_data["sta"]
-        station_obj_list = []
+        station_list_data = wireless_data["sta"]
         for i, station_data in enumerate(station_list_data):
             _LOGGER.info("    -> Checking Station object at index %s...", i)
             remote_data = station_data["remote"]
             _LOGGER.info("      -> Checking Remote object at index %s...", i)
             _LOGGER.info("Remote data = %s", remote_data)
             remote_obj = Remote.from_dict(remote_data)  # noqa: F841
             _LOGGER.info("         Success! Remote is valid.")
 
             station_obj = Station.from_dict(station_data)
-            station_obj_list.append(station_obj)
+            # Deserialization succeeded; no need to store the object.
             _LOGGER.info("      Success! Station at index %s is valid.", i)

55-56: Check for sensitive data in logs

Remote data may include identifiers or credentials; ensure this script isn’t used in contexts where logs are collected centrally.

script/generate_ha_fixture.py (4)

26-31: Normalize fixture paths

Joining with "../..." can leave parent segments; normalize with abspath for predictable locations.

-    fixture_dir = os.path.join(os.path.dirname(__file__), "../fixtures")  # noqa: PTH118, PTH120
-    userdata_dir = os.path.join(os.path.dirname(__file__), "../fixtures/userdata")  # noqa: PTH118, PTH120
+    fixture_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../fixtures"))  # noqa: PTH118, PTH120
+    userdata_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../fixtures/userdata"))  # noqa: PTH118, PTH120

33-33: Deterministic iteration order

Sorting ensures stable fixture generation across runs.

-    for filename in os.listdir(userdata_dir):  # noqa: PTH208
+    for filename in sorted(os.listdir(userdata_dir)):  # noqa: PTH208

45-46: Use json.load for simplicity

Minor cleanup; avoids reading the entire file into a string first.

-                with open(base_fixture_path, encoding="utf-8") as source:  # noqa: PTH123
-                    source_data = json.loads(source.read())
+                with open(base_fixture_path, encoding="utf-8") as source:  # noqa: PTH123
+                    source_data = json.load(source)

33-53: Consider skipping invalid files instead of aborting the whole run

Currently, a single invalid JSON or processing error raises and stops all further processing. For bulk fixture generation, it’s often more useful to log and continue.

Apply the diffs above; additionally, adjust the exception handling (outside this hunk) like so:

# Outside selected range: replace the two 'raise' statements to continue processing
except json.JSONDecodeError:
    _LOGGER.error("Skipping '%s': Not a valid JSON file.", filename)
    continue
except Exception as e:
    _LOGGER.error("Error processing '%s': %s", filename, e)
    continue
pyproject.toml (2)

751-759: Per-file ignores for scripts and temporary PTH suppressions

Reasonable tradeoff for DX; revisit the temporary PTH ignores when pathlib refactors are feasible.


374-551: PyTest warnings filter is extensive; ensure it doesn’t hide relevant deprecations

Large ignore lists copied from other ecosystems (e.g., Home Assistant) can mask actionable warnings in this project. Consider trimming over time.

Would you like me to propose a minimal filter set based on current test output to avoid over-suppressing?

tests/conftest.py (1)

3-3: Use public typing module path (nitpick)

Prefer collections.abc over the private _collections_abc to avoid relying on internal modules.

Apply this diff:

-from _collections_abc import AsyncGenerator, Generator
+from collections.abc import AsyncGenerator, Generator
airos/airos8.py (2)

263-273: Likely content-type/payload mismatch in update_check(force=True)

When force is True, ct_form=True is set but the payload is passed via json_data, not form_data. This can lead to inconsistent headers/body and server-side parsing issues.

Apply this diff:

-        if force:
-            return await self._request_json(
-                "POST",
-                self._update_check_url,
-                json_data={"force": True},
-                authenticated=True,
-                ct_form=True,
-            )
+        if force:
+            return await self._request_json(
+                "POST",
+                self._update_check_url,
+                form_data={"force": True},
+                authenticated=True,
+                ct_form=True,
+            )

122-123: Typo in comment (nitpick)

“Fist” → “first”.

Apply this diff:

-        # Fallback take fist alternate interface found
+        # Fallback: take first alternate interface found
airos/discovery.py (5)

32-33: Make the callback type hint asynchronous (Awaitable) to match create_task usage

asyncio.create_task(self.callback(...)) expects an awaitable; declare the callback accordingly for type-safety and clearer intent.

-from typing import Any
+from typing import Any, Awaitable

-    def __init__(self, callback: Callable[[dict[str, Any]], Any]) -> None:
+    def __init__(self, callback: Callable[[dict[str, Any]], Awaitable[Any]]) -> None:

60-63: Log exceptions raised by the scheduled callback task

If the callback coroutine raises after being scheduled, the exception will surface as an unhandled task exception. Attach a done-callback to log failures.

-                # Schedule the user-provided callback, don't await to keep listener responsive
-                asyncio.create_task(self.callback(parsed_data))  # noqa: RUF006
+                # Schedule the user-provided callback, don't await to keep listener responsive
+                task = asyncio.create_task(self.callback(parsed_data))  # noqa: RUF006
+                task.add_done_callback(
+                    lambda t: _LOGGER.exception("Discovery callback failed", exc_info=t.exception())
+                    if t.exception()  # type: ignore[truthy-bool]
+                    else None
+                )

269-274: Exception-chaining policy differs here vs. other malformed TLV raises

This path still sets __cause__ (from err) while other TLV-parse raises suppress chaining (from None). If that’s intentional (to preserve low-level struct/index errors), consider adding a short comment. If not, align with the rest.

-            log = f"Malformed packet: {log}"
-            raise AirOSEndpointError(log) from err
+            log = f"Malformed packet: {log}"
+            # Preserve or suppress cause? Align with project policy.
+            raise AirOSEndpointError(log) from err

If you prefer to suppress chaining here too:

-            raise AirOSEndpointError(log) from err
+            raise AirOSEndpointError(log) from None  # noqa: TRY301

319-323: Use errno constants instead of magic numbers for portability

Comparing to raw 98 is Linux-specific. Use errno.EADDRINUSE.

+import errno
@@
-        if err.errno == 98:
+        if err.errno == errno.EADDRINUSE:
             _LOGGER.error("Address in use, another instance may be running.")
             raise AirOSEndpointError("address_in_use") from err

88-104: Narrow the return type of parse_airos_packet; it never returns None

The implementation always returns a dict or raises. Tightening the type and simplifying the caller removes dead branches and clarifies semantics.

-    def parse_airos_packet(self, data: bytes, host_ip: str) -> dict[str, Any] | None:
+    def parse_airos_packet(self, data: bytes, host_ip: str) -> dict[str, Any]:
@@
-            A dictionary containing parsed device information if successful,
-            otherwise None. Values will be None if not found or cannot be parsed.
+            A dictionary containing parsed device information.
+            Values will be None if not found or cannot be parsed.

And in datagram_received:

-            parsed_data: dict[str, Any] | None = self.parse_airos_packet(data, host_ip)
-            if parsed_data:
-                # Schedule the user-provided callback, don't await to keep listener responsive
-                asyncio.create_task(self.callback(parsed_data))  # noqa: RUF006
+            parsed_data: dict[str, Any] = self.parse_airos_packet(data, host_ip)
+            # Schedule the user-provided callback, don't await to keep listener responsive
+            asyncio.create_task(self.callback(parsed_data))  # noqa: RUF006

Also applies to: 60-63

tests/test_discovery.py (5)

114-117: Byte-literal concatenation style: prefer consistency

Mixing implicit literal concatenation with explicit + reads fine but is stylistically uneven. Consider using one approach throughout the block for readability.

Example (explicit all the way):

-    truncated_data = (
-        b"\x01\x06\x00\x00\x00\x00"  # Header
-        b"\x06"
-        + bytes.fromhex("0123456789CD")  # Valid MAC (scrubbed)
-        + b"\x02\x00"  # TLV type 0x02, followed by only 1 byte for length (should be 2)
-    )
+    truncated_data = (
+        b"\x01\x06\x00\x00\x00\x00"  # Header
+        + b"\x06"
+        + bytes.fromhex("0123456789CD")  # Valid MAC (scrubbed)
+        + b"\x02\x00"  # TLV type 0x02, followed by only 1 byte for length (should be 2)
+    )

431-433: Nit: Keep bytes concatenation style consistent here as well

Mirror the earlier suggestion for consistent explicit concatenation.

-    data_with_fragment = (
-        b"\x01\x06\x00\x00\x00\x00"
-        b"\x06"
+    data_with_fragment = (
+        b"\x01\x06\x00\x00\x00\x00"
+        + b"\x06"
         + bytes.fromhex("0123456789CD")
         + b"\x0a\x00"  # TLV type 0x0a, followed by only 1 byte for length (should be 2)
     )

452-454: Nit: Consistent bytes concatenation

Same consistency suggestion as above.

-    data_with_fragment = (
-        b"\x01\x06\x00\x00\x00\x00"
-        b"\x06"
+    data_with_fragment = (
+        b"\x01\x06\x00\x00\x00\x00"
+        + b"\x06"
         + bytes.fromhex("0123456789CD")
         + b"\x02\x00"  # TLV type 0x02, followed by only 1 byte for length (should be 2)
     )

259-265: Optionally assert the function’s return value in the success path

You already capture discovered devices via the patched protocol callback. To strengthen the contract, also assert the dict returned by airos_discover_devices.

-            discovery_task = asyncio.create_task(airos_discover_devices(timeout=1))
+            discovery_task = asyncio.create_task(airos_discover_devices(timeout=1))
@@
-            await discovery_task
+            result = await discovery_task
+            # Validate return value mirrors discovered devices
+            assert "01:23:45:67:89:CD" in result
+            assert result["01:23:45:67:89:CD"]["hostname"] == "name"

436-442: Consider asserting suppressed exception chaining where applicable

For paths intentionally using raise ... from None, assert __cause__ is None to lock in the behavior.

-        with pytest.raises(AirOSEndpointError):
-            protocol.parse_airos_packet(data_with_fragment, host_ip)
+        with pytest.raises(AirOSEndpointError) as excinfo:
+            protocol.parse_airos_packet(data_with_fragment, host_ip)
+        assert excinfo.value.__cause__ is None
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 2ca459c and 7642ace.

📒 Files selected for processing (17)
  • CHANGELOG.md (1 hunks)
  • airos/airos8.py (2 hunks)
  • airos/data.py (1 hunks)
  • airos/discovery.py (6 hunks)
  • mypy.ini (1 hunks)
  • pyproject.toml (4 hunks)
  • requirements-test.txt (1 hunks)
  • script/__init__.py (1 hunks)
  • script/generate_discovery_fixture.py (2 hunks)
  • script/generate_ha_fixture.py (2 hunks)
  • script/mashumaro-step-debug.py (3 hunks)
  • tests/conftest.py (1 hunks)
  • tests/test_airos8.py (1 hunks)
  • tests/test_airos_request.py (6 hunks)
  • tests/test_data.py (1 hunks)
  • tests/test_discovery.py (9 hunks)
  • tests/test_stations.py (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (8)
airos/airos8.py (1)
airos/exceptions.py (1)
  • AirOSDeviceConnectionError (24-25)
tests/test_data.py (1)
airos/data.py (2)
  • Host (190-213)
  • Wireless (448-496)
tests/test_airos8.py (1)
airos/airos8.py (1)
  • AirOS (32-349)
script/generate_ha_fixture.py (1)
airos/airos8.py (2)
  • derived_data (77-134)
  • AirOS (32-349)
airos/discovery.py (1)
airos/exceptions.py (1)
  • AirOSEndpointError (36-37)
tests/test_airos_request.py (1)
airos/airos8.py (1)
  • _request_json (169-227)
tests/conftest.py (2)
airos/airos8.py (1)
  • AirOS (32-349)
airos/discovery.py (1)
  • AirOSDiscoveryProtocol (18-280)
tests/test_discovery.py (2)
airos/exceptions.py (1)
  • AirOSEndpointError (36-37)
airos/discovery.py (1)
  • airos_discover_devices (283-331)
🔇 Additional comments (22)
script/__init__.py (1)

1-1: LGTM on the package marker docstring.

Clear, concise, and harmless.

mypy.ini (3)

1-26: Strict mypy configuration looks solid

Good balance of strictness and practicality; warnings and error codes are tuned appropriately for a production codebase.


4-4: Validate Pydantic plugin selection

Both pydantic.mypy and pydantic.v1.mypy are enabled. Depending on your installed Pydantic versions, loading both may be redundant. Confirm that CI has a plugin-compatible Pydantic and that mypy isn't emitting plugin load warnings.

Would you like a quick check added to CI to print mypy’s loaded plugins for the job log?


36-44: Test-specific relaxations are appropriate

Relaxed rules under [mypy-tests.*] align with typical test code patterns and should reduce false positives.

pyproject.toml (1)

57-59: Enabling pylint code_style and typing extensions is a good move

These plugins catch useful issues; nice addition.

tests/test_data.py (1)

7-8: Import reordering LGTM

Matches the repository’s linting preferences; no behavioral changes.

tests/test_stations.py (1)

9-11: Import consolidation at top-level looks good

Moving aiofiles, MissingField, and pytest to the top eliminates duplicates and aligns with the broader test-suite organization/linting changes.

tests/conftest.py (2)

7-7: Add aiohttp import at top-level — necessary and correct

This prevents NameError in the aiohttp-based fixture and matches the updated import policy across tests.


10-12: Reordered imports — consistent with pytest-first ordering

Importing AirOS and AirOSDiscoveryProtocol after pytest is fine and helps ensure decorators/fixtures are available.

tests/test_airos8.py (2)

10-14: Import reordering and spacing — OK

Bringing pytest earlier and placing AirOS/imports after it is consistent; no runtime changes.


281-283: Cause chaining assertion matches current implementation

airos/airos8.py raises AirOSKeyDataMissingError from MissingField; asserting cause is MissingField is correct and valuable for diagnostics.

airos/airos8.py (2)

220-221: Shorter connection-error log with full traceback — good tradeoff

Switching to _LOGGER.exception("Error during API call to %s", url) keeps the traceback while shortening the message content. Behavior of raising AirOSDeviceConnectionError from err remains intact.


243-246: Direct return from AirOSData.from_dict — OK

Streamlines without changing behavior; exceptions are still handled below.

tests/test_airos_request.py (2)

6-8: Top-level aiohttp/pytest imports — consistent and fine

Matches the repo-wide test import reorg; no behavior change.


51-51: Accessing private method in tests with SLF001 suppression is acceptable

Using _request_json directly in tests is reasonable; the selective # noqa: SLF001 keeps lints clean without weakening production code.

Also applies to: 78-79, 102-103, 125-126, 147-149

airos/discovery.py (1)

154-155: LGTM: Suppressing exception chaining on malformed TLV raises is consistent

Using raise ... from None # noqa: TRY301 for TLV parse errors keeps logs concise and matches the stated intent. Please double-check whether you want the same policy for all parse-error paths (see struct/index catch below).

Also applies to: 168-173, 184-185, 195-200, 216-221, 267-268

tests/test_discovery.py (6)

10-11: LGTM: pytest import added

Needed for fixture utilities and assertions; matches usage below.


37-38: LGTM: Narrowing exception to OSError is appropriate

Catching only file I/O errors clarifies intent and avoids swallowing unrelated exceptions.


293-301: LGTM: Robust simulation of EADDRINUSE during endpoint creation

Patching get_running_loop().create_datagram_endpoint to raise OSError(98, ...) and asserting AirOSEndpointError("address_in_use") matches the production flow.


354-355: LGTM: Parametrization uses tuple of arg names

Switching to tuple form improves readability and avoids comma-in-string pitfalls.


388-396: LGTM: Generic OSError path coverage

Good separation from the EADDRINUSE case; asserting AirOSEndpointError("cannot_connect") aligns with the code.


466-471: LGTM: Parametrization tuple for unhandled TLVs

Clearer argument naming; matches test body expectations.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
pyproject.toml (1)

552-554: Coverage source fixed to target airos (good catch)

This addresses the earlier review correctly. Expect coverage to reflect project modules now.

🧹 Nitpick comments (4)
pyproject.toml (4)

343-348: Pylint per-file-ignores path matching may not trigger with a leading slash

Pylint’s per-file-ignores typically matches relative paths/globs (e.g., tests/*). The leading slash in "/tests/:" may prevent matches depending on runner CWD. Consider removing it.

Apply this diff:

-per-file-ignores = [
-  # redefined-outer-name: Tests reference fixtures in the test function
-  # use-implicit-booleaness-not-comparison: Tests need to validate that a list
-  # or a dict is returned
-  "/tests/:redefined-outer-name,use-implicit-booleaness-not-comparison",
-]
+per-file-ignores = [
+  # redefined-outer-name: Tests reference fixtures in the test function
+  # use-implicit-booleaness-not-comparison: Tests need to validate that a list
+  # or a dict is returned
+  "tests/**:redefined-outer-name,use-implicit-booleaness-not-comparison",
+]

374-551: Pytest warnings filter block is very broad; verify necessity per project scope

There are many HA- and third-party-specific suppressions. If they’re not exercised by airos tests, trimming reduces maintenance burden and risk of hiding real issues.

Would you like me to propose a minimal set derived from current test runs?


555-567: Consider enabling branch coverage and excluding typical “if name == 'main':” guards

Enabling branch coverage yields more accurate metrics; excluding main guards avoids noise.

Apply this diff:

 [tool.coverage.run]
-source = ["airos"]
+source = ["airos"]
+branch = true
+relative_files = true
+
 [tool.coverage.report]
 exclude_lines = [
   # Have to re-enable the standard pragma
   "pragma: no cover",
+  # Don't complain about script entry points:
+  "if __name__ == .__main__.:",
   # Don't complain about missing debug-only code:

590-594: Ruff “C” selector is ambiguous; prefer explicit families

If you intend flake8-comprehensions use “C4”; McCabe is “C90” (already configured via mccabe). Using bare “C” can be unclear across Ruff versions.

Apply this diff:

-  "BLE",
-  "C",      # complexity
+  "BLE",
+  "C4",     # flake8-comprehensions

If you also want McCabe via select (not only config), add:

+  "C90",    # mccabe (complexity)
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 7642ace and d9f657a.

📒 Files selected for processing (1)
  • pyproject.toml (4 hunks)
🔇 Additional comments (9)
pyproject.toml (9)

7-7: Confirm pre-release tagging (0.4.2a0) is intentional

Alpha tag will publish as a pre-release on PyPI. Double-check release process (changelog, CI publish conditions) to avoid unintentionally pushing a pre-release.


57-59: Pylint plugins addition looks good

Enabling code_style and typing extensions is appropriate given the Ruff/Pylint split that follows.


355-356: attrs typing quirk handled

Ignoring _CountingAttr for attrs is a common/harmless tweak. LGTM.


736-739: pytest-style mark-parentheses turned off intentionally

This aligns with common pytest ergonomics; no concerns.


741-744: Banned API rules are sensible

  • async_timeout -> asyncio.timeout
  • Prevent importing tests

Solid guardrails.


746-748: isort known-first-party set to “airos”

Correctly informs import sorter. Good.


751-759: Temporary PTH ignores for airos/tests

Reasonable while migrating to pathlib. Revisit later to re-enable.

Consider opening a tracking issue to remove these ignores once migration is complete.


761-762: Complexity threshold configured

max-complexity = 25 is pragmatic for this codebase. No issues.


764-765: Docstring convention switched to Google with property decorator allowance

Consistent with modern style guides; works well with cached_property configuration.

@codecov
Copy link

codecov bot commented Aug 17, 2025

Codecov Report

❌ Patch coverage is 66.66667% with 3 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (main@2ca459c). Learn more about missing BASE report.
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
airos/discovery.py 66.66% 2 Missing ⚠️
airos/data.py 0.00% 1 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main      #71   +/-   ##
=======================================
  Coverage        ?   91.36%           
=======================================
  Files           ?        4           
  Lines           ?      730           
  Branches        ?        0           
=======================================
  Hits            ?      667           
  Misses          ?       63           
  Partials        ?        0           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
.pre-commit-config.yaml (1)

95-95: Pytest logging flag is fine; optional: let pytest.ini drive logging to avoid duplication

Adding --log-level info is valid and will surface useful logs locally. If you already define logging in pyproject/pytest.ini, consider relying on that to keep a single source of truth.

Optional diff to delegate logging config to pytest.ini:

-        entry: script/run-in-env.sh pytest --log-level info
+        entry: script/run-in-env.sh pytest
.github/workflows/verify.yml (1)

99-99: Narrowing coverage to the package is good; optional: prefer package name over path and rely on pytest testpaths

Targeting airos/ avoids counting tests in coverage—nice. For portability and to respect pytest’s configured test discovery (testpaths), consider:

  • using --cov=airos (package name) instead of a path, and
  • omitting tests/*.py so pytest uses testpaths from config (helps if tests are reorganized later).

Suggested update:

-          pytest --log-level info tests/*.py --cov='airos/'
+          pytest --log-level info --cov=airos
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between d9f657a and 3c795ff.

📒 Files selected for processing (2)
  • .github/workflows/verify.yml (1 hunks)
  • .pre-commit-config.yaml (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Run pytest using Python 3.13

@sonarqubecloud
Copy link

@CoMPaTech CoMPaTech merged commit 496daf4 into main Aug 17, 2025
14 checks passed
@CoMPaTech CoMPaTech deleted the upd branch August 17, 2025 15:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant