Skip to content

Commit 618fde7

Browse files
authored
Inconsistencies in data models and alembic migrations (#266)
* data models * creds table * inconsistency fix migration * remove project id as non nullable * removing unneseccary * project id non nullable creds table * fixing formatting * test fixes * small fixes * removing instruction column ambiguity * conflicts * small fixes * fixing down revision
1 parent b404bee commit 618fde7

9 files changed

Lines changed: 127 additions & 31 deletions

File tree

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
"""Fixing inconsistencies
2+
3+
Revision ID: 4aa1f48c6321
4+
Revises: 3389c67fdcb4
5+
Create Date: 2025-07-03 16:46:13.642386
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
import sqlmodel.sql.sqltypes
11+
from sqlalchemy.dialects import postgresql
12+
13+
# revision identifiers, used by Alembic.
14+
revision = "4aa1f48c6321"
15+
down_revision = "f2589428c1d0"
16+
branch_labels = None
17+
depends_on = None
18+
19+
20+
def upgrade():
21+
op.alter_column(
22+
"collection", "project_id", existing_type=sa.INTEGER(), nullable=False
23+
)
24+
op.alter_column(
25+
"credential",
26+
"inserted_at",
27+
existing_type=postgresql.TIMESTAMP(),
28+
nullable=False,
29+
)
30+
op.alter_column(
31+
"credential", "updated_at", existing_type=postgresql.TIMESTAMP(), nullable=False
32+
)
33+
op.alter_column(
34+
"credential", "project_id", existing_type=sa.INTEGER(), nullable=False
35+
)
36+
op.create_index(
37+
op.f("ix_openai_assistant_assistant_id"),
38+
"openai_assistant",
39+
["assistant_id"],
40+
unique=True,
41+
)
42+
op.drop_constraint("project_organization_id_fkey", "project", type_="foreignkey")
43+
op.create_foreign_key(
44+
None, "project", "organization", ["organization_id"], ["id"], ondelete="CASCADE"
45+
)
46+
op.drop_constraint("credential_project_id_fkey", "credential", type_="foreignkey")
47+
op.create_foreign_key(
48+
None, "credential", "project", ["project_id"], ["id"], ondelete="CASCADE"
49+
)
50+
51+
52+
def downgrade():
53+
op.drop_constraint(None, "project", type_="foreignkey")
54+
op.create_foreign_key(
55+
"project_organization_id_fkey",
56+
"project",
57+
"organization",
58+
["organization_id"],
59+
["id"],
60+
)
61+
op.drop_index(
62+
op.f("ix_openai_assistant_assistant_id"), table_name="openai_assistant"
63+
)
64+
op.drop_constraint(None, "credential", type_="foreignkey")
65+
op.create_foreign_key(
66+
"credential_project_id_fkey",
67+
"credential",
68+
"project",
69+
["project_id"],
70+
["id"],
71+
ondelete="SET NULL",
72+
)
73+
op.alter_column(
74+
"credential", "updated_at", existing_type=postgresql.TIMESTAMP(), nullable=True
75+
)
76+
op.alter_column(
77+
"credential", "inserted_at", existing_type=postgresql.TIMESTAMP(), nullable=True
78+
)
79+
op.alter_column(
80+
"collection", "project_id", existing_type=sa.INTEGER(), nullable=True
81+
)
82+
op.alter_column(
83+
"credential", "project_id", existing_type=sa.INTEGER(), nullable=True
84+
)

backend/app/models/assistants.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from datetime import datetime
22
from typing import Optional, List
33
from sqlmodel import Field, Relationship, SQLModel
4-
from sqlalchemy import Column, String
4+
from sqlalchemy import Column, String, Text
55
from sqlalchemy.dialects.postgresql import ARRAY
66

77
from app.core.util import now
@@ -10,15 +10,19 @@
1010
class AssistantBase(SQLModel):
1111
assistant_id: str = Field(index=True, unique=True)
1212
name: str
13-
instructions: str
13+
instructions: str = Field(sa_column=Column(Text, nullable=False))
1414
model: str
1515
vector_store_ids: List[str] = Field(
1616
default_factory=list, sa_column=Column(ARRAY(String))
1717
)
1818
temperature: float = 0.1
1919
max_num_results: int = 20
20-
project_id: int = Field(foreign_key="project.id")
21-
organization_id: int = Field(foreign_key="organization.id")
20+
project_id: int = Field(
21+
foreign_key="project.id", nullable=False, ondelete="CASCADE"
22+
)
23+
organization_id: int = Field(
24+
foreign_key="organization.id", nullable=False, ondelete="CASCADE"
25+
)
2226

2327

2428
class Assistant(AssistantBase, table=True):

backend/app/models/collection.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ class Collection(SQLModel, table=True):
3535

3636
project_id: int = Field(
3737
foreign_key="project.id",
38-
nullable=True,
38+
nullable=False,
3939
ondelete="CASCADE",
4040
)
4141

backend/app/models/credentials.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,12 @@
77

88

99
class CredsBase(SQLModel):
10-
organization_id: int = Field(foreign_key="organization.id")
11-
project_id: Optional[int] = Field(default=None, foreign_key="project.id")
10+
organization_id: int = Field(
11+
foreign_key="organization.id", nullable=False, ondelete="CASCADE"
12+
)
13+
project_id: int = Field(
14+
default=None, foreign_key="project.id", nullable=False, ondelete="CASCADE"
15+
)
1216
is_active: bool = True
1317

1418

@@ -53,16 +57,16 @@ class Credential(CredsBase, table=True):
5357
index=True, description="Provider name like 'openai', 'gemini'"
5458
)
5559
credential: str = Field(
56-
sa_column=sa.Column(sa.String),
60+
sa_column=sa.Column(sa.String, nullable=False),
5761
description="Encrypted provider-specific credentials",
5862
)
5963
inserted_at: datetime = Field(
6064
default_factory=now,
61-
sa_column=sa.Column(sa.DateTime, default=datetime.utcnow),
65+
sa_column=sa.Column(sa.DateTime, default=datetime.utcnow, nullable=False),
6266
)
6367
updated_at: datetime = Field(
6468
default_factory=now,
65-
sa_column=sa.Column(sa.DateTime, onupdate=datetime.utcnow),
69+
sa_column=sa.Column(sa.DateTime, onupdate=datetime.utcnow, nullable=False),
6670
)
6771
deleted_at: Optional[datetime] = Field(
6872
default=None, sa_column=sa.Column(sa.DateTime, nullable=True)

backend/app/models/organization.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,19 +38,19 @@ class Organization(OrganizationBase, table=True):
3838

3939
# Relationship back to Creds
4040
api_keys: list["APIKey"] = Relationship(
41-
back_populates="organization", sa_relationship_kwargs={"cascade": "all, delete"}
41+
back_populates="organization", cascade_delete=True
4242
)
4343
creds: list["Credential"] = Relationship(
44-
back_populates="organization", sa_relationship_kwargs={"cascade": "all, delete"}
44+
back_populates="organization", cascade_delete=True
4545
)
4646
project: list["Project"] = Relationship(
47-
back_populates="organization", sa_relationship_kwargs={"cascade": "all, delete"}
47+
back_populates="organization", cascade_delete=True
4848
)
4949
assistants: list["Assistant"] = Relationship(
50-
back_populates="organization", sa_relationship_kwargs={"cascade": "all, delete"}
50+
back_populates="organization", cascade_delete=True
5151
)
5252
collections: list["Collection"] = Relationship(
53-
back_populates="organization", sa_relationship_kwargs={"cascade": "all, delete"}
53+
back_populates="organization", cascade_delete=True
5454
)
5555

5656

backend/app/models/project.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,25 +27,27 @@ class ProjectUpdate(SQLModel):
2727
# Database model for Project
2828
class Project(ProjectBase, table=True):
2929
id: int = Field(default=None, primary_key=True)
30-
organization_id: int = Field(foreign_key="organization.id", index=True)
30+
organization_id: int = Field(
31+
foreign_key="organization.id", index=True, nullable=False, ondelete="CASCADE"
32+
)
3133
inserted_at: datetime = Field(default_factory=now, nullable=False)
3234
updated_at: datetime = Field(default_factory=now, nullable=False)
3335

3436
users: list["ProjectUser"] = Relationship(
3537
back_populates="project", cascade_delete=True
3638
)
3739
creds: list["Credential"] = Relationship(
38-
back_populates="project", sa_relationship_kwargs={"cascade": "all, delete"}
40+
back_populates="project", cascade_delete=True
3941
)
4042
assistants: list["Assistant"] = Relationship(
41-
back_populates="project", sa_relationship_kwargs={"cascade": "all, delete"}
43+
back_populates="project", cascade_delete=True
4244
)
4345
api_keys: list["APIKey"] = Relationship(
44-
back_populates="project", sa_relationship_kwargs={"cascade": "all, delete"}
46+
back_populates="project", cascade_delete=True
4547
)
4648
organization: Optional["Organization"] = Relationship(back_populates="project")
4749
collections: list["Collection"] = Relationship(
48-
back_populates="project", sa_relationship_kwargs={"cascade": "all, delete"}
50+
back_populates="project", cascade_delete=True
4951
)
5052

5153

backend/app/models/user.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ class User(UserBase, table=True):
5757
projects: list["ProjectUser"] = Relationship(
5858
back_populates="user", cascade_delete=True
5959
)
60-
api_keys: list["APIKey"] = Relationship(back_populates="user")
60+
api_keys: list["APIKey"] = Relationship(back_populates="user", cascade_delete=True)
6161

6262

6363
class UserOrganization(UserBase):

backend/app/tests/api/routes/test_creds.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ def test_set_credential(db: Session, superuser_token_headers: dict[str, str]):
5656
data = response.json()["data"]
5757
assert isinstance(data, list)
5858
assert len(data) == 1
59+
5960
assert data[0]["organization_id"] == project.organization_id
6061
assert data[0]["provider"] == Provider.OPENAI.value
6162
assert data[0]["credential"]["model"] == "gpt-4"
@@ -70,7 +71,7 @@ def test_set_credentials_for_invalid_project_org_relationship(
7071
credential_data_invalid = {
7172
"organization_id": org1.id,
7273
"is_active": True,
73-
"project_id": project2.id, # Invalid project for org1
74+
"project_id": project2.id,
7475
"credential": {Provider.OPENAI.value: {"api_key": "sk-123", "model": "gpt-4"}},
7576
}
7677

@@ -389,11 +390,12 @@ def test_duplicate_credential_creation(
389390
def test_multiple_provider_credentials(
390391
db: Session, superuser_token_headers: dict[str, str]
391392
):
392-
org = create_test_organization(db)
393+
project = create_test_project(db)
393394

394395
# Create OpenAI credentials
395396
openai_credential = {
396-
"organization_id": org.id,
397+
"organization_id": project.organization_id,
398+
"project_id": project.id,
397399
"is_active": True,
398400
"credential": {
399401
Provider.OPENAI.value: {
@@ -406,7 +408,8 @@ def test_multiple_provider_credentials(
406408

407409
# Create Langfuse credentials
408410
langfuse_credential = {
409-
"organization_id": org.id,
411+
"organization_id": project.organization_id,
412+
"project_id": project.id,
410413
"is_active": True,
411414
"credential": {
412415
Provider.LANGFUSE.value: {
@@ -434,7 +437,7 @@ def test_multiple_provider_credentials(
434437

435438
# Fetch all credentials
436439
response = client.get(
437-
f"{settings.API_V1_STR}/credentials/{org.id}",
440+
f"{settings.API_V1_STR}/credentials/{project.organization_id}",
438441
headers=superuser_token_headers,
439442
)
440443
assert response.status_code == 200

backend/app/tests/crud/test_credentials.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
from sqlmodel import Session
21
import pytest
2+
from sqlmodel import Session
33

44
from app.crud import (
55
set_creds_for_org,
@@ -31,7 +31,6 @@ def test_set_credentials_for_org(db: Session) -> None:
3131
"host": "https://cloud.langfuse.com",
3232
},
3333
}
34-
3534
credentials_create = CredsCreate(
3635
organization_id=project.organization_id,
3736
project_id=project.id,
@@ -109,7 +108,9 @@ def test_update_creds_for_org(db: Session) -> None:
109108
)
110109
# Update credentials
111110
updated_creds = {"api_key": "updated-key"}
112-
creds_update = CredsUpdate(provider="openai", credential=updated_creds)
111+
creds_update = CredsUpdate(
112+
project_id=project.id, provider="openai", credential=updated_creds
113+
)
113114

114115
updated = update_creds_for_org(
115116
session=db, org_id=credential.organization_id, creds_in=creds_update
@@ -189,7 +190,6 @@ def test_invalid_provider(db: Session) -> None:
189190

190191
# Test with unsupported provider
191192
credentials_data = {"gemini": {"api_key": "test-key"}}
192-
193193
credentials_create = CredsCreate(
194194
organization_id=project.organization_id,
195195
project_id=project.id,
@@ -235,7 +235,6 @@ def test_langfuse_credential_validation(db: Session) -> None:
235235
# Missing host
236236
}
237237
}
238-
239238
credentials_create = CredsCreate(
240239
organization_id=project.organization_id,
241240
project_id=project.id,

0 commit comments

Comments
 (0)