Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions alembic/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ def run_migrations_offline() -> None:
literal_binds=True,
dialect_opts={"paramstyle": "named"},
compare_type=True,
include_schemas=True,
)

with context.begin_transaction():
Expand All @@ -52,6 +53,7 @@ def do_run_migrations(connection: Connection) -> None:
connection=connection,
target_metadata=target_metadata,
compare_type=True,
include_schemas=True,
)

with context.begin_transaction():
Expand Down
59 changes: 59 additions & 0 deletions alembic/versions/021805c35a4a_create_enterprise_schemas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"""create enterprise schemas

Revision ID: 021805c35a4a
Revises: 487eb0ed5647
Create Date: 2026-07-19 16:04:06.801291

"""
from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision: str = '021805c35a4a'
down_revision: Union[str, Sequence[str], None] = '487eb0ed5647'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


from alembic import op


def upgrade() -> None:
schemas = [
"auth",
"market",
"sentiment",
"strategy",
"risk",
"execution",
"learning",
"discipline",
"analytics",
"audit",
"system",
]

for schema in schemas:
op.execute(f'CREATE SCHEMA IF NOT EXISTS "{schema}"')


def downgrade() -> None:
schemas = [
"system",
"audit",
"analytics",
"discipline",
"learning",
"execution",
"risk",
"strategy",
"sentiment",
"market",
"auth",
]

for schema in schemas:
op.execute(f'DROP SCHEMA IF EXISTS "{schema}" CASCADE')
39 changes: 39 additions & 0 deletions alembic/versions/13a89530c1d2_move_auth_tables_to_auth_schema.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"""move auth tables to auth schema

Revision ID: 13a89530c1d2
Revises: 021805c35a4a
Create Date: 2026-07-19 16:41:04.715671

"""
from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision: str = '13a89530c1d2'
down_revision: Union[str, Sequence[str], None] = '021805c35a4a'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
"""Move authentication tables to the auth schema."""

op.execute("ALTER TABLE public.users SET SCHEMA auth")
op.execute("ALTER TABLE public.refresh_tokens SET SCHEMA auth")
op.execute("ALTER TABLE public.password_reset_tokens SET SCHEMA auth")
op.execute("ALTER TABLE public.email_verification_tokens SET SCHEMA auth")

op.execute("ALTER TYPE public.user_role SET SCHEMA auth")

def downgrade() -> None:
"""Move authentication tables back to the public schema."""

op.execute("ALTER TABLE auth.email_verification_tokens SET SCHEMA public")
op.execute("ALTER TABLE auth.password_reset_tokens SET SCHEMA public")
op.execute("ALTER TABLE auth.refresh_tokens SET SCHEMA public")
op.execute("ALTER TABLE auth.users SET SCHEMA public")

op.execute("ALTER TYPE auth.user_role SET SCHEMA public")
19 changes: 19 additions & 0 deletions docs/architecture/multi-schema-plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Multi Schema Plan

Current

public

Future

auth
market
sentiment
strategy
risk
execution
learning
discipline
analytics
audit
system
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class EmailVerificationToken(Base):
"""Stores one-time email verification tokens."""

__tablename__ = "email_verification_tokens"

__table_args__ = {"schema": "auth"}
id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True),
primary_key=True,
Expand All @@ -29,7 +29,7 @@ class EmailVerificationToken(Base):

user_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True),
ForeignKey("users.id", ondelete="CASCADE"),
ForeignKey("auth.users.id", ondelete="CASCADE"),
nullable=False,
index=True,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class PasswordResetToken(Base):
"""Stores one-time password reset tokens."""

__tablename__ = "password_reset_tokens"

__table_args__ = {"schema": "auth"}
id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True),
primary_key=True,
Expand All @@ -29,7 +29,7 @@ class PasswordResetToken(Base):

user_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True),
ForeignKey("users.id", ondelete="CASCADE"),
ForeignKey("auth.users.id", ondelete="CASCADE"),
nullable=False,
index=True,
)
Expand Down
4 changes: 2 additions & 2 deletions src/ai_trading_discipline_copilot/models/refresh_token.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class RefreshToken(Base):
"""Refresh token session for a user."""

__tablename__ = "refresh_tokens"

__table_args__ = {"schema": "auth"}
id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True),
primary_key=True,
Expand All @@ -27,7 +27,7 @@ class RefreshToken(Base):

user_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True),
ForeignKey("users.id", ondelete="CASCADE"),
ForeignKey("auth.users.id", ondelete="CASCADE"),
nullable=False,
)

Expand Down
7 changes: 6 additions & 1 deletion src/ai_trading_discipline_copilot/models/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class UserRole(enum.StrEnum):

class User(Base):
__tablename__ = "users"
__table_args__ = {"schema": "auth"}

id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True),
Expand Down Expand Up @@ -63,7 +64,11 @@ class User(Base):
cascade="all, delete-orphan",
)
role: Mapped[UserRole] = mapped_column(
Enum(UserRole, name="user_role"),
Enum(
UserRole,
name="user_role",
schema="auth",
),
default=UserRole.USER,
nullable=False,
)
Expand Down
23 changes: 22 additions & 1 deletion tests/fixtures/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,22 @@ async def create_test_db() -> None:
await engine.dispose()


# Enterprise PostgreSQL schemas used by the application.
SCHEMAS = (
"auth",
"market",
"sentiment",
"strategy",
"risk",
"execution",
"learning",
"discipline",
"analytics",
"audit",
"system",
)


@pytest.fixture
async def db_engine() -> AsyncGenerator[AsyncEngine]:
"""Create a database engine scoped to the test's event loop."""
Expand All @@ -44,7 +60,12 @@ async def db_engine() -> AsyncGenerator[AsyncEngine]:

# Recreate tables for every test to ensure a clean state and avoid loop mismatch
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.drop_all)
for schema in SCHEMAS:
await conn.execute(text(f'DROP SCHEMA IF EXISTS "{schema}" CASCADE'))

for schema in SCHEMAS:
await conn.execute(text(f'CREATE SCHEMA "{schema}"'))

await conn.run_sync(Base.metadata.create_all)

yield engine
Expand Down