Skip to content

Conversation

@dadodimauro
Copy link
Collaborator

@dadodimauro dadodimauro commented Dec 29, 2025

Summary by CodeRabbit

  • New Features

    • Added optional dependency groups to install Redis, RabbitMQ, and CLI support selectively.
    • Added test-fast and test-slow commands to run targeted test subsets.
  • Tests

    • Added end-to-end tests validating optional-install behavior and CLI availability.
    • Marked an existing worker test as slow to reflect longer runtime.

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

@dadodimauro dadodimauro added this to the v1.0.0 milestone Dec 29, 2025
@dadodimauro dadodimauro self-assigned this Dec 29, 2025
@dadodimauro dadodimauro added the bug Something isn't working label Dec 29, 2025
@dadodimauro dadodimauro requested a review from anvouk as a code owner December 29, 2025 21:58
@coderabbitai
Copy link

coderabbitai bot commented Dec 29, 2025

📝 Walkthrough

Walkthrough

Converted several unconditional imports to conditional/lazy imports so optional backends/brokers and the CLI are only exposed when their dependencies are present. Added pytest slow marker and Makefile targets for fast/slow test runs. New slow tests validate optional-install behavior in isolated venvs.

Changes

Cohort / File(s) Summary
Build & Test config
Makefile, pyproject.toml
Added test-fast and test-slow Makefile targets (pytest with markers). Added [project.optional-dependencies] all and defined slow marker under [tool.pytest.ini_options].
Top-level package exports
src/chicory/__init__.py
Replaced static imports of RabbitMQBroker, RedisBroker, RedisBackend with try/except guarded imports and dynamically extend __all__ only when imports succeed.
App factory (lazy imports)
src/chicory/app.py
Moved imports for broker/backend classes into _create_broker and _create_backend call sites to defer import-time dependency requirements.
Backend & Broker subpackages
src/chicory/backend/__init__.py, src/chicory/broker/__init__.py
Made RedisBackend, RabbitMQBroker, RedisBroker conditional exports by wrapping their imports in try/except and appending to __all__ only on success.
CLI export handling
src/chicory/cli/__init__.py
Changed static app import to guarded try/except; on ImportError raise a RuntimeError with installation guidance; __all__ populated only when import succeeds.
Tests
tests/unit/test_optional_imports.py, tests/unit/test_worker.py
Added test_optional_imports.py (slow) that creates isolated venvs to verify optional components and CLI presence/absence; marked one existing test (test_stop_consume_task_timeout) as @pytest.mark.slow.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐰 I hopped through modules, soft and fleet,

Lazy imports now make installs neat.
Slow tests nap while fast ones play,
Optional parts join only when they may.
A little hop, a tidy trail—dependencies tiptoe on the trail.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 69.23% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: implementing safe imports for optional dependencies across multiple modules.
✨ Finishing touches
  • 📝 Generate docstrings

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

🧹 Nitpick comments (5)
pyproject.toml (1)

45-49: LGTM! Consider dependency references to reduce duplication.

The all optional dependency group correctly aggregates the three optional dependencies. This provides a convenient way for users to install all optional features.

Note: If pyproject.toml supports it, you could reference existing groups like all = ["chicory[redis,cli,rabbitmq]"] to avoid version duplication, but the current approach is explicit and valid.

src/chicory/__init__.py (1)

65-83: LGTM! Optional imports properly implemented.

The try/except blocks correctly implement optional dependency handling. Components are only exposed in __all__ when their dependencies are installed, preventing import-time failures.

Note: The static analysis hint about sorting __all__ (lines 77-80) is a style preference. Since __all__ is built dynamically here, the current approach is acceptable. You could consider sorting after all appends if you prefer alphabetical order.

tests/unit/test_optional_imports.py (2)

8-8: Remove unused noqa directive.

The # noqa: F401 directive appears to be unused according to static analysis. If the import is truly needed for pytest-cov, the noqa may not be necessary since F401 is already ignored in some contexts. Consider removing it or verifying it's needed.

🔎 Proposed fix
-import chicory  # Import to avoid errors with pytest-cov # noqa: F401
+import chicory  # Import to avoid errors with pytest-cov

