Skip to content

Package python plugin#3

Merged
DevTGHa merged 8 commits intomainfrom
package_python_plugin
Nov 25, 2025
Merged

Package python plugin#3
DevTGHa merged 8 commits intomainfrom
package_python_plugin

Conversation

@DevTGHa
Copy link
Collaborator

@DevTGHa DevTGHa commented Nov 22, 2025

Package python plugin

Summary

Before I integrate the d3sdk(d3blobgen), I'd like to add several CI stuff:

  • pytest for unit test
  • mypy for type check
  • pre-commit-hook and ruff for python linting
  • github action for CI

Note that I've excluded /test from all above because I don't want to make writing test harder. It should be as easy as possible and lint/typecheck doesn't benefit as much IMO.

The client of this package will not require to install any of mypy, pre-commit or ruff.

Summary by CodeRabbit

  • Documentation

    • Clarified and expanded publishing instructions with examples and guidance for the plugin publish flow and config file.
  • Chores

    • Added CI workflow to run linting, typing, and tests on pushes/PRs.
    • Added pre-commit hooks and standardized formatting/type-checking tooling and config.
    • Updated project metadata and README reference.
  • Refactor

    • Refined plugin lifecycle management and explicit public exports for clearer behavior.
  • Tests

    • Updated test imports and expectations to align with code changes.

✏️ Tip: You can customize this high-level summary in your review settings.

@DevTGHa DevTGHa self-assigned this Nov 22, 2025
@DevTGHa DevTGHa added the enhancement New feature or request label Nov 22, 2025
@coderabbitai
Copy link

coderabbitai bot commented Nov 22, 2025

Walkthrough

Adds CI and pre-commit tooling, expands pyproject development config, updates README publishing docs, exposes DesignerPlugin via __all__, refactors DesignerPlugin with typed constructors and private Zeroconf lifecycle management, and updates tests and minor formatting.

Changes

Cohort / File(s) Summary
CI Workflow
\.github/workflows/ci.yml
Adds GitHub Actions workflow to run ruff, mypy, and pytest on push and PRs (Python 3.11 matrix).
Pre-commit config
\.pre-commit-config.yaml
Adds Ruff pre-commit hooks (ruff --fix and ruff-format) pinned to a specific rev.
Project tooling & metadata
pyproject.toml
Adds author and README field; defines dev dependency-group (mypy, pre-commit, pytest, ruff) and tooling configs for ruff, mypy, and pytest.
Documentation
README.md
Restructures top-level sections to "Publish Plugin" / "Publish options", adds guidance and a d3plugin.json example, preserves usage examples.
Package export
src/designer_plugin/__init__.py
Introduces explicit __all__ = ["DesignerPlugin"].
Core implementation
src/designer_plugin/designer_plugin.py
Adds type hints, new factory constructors (default_init, from_json_file), returns ServiceInfo from service_info, introduces private _zeroconf and _azeroconf attributes, and refactors sync/async context-manager lifecycle to initialize/register and cleanup using those private instances.
Tests
test/__init__.py, test/test_plugin.py
Minor formatting newline in test/__init__.py; reorganizes imports in test/test_plugin.py, adds JSON-related test imports, and adjusts an expected test value formatting.

Sequence Diagram

sequenceDiagram
    participant User
    participant DP as DesignerPlugin
    participant ZC as Zeroconf / AsyncZeroconf

    rect rgb(230, 245, 255)
    Note over User,DP: Synchronous enter → register (uses private _zeroconf)
    User->>DP: __enter__()
    DP->>DP: create _zeroconf
    DP->>ZC: register_service()
    ZC-->>DP: registered
    DP-->>User: self
    end

    rect rgb(255, 230, 230)
    Note over User,DP: Synchronous exit → unregister & close
    User->>DP: __exit__()
    DP->>ZC: unregister_service()
    DP->>ZC: close()
    DP->>DP: clear _zeroconf
    end

    rect rgb(230, 255, 230)
    Note over User,DP: Async enter → register (uses private _azeroconf)
    User->>DP: __aenter__()
    DP->>DP: create _azeroconf (async)
    DP->>ZC: register_service() [async]
    ZC-->>DP: registered
    DP-->>User: self
    end

    rect rgb(255, 245, 230)
    Note over User,DP: Async exit → unregister & close (async)
    User->>DP: __aexit__()
    DP->>ZC: unregister_service() [async]
    DP->>ZC: close() [async]
    DP->>DP: clear _azeroconf
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Focus areas:
    • src/designer_plugin/designer_plugin.py: verify context-manager (sync/async) lifecycles, correct creation/cleanup of _zeroconf and _azeroconf, and that public behavior/API is preserved.
    • pyproject.toml: confirm mypy/ruff settings and test options don't break CI or tests.
    • test/test_plugin.py: ensure import changes and adjusted expectations remain correct.

Poem

