Skip to content

[MISC] Refactor plugin loading for BE and prompt-service to use centralized get_plugin() mechanism#1626

Merged
chandrasekharan-zipstack merged 1 commit into
feat/UN-2930-plugin-infrastructurefrom
misc/refactored-prompt-service-and-backend-plugins
Nov 11, 2025
Merged

[MISC] Refactor plugin loading for BE and prompt-service to use centralized get_plugin() mechanism#1626
chandrasekharan-zipstack merged 1 commit into
feat/UN-2930-plugin-infrastructurefrom
misc/refactored-prompt-service-and-backend-plugins

Conversation

@chandrasekharan-zipstack
Copy link
Copy Markdown
Contributor

What

  • Refactored backend/configuration/config_registry.py to use centralized get_plugin() function
  • Replaced manual plugin loading implementations across backend, prompt-service, and core modules
  • Removed obsolete loader files: subscription_loader.py, modifier_loader.py, processor_loader.py, and prompt-service plugin.py

Why

  • Aligns configuration plugin loading with new unified plugin infrastructure
  • Reduces code duplication across multiple modules
  • Simplifies plugin loading mechanism maintenance
  • Follows DRY principle with single source of truth for plugin loading

How

  • Updated config_registry.py to use get_plugin() for configuration plugin loading
  • Refactored all plugin loading code paths to use the centralized mechanism
  • Removed redundant loader implementations that are now handled by get_plugin()

Can this PR break any existing features. If yes, please list possible items. If no, please explain why.

No - backward compatibility maintained. The new get_plugin() mechanism is a drop-in replacement for manual loading with identical behavior. All existing plugin interfaces and configuration loading remain unchanged.

Database Migrations

  • None

Env Config

  • None

Relevant Docs

  • See plugin infrastructure documentation in backend/plugins/README.md

Related Issues or PRs

  • Related to UN-2930 [FEAT] Add core plugin infrastructure

Dependencies Versions

  • No dependency changes

Notes on Testing

  • Verified configuration loading works correctly with get_plugin()
  • API deployment configuration loading tested and working
  • Tested plugin discovery with nested plugins (up to 2 levels)
  • All pre-commit hooks passed successfully

Replaced manual plugin loading implementations across backend, prompt-service,
and core modules with the new centralized get_plugin() function. This aligns
all plugin loading with the new infrastructure, reduces code duplication, and
simplifies maintenance. Removed obsolete loader files (subscription_loader,
modifier_loader, processor_loader) that are now replaced by get_plugin().

Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Oct 31, 2025

Caution

Review failed

Failed to post review comments

Summary by CodeRabbit

  • Chores

    • Refactored plugin loading architecture from static initialization to dynamic runtime discovery across the system for improved flexibility and maintainability.
  • Bug Fixes

    • Enhanced resilience by gracefully handling scenarios where optional plugins are unavailable, preventing failures and improving system stability.
  • New Features

    • Introduced unified plugin management framework supporting both web frameworks with improved discoverability and registration capabilities.

Walkthrough

This diff implements a unified plugin management system across the backend and prompt-service. It removes legacy, ad-hoc plugin loaders (subscription_loader, modifier_loader, processor_loader) and replaces them with a new framework-agnostic PluginManager in unstract.core, plus Django and Flask wrappers. Various backend views and utilities are updated to use dynamic plugin retrieval via get_plugin() instead of static plugin loading. Plugin metadata structures are introduced, and Django app configuration is updated accordingly.

Changes