82-158: LGTM! Well-structured parameterized test with minor cleanup needed.

The test comprehensively validates that optional extras install correctly and expose the expected components. The parameterization cleanly covers all scenarios (redis, rabbitmq, all).

Minor cleanup: Remove the unused # noqa: E501 directives on lines 93-94, 103, 109, 113-115 as indicated by static analysis.

🔎 Proposed cleanup
                [
-                    "from chicory import broker; print('RedisBroker' in broker.__all__)",  # noqa: E501
+                    "from chicory import broker; print('RedisBroker' in broker.__all__)",
-                    "from chicory import backend; print('RedisBackend' in backend.__all__)",  # noqa: E501
+                    "from chicory import backend; print('RedisBackend' in backend.__all__)",
                ],
            ),
            (
                "rabbitmq",
                [
                    "from chicory.broker import RabbitMQBroker; print('success')",
                ],
                [
-                    "from chicory import broker; print('RabbitMQBroker' in broker.__all__)",  # noqa: E501
+                    "from chicory import broker; print('RabbitMQBroker' in broker.__all__)",
                ],
            ),
            (
                "all",
                [
-                    "from chicory.broker import RedisBroker, RabbitMQBroker; print('success')",  # noqa: E501
+                    "from chicory.broker import RedisBroker, RabbitMQBroker; print('success')",
                    "from chicory.backend import RedisBackend; print('success')",
                ],
                [
-                    "from chicory import broker; print('RedisBroker' in broker.__all__)",  # noqa: E501
+                    "from chicory import broker; print('RedisBroker' in broker.__all__)",
-                    "from chicory import broker; print('RabbitMQBroker' in broker.__all__)",  # noqa: E501
+                    "from chicory import broker; print('RabbitMQBroker' in broker.__all__)",
-                    "from chicory import backend; print('RedisBackend' in backend.__all__)",  # noqa: E501
+                    "from chicory import backend; print('RedisBackend' in backend.__all__)",
                ],
            ),
src/chicory/cli/__init__.py (1)

10-14: Consider extracting the error message to a custom exception class.

The static analysis tool suggests avoiding long messages directly in exception instantiation. While this is a stylistic concern, using a custom exception class can improve maintainability and make the error more semantically meaningful.

🔎 Proposed refactor

Define a custom exception at the module level or in a shared exceptions module:

class CliNotInstalledError(RuntimeError):
    """Raised when CLI dependencies are not installed."""
    def __init__(self):
        super().__init__(
            "Chicory CLI is not installed. "
            "Please install it using 'pip install chicory[cli]' "
            "or with 'pip install chicory[all]'."
        )

Then use it:

 except ImportError:
-    raise RuntimeError(
-        "Chicory CLI is not installed. "
-        "Please install it to it using 'pip install chicory[cli]' "
-        "or with 'pip install chicory[all]'."
-    ) from None
+    raise CliNotInstalledError() from None

Based on static analysis hints from Ruff (TRY003).

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 744a318 and 457fd33.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (9)
  • Makefile
  • pyproject.toml
  • src/chicory/__init__.py
  • src/chicory/app.py
  • src/chicory/backend/__init__.py
  • src/chicory/broker/__init__.py
  • src/chicory/cli/__init__.py
  • tests/unit/test_optional_imports.py
  • tests/unit/test_worker.py
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-12-29T15:11:37.782Z
Learnt from: dadodimauro
Repo: dadodimauro/chicory PR: 18
File: src/chicory/broker/rabbitmq.py:86-93
Timestamp: 2025-12-29T15:11:37.782Z
Learning: When using aio-pika with a connection pool, acquire a connection from the pool and create a channel from that connection; do not close the connection when releasing it back to the pool. Pool.acquire() returns the connection to the pool for reuse, and RobustConnection objects stay open across acquire/release cycles. This pattern is demonstrated in official aio-pika examples and should be applied to files under src/chicory/broker (e.g., rabbitmq.py).

Applied to files:

  • src/chicory/broker/__init__.py
🧬 Code graph analysis (2)
src/chicory/__init__.py (4)
src/chicory/app.py (2)
  • broker (107-108)
  • backend (111-112)
