Skip to content

Conversation

@sainak
Copy link
Member

@sainak sainak commented Nov 13, 2025

Proposed Changes

  • Enhance plugin manager to autodetect installed plugs
  • Change plugs list to dict in manager attrs

Merge Checklist

  • Tests added/fixed
  • Update docs in /docs
  • Linting Complete
  • Any other necessary step

Only PR's with test cases included and passing lint and test pipelines will be reviewed

@ohcnetwork/care-backend-maintainers @ohcnetwork/care-backend-admins

Summary by CodeRabbit

  • New Features

    • Plugs are now automatically discovered and loaded based on installation and naming criteria, reducing manual configuration requirements.
  • Improvements

    • System combines auto-detected plugs with user-configured ones for flexible plug management.

@sainak sainak requested a review from a team as a code owner November 13, 2025 05:54
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 13, 2025

📝 Walkthrough

Walkthrough

The internal storage mechanism for plugs was refactored from a list to a dictionary indexed by plug name. Corresponding methods—install(), add_plug(), get_apps(), and get_config()—were updated to work with the new structure. A new autodetect_installed_plugs() method was introduced to discover installed plugs matching specific criteria.

Changes

Cohort / File(s) Change Summary
PlugManager refactoring
plugs/manager.py
Internal storage converted from list to dict (keyed by plug name); install() updated to iterate dict values with package name checks; add_plug() now inserts into dict instead of appending; get_apps() combines autodetected and explicit plugs into unique set; get_config() iterates plugs via values(); new method autodetect_installed_plugs() added to discover installed plugs with names starting with care_ that require Django.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • autodetect_installed_plugs() logic: Verify the distribution name filtering (care_ prefix) and Django dependency detection work as intended
  • Data structure consistency: Confirm all method transitions from list-based to dict-based access are complete and correct across install(), add_plug(), get_apps(), and get_config()
  • Uniqueness guarantee: Review get_apps() deduplication logic when combining autodetected and explicit plugs

Pre-merge checks and finishing touches

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Description check ❓ Inconclusive The description covers the two main changes but lacks an Associated Issue section and provides minimal detail about what problems this solves or architectural implications. Add an 'Associated Issue' section explaining which issue this addresses and how autodetection improves the plugin system, or provide more context about the motivation.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main feature addition—autodetecting installed plugs—which aligns with the core changes to the PlugManager class.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ 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 sainak/autodetect-plugs

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

Copy link
Contributor

@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 (1)
plugs/manager.py (1)

52-55: Consider using TypeError for type validation.

The type check at line 52-54 currently raises ValueError, but as the static analysis hint suggests, TypeError would be more semantically appropriate for invalid type issues. That said, the dict assignment at line 55 looks correct.

As per static analysis hints.

     if not isinstance(plug, Plug):
-        msg = "plug must be an instance of Plug"
-        raise ValueError(msg)
+        msg = "plug must be an instance of Plug"
+        raise TypeError(msg)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 815143a and d203462.

