Coverage for src/cstlcore/ydocs/models.py: 95%
58 statements
« prev ^ index » next coverage.py v7.9.1, created at 2026-02-19 12:46 +0000
« prev ^ index » next coverage.py v7.9.1, created at 2026-02-19 12:46 +0000
1import uuid
2from datetime import datetime
3from typing import TYPE_CHECKING, List, Optional
5from pydantic import BaseModel
6from sqlalchemy import CheckConstraint
7from sqlalchemy.dialects.postgresql import JSONB
8from sqlmodel import (
9 TIMESTAMP,
10 Boolean,
11 Column,
12 FetchedValue,
13 Field,
14 LargeBinary,
15 Relationship,
16 SQLModel,
17 UniqueConstraint,
18 func,
19 text,
20)
22if TYPE_CHECKING:
23 from cstlcore.collections.models import Collection
24 from cstlcore.maps.models import MapLayer
25 from cstlcore.users.models import User
27# TODO: Add DocType for elements, notes, etc... To be defined
30class YDoc(SQLModel, table=True):
31 ## Table args and constraints
32 __table_args__ = (
33 UniqueConstraint(
34 "collection_id", "parent_id", "name", name="uq_ydoc_parent_name"
35 ),
36 CheckConstraint(
37 "(content IS NULL AND size IS NULL AND checksum IS NULL) OR "
38 "(content IS NOT NULL AND size IS NOT NULL AND checksum IS NOT NULL)",
39 name="ck_ydoc_content_size_checksum",
40 ),
41 CheckConstraint(
42 "is_folder = TRUE AND content IS NULL AND size IS NULL AND checksum IS NULL OR "
43 "is_folder = FALSE",
44 name="ck_ydoc_folder_content",
45 ),
46 )
48 ## Primary key and foreign keys
49 id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
50 owner_id: uuid.UUID | None = Field(default=None, foreign_key="user.id", index=True)
51 collection_id: uuid.UUID = Field(
52 foreign_key="collection.id", index=True, ondelete="CASCADE"
53 )
54 name: str = Field(index=True, nullable=False)
55 parent_id: uuid.UUID | None = Field(
56 default=None,
57 foreign_key="ydoc.id",
58 index=True,
59 ondelete="CASCADE",
60 )
61 is_folder: bool = Field(
62 default=False, sa_column=Column(Boolean(), server_default=text("FALSE"))
63 )
65 ## file content and metadata
66 size: int | None = Field(default=None)
67 checksum: str | None = Field(default=None)
68 metadata_: dict | None = Field(
69 default=None,
70 sa_column=Column(name="metadata", type_=JSONB),
71 # alias="metadata",
72 schema_extra={"serialization_alias": "metadata"},
73 )
74 content: bytes | None = Field(default=None, sa_type=LargeBinary)
76 ## Timestamps
78 created_at: datetime | None = Field(
79 default=None,
80 sa_column=Column(
81 TIMESTAMP(timezone=True), server_default=func.now(), nullable=False
82 ),
83 )
84 updated_at: datetime | None = Field(
85 default=None,
86 sa_column=Column(
87 TIMESTAMP(timezone=True),
88 server_default=func.now(),
89 server_onupdate=FetchedValue(),
90 nullable=False,
91 ),
92 )
94 ## Relationships
96 owner: "User" = Relationship(back_populates="owned_files")
97 collection: "Collection" = Relationship(back_populates="files")
98 layers: List["MapLayer"] = Relationship(
99 back_populates="ydoc",
100 cascade_delete=True,
101 )
102 # Parent/children relationships for hierarchical YDocs (folders/files)
103 parent: Optional["YDoc"] = Relationship(
104 back_populates="children",
105 sa_relationship_kwargs=dict(remote_side="YDoc.id", single_parent=True),
106 )
107 children: List["YDoc"] = Relationship(
108 back_populates="parent",
109 sa_relationship_kwargs=dict(
110 cascade="all, delete-orphan",
111 ),
112 )
115class YDocPublic(BaseModel):
116 id: uuid.UUID
117 name: str
118 path: str | None = None
119 owner_id: uuid.UUID
120 collection_id: uuid.UUID
121 parent_id: uuid.UUID | None = None
122 is_folder: bool = False
123 size: int | None = 0
124 checksum: str | None = None
125 metadata_: dict | None = Field(default=None, alias="metadata")
126 created_at: datetime
127 updated_at: datetime
130class YDocCreate(BaseModel):
131 name: str
132 is_folder: bool = False
133 parent_id: uuid.UUID | None = None
134 # collection_id: uuid.UUID
135 metadata_: dict | None = Field(default=None, alias="metadata")
138class YDocUpdate(BaseModel):
139 name: str | None = None
140 parent_id: uuid.UUID | None = None
141 # collection_id: uuid.UUID | None = None
142 metadata_: dict | None = Field(default=None, alias="metadata")
145class YDocContentUpdate(BaseModel):
146 content: str | None = Field(
147 None, description="Base64 encoded content of the YDoc file"
148 )
151class YDocContentPublic(BaseModel):
152 content: str | None = Field(
153 None, description="Base64 encoded content of the YDoc file"
154 )
155 size: int | None = None
156 checksum: str | None = None