Cohort / File(s) Summary
Core Plugin Framework
unstract/core/src/unstract/core/plugins/plugin_manager.py, unstract/core/src/unstract/core/plugins/__init__.py, unstract/core/src/unstract/core/plugins/README.md, unstract/core/src/unstract/core/plugins/.gitignore
Introduces framework-agnostic PluginManager with support for plugin discovery, metadata validation, singleton behavior, and optional framework registration callbacks. Adds plugin documentation and gitignore rules.
Django Plugin Integration
unstract/core/src/unstract/core/django/plugin.py, unstract/core/src/unstract/core/django/__init__.py
Adds DjangoPluginManager singleton wrapper, delegate convenience accessors, plugin_loader helper, and public API re-exports.
Flask Plugin Integration
unstract/core/src/unstract/core/flask/plugin.py, unstract/core/src/unstract/core/flask/__init__.py
Adds FlaskPluginManager singleton wrapper with Flask app integration, blueprint registration support, and public API re-exports.
Backend Plugin Infrastructure
backend/plugins/__init__.py, backend/plugins/apps.py, backend/plugins/README.md, backend/plugins/.gitignore
Adds Django AppConfig for plugins, new get_plugin_manager() and get_plugin() public API, and updated documentation/ignore rules.
Backend Plugin Definitions
backend/plugins/api/dto/__init__.py
Replaces MetadataDto dataclass with ApiDtoService class; introduces populated metadata dict with version, name, is_active, description, and service_class.
Backend Views Migrating to Plugin System
backend/adapter_processor_v2/views.py, backend/api_v2/api_deployment_views.py, backend/pipeline_v2/views.py, backend/workflow_manager/workflow_v2/views.py
Replace hard-coded notification plugin checks and SharingNotificationService usage with dynamic get_plugin("notification") retrieval and runtime service instantiation.
Backend DTO Registry
backend/api_v2/api_deployment_dto_registry.py
Replaces metadata-based DTO loading with plugin-based approach using get_plugin("api_dto").
Backend Configuration & Utilities
backend/configuration/config_registry.py, backend/scheduler/tasks.py
Migrate from Django app registry and static imports to dynamic plugin loading and runtime helper imports; add fallback error handling for plugin absence.
Backend Prompt Studio Modules
backend/prompt_studio/prompt_studio_core_v2/prompt_studio_helper.py, backend/prompt_studio/prompt_studio_core_v2/views.py, backend/prompt_studio/prompt_studio_registry_v2/prompt_studio_registry_helper.py
Replace static modifier/processor plugin loading with dynamic get_plugin() retrieval; update control flow to conditionally use plugins at runtime.
Backend Removed Plugin Loaders
backend/account_v2/subscription_loader.py (deleted), backend/prompt_studio/modifier_loader.py (deleted), backend/prompt_studio/processor_loader.py (deleted)
Removes legacy Django-based plugin loaders for subscriptions, modifiers, and processors.
Prompt Service Plugin Migration
prompt-service/src/unstract/prompt_service/config.py, prompt-service/src/unstract/prompt_service/controllers/answer_prompt.py, prompt-service/src/unstract/prompt_service/services/answer_prompt.py
Migrate from local PluginManager singleton to unstract.core.flask.PluginManager import path; update config.py to use new plugin manager API.
Prompt Service Removed Plugin Code
prompt-service/src/unstract/prompt_service/helpers/plugin.py (deleted)
Removes legacy Flask-based PluginManager singleton and plugin_loader function.
Backend Django Settings
backend/backend/settings/base.py
Updates Django SHARED_APPS entry from "plugins" to "plugins.apps.PluginsConfig" to use AppConfig class.
Gitignore Updates
.gitignore, prompt-service/.gitignore
Removes plugin directory ignores in root .gitignore; consolidates prompt-service .gitignore to only ignore src/unstract/prompt_service/plugins.
Dependencies
workers/pyproject.toml
Adds debugpy>=1.8.14 to dev dependencies.
Redis Client Enhancement
unstract/core/src/unstract/core/cache/redis_client.py
Adds support for REDIS_USERNAME environment variable as fallback to REDIS_USER.

Sequence Diagram

sequenceDiagram
    participant OldFlow as Old: Static Load
    participant NewFlow as New: Dynamic Lookup
    participant PluginMgr as PluginManager
    participant Plugin as Plugin Module

    rect rgb(220, 240, 255)
    note over OldFlow: Previous: Load at Startup
    OldFlow->>OldFlow: Import loader module
    OldFlow->>OldFlow: Scan plugins directory (Django app)
    OldFlow->>Plugin: Import plugin modules
    OldFlow->>OldFlow: Cache all plugins globally
    OldFlow->>OldFlow: Use cached plugins in views
    end

    rect rgb(240, 255, 240)
    note over NewFlow: New: Load on Demand
    NewFlow->>PluginMgr: get_plugin("notification")
    PluginMgr->>PluginMgr: Check loaded plugins cache
    alt Plugin loaded
        PluginMgr->>NewFlow: Return plugin metadata + service_class
        NewFlow->>Plugin: service_class() instantiate
        NewFlow->>Plugin: Call method (e.g., send_sharing_notification)
    else Plugin not loaded
        PluginMgr->>NewFlow: Return None or empty dict
        NewFlow->>NewFlow: Skip notification, continue
    end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Core Plugin Framework: Introduces a new, multi-layered plugin architecture spanning framework-agnostic base, Django adapter, and Flask adapter. The PluginManager class includes singleton logic, plugin discovery with depth-limited traversal, metadata validation, and optional registration callbacks—requires careful review of discovery and initialization semantics.
  • Widespread Backend View Updates: Multiple views (adapter_processor, api_deployment, pipeline, workflow_manager, prompt_studio) follow a similar but not identical pattern of replacing hard-coded checks with dynamic get_plugin() calls. Each requires verification of error handling and fallback behavior.
  • Removed Components: Three backend plugin loaders (subscription, modifier, processor) and one prompt-service helper are deleted. Callers must be verified to not break due to removal.
  • Import Path Changes: Plugin manager imports shift from local modules to unstract.core in multiple locations; consistency must be verified.
  • Django Settings Change: Subtle but important change from app label to AppConfig class reference in SHARED_APPS.