src/chicory/broker/rabbitmq.py (1)
  • RabbitMQBroker (41-783)
src/chicory/backend/redis.py (1)
  • RedisBackend (13-179)
src/chicory/broker/redis.py (1)
  • RedisBroker (19-535)
src/chicory/app.py (3)
src/chicory/backend/base.py (1)
  • Backend (10-68)
src/chicory/broker/base.py (1)
  • Broker (38-141)
src/chicory/types.py (1)
  • BrokerType (234-236)
🪛 Ruff (0.14.10)
tests/unit/test_optional_imports.py

8-8: Unused noqa directive (non-enabled: F401)

Remove unused noqa directive

(RUF100)


22-22: subprocess call: check for execution of untrusted input

(S603)


33-33: subprocess call: check for execution of untrusted input

(S603)


60-60: subprocess call: check for execution of untrusted input

(S603)


93-93: Unused noqa directive (non-enabled: E501)

Remove unused noqa directive

(RUF100)


94-94: Unused noqa directive (non-enabled: E501)

Remove unused noqa directive

(RUF100)


103-103: Unused noqa directive (non-enabled: E501)

Remove unused noqa directive

(RUF100)


109-109: Unused noqa directive (non-enabled: E501)

Remove unused noqa directive

(RUF100)


113-113: Unused noqa directive (non-enabled: E501)

Remove unused noqa directive

(RUF100)


114-114: Unused noqa directive (non-enabled: E501)

Remove unused noqa directive

(RUF100)


115-115: Unused noqa directive (non-enabled: E501)

Remove unused noqa directive

(RUF100)


136-136: subprocess call: check for execution of untrusted input

(S603)


180-180: subprocess call: check for execution of untrusted input

(S603)

src/chicory/cli/__init__.py

10-14: Avoid specifying long messages outside the exception class

(TRY003)

src/chicory/__init__.py

77-80: __all__ is not sorted

Apply an isort-style sorting to __all__

(RUF022)

⏰ 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). (2)
  • GitHub Check: run (windows-latest, 3.11)
  • GitHub Check: run (windows-latest, 3.12)
🔇 Additional comments (12)
pyproject.toml (1)

79-81: LGTM! Pytest marker properly configured.

The slow marker is correctly defined and integrates well with the new Makefile targets (test-fast and test-slow). This enables efficient test segmentation for development workflows.

Makefile (1)

21-25: LGTM! Test targets properly configured.

The new test-fast and test-slow targets provide an excellent developer experience by enabling quick iteration (skipping slow tests) while still allowing full test runs when needed.

tests/unit/test_worker.py (1)

729-729: LGTM! Appropriately marked as slow test.

The @pytest.mark.slow decorator is correctly applied to this test, which contains a 6-second sleep to simulate a long-running consume loop. This allows developers to skip this test during rapid iteration.

src/chicory/app.py (4)

6-7: LGTM! Proper base class imports.

The change to import only the base Backend and Broker classes at module level is correct. This ensures the module can load without optional dependencies while still maintaining proper type annotations.


77-78: LGTM! Proper lazy import pattern.

The lazy import of RedisBroker within the _create_broker method ensures the Redis dependency is only required when explicitly using a Redis broker. This prevents import-time failures when Redis is not installed.


84-85: LGTM! Proper lazy import pattern.

The lazy import of RabbitMQBroker within the _create_broker method ensures the aio-pika dependency is only required when explicitly using a RabbitMQ broker.


98-99: LGTM! Proper lazy import pattern.

The lazy import of RedisBackend within the _create_backend method maintains consistency with the broker lazy import pattern and ensures Redis is only required when explicitly using a Redis backend.

src/chicory/backend/__init__.py (1)

5-12: LGTM! Clean optional import implementation.

The conditional export of RedisBackend is well-implemented. The base Backend class remains always available, while RedisBackend is only exposed when the Redis dependency is installed.

src/chicory/broker/__init__.py (1)

5-19: LGTM! Well-structured optional broker imports.

The separate try/except blocks for RabbitMQBroker and RedisBroker are correctly implemented. This allows each broker to be independently optional based on installed dependencies, maintaining flexibility for users who may only need one broker type.

