prism-models 0.6.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- prism_models-0.6.4/PKG-INFO +10 -0
- prism_models-0.6.4/README.md +0 -0
- prism_models-0.6.4/prism_models/__init__.py +45 -0
- prism_models-0.6.4/prism_models/agent_profile.py +214 -0
- prism_models-0.6.4/prism_models/base.py +75 -0
- prism_models-0.6.4/prism_models/chat.py +447 -0
- prism_models-0.6.4/prism_models/config.py +37 -0
- prism_models-0.6.4/prism_models/content.py +250 -0
- prism_models-0.6.4/prism_models/feedback.py +215 -0
- prism_models-0.6.4/prism_models/migration/README +1 -0
- prism_models-0.6.4/prism_models/migration/__init__.py +0 -0
- prism_models-0.6.4/prism_models/migration/env.py +131 -0
- prism_models-0.6.4/prism_models/migration/script.py.mako +28 -0
- prism_models-0.6.4/prism_models/migration/versions/2025_09_11_1516_161f8829d93f_initial_schema.py +492 -0
- prism_models-0.6.4/prism_models/migration/versions/2025_09_11_1558_5e011849ea76_changes_for_feedback.py +79 -0
- prism_models-0.6.4/prism_models/migration/versions/2025_09_14_2243_059af231c2b2_profile_entities.py +108 -0
- prism_models-0.6.4/prism_models/migration/versions/2025_09_15_1646_3219fec0bb10_agent_changes.py +32 -0
- prism_models-0.6.4/prism_models/migration/versions/2025_09_16_1627_f2013b08daac_rename_metadata_to_additional_data.py +37 -0
- prism_models-0.6.4/prism_models/migration/versions/2025_09_17_1147_327febbf555f_display_name_added.py +34 -0
- prism_models-0.6.4/prism_models/migration/versions/2025_09_18_1106_b0bcb7ca1dc9_add_support_for_profile_on_create_convo.py +41 -0
- prism_models-0.6.4/prism_models/migration/versions/2025_09_18_1511_bbc1955191e6_preview_mode.py +36 -0
- prism_models-0.6.4/prism_models/migration/versions/2025_09_26_1115_6eb70e848451_added_publish_status_to_document.py +38 -0
- prism_models-0.6.4/prism_models/migration/versions/2025_09_26_1240_f8b0ea2e743c_drop_unique_title_version_on_document.py +40 -0
- prism_models-0.6.4/prism_models/migration/versions/2025_09_26_1505_07dc8c2589e0_added_chunk_id_and_vector_embeddings_to_.py +44 -0
- prism_models-0.6.4/prism_models/migration/versions/2025_09_29_1220_46ba2693b883_add_markdown_markdown_file_path_s3_.py +32 -0
- prism_models-0.6.4/prism_models/migration/versions/2025_10_02_1520_bf1472a9b021_removed_doc_id_from_config_table_and_.py +34 -0
- prism_models-0.6.4/prism_models/migration/versions/2025_10_02_1525_6c0e63e0fef8_removed_doc_id_from_config_table_.py +34 -0
- prism_models-0.6.4/prism_models/migration/versions/2025_10_02_1608_1b3eb48f5017_config_id_on_delete_will_be_set_to_null.py +34 -0
- prism_models-0.6.4/prism_models/migration/versions/2025_10_03_1109_ac85b606d8a4_added_docling_hybrid_to_chunkstrategy.py +32 -0
- prism_models-0.6.4/prism_models/migration/versions/2025_10_03_1204_7d1cb343a63f_added_s3_bucket_and_s3_dir_in_source_.py +42 -0
- prism_models-0.6.4/prism_models/migration/versions/2025_10_03_1452_f9c750ec2a0b_1_to_1_relationship_between_collection_.py +52 -0
- prism_models-0.6.4/prism_models/migration/versions/2025_10_07_1722_5cfa0c462948_added_travel_advisory_enum.py +38 -0
- prism_models-0.6.4/prism_models/migration/versions/2025_10_08_1304_c91eb8e38cc7_added_destination_report_and_event_.py +38 -0
- prism_models-0.6.4/prism_models/migration/versions/2025_10_09_1308_796b720ea35f_added_qa_id_to_vovetor.py +42 -0
- prism_models-0.6.4/prism_models/migration/versions/2025_10_16_1611_663c66268631_added_sharepoint_drive_item_id_as_an_.py +32 -0
- prism_models-0.6.4/prism_models/migration/versions/2025_10_23_1228_919d07a93f83_added_sharepoint_directory_in_source.py +32 -0
- prism_models-0.6.4/prism_models/migration/versions/2025_10_27_1331_ff3be2c4311f_added_agentcollectionaccess_table.py +50 -0
- prism_models-0.6.4/prism_models/migration/versions/2025_10_28_1333_b789e61df26a_ad_enum_and_field_added.py +46 -0
- prism_models-0.6.4/prism_models/migration/versions/2025_11_03_1633_cc27b20a106e_removed_collection_from_source_.py +34 -0
- prism_models-0.6.4/prism_models/migration/versions/2025_11_03_1653_bf5c2ce928e3_removed_collection_from_source_.py +51 -0
- prism_models-0.6.4/prism_models/migration/versions/2025_11_03_1738_b733b48d78b5_remove_source.py +32 -0
- prism_models-0.6.4/prism_models/migration/versions/2025_11_06_1450_8a7e56260eba_added_collection_ids_sync_enabled_and_.py +36 -0
- prism_models-0.6.4/prism_models/migration/versions/2025_11_06_1626_4c4148ac2d21_removed_qna_related_columns_from_.py +40 -0
- prism_models-0.6.4/prism_models/migration/versions/2025_11_06_1648_b228888a01ee_added_chunk_config_to_source.py +32 -0
- prism_models-0.6.4/prism_models/migration/versions/2025_11_06_1656_7d0982bae4db_added_chunk_config_to_source_again_last_.py +36 -0
- prism_models-0.6.4/prism_models/migration/versions/2025_11_12_1727_b9663951b5c1_removed_un_used_fields_from_feedback.py +72 -0
- prism_models-0.6.4/prism_models/migration/versions/2025_11_12_1749_3d37806383a2_simplified_feedback.py +40 -0
- prism_models-0.6.4/prism_models/migration/versions/2025_11_12_1838_8c3c862b565e_removed_is_upvoted_from_message.py +34 -0
- prism_models-0.6.4/prism_models/migration/versions/2025_11_12_1841_54a2f12a1573_added_vote_field.py +43 -0
- prism_models-0.6.4/prism_models/migration/versions/2025_11_13_1215_fa00286fe6cd_add_type_column_to_chunk_table.py +32 -0
- prism_models-0.6.4/prism_models/migration/versions/2025_11_14_1216_2c62317520f9_add_rerieved_and_created_chunks_and_.py +42 -0
- prism_models-0.6.4/prism_models/migration/versions/2025_11_17_1259_a3bc4e4c08a0_agent_and_profile_entity_changes.py +81 -0
- prism_models-0.6.4/prism_models/migration/versions/2025_11_17_1325_df5b1cdc2c36_agentprofile_contact_entity_changes.py +42 -0
- prism_models-0.6.4/prism_models/migration/versions/2025_11_17_1716_ed91cdf82ef3_adding_feedback_enum.py +29 -0
- prism_models-0.6.4/prism_models/migration/versions/2025_11_20_0008_80f5954656f0_feedback_chunk_entity_added.py +63 -0
- prism_models-0.6.4/prism_models/migration/versions/2025_11_20_0109_58be389d0ed7_feedback_chunks_changes.py +34 -0
- prism_models-0.6.4/prism_models/migration/versions/2025_11_20_0158_98eb0c87e65e_feedback_chunk_chnages.py +36 -0
- prism_models-0.6.4/prism_models/migration/versions/2025_11_21_1207_bbee88729aae_add_feedback_columns.py +32 -0
- prism_models-0.6.4/prism_models/migration/versions/2025_11_21_1625_8c50f64287fe_added_sequence_column_to_chunk_table_.py +40 -0
- prism_models-0.6.4/prism_models/migration/versions/2025_11_21_1654_e7ae98ce2121_removed_sync_enabled.py +32 -0
- prism_models-0.6.4/prism_models/migration/versions/2025_11_21_1758_24b3f480777b_refactor_feedback_analysis_for_unified_.py +28 -0
- prism_models-0.6.4/prism_models/migration/versions/2025_11_21_1801_5a52fb845dc0_refactor_feedback_analysis_for_unified_.py +52 -0
- prism_models-0.6.4/prism_models/migration/versions/2025_11_24_1124_dc57fb290621_added_conversation_context_to_.py +32 -0
- prism_models-0.6.4/prism_models/migration/versions/2025_11_25_1847_c6c36663acd5_added_user_preferences_table.py +50 -0
- prism_models-0.6.4/prism_models/migration/versions/2025_12_07_2236_88f01df468f2_added_sparse_embeddings_field_in_vector_.py +33 -0
- prism_models-0.6.4/prism_models/migration/versions/2025_12_09_1146_6abdd3f001ab_adding_orchestrator_model_to_profile_.py +32 -0
- prism_models-0.6.4/prism_models/migration/versions/2025_12_17_1220_4d9291a3acb1_removed_qa_pair_chunk.py +79 -0
- prism_models-0.6.4/prism_models/qdrant.py +119 -0
- prism_models-0.6.4/prism_models.egg-info/PKG-INFO +10 -0
- prism_models-0.6.4/prism_models.egg-info/SOURCES.txt +73 -0
- prism_models-0.6.4/prism_models.egg-info/dependency_links.txt +1 -0
- prism_models-0.6.4/prism_models.egg-info/requires.txt +6 -0
- prism_models-0.6.4/prism_models.egg-info/top_level.txt +1 -0
- prism_models-0.6.4/pyproject.toml +22 -0
- prism_models-0.6.4/setup.cfg +4 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: prism-models
|
|
3
|
+
Version: 0.6.4
|
|
4
|
+
Requires-Python: >=3.12
|
|
5
|
+
Requires-Dist: sqlalchemy[asyncio]>=2.0.0
|
|
6
|
+
Requires-Dist: alembic>=1.16.0
|
|
7
|
+
Requires-Dist: asyncpg>=0.29.0
|
|
8
|
+
Requires-Dist: pydantic>=2.0.0
|
|
9
|
+
Requires-Dist: pydantic-settings>=2.1.0
|
|
10
|
+
Requires-Dist: uuid7>=0.1.0
|
|
File without changes
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""Data models for the Prism RAG system."""
|
|
2
|
+
|
|
3
|
+
__version__ = "0.1.0"
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
from prism_models.agent_profile import Agent, AgentCollectionAccess, AgentProfile, AgentProfileStatus, Profile, ProfileCollectionAccess
|
|
7
|
+
from prism_models.base import POSTGRES_NAMING_CONVENTION, Base, BaseModel, TimestampMixin
|
|
8
|
+
from prism_models.chat import Contact, Conversation, ConversationMessage, ConversationMessageMetadata, UserPreferences
|
|
9
|
+
from prism_models.content import Chunk, ChunkConfig, Collection, CollectionDocument, Document, IntegrationConfig, Source, Vector
|
|
10
|
+
from prism_models.feedback import Augmentation, Feedback, FeedbackAnalysis, FeedbackStatus, FeedbackType
|
|
11
|
+
from prism_models.qdrant import QdrantVectorPayload, DestinationVectorPayload, PydanticType
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"POSTGRES_NAMING_CONVENTION",
|
|
15
|
+
"Agent",
|
|
16
|
+
"AgentCollectionAccess",
|
|
17
|
+
"AgentProfile",
|
|
18
|
+
"AgentProfileStatus",
|
|
19
|
+
"Augmentation",
|
|
20
|
+
"Base",
|
|
21
|
+
"BaseModel",
|
|
22
|
+
"Chunk",
|
|
23
|
+
"ChunkConfig",
|
|
24
|
+
"Collection",
|
|
25
|
+
"CollectionDocument",
|
|
26
|
+
"Contact",
|
|
27
|
+
"Conversation",
|
|
28
|
+
"ConversationMessage",
|
|
29
|
+
"ConversationMessageMetadata",
|
|
30
|
+
"UserPreferences",
|
|
31
|
+
"DestinationVectorPayload",
|
|
32
|
+
"Document",
|
|
33
|
+
"Feedback",
|
|
34
|
+
"FeedbackAnalysis",
|
|
35
|
+
"FeedbackStatus",
|
|
36
|
+
"FeedbackType",
|
|
37
|
+
"IntegrationConfig",
|
|
38
|
+
"Profile",
|
|
39
|
+
"ProfileCollectionAccess",
|
|
40
|
+
"PydanticType",
|
|
41
|
+
"QdrantVectorPayload",
|
|
42
|
+
"Source",
|
|
43
|
+
"TimestampMixin",
|
|
44
|
+
"Vector",
|
|
45
|
+
]
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"""Profile model for storing user profile information."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from prism_models.chat import Contact
|
|
8
|
+
from sqlalchemy import TIMESTAMP, Boolean, Index, text
|
|
9
|
+
from sqlalchemy import Enum as SAEnum
|
|
10
|
+
from sqlalchemy import ForeignKey, Integer, String, UniqueConstraint, func
|
|
11
|
+
from sqlalchemy.dialects.postgresql import JSONB
|
|
12
|
+
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
13
|
+
from sqlalchemy.sql.sqltypes import Text
|
|
14
|
+
|
|
15
|
+
from prism_models.base import BaseModel
|
|
16
|
+
from prism_models.content import Collection
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Profile(BaseModel):
|
|
20
|
+
"""
|
|
21
|
+
Profile model for organizing prompt variants by team/use case.
|
|
22
|
+
|
|
23
|
+
Profiles allow admins to create different prompt variations for different
|
|
24
|
+
teams or scenarios (e.g., "marketing_aggressive", "support_friendly").
|
|
25
|
+
Each profile allows configuration of multiple agents.
|
|
26
|
+
|
|
27
|
+
Relationships:
|
|
28
|
+
- profile_prompts: One-to-many with ProfilePrompt
|
|
29
|
+
- agent_profiles: One-to-many with AgentProfile
|
|
30
|
+
- conversation_profiles: One-to-many with ConversationProfile
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
name: Mapped[str] = mapped_column(String(255), nullable=False, index=True)
|
|
34
|
+
description: Mapped[str | None] = mapped_column(String(1024))
|
|
35
|
+
orchestrator_model: Mapped[str | None] = mapped_column(
|
|
36
|
+
String(255), nullable=True, default="openai:gpt-4.1"
|
|
37
|
+
)
|
|
38
|
+
is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
|
|
39
|
+
|
|
40
|
+
__table_args__ = (UniqueConstraint("name", name="uq_profile_name"),)
|
|
41
|
+
|
|
42
|
+
def __repr__(self) -> str:
|
|
43
|
+
return f"<Profile(id={self.id}, name='{self.name}', active={self.is_active})>"
|
|
44
|
+
|
|
45
|
+
class AgentType(str, Enum):
|
|
46
|
+
ORCHESTRATOR_AGENT = "ORCHESTRATOR"
|
|
47
|
+
DOMAIN_AGENT = "DOMAIN"
|
|
48
|
+
UTILITY_AGENT = "UTILITY"
|
|
49
|
+
SUMMARIZER_AGENT = "SUMMARIZER"
|
|
50
|
+
|
|
51
|
+
class Agent(BaseModel):
|
|
52
|
+
"""Agent model for storing agent information."""
|
|
53
|
+
|
|
54
|
+
name: Mapped[str] = mapped_column(String(255), nullable=False, index=True)
|
|
55
|
+
display_name: Mapped[str | None] = mapped_column(String(255))
|
|
56
|
+
description: Mapped[str | None] = mapped_column(String(1024))
|
|
57
|
+
type: Mapped[AgentType] = mapped_column(SAEnum(AgentType, name="agent_type"), nullable=True)
|
|
58
|
+
default_prompt: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
59
|
+
functions: Mapped[dict[str, Any] | None] = mapped_column(
|
|
60
|
+
JSONB(), nullable=True
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
__table_args__ = (
|
|
65
|
+
UniqueConstraint("name", name="uq_agent_name"),
|
|
66
|
+
UniqueConstraint("display_name", name="uq_agent_display_name"),
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
def __repr__(self) -> str:
|
|
70
|
+
return f"<Agent(id={self.id}, name='{self.name}')>"
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class AgentProfileStatus(str, Enum):
|
|
74
|
+
"""
|
|
75
|
+
@deprecated - use AgentProfileVersionStatus instead
|
|
76
|
+
"""
|
|
77
|
+
ACTIVE = "ACTIVE"
|
|
78
|
+
INACTIVE = "INACTIVE"
|
|
79
|
+
PREVIEW = "PREVIEW"
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class AgentProfile(BaseModel):
|
|
83
|
+
"""
|
|
84
|
+
Binding between Agent and Profile.
|
|
85
|
+
Determines if the agent is enabled for this profile.
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
agent_id: Mapped[int] = mapped_column(
|
|
89
|
+
Integer, ForeignKey("agent.id"), nullable=False, index=True
|
|
90
|
+
)
|
|
91
|
+
profile_id: Mapped[int] = mapped_column(
|
|
92
|
+
Integer, ForeignKey("profile.id"), nullable=False, index=True
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
is_enabled: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
|
|
96
|
+
|
|
97
|
+
versions: Mapped[list["AgentProfileVersion"]] = relationship(
|
|
98
|
+
back_populates="agent_profile",
|
|
99
|
+
cascade="all, delete-orphan",
|
|
100
|
+
order_by="AgentProfileVersion.version.desc()"
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
created_by_contact_id: Mapped[int | None] = mapped_column(Integer, ForeignKey("contact.id"), nullable=True)
|
|
104
|
+
|
|
105
|
+
agent: Mapped["Agent"] = relationship()
|
|
106
|
+
profile: Mapped["Profile"] = relationship()
|
|
107
|
+
created_by_contact: Mapped["Contact"] = relationship(foreign_keys=[created_by_contact_id])
|
|
108
|
+
__table_args__ = (
|
|
109
|
+
UniqueConstraint("agent_id", "profile_id", name="uq_agent_profile_binding"),
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
class AgentProfileVersionStatus(str, Enum):
|
|
113
|
+
ACTIVE = "ACTIVE"
|
|
114
|
+
PREVIEW = "PREVIEW"
|
|
115
|
+
ARCHIVED = "ARCHIVED"
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class AgentProfileVersion(BaseModel):
|
|
119
|
+
"""
|
|
120
|
+
Versioned behavior for an Agent within a Profile.
|
|
121
|
+
Stores:
|
|
122
|
+
- prompt text
|
|
123
|
+
- status
|
|
124
|
+
- list of enabled functions for this version
|
|
125
|
+
"""
|
|
126
|
+
|
|
127
|
+
agent_profile_id: Mapped[int] = mapped_column(
|
|
128
|
+
Integer, ForeignKey("agent_profile.id", ondelete="CASCADE"), nullable=False, index=True
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
version: Mapped[int] = mapped_column(Integer, nullable=False)
|
|
132
|
+
|
|
133
|
+
status: Mapped[AgentProfileVersionStatus] = mapped_column(
|
|
134
|
+
SAEnum(AgentProfileVersionStatus),
|
|
135
|
+
nullable=False,
|
|
136
|
+
default=AgentProfileVersionStatus.PREVIEW,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
prompt: Mapped[str] = mapped_column(Text, nullable=False)
|
|
140
|
+
|
|
141
|
+
functions_enabled: Mapped[list[str] | None] = mapped_column(JSONB(), nullable=True)
|
|
142
|
+
|
|
143
|
+
agent_profile: Mapped["AgentProfile"] = relationship(back_populates="versions")
|
|
144
|
+
|
|
145
|
+
modified_by_contact_id: Mapped[int | None] = mapped_column(Integer, ForeignKey("contact.id"), nullable=True)
|
|
146
|
+
modified_by_contact: Mapped["Contact"] = relationship(foreign_keys=[modified_by_contact_id])
|
|
147
|
+
|
|
148
|
+
published_by_contact_id: Mapped[int | None] = mapped_column(Integer, ForeignKey("contact.id"), nullable=True)
|
|
149
|
+
published_by_contact: Mapped["Contact"] = relationship(foreign_keys=[published_by_contact_id])
|
|
150
|
+
|
|
151
|
+
__table_args__ = (
|
|
152
|
+
UniqueConstraint(
|
|
153
|
+
"agent_profile_id", "version",
|
|
154
|
+
name="uq_agent_profile_version_number",
|
|
155
|
+
),
|
|
156
|
+
Index(
|
|
157
|
+
"ix_agent_profile_only_one_active",
|
|
158
|
+
"agent_profile_id",
|
|
159
|
+
unique=True,
|
|
160
|
+
postgresql_where=text("status = 'ACTIVE'")
|
|
161
|
+
),
|
|
162
|
+
|
|
163
|
+
Index(
|
|
164
|
+
"ix_agent_profile_only_one_preview",
|
|
165
|
+
"agent_profile_id",
|
|
166
|
+
unique=True,
|
|
167
|
+
postgresql_where=text("status = 'PREVIEW'")
|
|
168
|
+
),
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class ProfileCollectionAccess(BaseModel):
|
|
174
|
+
"""ProfileCollectionAccess model for storing profile collection access information."""
|
|
175
|
+
|
|
176
|
+
profile_id: Mapped[int] = mapped_column(
|
|
177
|
+
Integer,
|
|
178
|
+
ForeignKey("profile.id", ondelete="CASCADE"),
|
|
179
|
+
nullable=False,
|
|
180
|
+
index=True,
|
|
181
|
+
)
|
|
182
|
+
collection_id: Mapped[int] = mapped_column(
|
|
183
|
+
Integer,
|
|
184
|
+
ForeignKey("collection.id", ondelete="CASCADE"),
|
|
185
|
+
nullable=False,
|
|
186
|
+
index=True,
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
collection: Mapped["Collection"] = relationship()
|
|
190
|
+
profile: Mapped["Profile"] = relationship()
|
|
191
|
+
|
|
192
|
+
__table_args__ = (UniqueConstraint("profile_id", "collection_id", name="uq_profile_collection"),)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
class AgentCollectionAccess(BaseModel):
|
|
196
|
+
"""AgentCollectionAccess model for storing agent collection information."""
|
|
197
|
+
|
|
198
|
+
agent_id: Mapped[int] = mapped_column(
|
|
199
|
+
Integer,
|
|
200
|
+
ForeignKey("agent.id", ondelete="CASCADE"),
|
|
201
|
+
nullable=False,
|
|
202
|
+
index=True,
|
|
203
|
+
)
|
|
204
|
+
collection_id: Mapped[int] = mapped_column(
|
|
205
|
+
Integer,
|
|
206
|
+
ForeignKey("collection.id", ondelete="CASCADE"),
|
|
207
|
+
nullable=False,
|
|
208
|
+
index=True,
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
agent: Mapped["Agent"] = relationship()
|
|
212
|
+
collection: Mapped["Collection"] = relationship()
|
|
213
|
+
|
|
214
|
+
__table_args__ = (UniqueConstraint("agent_id", "collection_id", name="uq_agent_collection"),)
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
from datetime import UTC, datetime
|
|
2
|
+
|
|
3
|
+
from sqlalchemy import Column, DateTime, Integer, MetaData, func
|
|
4
|
+
from sqlalchemy.orm import Mapped, declarative_base, declared_attr, mapped_column
|
|
5
|
+
|
|
6
|
+
# PostgreSQL naming convention for consistent constraint names
|
|
7
|
+
POSTGRES_NAMING_CONVENTION = {
|
|
8
|
+
"ix": "%(column_0_label)s_idx",
|
|
9
|
+
"uq": "%(table_name)s_%(column_0_name)s_key",
|
|
10
|
+
"ck": "%(table_name)s_%(constraint_name)s_check",
|
|
11
|
+
"fk": "%(table_name)s_%(column_0_name)s_fkey",
|
|
12
|
+
"pk": "%(table_name)s_pkey",
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
metadata = MetaData(naming_convention=POSTGRES_NAMING_CONVENTION)
|
|
16
|
+
Base = declarative_base(metadata=metadata)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class TimestampMixin:
|
|
20
|
+
"""
|
|
21
|
+
Mixin for automatic timestamp fields using database-level defaults.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
# Use server_default to make the database the single source of truth.
|
|
25
|
+
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), nullable=False)
|
|
26
|
+
|
|
27
|
+
updated_at: Mapped[datetime] = mapped_column(
|
|
28
|
+
DateTime(timezone=True),
|
|
29
|
+
server_default=func.now(),
|
|
30
|
+
onupdate=func.now(),
|
|
31
|
+
server_onupdate=func.now(),
|
|
32
|
+
nullable=False,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class SoftDeleteMixin:
|
|
37
|
+
"""
|
|
38
|
+
Mixin to add soft delete functionality to a model.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
deleted_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
42
|
+
|
|
43
|
+
def soft_delete(self):
|
|
44
|
+
"""Mark the object as deleted."""
|
|
45
|
+
if self.deleted_at is None:
|
|
46
|
+
self.deleted_at = datetime.now(UTC)
|
|
47
|
+
|
|
48
|
+
def undelete(self):
|
|
49
|
+
"""Mark the object as not deleted."""
|
|
50
|
+
self.deleted_at = None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class ChatSchemaMixin:
|
|
54
|
+
pass
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class BaseModel(Base, TimestampMixin, SoftDeleteMixin):
|
|
58
|
+
"""Base model with common fields for all entities."""
|
|
59
|
+
|
|
60
|
+
__abstract__ = True
|
|
61
|
+
|
|
62
|
+
id = Column(Integer, primary_key=True, index=True)
|
|
63
|
+
|
|
64
|
+
@declared_attr.directive
|
|
65
|
+
def __tablename__(cls):
|
|
66
|
+
"""Auto-generate table name from class name in snake_case (singular)."""
|
|
67
|
+
import re
|
|
68
|
+
|
|
69
|
+
# Convert CamelCase to snake_case
|
|
70
|
+
name = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", cls.__name__)
|
|
71
|
+
return re.sub("([a-z0-9])([A-Z])", r"\1_\2", name).lower()
|
|
72
|
+
|
|
73
|
+
def __repr__(self):
|
|
74
|
+
"""String representation for debugging."""
|
|
75
|
+
return f"<{self.__class__.__name__}(id={self.id})>"
|