Areas requiring extra attention:

  • Singleton initialization in PluginManager, DjangoPluginManager, and FlaskPluginManager—verify thread-safety and re-initialization logic
  • Plugin discovery depth traversal and edge cases (symlinks, special files, .so module naming)
  • Metadata validation and skip/skip semantics for disabled/inactive plugins across different wrappers
  • Backend view notification plugin integration—ensure no breaking changes to existing flow when plugin unavailable
  • Prompt service import path consolidation—verify no circular dependencies or missing re-exports
  • Django AppConfig ready() method timing and error handling during Django startup

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 78.85% 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
Title Check ✅ Passed The PR title "Refactor plugin loading to use centralized get_plugin() mechanism" accurately and directly describes the main change across the changeset. It is concise, specific, and clearly identifies the core refactoring effort—unifying plugin loading from multiple manual implementations into a single centralized mechanism. The title would help teammates quickly understand the scope of changes when scanning the repository history. No vague or misleading language is used, and the description directly maps to the actual changes shown in the raw_summary where multiple plugin loaders are being replaced with get_plugin() calls.
Description Check ✅ Passed The PR description comprehensively follows the provided template structure. All required sections are present and filled with substantive content: What (three specific refactoring points), Why (four clear justifications including DRY principle and reduced duplication), How (three implementation details), explicit "Can this PR break..." statement with backward-compatibility assurance, Database Migrations (None), Env Config (None), Relevant Docs (reference provided), Related Issues (UN-2930 cited), Dependencies Versions (addressed), and Notes on Testing (detailed verification steps provided). The description is well-organized, specific to the changes, and provides sufficient context for reviewers to understand the refactoring's scope and impact without being excessive.
✨ 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 misc/refactored-prompt-service-and-backend-plugins

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.

@github-actions
Copy link
Copy Markdown
Contributor

Test Results

Summary
  • Runner Tests: 11 passed, 0 failed (11 total)
  • SDK1 Tests: 66 passed, 0 failed (66 total)

