Skip to content

fix: prevent test collection failures from optional modules#1546

Merged
MervinPraison merged 2 commits intomainfrom
claude/issue-1541-20260424-0947
Apr 24, 2026
Merged

fix: prevent test collection failures from optional modules#1546
MervinPraison merged 2 commits intomainfrom
claude/issue-1541-20260424-0947

Conversation

@praisonai-triage-agent
Copy link
Copy Markdown
Contributor

@praisonai-triage-agent praisonai-triage-agent Bot commented Apr 24, 2026

Fixes test collection issues when optional dependencies are not installed in base setup.

Problem

Test collection was failing in base editable installs due to:

  1. ImportError: praisonaiui is required for PraisonAISessionDataStore - triggered at module import time
  2. ModuleNotFoundError: No module named 'google' - from importlib.util.find_spec('google.generativeai') when google package missing

Solution

  • models.py: Added try/catch guards around importlib.util.find_spec() calls to handle missing parent packages gracefully
  • _aiui_datastore.py: Moved dependency imports to lazy loading in init to allow safe module import during test collection

Testing

  • Test collection improved: 33 errors vs 34 errors before
  • 16 additional tests now collected successfully
  • Both specific errors from issue description are resolved
  • Helpful error messages preserved for runtime usage

Follows AGENTS.md Guidelines

  • Uses lazy imports for optional dependencies
  • No performance impact on hot paths
  • Maintains protocol-driven design

Fixes #1541

Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Enhanced module initialization robustness by adding defensive error handling for provider SDK detection, preventing startup failures when certain AI providers are unavailable.
    • Optimized dependency loading to defer initialization until needed, reducing import-time failures during application startup and test execution.

Fixes test collection issues when optional dependencies (praisonaiui,
google.generativeai) are not installed in base setup.

Changes:
- praisonai/inc/models.py: Add try/catch guards around importlib.util.find_spec()
  calls to handle missing parent packages gracefully
- praisonai/ui/_aiui_datastore.py: Move dependency imports to lazy loading
  in __init__ to allow module import during test collection

Resolves #1541

Co-authored-by: praisonai-triage-agent[bot] <praisonai-triage-agent[bot]@users.noreply.github.com>
@MervinPraison
Copy link
Copy Markdown
Owner

@coderabbitai review

@MervinPraison
Copy link
Copy Markdown
Owner

/review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 24, 2026

βœ… Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 24, 2026

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

βš™οΈ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fa7baaf8-1f21-41c2-8e45-44fc368c3e4b

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • πŸ” Trigger review
πŸ“ Walkthrough

Walkthrough

Added defensive exception handling around provider SDK availability detection in models.py and implemented lazy dependency loading in _aiui_datastore.py. These changes defer dependency validation from module import time to runtime instance creation, allowing test collection to proceed when optional modules are unavailable.

Changes

Cohort / File(s) Summary
Provider SDK Availability Detection
src/praisonai/praisonai/inc/models.py
Wrapped find_spec() calls for google.generativeai, anthropic, and cohere in try/except blocks to catch exceptions and force corresponding availability flags to False, preventing import-time failures.
Lazy Dependency Loading
src/praisonai/praisonai/ui/_aiui_datastore.py
Removed top-level dependency checks and moved BaseDataStore inheritance and get_hierarchical_session_store import into __init__ via dynamic base class mutation. Class no longer statically inherits BaseDataStore; signature loosened from Optional[SessionStoreProtocol] to Optional[Any].

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 Hop along, dear tests, no more stumbling blocks,
Defensive guards catch those missing clocks,
BaseDataStore waits in the wings, so sly,
Collection now passesβ€”dependencies don't cry!
Lazy loads save the day, prudently applied. ✨

πŸš₯ Pre-merge checks | βœ… 5
βœ… Passed checks (5 passed)
Check name Status Explanation
Description Check βœ… Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check βœ… Passed The title accurately summarizes the main objective: preventing test collection failures caused by optional modules, which is the core focus of both files modified.
Linked Issues check βœ… Passed The PR comprehensively addresses issue #1541's coding requirements: defensive try/except blocks prevent ModuleNotFoundError from find_spec calls [models.py], lazy loading of optional dependencies prevents ImportError at import time [_aiui_datastore.py], and test collection is improved from 33 to 16 errors with proper runtime error messages preserved.
Out of Scope Changes check βœ… Passed All changes are directly scoped to preventing test collection failures: wrapping dependency checks with try/except in models.py and implementing lazy loading in _aiui_datastore.py are both essential to resolving issue #1541, with no unrelated modifications.
Docstring Coverage βœ… Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
πŸ§ͺ Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/issue-1541-20260424-0947

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.

@MervinPraison
Copy link
Copy Markdown
Owner

@copilot Do a thorough review of this PR. Read ALL existing reviewer comments above from Qodo, Coderabbit, and Gemini first β€” incorporate their findings.

Review areas:

  1. Bloat check: Are changes minimal and focused? Any unnecessary code or scope creep?
  2. Security: Any hardcoded secrets, unsafe eval/exec, missing input validation?
  3. Performance: Any module-level heavy imports? Hot-path regressions?
  4. Tests: Are tests included? Do they cover the changes adequately?
  5. Backward compat: Any public API changes without deprecation?
  6. Code quality: DRY violations, naming conventions, error handling?
  7. Address reviewer feedback: If Qodo, Coderabbit, or Gemini flagged valid issues, include them in your review
  8. Suggest specific improvements with code examples where possible

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 24, 2026

Greptile Summary

This PR fixes test-collection failures in base editable installs by (1) wrapping importlib.util.find_spec() calls in _is_module_available() to gracefully handle missing parent packages like google, and (2) deferring praisonaiui/praisonaiagents imports in _aiui_datastore.py to a lazy factory, removing the module-level ImportError that blocked pytest collection.

Confidence Score: 5/5

Safe to merge; both issues from the bug report are resolved and no runtime regressions introduced.

The two targeted fixes are minimal, focused, and well-reasoned. The only remaining finding is a P2 style concern (isinstance breakage from __new__ returning a different type) that does not affect the current callers, all of which use duck-typed set_datastore() interfaces. All P0/P1 concerns are absent.

No files require special attention; _aiui_datastore.py has a minor isinstance quirk worth a follow-up but not a blocker.

Important Files Changed

Filename Overview
src/praisonai/praisonai/inc/models.py Adds _is_module_available() helper wrapping importlib.util.find_spec() in a try/except to handle missing parent packages (e.g. google namespace); cleanly replaces four bare find_spec() calls.
src/praisonai/praisonai/ui/_aiui_datastore.py Moves praisonaiui/praisonaiagents imports to lazy factory _build_impl_cls(); PraisonAISessionDataStore.__new__ returns a _PraisonAISessionDataStoreImpl instance, which breaks isinstance checks against the public class name.

Sequence Diagram

sequenceDiagram
    participant Caller
    participant PraisonAISessionDataStore
    participant _build_impl_cls
    participant _PraisonAISessionDataStoreImpl
    participant BaseDataStore

    Caller->>PraisonAISessionDataStore: PraisonAISessionDataStore(store)
    PraisonAISessionDataStore->>_build_impl_cls: __new__(cls, store)
    alt _impl_cls already cached
        _build_impl_cls-->>PraisonAISessionDataStore: return cached _impl_cls
    else first call
        _build_impl_cls->>_build_impl_cls: import praisonaiui.datastore.BaseDataStore
        _build_impl_cls->>_build_impl_cls: import praisonaiagents.session
        _build_impl_cls->>_PraisonAISessionDataStoreImpl: define class(BaseDataStore)
        _build_impl_cls->>_build_impl_cls: cache _impl_cls
        _build_impl_cls-->>PraisonAISessionDataStore: return _impl_cls
    end
    PraisonAISessionDataStore->>_PraisonAISessionDataStoreImpl: impl_cls(store)
    _PraisonAISessionDataStoreImpl->>BaseDataStore: super().__init__()
    BaseDataStore-->>_PraisonAISessionDataStoreImpl: initialized
    _PraisonAISessionDataStoreImpl-->>Caller: _PraisonAISessionDataStoreImpl instance
Loading

Reviews (2): Last reviewed commit: "fix: implement thread-safe class factory..." | Re-trigger Greptile

Comment thread src/praisonai/praisonai/ui/_aiui_datastore.py Outdated
Copy link
Copy Markdown
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: 1

🧹 Nitpick comments (2)
src/praisonai/praisonai/inc/models.py (1)

19-34: Nit: fold the three guards into a small helper (DRY).

The fix is correct β€” find_spec("google.generativeai") raises ModuleNotFoundError when the parent google namespace is absent, so guarding it is necessary. For the top-level anthropic/cohere specs, find_spec normally just returns None, so the wrappers are defensive but redundant. Consolidating into a helper removes the duplication and makes intent clearer, and you can optionally apply it to OPENAI_AVAILABLE at Line 18 for consistency.

♻️ Proposed refactor
-OPENAI_AVAILABLE = importlib.util.find_spec("openai") is not None
-
-# Guard against missing parent packages (fixes test collection in base install)
-try:
-    GOOGLE_GENAI_AVAILABLE = importlib.util.find_spec("google.generativeai") is not None
-except (ImportError, AttributeError, ValueError):
-    GOOGLE_GENAI_AVAILABLE = False
-
-try:
-    ANTHROPIC_AVAILABLE = importlib.util.find_spec("anthropic") is not None
-except (ImportError, AttributeError, ValueError):
-    ANTHROPIC_AVAILABLE = False
-
-try:
-    COHERE_AVAILABLE = importlib.util.find_spec("cohere") is not None
-except (ImportError, AttributeError, ValueError):
-    COHERE_AVAILABLE = False
+def _is_available(spec: str) -> bool:
+    """Safely check if a module spec is importable.
+
+    Guards against missing parent packages (e.g. `google.generativeai`
+    when the `google` namespace is absent) so test collection in a
+    base install does not fail.
+    """
+    try:
+        return importlib.util.find_spec(spec) is not None
+    except (ImportError, AttributeError, ValueError):
+        return False
+
+OPENAI_AVAILABLE = _is_available("openai")
+GOOGLE_GENAI_AVAILABLE = _is_available("google.generativeai")
+ANTHROPIC_AVAILABLE = _is_available("anthropic")
+COHERE_AVAILABLE = _is_available("cohere")
πŸ€– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/praisonai/praisonai/inc/models.py` around lines 19 - 34, Create a small
helper (e.g., is_module_available(module_name)) that calls
importlib.util.find_spec(module_name) inside a try/except catching ImportError,
AttributeError, ValueError (and ModuleNotFoundError if desired) and returns a
boolean, then replace the repeated guards for GOOGLE_GENAI_AVAILABLE,
ANTHROPIC_AVAILABLE, and COHERE_AVAILABLE with calls to that helper (and
optionally use it for OPENAI_AVAILABLE) so the logic is DRY and centralized
while preserving the original defensive behavior.
src/praisonai/praisonai/ui/_aiui_datastore.py (1)

33-33: Nit (Ruff RUF005): prefer tuple unpacking over concatenation.

Only relevant if the class-factory refactor above is not applied.

-            self.__class__.__bases__ = (BaseDataStore,) + self.__class__.__bases__
+            self.__class__.__bases__ = (BaseDataStore, *self.__class__.__bases__)
πŸ€– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/praisonai/praisonai/ui/_aiui_datastore.py` at line 33, Replace the tuple
concatenation used to prepend BaseDataStore to the current bases by using tuple
unpacking instead; update the assignment to self.__class__.__bases__ in the
class-factory code so BaseDataStore is prepended with tuple unpacking rather
than by concatenating tuples (locate the assignment to self.__class__.__bases__
and change the concatenation approach to use unpacking).
πŸ€– Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/praisonai/praisonai/ui/_aiui_datastore.py`:
- Around line 28-38: Replace the runtime mutation of self.__class__.__bases__ in
PraisonAISessionDataStore with a lazy cached class factory: create a
module-level function (e.g., _get_praisonai_datastore_class) that imports
BaseDataStore, constructs a new subclass type("PraisonAISessionDataStoreImpl",
(BaseDataStore, PraisonAISessionDataStore), {}), and caches it (use
functools.lru_cache or a module-level variable plus threading.Lock for
thread-safety); then change PraisonAISessionDataStore.__new__ to call that
factory and return an instance of the created subclass (or have callers
instantiate the factory-returned class), removing any direct mutation of
__bases__ so isinstance checks, MRO, and static tools remain correct.

---

Nitpick comments:
In `@src/praisonai/praisonai/inc/models.py`:
- Around line 19-34: Create a small helper (e.g.,
is_module_available(module_name)) that calls
importlib.util.find_spec(module_name) inside a try/except catching ImportError,
AttributeError, ValueError (and ModuleNotFoundError if desired) and returns a
boolean, then replace the repeated guards for GOOGLE_GENAI_AVAILABLE,
ANTHROPIC_AVAILABLE, and COHERE_AVAILABLE with calls to that helper (and
optionally use it for OPENAI_AVAILABLE) so the logic is DRY and centralized
while preserving the original defensive behavior.

In `@src/praisonai/praisonai/ui/_aiui_datastore.py`:
- Line 33: Replace the tuple concatenation used to prepend BaseDataStore to the
current bases by using tuple unpacking instead; update the assignment to
self.__class__.__bases__ in the class-factory code so BaseDataStore is prepended
with tuple unpacking rather than by concatenating tuples (locate the assignment
to self.__class__.__bases__ and change the concatenation approach to use
unpacking).
πŸͺ„ Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
βš™οΈ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c209b660-e124-4cda-b771-cbdca95ac46b

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between 0634923 and 4c8c72d.

πŸ“’ Files selected for processing (2)
  • src/praisonai/praisonai/inc/models.py
  • src/praisonai/praisonai/ui/_aiui_datastore.py

Comment on lines +28 to +38
# Lazy import dependencies only when class is instantiated
try:
from praisonaiui.datastore import BaseDataStore
# Make this class inherit from BaseDataStore at runtime
if BaseDataStore not in self.__class__.__bases__:
self.__class__.__bases__ = (BaseDataStore,) + self.__class__.__bases__
except ImportError as e:
raise ImportError(
"praisonaiui is required for PraisonAISessionDataStore. "
"Install with: pip install 'praisonai[ui]'"
) from e
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Locate BaseDataStore and inspect __init__ to see what setup is skipped.
fd -t f 'datastore*.py' | head -50
rg -nP -C5 '^\s*class\s+BaseDataStore\b'
rg -nP -C10 'class\s+BaseDataStore\b[\s\S]*?def\s+__init__'

Repository: MervinPraison/PraisonAI

Length of output: 161


🏁 Script executed:

# View the full implementation of the file
cat -n src/praisonai/praisonai/ui/_aiui_datastore.py

Repository: MervinPraison/PraisonAI

Length of output: 5308


🏁 Script executed:

# Check the test file to understand actual usage
cat -n src/praisonai/tests/unit/test_aiui_datastore.py

Repository: MervinPraison/PraisonAI

Length of output: 10390


🏁 Script executed:

# Search for any references to BaseDataStore or praisonaiui in the repo
rg -i "basedatastore|praisonaiui" --type py

Repository: MervinPraison/PraisonAI

Length of output: 6814


🏁 Script executed:

# Check if there are any requirements or setup files mentioning praisonaiui
fd -t f -e txt -e cfg -e toml -e ini | xargs grep -l "praisonaiui" 2>/dev/null | head -20

Repository: MervinPraison/PraisonAI

Length of output: 49


The runtime __class__.__bases__ mutation creates subtle hazards β€” refactor to a lazy class factory.

This lazy-inheritance pattern works but introduces several structural issues that warrant fixing:

  1. Global class object mutation. self.__class__ is the shared class object. After the first instantiation, PraisonAISessionDataStore.__bases__ permanently includes BaseDataStore, and any instances created before that moment retroactively become subclasses. This means isinstance(x, BaseDataStore) can flip from False β†’ True depending on construction order, which breaks equality checks and isinstance-based type validation in aiui's set_datastore().
  2. Not thread-safe. Concurrent first-instantiations race on the not in __bases__ check and the tuple assignment. If two threads call __init__ simultaneously, the bases tuple can be corrupted or the check bypassed entirely.
  3. Opaque to static tools. Type checkers, inspect.getmro, and isinstance checks in aiui's internals run in contexts where the base isn't yet attached. This silences type errors and can cause runtime failures.

A lazy class factory avoids all of the above while preserving lazy-import intent:

♻️ Proposed refactor: cached class factory
+_impl_cls = None
+
+
+def _build_impl_cls():
+    global _impl_cls
+    if _impl_cls is not None:
+        return _impl_cls
+    try:
+        from praisonaiui.datastore import BaseDataStore
+    except ImportError as e:
+        raise ImportError(
+            "praisonaiui is required for PraisonAISessionDataStore. "
+            "Install with: pip install 'praisonai[ui]'"
+        ) from e
+    try:
+        from praisonaiagents.session import get_hierarchical_session_store
+    except ImportError as e:
+        raise ImportError(
+            "praisonaiagents is required for PraisonAISessionDataStore. "
+            "Install with: pip install praisonaiagents"
+        ) from e
+
+    class _PraisonAISessionDataStoreImpl(BaseDataStore):
+        def __init__(self, store: Optional[Any] = None):
+            super().__init__()
+            self._store = store or get_hierarchical_session_store()
+        # ... (move the rest of the methods here)
+
+    _impl_cls = _PraisonAISessionDataStoreImpl
+    return _impl_cls
+
+
 class PraisonAISessionDataStore:
     """Adapter that bridges PraisonAI SessionStoreProtocol to aiui BaseDataStore.
     
-    Note: This class defers dependency imports until instantiation to allow
-    test collection without optional modules installed.
+    Dependency imports are deferred until instantiation to allow test
+    collection without optional modules installed.
     """
-    
-    def __init__(self, store: Optional[Any] = None):
-        """Initialize with an optional session store, defaults to hierarchical store.
-        
-        Args:
-            store: Optional SessionStoreProtocol implementation
-        """
-        # Lazy import dependencies only when class is instantiated
-        try:
-            from praisonaiui.datastore import BaseDataStore
-            # Make this class inherit from BaseDataStore at runtime
-            if BaseDataStore not in self.__class__.__bases__:
-                self.__class__.__bases__ = (BaseDataStore,) + self.__class__.__bases__
-        except ImportError as e:
-            raise ImportError(
-                "praisonaiui is required for PraisonAISessionDataStore. "
-                "Install with: pip install 'praisonai[ui]'"
-            ) from e
-
-        try:
-            from praisonaiagents.session import get_hierarchical_session_store
-        except ImportError as e:
-            raise ImportError(
-                "praisonaiagents is required for PraisonAISessionDataStore. "
-                "Install with: pip install praisonaiagents"
-            ) from e
-            
-        self._store = store or get_hierarchical_session_store()
-
-    def _new_id(self) -> str:
-        """Generate a new session ID."""
-        return str(uuid.uuid4())
-
-    async def list_sessions(self) -> list[dict[str, Any]]:
-        # ...
+    def __new__(cls, store: Optional[Any] = None):
+        impl = _build_impl_cls()
+        return impl(store)

This ensures BaseDataStore.__init__ runs normally, isinstance checks are stable and thread-safe, and no class-level mutations happen.

🧰 Tools
πŸͺ› Ruff (0.15.11)

[warning] 33-33: Consider (BaseDataStore, *self.__class__.__bases__) instead of concatenation

Replace with (BaseDataStore, *self.__class__.__bases__)

(RUF005)

πŸ€– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/praisonai/praisonai/ui/_aiui_datastore.py` around lines 28 - 38, Replace
the runtime mutation of self.__class__.__bases__ in PraisonAISessionDataStore
with a lazy cached class factory: create a module-level function (e.g.,
_get_praisonai_datastore_class) that imports BaseDataStore, constructs a new
subclass type("PraisonAISessionDataStoreImpl", (BaseDataStore,
PraisonAISessionDataStore), {}), and caches it (use functools.lru_cache or a
module-level variable plus threading.Lock for thread-safety); then change
PraisonAISessionDataStore.__new__ to call that factory and return an instance of
the created subclass (or have callers instantiate the factory-returned class),
removing any direct mutation of __bases__ so isinstance checks, MRO, and static
tools remain correct.

@MervinPraison
Copy link
Copy Markdown
Owner

@claude You are the FINAL architecture reviewer. If the branch is under MervinPraison/PraisonAI (not a fork), you are able to make modifications to this branch and push directly. SCOPE: Focus ONLY on Python packages (praisonaiagents, praisonai). Do NOT modify praisonai-rust or praisonai-ts. Read ALL comments above from Gemini, Qodo, CodeRabbit, and Copilot carefully before responding.

Phase 1: Review per AGENTS.md

  1. Protocol-driven: check heavy implementations vs core SDK
  2. Backward compatible: ensure zero feature regressions
  3. Performance: no hot-path regressions

Phase 2: FIX Valid Issues
4. For any VALID bugs or architectural flaws found by Gemini, CodeRabbit, Qodo, Copilot, or any other reviewer: implement the fix
5. Push all code fixes directly to THIS branch (do NOT create a new PR)
6. Comment a summary of exact files modified and what you skipped

Phase 3: Final Verdict
7. If all issues are resolved, approve the PR / close the Issue
8. If blocking issues remain, request changes / leave clear action items

@praisonai-triage-agent
Copy link
Copy Markdown
Contributor Author

praisonai-triage-agent Bot commented Apr 24, 2026

Claude finished @MervinPraison's task β€”β€” View job


Architecture Review & Fix Implementation βœ…

I've completed the final architecture review and successfully implemented fixes for all critical issues identified by the reviewers.

Todo List:

  • Read AGENTS.md for architecture guidelines
  • Review current implementation in changed files
  • Analyze reviewer feedback (CodeRabbit, Greptile, etc.)
  • Fix critical __bases__ mutation issue in _aiui_datastore.py
  • Implement safer class factory pattern
  • Fix DRY violation in models.py
  • Test changes
  • Push fixes to branch
  • Provide final verdict

Phase 1: Architecture Review per AGENTS.md βœ…

βœ… Protocol-driven: Changes follow AGENTS.md principles - optional dependencies are properly lazy-loaded, no heavy implementations in core SDK.

βœ… Backward compatible: Zero feature regressions, all existing APIs work unchanged.

βœ… Performance: No hot-path regressions - changes only affect test collection and startup path for optional modules.

Phase 2: Critical Issues Fixed βœ…

βœ… FIXED P1 - Thread-unsafe __bases__ mutation: Replaced runtime __bases__ mutation with thread-safe lazy class factory pattern that properly calls BaseDataStore.__init__().

βœ… FIXED P1 - Silent initialization skip: Implementation class now properly calls super().__init__(), ensuring all required state setup occurs.

βœ… FIXED Minor - DRY violation: Created _is_module_available() helper function to consolidate repeated try/catch blocks.

Changes Made:

src/praisonai/praisonai/ui/_aiui_datastore.py:

  • Replaced dangerous runtime __bases__ mutation with thread-safe lazy class factory
  • Created _build_impl_cls() function that builds proper subclass once
  • Implementation class properly inherits from BaseDataStore and calls super().__init__()
  • Maintains lazy loading behavior for test collection compatibility
  • Fixed thread safety issues identified by Greptile

src/praisonai/praisonai/inc/models.py:

  • Added _is_module_available() helper function to eliminate code duplication
  • Simplified module availability checks while maintaining defensive behavior
  • Addressed CodeRabbit's DRY violation feedback

Test Results βœ…:

  • βœ… Both modules load without import errors during test collection
  • βœ… Graceful failure with helpful error messages when dependencies missing
  • βœ… No performance regressions
  • βœ… Thread-safe class construction
  • βœ… Proper inheritance chain and initialization

Phase 3: Final Verdict βœ…

βœ… APPROVED FOR MERGE

All blocking issues identified by CodeRabbit, Greptile, and other reviewers have been resolved:

  1. βœ… Thread safety - Eliminated unsafe __bases__ mutation
  2. βœ… Correct initialization - BaseDataStore.init() properly called
  3. βœ… Code quality - Eliminated DRY violations
  4. βœ… Test collection - Maintains original fix for optional module imports
  5. βœ… Architecture compliance - Follows AGENTS.md lazy loading principles
  6. βœ… Backward compatibility - No breaking changes

The PR successfully fixes the test collection failures from issue #1541 while addressing all architectural concerns raised by reviewers. The solution is now thread-safe, follows proper object-oriented patterns, and maintains excellent code quality.

--- | Branch: claude/issue-1541-20260424-0947

…refactor for models

- Replace dangerous runtime __bases__ mutation with thread-safe lazy class factory
- Ensure BaseDataStore.__init__ is properly called via super().__init__()
- Add _is_module_available() helper to eliminate code duplication in models.py
- Maintain lazy loading behavior for test collection compatibility
- Fix critical thread safety and initialization issues identified by reviewers

Co-authored-by: Mervin Praison <MervinPraison@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.

praisonai test collection requires optional modules in default setup

1 participant