tests/unit/test_optional_imports.py (2)

39-80: LGTM! Comprehensive base installation test.

This test effectively validates that chicory can be imported without optional dependencies and that optional components are correctly absent from __all__. The use of isolated virtual environments ensures reliable testing of the installation behavior.


160-202: LGTM! CLI installation test properly validates optional CLI dependency.

This test effectively validates that the CLI is only available when the appropriate extras are installed. The test correctly handles both the absence case (expecting failure with a specific error) and presence case (expecting help output).

src/chicory/cli/__init__.py (1)

5-14: Verify that immediate import failure is the intended behavior.

The current pattern raises RuntimeError immediately when the CLI dependencies are missing, which prevents the module import from succeeding at all. This is more aggressive than typical optional dependency patterns, which allow imports to succeed but make functionality unavailable.

While this provides clear, immediate feedback to users, it prevents graceful degradation. For example, code that conditionally uses CLI features cannot check for availability without triggering an exception.

Consider whether this immediate-failure approach aligns with your design goals, or if a softer pattern would be more appropriate:

__all__ = []

try:
    from .cli import app
    __all__.extend(["app"])
except ImportError:
    # Module import succeeds, but 'app' is unavailable
    # Users can check availability or handle AttributeError when accessing 'app'
    pass

@dadodimauro dadodimauro merged commit c365df9 into main Dec 29, 2025
14 of 15 checks passed
@dadodimauro dadodimauro deleted the optional-imports branch December 29, 2025 22:13
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

♻️ Duplicate comments (1)
src/chicory/cli/__init__.py (1)

12-12: Typo already resolved.

The previous review comment about the redundant "to it" in the error message has been successfully addressed. The message now correctly reads "Please install it using...".

🧹 Nitpick comments (2)
src/chicory/cli/__init__.py (1)

10-14: Consider the TRY003 linting suggestion.

The static analyzer suggests avoiding long error messages outside the exception class (TRY003). While this is functional, consider extracting the message to a constant or a custom exception class if this pattern is used elsewhere in the codebase.

🔎 Optional refactor to address TRY003
+CLI_NOT_INSTALLED_MSG = (
+    "Chicory CLI is not installed. "
+    "Please install it using 'pip install chicory[cli]' "
+    "or with 'pip install chicory[all]'."
+)
+
 try:
     from .cli import app
 
     __all__.extend(["app"])
 except ImportError:
-    raise RuntimeError(
-        "Chicory CLI is not installed. "
-        "Please install it using 'pip install chicory[cli]' "
-        "or with 'pip install chicory[all]'."
-    ) from None
+    raise RuntimeError(CLI_NOT_INSTALLED_MSG) from None
tests/unit/test_optional_imports.py (1)

102-180: Well-structured test for optional extras.

The test effectively validates that optional dependencies (redis, rabbitmq, all) install correctly and their components are properly exposed. The dual verification approach—checking both direct imports and __all__ membership—ensures comprehensive coverage.

Note: The unused noqa: E501 directives flagged by static analysis can be safely removed if line-length checking (E501) is not enabled in your linter configuration, but this is a minor style consideration.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 457fd33 and 081bc95.

📒 Files selected for processing (2)
  • src/chicory/cli/__init__.py
  • tests/unit/test_optional_imports.py
🧰 Additional context used
🪛 Ruff (0.14.10)
src/chicory/cli/__init__.py

10-14: Avoid specifying long messages outside the exception class

(TRY003)

tests/unit/test_optional_imports.py

9-9: Unused noqa directive (non-enabled: F401)

Remove unused noqa directive

(RUF100)


41-41: subprocess call: check for execution of untrusted input

(S603)


52-52: subprocess call: check for execution of untrusted input

(S603)


80-80: subprocess call: check for execution of untrusted input

(S603)


113-113: Unused noqa directive (non-enabled: E501)

Remove unused noqa directive

(RUF100)


114-114: Unused noqa directive (non-enabled: E501)

Remove unused noqa directive

(RUF100)


123-123: Unused noqa directive (non-enabled: E501)

Remove unused noqa directive

(RUF100)


129-129: Unused noqa directive (non-enabled: E501)

