corvic-engine 0.3.0rc76__cp38-abi3-win_amd64.whl → 0.3.0rc78__cp38-abi3-win_amd64.whl

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.
corvic/orm/__init__.py CHANGED
@@ -1,38 +1,36 @@
1
- """Data model definitions; backed by an RDBMS."""
1
+ """Object-Relational Mappings."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from datetime import datetime
5
+ from collections.abc import Callable
6
+ from datetime import UTC, datetime
7
+ from typing import Any, ClassVar, cast
6
8
 
7
9
  import sqlalchemy as sa
10
+ from sqlalchemy import event as sa_event
8
11
  from sqlalchemy import orm as sa_orm
12
+ from sqlalchemy.ext import hybrid
9
13
 
14
+ import corvic.context
10
15
  from corvic.orm._proto_columns import ProtoMessageDecorator
11
- from corvic.orm.base import Base, OrgBase
16
+ from corvic.orm._soft_delete import (
17
+ BadDeleteError,
18
+ Session,
19
+ SoftDeleteMixin,
20
+ live_unique_constraint,
21
+ )
12
22
  from corvic.orm.errors import (
13
23
  DeletedObjectError,
14
24
  InvalidORMIdentifierError,
15
25
  RequestedObjectsForNobodyError,
16
26
  )
27
+ from corvic.orm.func import utc_now
17
28
  from corvic.orm.ids import (
18
- AgentID,
19
- AgentMessageID,
20
29
  BaseID,
21
30
  BaseIDFromInt,
22
- CompletionModelID,
23
- FeatureViewID,
24
- FeatureViewSourceID,
31
+ BaseIDFromStr,
25
32
  IntIDDecorator,
26
- MessageEntryID,
27
- OrgID,
28
- PipelineID,
29
- ResourceID,
30
- RoomID,
31
- SourceID,
32
- SpaceID,
33
- SpaceParametersID,
34
- SpaceRunID,
35
- UserMessageID,
33
+ StrIDDecorator,
36
34
  )
37
35
  from corvic.orm.keys import (
38
36
  INT_PK_TYPE,
@@ -41,346 +39,243 @@ from corvic.orm.keys import (
41
39
  primary_key_identity_column,
42
40
  primary_key_uuid_column,
43
41
  )
44
- from corvic.orm.mixins import (
45
- BelongsToOrgMixin,
46
- Session,
47
- SoftDeleteMixin,
48
- live_unique_constraint,
49
- )
50
- from corvic_generated.orm.v1 import (
51
- common_pb2,
52
- completion_model_pb2,
53
- feature_view_pb2,
54
- pipeline_pb2,
55
- space_pb2,
56
- table_pb2,
57
- )
58
- from corvic_generated.status.v1 import event_pb2
59
-
60
- # NOTE: The only safe use of "sa_orm.relationship" uses the args:
61
- # `viewonly=True` and `init=False`. Writes quickly become
62
- # a complex mess when implementers of commit need to reason about
63
- # which sub-object should be updated.
64
- #
65
- # Rather, classes in corvic.model define their own commit protocols,
66
- # and if sub-orm-model updates are required they are explicit.
67
-
42
+ from corvic.result import InvalidArgumentError
68
43
 
69
- class Org(SoftDeleteMixin, OrgBase, kw_only=True):
70
- """An organization it a top level grouping of resources."""
71
44
 
72
-
73
- class Room(BelongsToOrgMixin, SoftDeleteMixin, Base, kw_only=True):
74
- """A Room is a logical collection of Documents."""
75
-
76
- __tablename__ = "room"
77
- __table_args__ = (live_unique_constraint("name", "org_id"),)
78
-
79
- name: sa_orm.Mapped[str] = sa_orm.mapped_column(sa.Text, default=None)
80
- id: sa_orm.Mapped[RoomID | None] = primary_key_identity_column()
45
+ class OrgID(BaseIDFromStr):
46
+ """A unique identifier for an organization."""
81
47
 
82
48
  @property
83
- def room_key(self):
84
- return self.name
85
-
86
-
87
- class BelongsToRoomMixin(sa_orm.MappedAsDataclass):
88
- room_id: sa_orm.Mapped[RoomID | None] = sa_orm.mapped_column(
89
- ForeignKey(Room).make(ondelete="CASCADE"),
90
- nullable=True,
91
- default=None,
92
- )
93
-
94
-
95
- class DefaultObjects(Base, kw_only=True):
96
- """Holds the identifiers for default objects."""
97
-
98
- __tablename__ = "default_objects"
99
- default_org: sa_orm.Mapped[OrgID | None] = sa_orm.mapped_column(
100
- ForeignKey(Org).make(ondelete="CASCADE"),
101
- nullable=False,
102
- )
103
- default_room: sa_orm.Mapped[RoomID | None] = sa_orm.mapped_column(
104
- ForeignKey(Room).make(ondelete="CASCADE"), nullable=True, default=None
105
- )
106
- version: sa_orm.Mapped[int | None] = primary_key_identity_column(type_=INT_PK_TYPE)
107
-
108
-
109
- class Resource(BelongsToOrgMixin, BelongsToRoomMixin, Base, kw_only=True):
110
- """A Resource is a reference to some durably stored file.
111
-
112
- E.g., a document could be a PDF file, an image, or a text transcript of a
113
- conversation
114
- """
115
-
116
- __tablename__ = "resource"
117
-
118
- name: sa_orm.Mapped[str] = sa_orm.mapped_column(sa.Text)
119
- mime_type: sa_orm.Mapped[str] = sa_orm.mapped_column(sa.Text)
120
- url: sa_orm.Mapped[str] = sa_orm.mapped_column(sa.Text)
121
- md5: sa_orm.Mapped[str] = sa_orm.mapped_column(sa.CHAR(32), nullable=True)
122
- size: sa_orm.Mapped[int] = sa_orm.mapped_column(nullable=True)
123
- original_path: sa_orm.Mapped[str] = sa_orm.mapped_column(nullable=True)
124
- description: sa_orm.Mapped[str] = sa_orm.mapped_column(nullable=True)
125
- id: sa_orm.Mapped[ResourceID | None] = primary_key_identity_column()
126
- latest_event: sa_orm.Mapped[event_pb2.Event | None] = sa_orm.mapped_column(
127
- default=None, nullable=True
128
- )
129
- is_terminal: sa_orm.Mapped[bool | None] = sa_orm.mapped_column(
130
- default=None, nullable=True
131
- )
132
- pipeline_ref: sa_orm.Mapped[PipelineInput | None] = sa_orm.relationship(
133
- init=False, viewonly=True
134
- )
135
-
136
-
137
- class Source(BelongsToOrgMixin, BelongsToRoomMixin, Base, kw_only=True):
138
- """A source."""
139
-
140
- __tablename__ = "source"
141
- __table_args__ = (sa.UniqueConstraint("name", "room_id"),)
142
-
143
- name: sa_orm.Mapped[str] = sa_orm.mapped_column(sa.Text)
144
- # protobuf describing the operations required to construct a table
145
- table_op_graph: sa_orm.Mapped[table_pb2.TableComputeOp] = sa_orm.mapped_column()
146
- id: sa_orm.Mapped[SourceID | None] = primary_key_identity_column()
147
-
148
- source_files: sa_orm.Mapped[common_pb2.BlobUrlList | None] = sa_orm.mapped_column(
149
- default=None
150
- )
151
- pipeline_ref: sa_orm.Mapped[PipelineOutput | None] = sa_orm.relationship(
152
- init=False, viewonly=True
153
- )
49
+ def is_super_user(self):
50
+ return self._value == corvic.context.SUPERUSER_ORG_ID
154
51
 
155
52
  @property
156
- def source_key(self):
157
- return self.name
53
+ def is_nobody(self):
54
+ return self._value == corvic.context.NOBODY_ORG_ID
158
55
 
159
56
 
160
- class Pipeline(BelongsToOrgMixin, BelongsToRoomMixin, Base, kw_only=True):
161
- """A resource to source pipeline."""
57
+ # A quick primer on SQLAlchemy (sa) hybrid methods:
58
+ #
59
+ # Hybrid just means functionality that is different at the class-level versus
60
+ # the instance-level, and in the sa documentation, the authors really
61
+ # want to stress that class-versus-instance (decorators) is orthgonal to an
62
+ # ORM.
63
+ #
64
+ # However, this distinction is not particularly helpful for users of sa.
65
+ # It is best to have a working model of instance-level means Python-world and
66
+ # class-level means SQL-world. So, a hybrid method is something that has
67
+ # a different Python representation from its SQL representation.
68
+ #
69
+ # Since sa already handles conversions like "Python str" to "SQL text",
70
+ # certain representation differences between Python and SQL are already handled
71
+ # (not considered differences at all).
72
+ #
73
+ # Hybrid methods are for cases where we want to do non-trivial transformations
74
+ # between SQL and Python representations.
75
+ #
76
+ # The recipe is:
77
+ #
78
+ # 1. Define a hybrid_method / hybrid_property (wlog property) that produces the Python
79
+ # object you want.
80
+ # 2. If the property doesn't need to be used in any sa query again, you are done.
81
+ # 3. If the property is simple enough for sa to also use it to produce the SQL
82
+ # representation, you are also done. E.g., comparisons and bitwise operations
83
+ # on columns.
84
+ # 4. Otherwise, you need to define a class-level function, hybrid_property.expression,
85
+ # which gives the SQL representation of your property when it is passed to
86
+ # a sa query.
87
+ # 5. Because of how redefining decorators is supposed to work in Python [1], you
88
+ # should use @<property_method_name>.inplace.expression to define your
89
+ # class-level function that describes how the property should be represented
90
+ # in SQL.
91
+ #
92
+ # [1] https://docs.sqlalchemy.org/en/20/orm/extensions/hybrid.html#using-inplace-to-create-pep-484-compliant-hybrid-properties
162
93
 
163
- __tablename__ = "pipeline"
164
- __table_args__ = (sa.UniqueConstraint("name", "room_id"),)
165
94
 
166
- transformation: sa_orm.Mapped[pipeline_pb2.PipelineTransformation] = (
167
- sa_orm.mapped_column()
168
- )
169
- name: sa_orm.Mapped[str] = sa_orm.mapped_column()
170
- description: sa_orm.Mapped[str | None] = sa_orm.mapped_column()
171
- id: sa_orm.Mapped[PipelineID | None] = primary_key_identity_column()
95
+ class Base(sa_orm.MappedAsDataclass, sa_orm.DeclarativeBase):
96
+ """Base class for all DB mapped classes."""
97
+
98
+ type_annotation_map: ClassVar = {
99
+ OrgID: StrIDDecorator(OrgID()),
100
+ }
172
101
 
173
- outputs: sa_orm.Mapped[list[PipelineOutput]] = sa_orm.relationship(
174
- viewonly=True,
102
+ _created_at: sa_orm.Mapped[datetime] = sa_orm.mapped_column(
103
+ "created_at",
104
+ sa.DateTime(timezone=True),
105
+ server_default=utc_now(),
175
106
  init=False,
176
- default_factory=list,
107
+ index=True,
177
108
  )
178
109
 
179
-
180
- class PipelineInput(BelongsToOrgMixin, BelongsToRoomMixin, Base, kw_only=True):
181
- """Pipeline input resources."""
182
-
183
- __tablename__ = "pipeline_input"
184
- __table_args__ = (sa.UniqueConstraint("name", "pipeline_id"),)
185
-
186
- resource: sa_orm.Mapped[Resource] = sa_orm.relationship(viewonly=True, init=False)
187
- name: sa_orm.Mapped[str]
188
- """A name the pipeline uses to refer to this input."""
189
-
190
- pipeline_id: sa_orm.Mapped[PipelineID] = primary_key_foreign_column(
191
- ForeignKey(Pipeline).make(ondelete="CASCADE")
192
- )
193
- resource_id: sa_orm.Mapped[ResourceID] = primary_key_foreign_column(
194
- ForeignKey(Resource).make(ondelete="CASCADE")
110
+ _updated_at: sa_orm.Mapped[datetime] = sa_orm.mapped_column(
111
+ "updated_at",
112
+ sa.DateTime(timezone=True),
113
+ onupdate=utc_now(),
114
+ server_default=utc_now(),
115
+ init=False,
116
+ nullable=True,
195
117
  )
196
118
 
119
+ @hybrid.hybrid_property
120
+ def created_at(self) -> datetime | None:
121
+ if not self._created_at:
122
+ return None
123
+ return self._created_at.replace(tzinfo=UTC)
197
124
 
198
- class PipelineOutput(BelongsToOrgMixin, BelongsToRoomMixin, Base, kw_only=True):
199
- """Objects for tracking pipeline output sources."""
200
-
201
- __tablename__ = "pipeline_output"
202
- __table_args__ = (sa.UniqueConstraint("name", "pipeline_id"),)
125
+ @created_at.inplace.expression
126
+ @classmethod
127
+ def _created_at_expression(cls):
128
+ return cls._created_at
203
129
 
204
- source: sa_orm.Mapped[Source] = sa_orm.relationship(viewonly=True, init=False)
205
- name: sa_orm.Mapped[str]
206
- """A name the pipeline uses to refer to this output."""
207
-
208
- pipeline_id: sa_orm.Mapped[PipelineID] = primary_key_foreign_column(
209
- ForeignKey(Pipeline).make(ondelete="CASCADE")
210
- )
211
- source_id: sa_orm.Mapped[SourceID] = primary_key_foreign_column(
212
- ForeignKey(Source).make(ondelete="CASCADE")
213
- )
130
+ @hybrid.hybrid_property
131
+ def updated_at(self) -> datetime | None:
132
+ if not self._updated_at:
133
+ return None
134
+ return self._updated_at.replace(tzinfo=UTC)
214
135
 
136
+ @updated_at.inplace.expression
137
+ @classmethod
138
+ def _updated_at_expression(cls):
139
+ return cls._updated_at
215
140
 
216
- class FeatureView(
217
- SoftDeleteMixin, BelongsToOrgMixin, BelongsToRoomMixin, Base, kw_only=True
218
- ):
219
- """A FeatureView is a logical collection of sources used by various spaces."""
220
141
 
221
- __tablename__ = "feature_view"
222
- __table_args__ = (live_unique_constraint("name", "room_id"),)
142
+ class Org(Base, kw_only=True):
143
+ """An organization is a top-level grouping of resources."""
223
144
 
224
- id: sa_orm.Mapped[FeatureViewID | None] = primary_key_identity_column()
225
- name: sa_orm.Mapped[str] = sa_orm.mapped_column(sa.Text, default=None)
226
- description: sa_orm.Mapped[str] = sa_orm.mapped_column(sa.Text, default="")
145
+ __tablename__ = "org"
227
146
 
228
- feature_view_output: sa_orm.Mapped[feature_view_pb2.FeatureViewOutput | None] = (
229
- sa_orm.mapped_column(default_factory=feature_view_pb2.FeatureViewOutput)
230
- )
147
+ id: sa_orm.Mapped[OrgID | None] = primary_key_uuid_column()
231
148
 
232
149
  @property
233
- def feature_view_key(self):
234
- return self.name
235
-
236
- feature_view_sources: sa_orm.Mapped[list[FeatureViewSource]] = sa_orm.relationship(
237
- viewonly=True,
238
- init=False,
239
- default_factory=list,
240
- )
241
-
242
-
243
- class FeatureViewSource(BelongsToOrgMixin, BelongsToRoomMixin, Base, kw_only=True):
244
- """A source inside of a feature view."""
245
-
246
- __tablename__ = "feature_view_source"
247
-
248
- table_op_graph: sa_orm.Mapped[table_pb2.TableComputeOp] = sa_orm.mapped_column()
249
- feature_view_id: sa_orm.Mapped[FeatureViewID] = sa_orm.mapped_column(
250
- ForeignKey(FeatureView).make(ondelete="CASCADE"),
251
- nullable=False,
252
- )
253
- id: sa_orm.Mapped[FeatureViewSourceID | None] = primary_key_identity_column()
254
- drop_disconnected: sa_orm.Mapped[bool] = sa_orm.mapped_column(default=False)
255
- # this should be legal but pyright complains that it makes Source depend
256
- # on itself
257
- source_id: sa_orm.Mapped[SourceID] = sa_orm.mapped_column(
258
- ForeignKey(Source).make(ondelete="CASCADE"),
150
+ def name(self) -> str:
151
+ return str(self.id)
152
+
153
+
154
+ def _filter_org_objects(orm_execute_state: sa_orm.ORMExecuteState):
155
+ if all(
156
+ not issubclass(mapper.class_, BelongsToOrgMixin | Org)
157
+ for mapper in orm_execute_state.all_mappers
158
+ ):
159
+ # operation has nothing to do with models owned by org
160
+ return
161
+ if orm_execute_state.is_select:
162
+ requester = corvic.context.get_requester()
163
+ org_id = OrgID(requester.org_id)
164
+ if org_id.is_super_user:
165
+ return
166
+
167
+ if org_id.is_nobody:
168
+ raise RequestedObjectsForNobodyError(
169
+ "requester org from context was nobody"
170
+ )
171
+ # we need the real value in in expression world and
172
+ # because of sqlalchemys weird runtime parsing of this it
173
+ # needs to be a real local with a name
174
+ db_id = org_id.to_db().unwrap_or_raise()
175
+
176
+ # this goofy syntax doesn't typecheck well, but is the documented way to apply
177
+ # these operations to all subclasses (recursive). Sqlalchemy is inspecting the
178
+ # lambda rather than just executing it so a function won't work.
179
+ # https://docs.sqlalchemy.org/en/20/orm/queryguide/api.html#sqlalchemy.orm.with_loader_criteria
180
+ check_org_id_lambda: Callable[ # noqa: E731
181
+ [type[BelongsToOrgMixin]], sa.ColumnElement[bool]
182
+ ] = lambda cls: cls.org_id == db_id
183
+ orm_execute_state.statement = orm_execute_state.statement.options(
184
+ sa_orm.with_loader_criteria(
185
+ BelongsToOrgMixin,
186
+ cast(Any, check_org_id_lambda),
187
+ include_aliases=True,
188
+ track_closure_variables=False,
189
+ ),
190
+ sa_orm.with_loader_criteria(
191
+ Org,
192
+ Org.id == org_id,
193
+ include_aliases=True,
194
+ track_closure_variables=False,
195
+ ),
196
+ )
197
+
198
+
199
+ class BelongsToOrgMixin(sa_orm.MappedAsDataclass):
200
+ """Mark models that should be subject to org level access control."""
201
+
202
+ @staticmethod
203
+ def _current_org_id_from_context():
204
+ requester = corvic.context.get_requester()
205
+ return OrgID(requester.org_id)
206
+
207
+ @staticmethod
208
+ def _make_org_id_default() -> OrgID | None:
209
+ org_id = BelongsToOrgMixin._current_org_id_from_context()
210
+
211
+ if org_id.is_nobody:
212
+ raise RequestedObjectsForNobodyError(
213
+ "the nobody org cannot change orm objects"
214
+ )
215
+
216
+ if org_id.is_super_user:
217
+ return None
218
+
219
+ return org_id
220
+
221
+ org_id: sa_orm.Mapped[OrgID | None] = sa_orm.mapped_column(
222
+ ForeignKey(Org).make(ondelete="CASCADE"),
259
223
  nullable=False,
260
- default=None,
261
- )
262
- source: sa_orm.Mapped[Source] = sa_orm.relationship(
263
- init=True, viewonly=True, default=None
224
+ default_factory=_make_org_id_default,
225
+ init=False,
264
226
  )
265
227
 
228
+ @sa_orm.validates("org_id")
229
+ def validate_org_id(self, _key: str, orm_id: OrgID | None):
230
+ expected_org_id = self._current_org_id_from_context()
231
+ if expected_org_id.is_nobody:
232
+ raise RequestedObjectsForNobodyError(
233
+ "the nobody org cannot change orm objects"
234
+ )
266
235
 
267
- class Space(BelongsToOrgMixin, BelongsToRoomMixin, Base, kw_only=True):
268
- """A space is a named evaluation of space parameters."""
236
+ if expected_org_id.is_super_user:
237
+ return orm_id
269
238
 
270
- __tablename__ = "space"
271
- __table_args__ = (sa.UniqueConstraint("name", "room_id"),)
239
+ if orm_id != expected_org_id:
240
+ raise InvalidArgumentError(
241
+ "provided org_id must match the current org",
242
+ provided=orm_id,
243
+ expected=expected_org_id,
244
+ )
272
245
 
273
- id: sa_orm.Mapped[SpaceID | None] = primary_key_identity_column()
274
- name: sa_orm.Mapped[str] = sa_orm.mapped_column(sa.Text, default=None)
275
- description: sa_orm.Mapped[str] = sa_orm.mapped_column(sa.Text, default="")
246
+ return orm_id
276
247
 
277
- feature_view_id: sa_orm.Mapped[FeatureViewID] = sa_orm.mapped_column(
278
- ForeignKey(FeatureView).make(ondelete="CASCADE"),
279
- nullable=False,
280
- default=None,
281
- )
282
- parameters: sa_orm.Mapped[space_pb2.SpaceParameters | None] = sa_orm.mapped_column(
283
- default=None
284
- )
285
- auto_sync: sa_orm.Mapped[bool | None] = sa_orm.mapped_column(default=None)
286
- feature_view: sa_orm.Mapped[FeatureView] = sa_orm.relationship(
287
- init=False,
288
- default=None,
289
- viewonly=True,
290
- )
248
+ @staticmethod
249
+ def register_session_event_listeners(session: type[sa_orm.Session]):
250
+ sa_event.listen(session, "do_orm_execute", _filter_org_objects)
291
251
 
292
- @property
293
- def space_key(self):
294
- return self.name
295
252
 
253
+ BelongsToOrgMixin.register_session_event_listeners(Session)
296
254
 
297
- class CompletionModel(SoftDeleteMixin, BelongsToOrgMixin, Base, kw_only=True):
298
- """A customer's custom completion model definition."""
299
255
 