🐰 I hopped through code with careful paws,
Types and lifecycles fixed their flaws.
CI hums, pre-commit sings a tune,
Services register under moon.
A rabbit cheers — the plugin's new! 🎉

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title 'Package python plugin' is vague and generic, using non-descriptive terminology that doesn't clearly convey the specific changes in the PR. Consider a more specific title that highlights the main change, such as 'Add CI and developer tooling (pytest, mypy, ruff)' or 'Set up Python packaging and development workflow'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch package_python_plugin

Tip

📝 Customizable high-level summaries are now available in beta!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide your own instructions using the high_level_summary_instructions setting.
  • Format the summary however you like (bullet lists, tables, multi-section layouts, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example instruction:

"Divide the high-level summary into five sections:

  1. 📝 Description — Summarize the main change in 50–60 words, explaining what was done.
  2. 📓 References — List relevant issues, discussions, documentation, or related PRs.
  3. 📦 Dependencies & Requirements — Mention any new/updated dependencies, environment variable changes, or configuration updates.
  4. 📊 Contributor Summary — Include a Markdown table showing contributions:
    | Contributor | Lines Added | Lines Removed | Files Changed |
  5. ✔️ Additional Notes — Add any extra reviewer context.
    Keep each section concise (under 200 words) and use bullet or numbered lists for clarity."

Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later.


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

Comment @coderabbitai help to get the list of available commands and usage tips.

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: 2

🧹 Nitpick comments (2)
.github/workflows/ci.yml (1)

12-14: Consider testing multiple Python versions.

The package supports Python >=3.11 (per pyproject.toml), but CI only tests 3.11. Consider adding 3.12 and 3.13 to the matrix to ensure compatibility across supported versions.

Apply this diff to expand test coverage:

    strategy:
      matrix:
-        python-version: ["3.11"]
+        python-version: ["3.11", "3.12", "3.13"]
src/designer_plugin/designer_plugin.py (1)

77-77: Replace # type: ignore with proper type hints.

The __exit__ and __aexit__ parameters can be properly typed instead of using # type: ignore comments.

Apply this diff to add proper type hints:

-def __exit__(self, exc_type, exc_value, traceback):  # type: ignore
+def __exit__(
+    self,
+    exc_type: type[BaseException] | None,
+    exc_value: BaseException | None,
+    traceback: object,
+) -> None:
     if self._zeroconf:
         self._zeroconf.close()
         self._zeroconf = None

-async def __aexit__(self, exc_type, exc_value, traceback):  # type: ignore
+async def __aexit__(
+    self,
+    exc_type: type[BaseException] | None,
+    exc_value: BaseException | None,
+    traceback: object,
+) -> None:
     if self._azeroconf:
         await self._azeroconf.async_close()
         self._azeroconf = None

Also applies to: 87-87

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira 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 2bd957d and b8dd589.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (8)
  • .github/workflows/ci.yml (1 hunks)
  • .pre-commit-config.yaml (1 hunks)
  • README.md (2 hunks)
  • pyproject.toml (1 hunks)
  • src/designer_plugin/__init__.py (1 hunks)
  • src/designer_plugin/designer_plugin.py (2 hunks)
  • test/__init__.py (1 hunks)
  • test/test_plugin.py (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
test/test_plugin.py (1)
src/designer_plugin/designer_plugin.py (2)
  • DesignerPlugin (9-90)
  • service_info (54-70)
🔇 Additional comments (8)
src/designer_plugin/__init__.py (1)

1-3: LGTM! Explicit public API definition.

Adding __all__ clearly defines the module's public interface, which is a best practice for library packages.

test/test_plugin.py (1)

1-4: LGTM! Improved import organization.

Separating imports and adding explicit JSON handling utilities (JSONDecodeError, json_dumps) improves clarity and prepares for robust JSON error handling.

README.md (1)

13-24: LGTM! Improved documentation clarity.

The restructured sections and explicit d3plugin.json example significantly improve the getting-started experience for plugin developers.

pyproject.toml (1)

31-105: LGTM! Comprehensive development tooling configuration.

The dev dependencies and tool configurations for ruff, mypy, and pytest are well-structured. The strict mypy settings and appropriate exclusions align with modern Python best practices.

src/designer_plugin/designer_plugin.py (4)

1-7: LGTM! Improved imports with explicit typing.

The addition of explicit imports and proper typing support modernizes the codebase and aligns with the new mypy configuration.


12-31: LGTM! Enhanced type safety and lifecycle management.

The explicit type hints and introduction of private _zeroconf and _azeroconf attributes provide better lifecycle control and prevent external interference with the Zeroconf instances.


33-51: LGTM! Type-safe static constructors.

The type hints and simplified file handling improve clarity and type safety. The use of .get() with defaults for optional JSON fields is appropriate.


54-70: LGTM! Properly typed service info property.

The explicit ServiceInfo return type and standardized string literals improve type safety and consistency.

Comment on lines +82 to 85
async def __aenter__(self) -> "DesignerPlugin":
self._azeroconf = AsyncZeroconf()
asyncio.create_task(self._azeroconf.async_register_service(self.service_info))
return self
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Async service registration may not complete before context entry.

Line 84 creates a background task for service registration but returns immediately without awaiting it. This creates a race condition where the context manager appears "entered" but the service may not be registered yet, potentially causing the plugin to be invisible to Designer.

Apply this diff to await registration completion:

 async def __aenter__(self) -> "DesignerPlugin":
     self._azeroconf = AsyncZeroconf()
-    asyncio.create_task(self._azeroconf.async_register_service(self.service_info))
+    await self._azeroconf.async_register_service(self.service_info)
     return self
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async def __aenter__(self) -> "DesignerPlugin":
self._azeroconf = AsyncZeroconf()
asyncio.create_task(self._azeroconf.async_register_service(self.service_info))
return self
async def __aenter__(self) -> "DesignerPlugin":
self._azeroconf = AsyncZeroconf()
await self._azeroconf.async_register_service(self.service_info)
return self
🤖 Prompt for AI Agents
In src/designer_plugin/designer_plugin.py around lines 82 to 85, the async
context manager starts service registration with asyncio.create_task and returns
immediately, causing a race where the plugin may appear entered before the
Zeroconf service is registered; change this to await
self._azeroconf.async_register_service(self.service_info) (i.e., call and await
the registration coroutine directly), optionally wrap it in try/except to log or
re-raise registration errors and ensure proper cleanup on failure so the context
only returns after registration completes.

Comment on lines +21 to +24
self.name: str = name
self.port: int = port
self.hostname: str = hostname or socket.gethostname()
self.custom_url: str | None = url
Copy link
Contributor

Choose a reason for hiding this comment

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

There's no benefit repeating the type annotations here

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I've removed them and agree they are not necessary.
Originally, I've put it because I like having explicit type hint for all class attributes.

@DevTGHa DevTGHa merged commit 7465986 into main Nov 25, 2025
1 of 2 checks passed
@DevTGHa DevTGHa deleted the package_python_plugin branch November 25, 2025 15:52
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)
src/designer_plugin/designer_plugin.py (1)

82-85: Critical: Async service registration may not complete before context entry.

Line 84 creates a background task for service registration but returns immediately without awaiting it. This creates a race condition where the context manager appears "entered" but the service may not be registered yet, potentially causing the plugin to be invisible to Designer.

Apply this diff to await registration completion:

 async def __aenter__(self) -> "DesignerPlugin":
     self._azeroconf = AsyncZeroconf()
-    asyncio.create_task(self._azeroconf.async_register_service(self.service_info))
+    await self._azeroconf.async_register_service(self.service_info)
     return self
🧹 Nitpick comments (3)
src/designer_plugin/designer_plugin.py (3)

40-52: Consider adding error handling for JSON parsing.

The method uses a context manager correctly and has clean type annotations. However, missing required keys (like "name") will raise KeyError, and invalid JSON will raise JSONDecodeError. Consider whether explicit error handling with informative messages would improve the developer experience.

Example:

     def from_json_file(file_path: str, port: int, hostname: str | None = None) -> "DesignerPlugin":
         """Convert a JSON file (expected d3plugin.json) to PluginOptions. hostname and port are required."""
-        with open(file_path) as f:
-            options = json_load(f)
+        try:
+            with open(file_path) as f:
+                options = json_load(f)
+        except (FileNotFoundError, JSONDecodeError) as e:
+            raise ValueError(f"Failed to load plugin config from {file_path}: {e}") from e
+        
+        if "name" not in options:
+            raise ValueError(f"Missing required 'name' field in {file_path}")
+        
         return DesignerPlugin(
             name=options["name"],
             port=port,

72-80: LGTM! Proper sync context manager implementation.

The synchronous context manager correctly initializes, registers, and cleans up the Zeroconf service. The None check in __exit__ ensures safe cleanup.

Optional: The # type: ignore on line 77 could be avoided by properly typing the parameters:

def __exit__(
    self,
    exc_type: type[BaseException] | None,
    exc_value: BaseException | None,
    traceback: object
) -> None:

87-90: Proper async cleanup; consider typing the parameters.

The async cleanup logic correctly awaits the close operation and guards against None. The # type: ignore on line 87 could optionally be removed with proper parameter types (same suggestion as the sync version).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira 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 2fb1c5d and 432f2b3.

📒 Files selected for processing (1)
  • src/designer_plugin/designer_plugin.py (3 hunks)
🔇 Additional comments (4)
src/designer_plugin/designer_plugin.py (4)

1-7: LGTM! Clean imports.

The imports are well-organized and all are used within the file.


12-31: LGTM! Well-typed constructor with proper lifecycle state.

The explicit type annotations using Python 3.10+ union syntax are clean, and the private _zeroconf and _azeroconf attributes properly support the context manager lifecycle.


33-37: LGTM! Properly typed factory method.

The return type annotation is correct and the method provides a clean convenience interface.


54-70: LGTM! Correct ServiceInfo construction.

The property correctly returns a ServiceInfo object with properly encoded byte properties. The implementation aligns with Zeroconf requirements.

@coderabbitai coderabbitai bot mentioned this pull request Nov 29, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Development

Successfully merging this pull request may close these issues.

3 participants