📒 Files selected for processing (1)
  • plugs/manager.py (3 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.py

📄 CodeRabbit inference engine (.cursorrules)

**/*.py: Prioritize readability and maintainability; follow Django's coding style guide (PEP 8 compliance).
Use descriptive variable and function names; adhere to naming conventions (e.g., lowercase with underscores for functions and variables).

Files:

  • plugs/manager.py
🧠 Learnings (2)
📓 Common learnings
Learnt from: rithviknishad
Repo: ohcnetwork/care PR: 2610
File: plug_config.py:18-23
Timestamp: 2024-11-25T11:38:30.225Z
Learning: In the `care` project, when camera-related code is moved from the main backend to be handled by a plugin, adding a camera plugin configuration (e.g., in `plug_config.py`) is expected, even if the PR title mentions removing camera code.
Learnt from: rithviknishad
Repo: ohcnetwork/care PR: 2610
File: plug_config.py:25-25
Timestamp: 2024-11-25T11:39:05.352Z
Learning: In `plug_config.py`, when moving camera-related code from the main backend to a plugin, adding `camera_plugin` to the plugs list is appropriate because the plugin will handle the camera functionality.
📚 Learning: 2024-11-25T11:39:05.352Z
Learnt from: rithviknishad
Repo: ohcnetwork/care PR: 2610
File: plug_config.py:25-25
Timestamp: 2024-11-25T11:39:05.352Z
Learning: In `plug_config.py`, when moving camera-related code from the main backend to a plugin, adding `camera_plugin` to the plugs list is appropriate because the plugin will handle the camera functionality.

Applied to files:

  • plugs/manager.py
🧬 Code graph analysis (1)
plugs/manager.py (1)
plugs/plug.py (1)
  • Plug (5-30)
🪛 Ruff (0.14.4)
plugs/manager.py

54-54: Prefer TypeError exception for invalid type

(TRY004)

⏰ 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: Test / Test
🔇 Additional comments (4)
plugs/manager.py (4)

20-20: Nice refactor to dict-based storage.

The dict comprehension provides O(1) lookups by plug name and simplifies the add/get operations throughout the class.


31-36: LGTM!

The use of a set for deduplication and the conditional check for plug.package_name are both solid improvements.


57-61: Clean set-based deduplication.

The use of a set to combine autodetected and explicit plugs, followed by conversion to a list, handles potential duplicates nicely. Just ensure the names from both sources are compatible (addressed in the previous comment about autodetection).


63-70: LGTM!

The iteration over values() correctly adapts to the new dict structure while maintaining the existing config aggregation logic.

Comment on lines +38 to +49
def autodetect_installed_plugs(self) -> list[str]:
# autodetect installed plugs starting with 'care_'
# these plugins will be using environment variables for configs
installed_plugins = []
for dist in distributions():
if (
dist.name.startswith("care_")
and dist.requires
and any("django" in req.lower() for req in dist.requires)
):
installed_plugins.append(dist.name)
return installed_plugins
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify that distribution names match Django app names.

This method returns distribution names (e.g., care_camera), which are then used in get_apps(). Ensure that these distribution names match the expected Django app configuration names, since there's no guarantee that a package's distribution name is identical to its Django app name.

Run the following script to check if autodetected distributions have corresponding Django apps:


🏁 Script executed:

#!/bin/bash
# Check for care_ packages and inspect their app configurations

# Find all installed care_ packages
pip list | grep -i "^care-" || echo "No care_ packages found with pip list"

# Search for Django app configs in installed packages
python -c "
from importlib.metadata import distributions
for dist in distributions():
    if dist.name.startswith('care_'):
        print(f'Distribution: {dist.name}')
        try:
            # Try to find AppConfig
            if dist.files:
                for file in dist.files:
                    if 'apps.py' in str(file):
                        print(f'  Found apps.py: {file}')
        except Exception as e:
            print(f'  Error: {e}')
"

Length of output: 37


I need to examine the codebase to understand how these distribution names are used. The sandbox doesn't have the packages installed, so let me look at the actual implementation context.


---

🏁 Script executed:

```shell
#!/bin/bash
# Find how autodetect_installed_plugs is used and get_apps method
rg -n "get_apps" --type=py -B 3 -A 3

# Find Django settings or app configuration
fd -type f -name "settings.py" -o -name "apps.py" -o -name "*.txt" | head -20

# Look for references to care_ packages
rg "care_" --type=py -n

Length of output: 40205


🏁 Script executed:

#!/bin/bash
# Check the manager.py file context around the method
cat plugs/manager.py

Length of output: 2376


Let me examine the Plug class and look for any tests that show how these packages work:

Let me search for the Plug class and look for any tests:Perfect. The web search confirms the concern is valid. Most of the time, a distribution package provides one single import package with a matching name, but this is only a convention and is not enforced by PyPI or other package indices. Classic examples include Pillow (distribution) vs PIL (import package) and mysql-python (distribution) vs MySQLdb (import).

Now let me search the codebase to check if there's any validation or if the code attempts to import these names:


---

🏁 Script executed:

```shell
#!/bin/bash
# Look for how PLUGIN_APPS is used in Django settings
rg "PLUGIN_APPS" --type=py -B 2 -A 5

# Check if there's any validation/import attempt
rg "INSTALLED_APPS" --type=py -B 3 -A 3 | head -50

# Look for plugs/plug.py to understand Plug.name
fd -type f -name "plug.py" -o -name "*plug*.py" | grep -v __pycache__ | head -10

Length of output: 3919


Perfect confirmation. Now let me search for the plugs/plug.py file and any tests:


---

🏁 Script executed:

```shell
#!/bin/bash
# Find and display plugs/plug.py
cat plugs/plug.py

# Look for tests using PlugManager
rg "PlugManager\|autodetect_installed_plugs" --type=py -l

Length of output: 1010


Verify that distribution names match Django app names — confirmed as a critical issue.

The review comment is accurate. The autodetect_installed_plugs() method returns distribution names (e.g., care_camera) directly without validating they're importable as Django apps. Since the Plug class distinguishes between name (Django app name) and package_name (pip package), but autodetection has no such mapping, there's a real risk:

  • Distribution name normalization (hyphens → underscores) is convention, not law
  • Nothing guarantees dist.name matches the actual Python module or app configuration name
  • The returned names are used directly in config/urls.py (include(f"{plug}.urls")) and INSTALLED_APPS, which will fail at runtime if the names don't resolve
  • Manually registered plugs allow name customization, but autodetected ones bypass this entirely

There's no validation that these distribution names are actually importable Django apps before they're added to configuration.

🤖 Prompt for AI Agents
In plugs/manager.py around lines 38 to 49, the autodetection currently returns
dist.name directly which may not be an importable Django app; change it to
resolve and validate the actual importable module/app name before adding: for
each distribution, read its top-level package(s) (top_level.txt or
distribution.read_text('top_level.txt')), try importing those module names and,
for the first importable one, verify it looks like a Django app (has an
AppConfig subclass or a urls module) and append that importable app/module name
(not the raw distribution name) to installed_plugins; if top_level.txt is absent
fall back to attempting normalized variants (replace hyphens with underscores)
but only include a plug when an import succeeds, and store the original
distribution/package name separately if you need package_name metadata.

Comment on lines +43 to +47
if (
dist.name.startswith("care_")
and dist.requires
and any("django" in req.lower() for req in dist.requires)
):
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

The "django" requirement check could match unintended packages.

The condition "django" in req.lower() is quite permissive and will match any requirement containing "django" as a substring, such as "djangorestframework" or even "django-cors-headers". While this might be acceptable, it could lead to false positives if a package happens to have a dependency that merely mentions django in its description.

Consider using a more precise check, perhaps parsing the requirement name more carefully or checking against known django package names.

-                and any("django" in req.lower() for req in dist.requires)
+                and any(req.lower().split()[0].startswith("django") for req in dist.requires)
🤖 Prompt for AI Agents
In plugs/manager.py around lines 43 to 47, the check `"django" in req.lower()`
is too permissive and can match packages like "djangorestframework" or
"django-cors-headers"; replace it by parsing each requirement string into a
Requirement object (from packaging.requirements import Requirement) and compare
the requirement's .name (lowercased) to "django" (or to a small explicit
allowlist if you intend to accept related packages), e.g. parse req into
Requirement(req) and use req_obj.name.lower() == "django" to decide inclusion.

@codecov
Copy link

codecov bot commented Nov 13, 2025

Codecov Report

❌ Patch coverage is 44.44444% with 10 lines in your changes missing coverage. Please review.
✅ Project coverage is 53.87%. Comparing base (815143a) to head (d203462).

Files with missing lines Patch % Lines
plugs/manager.py 44.44% 7 Missing and 3 partials ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           develop    #3355      +/-   ##
===========================================
- Coverage    53.88%   53.87%   -0.01%     
===========================================
  Files          434      434              
  Lines        19686    19699      +13     
  Branches      2129     2134       +5     
===========================================
+ Hits         10607    10613       +6     
- Misses        9051     9056       +5     
- Partials        28       30       +2     

☔ 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.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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.

2 participants