300
- __tablename__ = "completion_model"
301
- __table_args__ = (live_unique_constraint("name", "org_id"),)
302
-
303
- id: sa_orm.Mapped[CompletionModelID | None] = primary_key_identity_column()
304
- name: sa_orm.Mapped[str] = sa_orm.mapped_column(sa.Text, default=None)
305
- description: sa_orm.Mapped[str] = sa_orm.mapped_column(sa.Text, default=None)
306
- parameters: sa_orm.Mapped[completion_model_pb2.CompletionModelParameters | None] = (
307
- sa_orm.mapped_column(default=None)
308
- )
309
- secret_api_key: sa_orm.Mapped[str] = sa_orm.mapped_column(sa.Text, default=None)
310
- last_validation_time: sa_orm.Mapped[datetime | None] = sa_orm.mapped_column(
311
- sa.DateTime(timezone=True),
312
- server_default=None,
313
- default=None,
314
- )
315
- last_successful_validation: sa_orm.Mapped[datetime | None] = sa_orm.mapped_column(
316
- sa.DateTime(timezone=True),
317
- server_default=None,
318
- default=None,
319
- )
320
-
321
- @property
322
- def model_key(self):
323
- return self.name
324
-
325
-
326
- ID = (
327
- AgentID
328
- | AgentMessageID
329
- | CompletionModelID
330
- | FeatureViewID
331
- | FeatureViewSourceID
332
- | MessageEntryID
333
- | OrgID
334
- | PipelineID
335
- | ResourceID
336
- | RoomID
337
- | SourceID
338
- | SpaceID
339
- | SpaceParametersID
340
- | SpaceRunID
341
- | UserMessageID
342
- )
256
+ ID = OrgID
343
257
 
344
258
 
345
259
  __all__ = [
346
- "AgentID",
347
- "AgentMessageID",
260
+ "BadDeleteError",
348
261
  "Base",
349
262
  "BaseID",
350
263
  "BaseIDFromInt",
351
264
  "BelongsToOrgMixin",
352
- "CompletionModel",
353
- "CompletionModelID",
354
- "DefaultObjects",
355
265
  "DeletedObjectError",
356
- "FeatureView",
357
- "FeatureViewID",
358
- "FeatureViewSource",
359
- "FeatureViewSourceID",
266
+ "ForeignKey",
360
267
  "ID",
268
+ "INT_PK_TYPE",
269
+ "IntIDDecorator",
361
270
  "InvalidORMIdentifierError",
362
- "MessageEntryID",
363
271
  "Org",
364
272
  "OrgID",
365
- "PipelineID",
366
- "PipelineInput",
367
- "PipelineOutput",
273
+ "ProtoMessageDecorator",
368
274
  "RequestedObjectsForNobodyError",
369
- "Resource",
370
- "ResourceID",
371
- "Room",
372
- "RoomID",
373
275
  "Session",
374
- "Source",
375
- "SourceID",
376
- "Space",
377
- "SpaceID",
378
- "SpaceParametersID",
379
- "SpaceRunID",
380
- "UserMessageID",
276
+ "SoftDeleteMixin",
277
+ "live_unique_constraint",
381
278
  "primary_key_foreign_column",
382
279
  "primary_key_identity_column",
383
280
  "primary_key_uuid_column",
384
- "ProtoMessageDecorator",
385
- "IntIDDecorator",
386
281
  ]