Remove unused noqa directive

(RUF100)


133-133: Unused noqa directive (non-enabled: E501)

Remove unused noqa directive

(RUF100)


134-134: Unused noqa directive (non-enabled: E501)

Remove unused noqa directive

(RUF100)


135-135: Unused noqa directive (non-enabled: E501)

Remove unused noqa directive

(RUF100)


157-157: subprocess call: check for execution of untrusted input

(S603)


202-202: subprocess call: check for execution of untrusted input

(S603)

⏰ 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). (4)
  • GitHub Check: run (windows-latest, 3.14)
  • GitHub Check: run (windows-latest, 3.13)
  • GitHub Check: run (windows-latest, 3.12)
  • GitHub Check: run (windows-latest, 3.11)
🔇 Additional comments (6)
tests/unit/test_optional_imports.py (5)

1-11: LGTM! Clean imports and setup.

The import structure is appropriate for isolated venv testing. The explicit chicory import with the noqa comment is correctly documented for pytest-cov compatibility.


15-18: LGTM! Fixture correctly locates project root.

The fixture properly navigates from tests/unit/test_optional_imports.py up three levels to reach the project root.


20-56: LGTM! Well-structured helper methods with cross-platform support.

The helper methods correctly handle platform differences between Windows and Unix systems for venv paths and command execution. The subprocess security warnings (S603) from static analysis are false positives in this test context where all inputs are controlled by the test code.


58-101: LGTM! Comprehensive test for base installation.

The test correctly validates that chicory installs and imports without optional dependencies, and verifies that optional components are not exposed in __all__. The parametrization efficiently covers Redis broker, Redis backend, and RabbitMQ broker scenarios.


181-224: LGTM! Thorough CLI installation testing.

The test correctly validates CLI behavior across different installation scenarios:

  • Without extras: CLI is unavailable and shows helpful error message
  • With cli or all extras: CLI is available and displays help text

The conditional logic and assertions appropriately handle each scenario.

src/chicory/cli/__init__.py (1)

10-14: Reconsider error handling pattern for missing CLI dependencies.

The CLI module raises RuntimeError when dependencies are missing, but broker and backend modules use conditional imports with silent degradation (try-except-pass, conditionally updating __all__). This is the only place in the codebase where optional dependencies trigger an import-time error rather than graceful degradation.

If raising an error is intentional to enforce CLI dependency installation, that should be documented. Otherwise, align with the conditional export pattern used in broker and backend modules.

Comment on lines +141 to +148
def test_optional_extras_install(
self,
project_root: Path,
extra: str,
import_commands: list[str],
import_command_all_names: list[str],
) -> None:
"""Test that redis extra installs and works correctly."""
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Update docstring to reflect parametrization.

The docstring states "Test that redis extra installs and works correctly," but the test is parametrized over multiple extras (redis, rabbitmq, all). Update the docstring to be more generic.

🔎 Proposed fix
-    def test_optional_extras_install(
-        self,
-        project_root: Path,
-        extra: str,
-        import_commands: list[str],
-        import_command_all_names: list[str],
-    ) -> None:
-        """Test that redis extra installs and works correctly."""
+    def test_optional_extras_install(
+        self,
+        project_root: Path,
+        extra: str,
+        import_commands: list[str],
+        import_command_all_names: list[str],
+    ) -> None:
+        """Test that optional extras install and work correctly."""
📝 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
def test_optional_extras_install(
self,
project_root: Path,
extra: str,
import_commands: list[str],
import_command_all_names: list[str],
) -> None:
"""Test that redis extra installs and works correctly."""
def test_optional_extras_install(
self,
project_root: Path,
extra: str,
import_commands: list[str],
import_command_all_names: list[str],
) -> None:
"""Test that optional extras install and work correctly."""
🤖 Prompt for AI Agents
In tests/unit/test_optional_imports.py around lines 141 to 148, the test
docstring specifically mentions "redis" but the test is parametrized over
multiple extras (redis, rabbitmq, all); update the docstring to a generic
description reflecting parametrization such as "Test that optional extras
install and work correctly" or "Test that each optional extra installs and works
correctly" so it accurately describes all parameter cases.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants