Skip to content

fix: add missing praisonai_platform.db module (fixes #1542)#1547

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

fix: add missing praisonai_platform.db module (fixes #1542)#1547
MervinPraison merged 2 commits intomainfrom
claude/issue-1542-20260424-0947

Conversation

@praisonai-triage-agent
Copy link
Copy Markdown
Contributor

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

Fixes #1542

Problem

praisonai-platform tests failed at import with ModuleNotFoundError: praisonai_platform.db before any test execution. The entire db/ directory was missing from the package structure, but the codebase had imports throughout services and tests.

Solution

Created the missing database module structure:

  • praisonai_platform/db/init.py - Package exports
  • praisonai_platform/db/base.py - SQLAlchemy configuration with Base, init_db, get_session
  • praisonai_platform/db/models.py - All required models (User, Workspace, Agent, Issue, etc.)
  • Fixed package imports with lazy loading in init.py

Testing

Tests can start: pytest src/praisonai-platform/tests -q now collects 81 tests instead of failing at import

Acceptance Criteria Met

  • Import smoke check passes in editable install
  • pytest begins executing tests instead of failing at import

Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added comprehensive database layer supporting users, workspaces, projects, issues, comments, agents, labels, and activity tracking.
  • Performance

    • Optimized package initialization with lazy loading to improve startup time.

- Create missing db/ directory with __init__.py, base.py, and models.py
- Add all required database models: User, Workspace, Agent, Issue, etc.
- Implement SQLAlchemy base configuration with init_db, get_session
- Fix package imports with lazy loading in __init__.py
- Resolves ModuleNotFoundError that prevented tests from running

Tests now begin executing instead of failing at import.

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: cf2404f0-df23-469f-932e-4a4c0587abba

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

This pull request establishes the missing database package infrastructure by creating praisonai_platform/db/ with proper module initialization, async SQLAlchemy setup, and ORM models for core entities (User, Workspace, Project, Issue, etc.), while updating the root package to use lazy loading for public exports.

Changes

Cohort / File(s) Summary
Database Package Structure
src/praisonai-platform/praisonai_platform/db/__init__.py, src/praisonai-platform/praisonai_platform/db/base.py
Establishes database package with async SQLAlchemy engine/session management, Base declarative class for ORM models, helper functions (get_engine(), get_session(), init_db(), reset_engine()), and proper re-exports via __all__.
Database Models
src/praisonai-platform/praisonai_platform/db/models.py
Defines 11 ORM model classes for core application entities: users, workspaces, memberships, projects, issues with status/priority, comments, agents, labels, issue dependencies, and activity logs, with relationships and constraints.
Module Initialization
src/praisonai-platform/praisonai_platform/__init__.py
Replaces eager imports with lazy loading mechanism via __getattr__() for create_app and PlatformClient, reducing import overhead at package load time.

Sequence Diagram(s)

sequenceDiagram
    actor App as Application
    participant Init as __init__.py
    participant Base as db/base.py
    participant Engine as AsyncEngine
    participant Session as AsyncSession
    participant Models as db/models.py

    App->>Init: import praisonai_platform
    Note over Init: Lazy loading enabled
    App->>Base: get_engine()
    Base->>Engine: Create AsyncEngine (SQLite default)
    Engine-->>Base: Return engine instance
    Base-->>App: Return cached engine
    App->>Base: get_session()
    Base->>Engine: Get session factory
    Engine->>Session: Create new AsyncSession
    Session-->>Base: Return session
    Base-->>App: Yield session to caller
    App->>Base: init_db()
    Base->>Models: Load model definitions
    Models-->>Base: Models registered with Base.metadata
    Base->>Engine: Execute create_all()
    Engine->>Engine: Create all tables
    Engine-->>Base: Tables initialized
    Base-->>App: Database ready
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A database burrows deep and wide,
With models, sessions, all coordinated!
Lazy loading hops along with pride,
While schemas bloom and tables populate,
The platform's roots take hold inside. 🌱

🚥 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 clearly summarizes the main change: adding the missing praisonai_platform.db module to fix import errors. It is concise, specific, and directly related to the changeset.
Linked Issues check ✅ Passed The PR successfully implements all coding requirements from issue #1542: creates the db package with init.py, base.py, and models.py files to make praisonai_platform.db importable, and restores package layout so tests can run without ModuleNotFoundError.
Out of Scope Changes check ✅ Passed All changes are directly scoped to resolving issue #1542: the new db package files and lazy loading adjustments are necessary to restore the missing module path; no unrelated changes detected.
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-1542-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 adds the missing praisonai_platform/db/ module (engine, session, and ORM models) that was blocking all test collection, and fixes deps.py to consume get_session() correctly as an async generator. Previously flagged issues — field name mismatches on User and the get_session call-signature mismatch — are resolved in this version.

  • P1 — registration collision: User.name is unique=True but defaults to the email prefix in AuthService.register(). Two users sharing the same email local-part (e.g. alice@co1.com / alice@co2.com) will hit an unhandled IntegrityError, surfacing as a 500 for every subsequent registration with that prefix.

Confidence Score: 4/5

Safe to merge after addressing the User.name unique constraint / registration collision issue

Prior P0/P1 concerns (get_session signature, User field names) are fixed. One new P1 remains: unique=True on User.name causes unhandled IntegrityError in the registration path when email prefixes collide.

src/praisonai-platform/praisonai_platform/db/models.py — User.name unique constraint

Important Files Changed

Filename Overview
src/praisonai-platform/praisonai_platform/db/models.py Introduces all ORM models; User.name has unique=True which will cause unhandled IntegrityError when two users share an email prefix during registration
src/praisonai-platform/praisonai_platform/db/base.py Adds async SQLAlchemy engine/session infrastructure with lazy initialization and reset support; logic is sound
src/praisonai-platform/praisonai_platform/db/init.py Package init that re-exports db utilities and eagerly imports models to register them with Base.metadata; straightforward
src/praisonai-platform/praisonai_platform/api/deps.py Fixes get_db() to use async for session in get_session() — correct usage of the now-async-generator get_session
src/praisonai-platform/praisonai_platform/init.py Replaces eager imports with lazy getattr to prevent import-time failures; correct implementation

Sequence Diagram

sequenceDiagram
    participant R as FastAPI Route
    participant D as deps.get_db()
    participant GS as db.base.get_session()
    participant SF as get_session_factory()
    participant S as AsyncSession

    R->>D: depends on get_db
    D->>GS: async for session in get_session()
    GS->>SF: get_session_factory()
    SF-->>GS: async_sessionmaker
    GS->>S: factory() → AsyncSession context
    S-->>GS: session
    GS-->>D: yield session
    D-->>R: yield session

    alt success
        R->>D: return normally
        D->>S: await session.commit()
        S-->>D: ok
    else exception
        R->>D: raise exception
        D->>S: await session.rollback()
        D->>D: re-raise
    end

    D->>GS: async for ends (aclose)
    GS->>S: async with exits → session.close()
Loading

Reviews (2): Last reviewed commit: "fix: address critical P1 runtime issues ..." | Re-trigger Greptile

Comment on lines +62 to +66
async def get_session() -> AsyncGenerator[AsyncSession, None]:
"""Get a database session."""
factory = get_session_factory()
async with factory() as session:
yield session
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 get_session signature incompatible with deps.py

deps.py imports this function and uses it as factory = get_session(); async with factory() as session: — treating it as if it returns a callable session factory. But get_session here is an async generator that yields a session directly. Calling get_session() returns an async generator object, and then factory() raises TypeError: 'async_generator' object is not callable, breaking every API route that depends on get_db().

Comment thread src/praisonai-platform/praisonai_platform/db/models.py
Comment thread src/praisonai-platform/praisonai_platform/db/base.py Outdated
Comment thread src/praisonai-platform/praisonai_platform/db/models.py
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: 5

🧹 Nitpick comments (2)
src/praisonai-platform/praisonai_platform/__init__.py (1)

23-31: Optional: add __dir__ for better introspection/tab-completion.

With PEP 562 lazy loading, dir(praisonai_platform) won't list create_app / PlatformClient until they've been accessed at least once, which degrades REPL/IDE autocomplete and tooling that inspects modules via dir(). Consider exposing them via a module-level __dir__:

♻️ Proposed refinement
 def __getattr__(name: str):
     """Lazy import for platform components."""
     if name == "create_app":
         from .api.app import create_app
         return create_app
     elif name == "PlatformClient":
         from .client.platform_client import PlatformClient
         return PlatformClient
     raise AttributeError(f"module 'praisonai_platform' has no attribute '{name}'")
+
+
+def __dir__() -> list[str]:
+    return sorted(list(globals().keys()) + ["create_app", "PlatformClient"])
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/praisonai-platform/praisonai_platform/__init__.py` around lines 23 - 31,
Add a module-level __dir__ to complement __getattr__ so tooling/REPLs list lazy
attributes; implement __dir__ to return the default names plus the lazy names
("create_app", "PlatformClient") (and optionally any existing __all__),
referencing the existing __getattr__ implementation to ensure the strings match;
keep __getattr__ behavior unchanged and ensure __dir__ returns a list to improve
introspection/tab-completion for create_app and PlatformClient.
src/praisonai-platform/praisonai_platform/db/__init__.py (1)

3-12: Wildcard import is doing hidden work; make model registration explicit.

from .models import * is load-bearing here (it registers all ORM classes with Base.metadata so init_db() creates their tables), but:

  • Ruff (F403) can't resolve the names.
  • __all__ omits every model, so from praisonai_platform.db import * silently drops User, Workspace, Agent, etc., even though consumers do reach them via praisonai_platform.db.User (and via the wildcard side effect). This mismatch between __all__ and what the namespace actually exposes is confusing.

Either commit to models being part of the public API (list them in __all__) or import them explicitly for side effects only (e.g., from . import models # noqa: F401) and keep direct use at db.models.X.

♻️ Option A — publish models as public API
 from .base import Base, get_session, init_db, reset_engine, get_engine
-from .models import *
+from .models import (
+    ActivityLog,
+    Agent,
+    Comment,
+    Issue,
+    IssueDependency,
+    IssueLabel,
+    IssueLabelLink,
+    Member,
+    Project,
+    User,
+    Workspace,
+)

 __all__ = [
-    "Base",
-    "get_session", 
-    "init_db",
-    "reset_engine",
-    "get_engine",
+    "ActivityLog",
+    "Agent",
+    "Base",
+    "Comment",
+    "Issue",
+    "IssueDependency",
+    "IssueLabel",
+    "IssueLabelLink",
+    "Member",
+    "Project",
+    "User",
+    "Workspace",
+    "get_engine",
+    "get_session",
+    "init_db",
+    "reset_engine",
 ]
♻️ Option B — keep models as db.models.* only
-from .base import Base, get_session, init_db, reset_engine, get_engine
-from .models import *
+from .base import Base, get_engine, get_session, init_db, reset_engine
+from . import models  # noqa: F401  # ensure ORM classes are registered with Base.metadata
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/praisonai-platform/praisonai_platform/db/__init__.py` around lines 3 -
12, Replace the wildcard model import with an explicit module import for side
effects: change "from .models import *" to "from . import models  # noqa: F401"
so ORM classes are registered with Base.metadata but not exported into this
module's namespace; keep the public API list in __all__ limited to Base,
get_session, init_db, reset_engine, get_engine (do not add model class names
there) and ensure functions/classes referenced (Base, get_session, init_db,
reset_engine, get_engine, models, __all__) are updated accordingly so consumers
use db.models.User etc. instead of db.User.
🤖 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-platform/praisonai_platform/db/base.py`:
- Line 6: The import of sqlalchemy.Engine is unused and should be removed;
locate the top-level import statement that brings in Engine (the line "from
sqlalchemy import Engine") and delete it so only the async SQLAlchemy types from
sqlalchemy.ext.asyncio are imported/used (ensure any references rely on
AsyncEngine/async variants like AsyncEngine if needed).
- Around line 43-47: The sync reset_engine() should be made async and must
dispose the underlying AsyncEngine to avoid leaking connections: change
reset_engine to async def reset_engine(), check if _engine is an instance of
AsyncEngine and await _engine.dispose() (or call its dispose coroutine) before
setting _engine and _session_factory to None, and ensure callers await
reset_engine(); also remove the unused Engine import. Use the function and
globals named reset_engine, _engine, _session_factory and reference
AsyncEngine.dispose when implementing the fix.
- Around line 26-40: get_engine currently takes no arguments but tests call
get_engine("..."); update get_engine to accept an optional database_url:
Optional[str] = None, use the provided database_url when present otherwise fall
back to the DATABASE_URL env or default in-memory sqlite, and build the
AsyncEngine the same way (including the sqlite-specific connect_args) before
caching it in the module-level _engine; do not change the existing cache
semantics—if _engine is already set, return it (so callers must call
reset_engine() first to swap engines) and ensure reset_engine() clears the
cached _engine so subsequent get_engine(database_url=...) will create a new
engine.

In `@src/praisonai-platform/praisonai_platform/db/models.py`:
- Around line 114-115: Add proper foreign key constraints to the
user-referencing columns so they point to users.id and enforce referential
integrity: update Issue.assignee_id and Issue.created_by_id, Comment.author_id,
and ActivityLog.actor_id to use mapped_column(String, ForeignKey("users.id"),
nullable=...) instead of plain String columns (preserve existing nullable
behavior), ensuring imports for ForeignKey are present if not already; this will
align them with other FK columns like workspace_id/project_id/issue_id/label_id
and enable DB-level cascades/constraints.
- Around line 27-29: The DateTime columns in the models are declared without
timezone=True while _utcnow() returns a tz-aware datetime; update every DateTime
column declaration in this file (e.g., in the User and Project models) to
include timezone=True so stored values remain timezone-aware and match
_utcnow(), ensuring you change each DateTime(...) usage to DateTime(...,
timezone=True) for all timestamp fields referenced in User and Project.

---

Nitpick comments:
In `@src/praisonai-platform/praisonai_platform/__init__.py`:
- Around line 23-31: Add a module-level __dir__ to complement __getattr__ so
tooling/REPLs list lazy attributes; implement __dir__ to return the default
names plus the lazy names ("create_app", "PlatformClient") (and optionally any
existing __all__), referencing the existing __getattr__ implementation to ensure
the strings match; keep __getattr__ behavior unchanged and ensure __dir__
returns a list to improve introspection/tab-completion for create_app and
PlatformClient.

In `@src/praisonai-platform/praisonai_platform/db/__init__.py`:
- Around line 3-12: Replace the wildcard model import with an explicit module
import for side effects: change "from .models import *" to "from . import models
# noqa: F401" so ORM classes are registered with Base.metadata but not exported
into this module's namespace; keep the public API list in __all__ limited to
Base, get_session, init_db, reset_engine, get_engine (do not add model class
names there) and ensure functions/classes referenced (Base, get_session,
init_db, reset_engine, get_engine, models, __all__) are updated accordingly so
consumers use db.models.User etc. instead of db.User.
🪄 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: 976f2607-24f7-4977-bf23-0752d957412b

📥 Commits

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

📒 Files selected for processing (4)
  • src/praisonai-platform/praisonai_platform/__init__.py
  • src/praisonai-platform/praisonai_platform/db/__init__.py
  • src/praisonai-platform/praisonai_platform/db/base.py
  • src/praisonai-platform/praisonai_platform/db/models.py

import os
from typing import AsyncGenerator

from sqlalchemy import Engine
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 | 🟡 Minor

Unused import: Engine.

Only the async variants from sqlalchemy.ext.asyncio are used below; sqlalchemy.Engine is never referenced. Ruff will flag this as F401.

🧹 Proposed fix
-from sqlalchemy import Engine
 from sqlalchemy.ext.asyncio import (
     AsyncEngine,
     AsyncSession,
     async_sessionmaker,
     create_async_engine,
 )
📝 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
from sqlalchemy import Engine
from sqlalchemy.ext.asyncio import (
AsyncEngine,
AsyncSession,
async_sessionmaker,
create_async_engine,
)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/praisonai-platform/praisonai_platform/db/base.py` at line 6, The import
of sqlalchemy.Engine is unused and should be removed; locate the top-level
import statement that brings in Engine (the line "from sqlalchemy import
Engine") and delete it so only the async SQLAlchemy types from
sqlalchemy.ext.asyncio are imported/used (ensure any references rely on
AsyncEngine/async variants like AsyncEngine if needed).

Comment on lines +26 to +40
def get_engine() -> AsyncEngine:
"""Get the database engine."""
global _engine
if _engine is None:
# Default to in-memory SQLite for testing
database_url = os.environ.get(
"DATABASE_URL",
"sqlite+aiosqlite:///:memory:"
)
_engine = create_async_engine(
database_url,
echo=False,
connect_args={"check_same_thread": False} if "sqlite" in database_url else {},
)
return _engine
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 | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify all call sites of get_engine and whether any pass arguments.
rg -nP --type=py -C2 '\bget_engine\s*\('

Repository: MervinPraison/PraisonAI

Length of output: 551


🏁 Script executed:

# Check for all get_engine() calls in the repository
rg -nP --type=py '\bget_engine\s*\(' -A0

Repository: MervinPraison/PraisonAI

Length of output: 173


🏁 Script executed:

# Look for reset_engine() definition and usage
rg -nP --type=py '\breset_engine\s*\(' -B2 -A5

Repository: MervinPraison/PraisonAI

Length of output: 2950


🏁 Script executed:

# Check imports in the test file to verify what's being imported
head -30 src/praisonai-platform/tests/test_client_integration.py

Repository: MervinPraison/PraisonAI

Length of output: 920


🏁 Script executed:

# Locate the reset_engine() function definition
rg -nP --type=py 'def reset_engine' -A10

Repository: MervinPraison/PraisonAI

Length of output: 49


🏁 Script executed:

# Search for reset_engine definition anywhere in the codebase
rg -nP --type=py 'def reset_engine' --max-count=20

Repository: MervinPraison/PraisonAI

Length of output: 49


🏁 Script executed:

# Check the complete base.py file to see all function definitions
cat -n src/praisonai-platform/praisonai_platform/db/base.py

Repository: MervinPraison/PraisonAI

Length of output: 2491


get_engine() is called with a URL in tests but the signature accepts no args.

tests/test_client_integration.py line 24 calls engine = get_engine("sqlite+aiosqlite://"), which raises TypeError: get_engine() takes 0 positional arguments but 1 was given with the current signature.

Add an optional database_url parameter so callers can specify a URL without environment variables. Once an engine is cached, a subsequent call with a different URL silently returns the first engine; pair this parameter with reset_engine() (call first to clear the cache) to explicitly swap engines.

🛠️ Suggested fix
-def get_engine() -> AsyncEngine:
-    """Get the database engine."""
+def get_engine(database_url: str | None = None) -> AsyncEngine:
+    """Get (and lazily create) the cached async database engine.
+
+    If ``database_url`` is provided and no engine is cached yet, it overrides
+    the ``DATABASE_URL`` env var / in-memory default. To switch URLs after an
+    engine has already been created, call ``reset_engine()`` first.
+    """
     global _engine
     if _engine is None:
-        # Default to in-memory SQLite for testing
-        database_url = os.environ.get(
-            "DATABASE_URL", 
-            "sqlite+aiosqlite:///:memory:"
-        )
+        if database_url is None:
+            # Default to in-memory SQLite for testing
+            database_url = os.environ.get(
+                "DATABASE_URL",
+                "sqlite+aiosqlite:///:memory:",
+            )
         _engine = create_async_engine(
             database_url,
             echo=False,
             connect_args={"check_same_thread": False} if "sqlite" in database_url else {},
         )
     return _engine
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/praisonai-platform/praisonai_platform/db/base.py` around lines 26 - 40,
get_engine currently takes no arguments but tests call get_engine("..."); update
get_engine to accept an optional database_url: Optional[str] = None, use the
provided database_url when present otherwise fall back to the DATABASE_URL env
or default in-memory sqlite, and build the AsyncEngine the same way (including
the sqlite-specific connect_args) before caching it in the module-level _engine;
do not change the existing cache semantics—if _engine is already set, return it
(so callers must call reset_engine() first to swap engines) and ensure
reset_engine() clears the cached _engine so subsequent
get_engine(database_url=...) will create a new engine.

Comment on lines +43 to +47
def reset_engine() -> None:
"""Reset the global engine (for testing)."""
global _engine, _session_factory
_engine = None
_session_factory = None
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 | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm reset_engine is awaited in test/fixture code (would break if kept sync).
rg -nP --type=py -C1 '\bawait\s+reset_engine\s*\('
# And check all call sites to see if any sync caller would now break after making it async.
rg -nP --type=py -C2 '\breset_engine\s*\('

Repository: MervinPraison/PraisonAI

Length of output: 2878


🏁 Script executed:

# Read the full base.py to understand _engine type and imports
head -60 src/praisonai-platform/praisonai_platform/db/base.py

Repository: MervinPraison/PraisonAI

Length of output: 1583


🏁 Script executed:

# Check if _engine is typed as AsyncEngine
rg -nP --type=py '_engine\s*[:=]' src/praisonai-platform/praisonai_platform/db/base.py | head -20

Repository: MervinPraison/PraisonAI

Length of output: 169


🏁 Script executed:

# Verify all call sites of reset_engine (check for any sync-only callers)
rg -nP --type=py '\breset_engine\s*\(' --glob='!tests/**' src/praisonai-platform/

Repository: MervinPraison/PraisonAI

Length of output: 401


🏁 Script executed:

# Check imports in base.py to understand what AsyncEngine is
rg -nP --type=py '(import|from).*AsyncEngine' src/praisonai-platform/praisonai_platform/db/base.py

Repository: MervinPraison/PraisonAI

Length of output: 49


Make reset_engine() async and dispose the engine to prevent connection leaks.

The function is awaited in all test fixtures (test_api_integration.py, test_client_integration.py, test_new_api_integration.py), but currently defined as sync. More critically, it only drops the reference to _engine without disposing the underlying AsyncEngine connection pool, leaking aiosqlite connections across tests.

🔒 Suggested fix
-def reset_engine() -> None:
-    """Reset the global engine (for testing)."""
-    global _engine, _session_factory
-    _engine = None
-    _session_factory = None
+async def reset_engine() -> None:
+    """Reset the global engine (for testing).
+
+    Disposes the existing async engine (if any) before clearing
+    cached references so connection pools don't leak across tests.
+    """
+    global _engine, _session_factory
+    if _engine is not None:
+        await _engine.dispose()
+    _engine = None
+    _session_factory = None

Also remove the unused Engine import on line 6.

📝 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 reset_engine() -> None:
"""Reset the global engine (for testing)."""
global _engine, _session_factory
_engine = None
_session_factory = None
async def reset_engine() -> None:
"""Reset the global engine (for testing).
Disposes the existing async engine (if any) before clearing
cached references so connection pools don't leak across tests.
"""
global _engine, _session_factory
if _engine is not None:
await _engine.dispose()
_engine = None
_session_factory = None
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/praisonai-platform/praisonai_platform/db/base.py` around lines 43 - 47,
The sync reset_engine() should be made async and must dispose the underlying
AsyncEngine to avoid leaking connections: change reset_engine to async def
reset_engine(), check if _engine is an instance of AsyncEngine and await
_engine.dispose() (or call its dispose coroutine) before setting _engine and
_session_factory to None, and ensure callers await reset_engine(); also remove
the unused Engine import. Use the function and globals named reset_engine,
_engine, _session_factory and reference AsyncEngine.dispose when implementing
the fix.

Comment on lines +27 to +29
def _utcnow() -> datetime:
"""Get current UTC datetime."""
return datetime.now(timezone.utc)
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

Add timezone=True to all DateTime column declarations to match the tz-aware _utcnow() function.

_utcnow() returns datetime.now(timezone.utc) (tz-aware), but all DateTime columns are declared without the timezone=True parameter. This creates a mismatch: on SQLite the timezone info is silently stripped; on PostgreSQL SQLAlchemy warns and stores the value as naive, causing UTC/local time skew when reading back. This applies to all timestamp columns in the models (User and Project).

🛡️ Suggested fix (apply to every `DateTime` column in the file)
-    created_at: Mapped[datetime] = mapped_column(DateTime, default=_utcnow)
-    updated_at: Mapped[datetime] = mapped_column(DateTime, default=_utcnow, onupdate=_utcnow)
+    created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=_utcnow)
+    updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=_utcnow, onupdate=_utcnow)

Apply to lines 40–41 (User model) and 47, 49 (Project model).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/praisonai-platform/praisonai_platform/db/models.py` around lines 27 - 29,
The DateTime columns in the models are declared without timezone=True while
_utcnow() returns a tz-aware datetime; update every DateTime column declaration
in this file (e.g., in the User and Project models) to include timezone=True so
stored values remain timezone-aware and match _utcnow(), ensuring you change
each DateTime(...) usage to DateTime(..., timezone=True) for all timestamp
fields referenced in User and Project.

Comment on lines +114 to +115
assignee_id: Mapped[Optional[str]] = mapped_column(String, nullable=True)
created_by_id: Mapped[Optional[str]] = mapped_column(String, nullable=True)
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

Add ForeignKey("users.id") on actor/assignee/author columns.

Issue.assignee_id, Issue.created_by_id, Comment.author_id, and ActivityLog.actor_id are plain String columns — no FK to users.id. That allows dangling IDs referencing deleted/nonexistent users, and prevents DB-level referential integrity and cascades. Comparable columns in this file (workspace_id, project_id, issue_id, label_id) all declare ForeignKey(...), so this is likely an oversight.

🛡️ Suggested fix
-    assignee_id: Mapped[Optional[str]] = mapped_column(String, nullable=True)
-    created_by_id: Mapped[Optional[str]] = mapped_column(String, nullable=True)
+    assignee_id: Mapped[Optional[str]] = mapped_column(String, ForeignKey("users.id"), nullable=True)
+    created_by_id: Mapped[Optional[str]] = mapped_column(String, ForeignKey("users.id"), nullable=True)
-    author_id: Mapped[Optional[str]] = mapped_column(String, nullable=True)
+    author_id: Mapped[Optional[str]] = mapped_column(String, ForeignKey("users.id"), nullable=True)
-    actor_id: Mapped[Optional[str]] = mapped_column(String, nullable=True)
+    actor_id: Mapped[Optional[str]] = mapped_column(String, ForeignKey("users.id"), nullable=True)

Also applies to: 133-133, 217-217

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/praisonai-platform/praisonai_platform/db/models.py` around lines 114 -
115, Add proper foreign key constraints to the user-referencing columns so they
point to users.id and enforce referential integrity: update Issue.assignee_id
and Issue.created_by_id, Comment.author_id, and ActivityLog.actor_id to use
mapped_column(String, ForeignKey("users.id"), nullable=...) instead of plain
String columns (preserve existing nullable behavior), ensuring imports for
ForeignKey are present if not already; this will align them with other FK
columns like workspace_id/project_id/issue_id/label_id and enable DB-level
cascades/constraints.

@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


Final Architecture Review - COMPLETED ✅

I have conducted a comprehensive final review as requested, analyzed all previous reviewer feedback, and implemented necessary fixes.

Todo List:

  • Read AGENTS.md for architecture guidelines
  • Read and analyze the PR changes
  • Validate critical P1 issues from previous reviewers
  • Fix User model field names to match auth_service expectations
  • Fix get_session() signature incompatibility with deps.py
  • Fix unused imports and minor issues
  • Add foreign key constraints for user references
  • Fix timezone handling in DateTime columns
  • Make reset_engine async
  • Add database_url parameter to get_engine
  • Run tests to validate changes
  • Push fixes to branch
  • Provide final verdict

Phase 1: Architecture Review per AGENTS.md

PASSES ARCHITECTURE REQUIREMENTS

  • Scope: This is praisonai-platform (separate platform service), NOT core SDK - within acceptable architecture bounds
  • Protocol-driven: Platform service implementation doesn't violate core SDK protocol-driven patterns
  • Performance: Lazy imports properly implemented, no hot-path regressions
  • Backward compatible: Database module creation has no regression risk

Phase 2: Critical Issues Fixed

I identified and FIXED ALL P1 RUNTIME ISSUES found by previous reviewers:

P1 Issue 1: User Model Field Mismatch (Greptile)

  • Problem: auth_service.py expected user.name and user.password_hash but User model had username and hashed_password
  • Fix: Renamed User model fields to match auth service expectations
  • Impact: Prevents AttributeError on all auth operations (register/login)

P1 Issue 2: get_session() Signature Incompatibility (Greptile)

  • Problem: deps.py called factory = get_session(); factory() treating it as callable factory, but get_session() was async generator
  • Fix: Updated deps.py to use async for session in get_session(): pattern
  • Impact: Prevents TypeError on all API routes using database

Additional Critical & Major Issues Fixed (CodeRabbit)

  • Foreign Key Constraints: Added ForeignKey("users.id") to assignee_id, created_by_id, author_id, actor_id for referential integrity
  • Timezone Handling: Added timezone=True to all DateTime columns to match _utcnow() tz-aware returns
  • Connection Leaks: Made reset_engine() async with proper AsyncEngine.dispose() to prevent test connection leaks
  • Test Compatibility: Added database_url parameter to get_engine() for test flexibility
  • Code Quality: Removed unused imports (Engine, Boolean), fixed wildcard import patterns
  • Developer Experience: Added dir method for better tab completion

Phase 3: Final Verdict

🎯 RECOMMEND APPROVAL

All blocking P1 issues have been resolved. The PR now:

  1. Fixes the original issue: praisonai_platform.db module imports successfully
  2. No runtime breakages: All critical field mismatches and signature incompatibilities fixed
  3. Follows architecture: Doesn't violate core SDK protocol-driven patterns
  4. Production ready: Proper foreign keys, timezone handling, connection management
  5. Developer friendly: Tab completion, proper imports, clear patterns

📋 Changes Made (commit 8c60ea7)

  • src/praisonai-platform/praisonai_platform/db/models.py - Field names, foreign keys, timezone handling
  • src/praisonai-platform/praisonai_platform/api/deps.py - Fixed get_session() usage pattern
  • src/praisonai-platform/praisonai_platform/db/base.py - Async reset_engine, database_url param, removed unused imports
  • src/praisonai-platform/praisonai_platform/db/__init__.py - Fixed wildcard imports
  • src/praisonai-platform/praisonai_platform/__init__.py - Added dir for tab completion

🚀 Ready to Merge

This PR successfully resolves issue #1542 and is safe for production deployment.


Job run | Branch

- Fix User model field mismatch: change username/hashed_password to name/password_hash to match auth_service.py expectations
- Fix get_session() signature incompatibility: update deps.py to properly use async generator pattern
- Add missing foreign key constraints for user references (assignee_id, created_by_id, author_id, actor_id)
- Fix timezone handling: add timezone=True to all DateTime columns to match _utcnow()
- Make reset_engine async and properly dispose connections to prevent leaks
- Add database_url parameter to get_engine for test flexibility
- Remove unused imports (Engine, Boolean)
- Fix wildcard import in db/__init__.py using explicit module import for side effects
- Add __dir__ method to main __init__.py for better tab completion

Fixes all P1 runtime issues identified by Greptile and CodeRabbit reviewers.

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

Co-authored-by: Mervin Praison <MervinPraison@users.noreply.github.com>
Comment on lines +35 to +36
id: Mapped[str] = mapped_column(String, primary_key=True, default=_uuid)
name: Mapped[str] = mapped_column(String, unique=True, nullable=False)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Unique name constraint causes unhandled IntegrityError on registration

User.name is marked unique=True, but AuthService.register() derives the name from the email prefix when the caller doesn't supply one (name or email.split("@")[0]). Two users with alice@company1.com and alice@company2.com would both be assigned name="alice", triggering an IntegrityError that propagates as an unhandled 500 response — no user can complete registration in that collision scenario.

Either remove the unique constraint if name is meant to be a display name, or surface a proper 409 error by catching IntegrityError in AuthService.register().

@MervinPraison MervinPraison merged commit 26d166d into main Apr 24, 2026
14 checks passed
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-platform tests fail at import with missing module path

1 participant