Runner Tests - Full Report
filepath function $$\textcolor{#23d18b}{\tt{passed}}$$ SUBTOTAL
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_logs}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_cleanup}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_cleanup\_skip}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_client\_init}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_get\_image\_exists}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_get\_image}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_get\_container\_run\_config}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_get\_container\_run\_config\_without\_mount}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_run\_container}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_get\_image\_for\_sidecar}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{runner/src/unstract/runner/clients/test\_docker.py}}$$ $$\textcolor{#23d18b}{\tt{test\_sidecar\_container}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{TOTAL}}$$ $$\textcolor{#23d18b}{\tt{11}}$$ $$\textcolor{#23d18b}{\tt{11}}$$
SDK1 Tests - Full Report
filepath function $$\textcolor{#23d18b}{\tt{passed}}$$ SUBTOTAL
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_success\_on\_first\_attempt}}$$ $$\textcolor{#23d18b}{\tt{2}}$$ $$\textcolor{#23d18b}{\tt{2}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_retry\_on\_connection\_error}}$$ $$\textcolor{#23d18b}{\tt{2}}$$ $$\textcolor{#23d18b}{\tt{2}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_non\_retryable\_http\_error}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_retryable\_http\_errors}}$$ $$\textcolor{#23d18b}{\tt{3}}$$ $$\textcolor{#23d18b}{\tt{3}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_post\_method\_retry}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_platform.py}}$$ $$\textcolor{#23d18b}{\tt{TestPlatformHelperRetry.test\_retry\_logging}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_prompt.py}}$$ $$\textcolor{#23d18b}{\tt{TestPromptToolRetry.test\_success\_on\_first\_attempt}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_prompt.py}}$$ $$\textcolor{#23d18b}{\tt{TestPromptToolRetry.test\_retry\_on\_errors}}$$ $$\textcolor{#23d18b}{\tt{2}}$$ $$\textcolor{#23d18b}{\tt{2}}$$
$$\textcolor{#23d18b}{\tt{tests/test\_prompt.py}}$$ $$\textcolor{#23d18b}{\tt{TestPromptToolRetry.test\_wrapper\_methods\_retry}}$$ $$\textcolor{#23d18b}{\tt{4}}$$ $$\textcolor{#23d18b}{\tt{4}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_connection\_error\_is\_retryable}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_timeout\_is\_retryable}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_http\_error\_retryable\_status\_codes}}$$ $$\textcolor{#23d18b}{\tt{3}}$$ $$\textcolor{#23d18b}{\tt{3}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_http\_error\_non\_retryable\_status\_codes}}$$ $$\textcolor{#23d18b}{\tt{5}}$$ $$\textcolor{#23d18b}{\tt{5}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_http\_error\_without\_response}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_os\_error\_retryable\_errno}}$$ $$\textcolor{#23d18b}{\tt{5}}$$ $$\textcolor{#23d18b}{\tt{5}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_os\_error\_non\_retryable\_errno}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestIsRetryableError.test\_other\_exception\_not\_retryable}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCalculateDelay.test\_exponential\_backoff\_without\_jitter}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCalculateDelay.test\_exponential\_backoff\_with\_jitter}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCalculateDelay.test\_max\_delay\_cap}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCalculateDelay.test\_max\_delay\_cap\_with\_jitter}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_successful\_call\_first\_attempt}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_retry\_after\_transient\_failure}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_max\_retries\_exceeded}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_max\_time\_exceeded}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_retry\_with\_custom\_predicate}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_no\_retry\_with\_predicate\_false}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_exception\_not\_in\_tuple\_not\_retried}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryWithExponentialBackoff.test\_delay\_would\_exceed\_max\_time}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_default\_configuration}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_environment\_variable\_configuration}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_invalid\_max\_retries}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_invalid\_max\_time}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_invalid\_base\_delay}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_invalid\_multiplier}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_jitter\_values}}$$ $$\textcolor{#23d18b}{\tt{2}}$$ $$\textcolor{#23d18b}{\tt{2}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_custom\_exceptions\_only}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_custom\_predicate\_only}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_both\_exceptions\_and\_predicate}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestCreateRetryDecorator.test\_exceptions\_match\_but\_predicate\_false}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestPreconfiguredDecorators.test\_retry\_platform\_service\_call\_exists}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestPreconfiguredDecorators.test\_retry\_prompt\_service\_call\_exists}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestPreconfiguredDecorators.test\_platform\_service\_decorator\_retries\_on\_connection\_error}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestPreconfiguredDecorators.test\_prompt\_service\_decorator\_retries\_on\_timeout}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryLogging.test\_warning\_logged\_on\_retry}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryLogging.test\_info\_logged\_on\_success\_after\_retry}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{tests/utils/test\_retry\_utils.py}}$$ $$\textcolor{#23d18b}{\tt{TestRetryLogging.test\_exception\_logged\_on\_giving\_up}}$$ $$\textcolor{#23d18b}{\tt{1}}$$ $$\textcolor{#23d18b}{\tt{1}}$$
$$\textcolor{#23d18b}{\tt{TOTAL}}$$ $$\textcolor{#23d18b}{\tt{66}}$$ $$\textcolor{#23d18b}{\tt{66}}$$

@sonarqubecloud
Copy link
Copy Markdown

@chandrasekharan-zipstack chandrasekharan-zipstack changed the base branch from main to feat/UN-2930-plugin-infrastructure October 31, 2025 17:47
@chandrasekharan-zipstack chandrasekharan-zipstack changed the title Refactor plugin loading to use centralized get_plugin() mechanism [MISC] Refactor plugin loading for BE and prompt-service to use centralized get_plugin() mechanism Oct 31, 2025
@chandrasekharan-zipstack chandrasekharan-zipstack merged commit 94463f2 into feat/UN-2930-plugin-infrastructure Nov 11, 2025
8 checks passed
@chandrasekharan-zipstack chandrasekharan-zipstack deleted the misc/refactored-prompt-service-and-backend-plugins branch November 11, 2025 04:33
chandrasekharan-zipstack added a commit that referenced this pull request Nov 11, 2025
* UN-2930 [FEAT] Add core plugin infrastructure

Introduce a generic, framework-agnostic plugin system that supports
Django, Flask, and Workers. This foundation enables dynamic loading
of plugins with metadata validation, singleton/non-singleton patterns,
and support for compiled extensions.

Key components:
- Generic PluginManager with plugin discovery and validation
- DjangoPluginManager wrapper for Django apps
- FlaskPluginManager wrapper for Flask apps
- Redis client enhancement (REDIS_USERNAME support)
- Plugin directory exclusions in .gitignore
- Development dependency: debugpy for debugging

This is the first PR in a series for UN-2930, establishing the
infrastructure needed for subscription usage tracking and other
plugin-based features.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Refactored plugin gitignore to avoid explicitly stating plugin names

* Added new plugin loading mechanism to BE
NOTE: Existing mechanism is left untouched to avoid breaking anything

* Addressed code rabbit's review comment

* Minor code rabbit comment addressed, updated docs on adding and using plugins

* misc: Updated plugin discovery to supported nested plugins upto 2 levels

* UN-2930 [FEAT] Enhance plugin_loader with required parameter validation

- Update plugin_loader docstring to clarify plugins_dir and plugins_pkg are required
- Add parameter validation with helpful ValueError message
- Include practical example showing correct usage pattern
- Improves developer experience with clear setup requirements

Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* UN-2930 [FIX] Add thread-safety to FlaskPluginManager singleton

- Add threading.Lock at class level to protect singleton instance
- Guard instance creation and state mutations with lock
- Prevent race conditions in multi-threaded Flask environments
- Ensure thread-safe initialization of app, plugins_dir, and plugins_pkg

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* UN-2930 [REFACTOR] Merge nested conditions and simplify exception handling

- Merge nested if statements in FlaskPluginManager.getInstance() into single compound condition
- Remove redundant PermissionError from plugin_manager (OSError parent class covers it)

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>

* [MISC] Refactor plugin loading for BE and prompt-service to use centralized get_plugin() mechanism (#1626)

Refactor plugin loading to use centralized get_plugin() mechanism

Replaced manual plugin loading implementations across backend, prompt-service,
and core modules with the new centralized get_plugin() function. This aligns
all plugin loading with the new infrastructure, reduces code duplication, and
simplifies maintenance. Removed obsolete loader files (subscription_loader,
modifier_loader, processor_loader) that are now replaced by get_plugin().

Generated with Claude Code

Co-authored-by: Claude <noreply@anthropic.com>

* UN-2930 [FIX] Track subscription usage only for successful runs (#1620)

UN-2930 [FEAT] Implement subscription tracking for successful executions only

This PR implements the complete UN-2930 fix to track subscription usage
only for successful API calls and workflow executions.

Key changes:
- Add subscription usage tracking decorator for Django views
- Apply decorator to Prompt Studio index and answer-prompt operations
- Add workflow completion/failure handlers for defer/commit/discard pattern
- Implement batch tracking in workers for completed workflows
- Add platform-service plugin integration for subscription tracking
- Remove hardcoded SQL from platform-service, use plugin-based approach

Components:
1. Decorator (backend/utils/subscription_usage_decorator.py):
   - Auto-commits usage on success
   - Auto-discards usage on failure
   - Non-blocking error handling

2. Prompt Studio Integration:
   - Track usage for index_document()
   - Track usage for prompt_responder()

3. Workflow Integration:
   - Completion handler commits deferred usage for COMPLETED workflows
   - Failure handler discards all deferred usage

4. Workers Integration:
   - Batch commit for successful file executions
   - Calls internal API for efficient batch processing

5. Platform Service:
   - Plugin-based subscription tracking
   - Cleaner separation of concerns

This ensures usage is only recorded when operations actually succeed,
fixing the issue where failed operations were incorrectly tracked.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update backend/configuration/config_registry.py

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Signed-off-by: Chandrasekharan M <117059509+chandrasekharan-zipstack@users.noreply.github.com>

---------

Signed-off-by: Chandrasekharan M <117059509+chandrasekharan-zipstack@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
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