Skip to content

Models

Models

SQLModels for DB and validation

AssetType

Bases: Enum

Asset type enum Type of SonyCi asset: audio, video, or document / other.

This is not the same as the MediaType of the asset (Audio or Video).

Note: Assets with "status: Uploading" have no type

Source code in chowda/models.py
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
class AssetType(enum.Enum):
    """Asset type enum
    Type of SonyCi asset: audio, video, or document / other.

    This is not the same as the `MediaType` of the asset (Audio or Video).

    Note: Assets with "status: Uploading" have no type
    """

    Audio = 'Audio'
    Video = 'Video'
    Image = 'Image'
    Document = 'Document'
    TimedText = 'TimedText'
    Other = 'Other'
    NoneType = None

Batch

Bases: SQLModel

Source code in chowda/models.py
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
class Batch(SQLModel, table=True):
    __tablename__ = 'batches'
    id: int | None = Field(primary_key=True, default=None)
    name: str
    description: str
    pipeline_id: int | None = Field(default=None, foreign_key='pipelines.id')
    pipeline: Optional['Pipeline'] = Relationship(back_populates='batches')
    media_files: list[MediaFile] = Relationship(
        back_populates='batches', link_model=MediaFileBatchLink
    )
    output_mmifs: list['MMIF'] = Relationship(
        back_populates='batch_output',
        sa_relationship_kwargs={
            "primaryjoin": "Batch.id==MMIF.batch_output_id",
        },
    )

    input_mmifs: list['MMIF'] = Relationship(
        back_populates='batch_inputs',
        link_model=MMIFBatchInputLink,
    )
    metaflow_runs: list['MetaflowRun'] = Relationship(back_populates='batch')

    def unstarted_guids(self) -> set:
        """Returns the set of GUIDs that are not currently running"""
        ids: set = {media_file.guid for media_file in self.media_files}
        running_guids: set = {run.media_file.guid for run in self.metaflow_runs}
        return ids - running_guids

    async def __admin_repr__(self, request: Request) -> str:
        return f'{self.name or self.id}'

    async def __admin_select2_repr__(self, request: Request) -> str:
        return f'<span><strong>{self.name or self.id}</span>'

unstarted_guids()

Returns the set of GUIDs that are not currently running

Source code in chowda/models.py
470
471
472
473
474
def unstarted_guids(self) -> set:
    """Returns the set of GUIDs that are not currently running"""
    ids: set = {media_file.guid for media_file in self.media_files}
    running_guids: set = {run.media_file.guid for run in self.metaflow_runs}
    return ids - running_guids

MMIF

Bases: SQLModel

MMIF model

Attributes:

Name Type Description
id int | None

Primary key

created_at datetime | None

Creation timestamp

media_file_id str | None

GUID

media_file MediaFile | None

MediaFile

metaflow_run_id str | None

MetaflowRun ID

metaflow_run MetaflowRun | None

MetaflowRun

batch_output Batch | None

Batch that generated this MMIF

batch_inputs list[Batch]

Batch that uses this as an input

mmif_location str | None

S3 URL of the mmif

Source code in chowda/models.py
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
class MMIF(SQLModel, table=True):
    """MMIF model

    Attributes:
        id: Primary key
        created_at: Creation timestamp
        media_file_id: GUID
        media_file: MediaFile
        metaflow_run_id: MetaflowRun ID
        metaflow_run: MetaflowRun
        batch_output: Batch that generated this MMIF
        batch_inputs: Batch that uses this as an input
        mmif_location: S3 URL of the mmif
    """

    __tablename__ = 'mmifs'
    id: int | None = Field(primary_key=True, default=None, index=True)
    created_at: datetime | None = Field(
        sa_column=Column(DateTime(timezone=True), default=datetime.utcnow)
    )
    media_file_id: str | None = Field(
        default=None, foreign_key='media_files.guid', index=True
    )
    media_file: MediaFile | None = Relationship(back_populates='mmifs')
    metaflow_run_id: str | None = Field(default=None, foreign_key='metaflow_runs.id')
    metaflow_run: MetaflowRun | None = Relationship(back_populates='mmif')
    batch_output_id: int | None = Field(default=None, foreign_key='batches.id')
    batch_output: Batch | None = Relationship(
        back_populates='output_mmifs',
        sa_relationship_kwargs={
            "primaryjoin": "MMIF.batch_output_id==Batch.id",
        },
    )
    batch_inputs: list[Batch] = Relationship(
        back_populates='input_mmifs',
        link_model=MMIFBatchInputLink,
    )

    mmif_location: str | None = Field(default=None)

    async def __admin_repr__(self, request: Request):
        return (
            f'{self.metaflow_run.batch.name}'
            if self.metaflow_run and self.metaflow_run.batch
            else self.id
        )

    async def __admin_select2_repr__(self, request: Request) -> str:
        text = (
            self.metaflow_run.batch.name
            if self.metaflow_run and self.metaflow_run.batch
            else self.id
        )
        return f'<span>{text}</span>'

MediaFile

Bases: SQLModel

Media file model

Attributes:

Name Type Description
guid str | None

MediaFile GUID

assets list[SonyCiAsset]

List of SonyCiAssets

collections list[Collection]

List of Collections

batches list[Batch]

List of Batches

metaflow_runs list[MetaflowRun]

List of MetaflowRuns

mmifs list[MMIF]

List of MMIFs

Source code in chowda/models.py
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
class MediaFile(SQLModel, table=True):
    """Media file model

    Attributes:
        guid: MediaFile GUID
        assets: List of SonyCiAssets
        collections: List of Collections
        batches: List of Batches
        metaflow_runs: List of MetaflowRuns
        mmifs: List of MMIFs
    """

    __tablename__ = 'media_files'
    guid: str | None = Field(primary_key=True, default=None, index=True)
    mmifs: list['MMIF'] = Relationship(back_populates='media_file')
    assets: list['SonyCiAsset'] = Relationship(back_populates='media_files')
    trashbin: list['SonyCiTrashbin'] = Relationship(back_populates='media_files')
    collections: list['Collection'] = Relationship(
        back_populates='media_files', link_model=MediaFileCollectionLink
    )
    batches: list['Batch'] = Relationship(
        back_populates='media_files', link_model=MediaFileBatchLink
    )
    metaflow_runs: list['MetaflowRun'] = Relationship(back_populates='media_file')

    def metaflow_runs_for_batch(self, batch_id: int):
        return [
            metaflow_run
            for metaflow_run in self.metaflow_runs
            if metaflow_run.batch_id == batch_id
        ]

    def last_metaflow_run_for_batch(self, batch_id: int):
        # TODO: is getting the last one sufficient, or do we need to add sortable
        # timestamps?
        runs = self.metaflow_runs_for_batch(batch_id=batch_id)
        return runs[-1] if len(runs) > 0 else None

    async def __admin_repr__(self, request: Request):
        return self.guid

    async def __admin_select2_repr__(self, request: Request) -> str:
        return f'<span><strong>{self.guid}</strong></span>'

MediaType

Bases: Enum

Media type enum Type of Media: video or audio. This is not the same as the format of the media file.

FIXME:

Enum class attributes are the values that are stored in the database. The value of the class attribute is the value returned by SonyCi (for validation) But starlette-admin + SQLAlchemy send the value to the db, not the name. Therefore, we need to Capatalize the name to make it match the db value.

Source code in chowda/models.py
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class MediaType(enum.Enum):
    """Media type enum
    Type of Media: video or audio.
    This is not the same as the format of the media file.

    # FIXME:
    Enum class attributes are the values that are stored in the database.
    The value of the class attribute is the value returned by SonyCi (for validation)
    But starlette-admin + SQLAlchemy send the value to the db, not the name.
    Therefore, we need to Capatalize the name to make it match the db value.
    """

    Audio = 'Audio'
    Video = 'Video'

SonyCiArchiveStatus

Bases: Enum

SonyCi archive status

Accepted values: - Not archived - Archive in progress - Archived - Restore in progress - Restored

Source code in chowda/models.py
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
class SonyCiArchiveStatus(enum.Enum):
    """SonyCi archive status

    Accepted values:
    - Not archived
    - Archive in progress
    - Archived
    - Restore in progress
    - Restored
    """

    NotArchived = 'Not archived'
    ArchiveInProgress = 'Archive in progress'
    Archived = 'Archived'
    RestoreInProgress = 'Restore in progress'
    Restored = 'Restored'

SonyCiAsset

Bases: SonyCiAssetBase

SonyCiAsset model

Source code in chowda/models.py
409
410
411
412
413
414
415
416
417
class SonyCiAsset(SonyCiAssetBase, table=True):
    """SonyCiAsset model"""

    __tablename__ = 'sonyci_assets'

    media_files: MediaFile | None = Relationship(back_populates='assets')
    events: list[SonyCiEvent] = Relationship(
        back_populates='assets', link_model=SonyCiEventAssetLink
    )

SonyCiAssetBase

Bases: SQLModel

Shared columns for SonyCi assets and trashbin entries.

Non-table base mixin. Uses sa_type (not sa_column) so each concrete table subclass gets its own Column instances. Relationships are declared on the subclasses, since a Column/relationship belongs to a single mapper.

Source code in chowda/models.py
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
class SonyCiAssetBase(SQLModel):
    """Shared columns for SonyCi assets and trashbin entries.

    Non-table base mixin. Uses ``sa_type`` (not ``sa_column``) so each concrete
    table subclass gets its own Column instances. Relationships are declared on
    the subclasses, since a Column/relationship belongs to a single mapper.
    """

    id: str | None = Field(primary_key=True, index=True, default=None)
    name: str = Field(index=True)
    size: int = Field(sa_type=postgresql.BIGINT)
    createdOn: datetime | None = Field(default=None, index=True)
    createdBy: dict[str, Any] | None = Field(default=None, sa_type=JSON)
    modifiedOn: datetime | None = Field(default=None, index=True)
    lastActivityOn: datetime | None = Field(default=None, index=True)
    acquisitionSource: dict[str, Any] | None = Field(default=None, sa_type=JSON)
    type: AssetType | None = Field(default=None, sa_type=Enum(AssetType))
    format: str | None = Field(default=None, index=True)
    folder: dict[str, Any] | None = Field(default=None, sa_type=JSON)
    md5Checksum: str | None = Field(default=None, index=True)
    # SonyCi returns the enum *values* (e.g. 'Not archived'); table models skip
    # validation, so those raw strings are stored as-is. values_callable makes
    # the DB enum store values instead of member names so they round-trip.
    status: SonyCiAssetStatus | None = Field(
        default=None, sa_type=Enum(SonyCiAssetStatus, values_callable=_enum_values)
    )
    archiveStatus: SonyCiArchiveStatus | None = Field(
        default=None, sa_type=Enum(SonyCiArchiveStatus, values_callable=_enum_values)
    )
    restoreStatus: SonyCiRestoreStatus | None = Field(
        default=None, sa_type=Enum(SonyCiRestoreStatus, values_callable=_enum_values)
    )
    isTrashed: bool | None = Field(default=None, index=True)
    runtime: float | None = Field(default=None, index=True)
    totalFolderCount: int | None = Field(default=None, index=True)
    asset_metadata: list[dict[str, Any]] | None = Field(
        default=None, sa_type=postgresql.ARRAY(JSON)
    )
    uploadCompleteDate: datetime | None = Field(default=None, index=True)
    uploadTransferType: SonyCiUploadTransferType | None = Field(
        default=None, sa_type=Enum(SonyCiUploadTransferType)
    )
    hasPlayableProxies: bool | None = Field(default=None, index=True)
    generatingPlayableProxies: bool | None = Field(default=None, index=True)
    thumbnails: list[dict[str, Any]] | None = Field(
        default=None, sa_type=postgresql.ARRAY(JSON)
    )
    proxies: list[dict[str, Any]] | None = Field(
        default=None, sa_type=postgresql.ARRAY(JSON)
    )
    filmstrips: list[dict[str, Any]] | None = Field(
        default=None, sa_type=postgresql.ARRAY(JSON)
    )
    technicalMetadata: dict[str, Any] | None = Field(default=None, sa_type=JSON)
    technicalMetadataUpdates: dict[str, Any] | None = Field(default=None, sa_type=JSON)
    waveforms: list[dict[str, Any]] | None = Field(
        default=None, sa_type=postgresql.ARRAY(JSON)
    )
    media_file_id: str | None = Field(
        default=None, foreign_key='media_files.guid', index=True
    )

    @property
    def thumbnails_by_type(self):
        return {thumbnail['type']: thumbnail for thumbnail in self.thumbnails}

    async def __admin_repr__(self, request: Request):
        return self.name

SonyCiAssetStatus

Bases: Enum

SonyCi Asset status Status of SonyCi asset

Typical statuses

Created - The asset record has been created in our database. Waiting - Once uploaded the asset's status will transition to Waiting to indicate it is waiting for backend processing jobs to begin (thumbnails, preview proxies, calculating MD5 checksum, extract technical metadata, check for viruses, etc.). Processing - Once those jobs begin its status moves to Processing. Complete - The status will go to Complete when all applicable thumbnails and preview proxies are finished (note: other jobs like calculating MD5 checksum, gathering technical metadata, and performing other verifications may not be completed even though the status is Complete). Failed - If a failure happens during upload the status will go to Failed.

Other statuses

Limited - There were problems generating thumbnails and/or preview proxies, The source file is still available for download. Virus Detected - A virus was found and the file is not downloadable and generally cannot be used in our system. Executable Detected - An executable file was found and the file is not downloadable and generally cannot be used in our system. Deleted - The file has been deleted.

Source code in chowda/models.py
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
class SonyCiAssetStatus(enum.Enum):
    """SonyCi Asset status
    Status of SonyCi asset

    ## Typical statuses
    Created - The asset record has been created in our database.
    Waiting - Once uploaded the asset's status will transition to Waiting to indicate it is waiting for backend processing jobs to begin (thumbnails, preview proxies, calculating MD5 checksum, extract technical metadata, check for viruses, etc.).
    Processing - Once those jobs begin its status moves to Processing.
    Complete - The status will go to Complete when all applicable thumbnails and preview proxies are finished (note: other jobs like calculating MD5 checksum, gathering technical metadata, and performing other verifications may not be completed even though the status is Complete).
    Failed - If a failure happens during upload the status will go to Failed.

    ## Other statuses
    Limited - There were problems generating thumbnails and/or preview proxies, The source file is still available for download.
    Virus Detected - A virus was found and the file is not downloadable and generally cannot be used in our system.
    Executable Detected - An executable file was found and the file is not downloadable and generally cannot be used in our system.
    Deleted - The file has been deleted.
    """

    Created = 'Created'
    Complete = 'Complete'
    Deleted = 'Deleted'
    ExecutableDetected = 'Executable Detected'
    Failed = 'Failed'
    Limited = 'Limited'
    Processing = 'Processing'
    Uploading = 'Uploading'
    VirusDetected = 'Virus Detected'
    Waiting = 'Waiting'

SonyCiEvent

Bases: SQLModel

SonyCiEvent model

A webhook notification received from SonyCi.

Attributes:

Name Type Description
id str | None

SonyCi event id

type SonyCiEventType | None

Type of the event

createdOn datetime | None

When SonyCi created the event

createdBy dict[str, Any] | None

SonyCi user that triggered the event

payload dict[str, Any] | None

The complete event as received from SonyCi

created_at datetime | None

When Chowda received the event

assets list[SonyCiAsset]

SonyCiAssets referenced by the event

Source code in chowda/models.py
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
class SonyCiEvent(SQLModel, table=True):
    """SonyCiEvent model

    A webhook notification received from SonyCi.

    Attributes:
        id: SonyCi event id
        type: Type of the event
        createdOn: When SonyCi created the event
        createdBy: SonyCi user that triggered the event
        payload: The complete event as received from SonyCi
        created_at: When Chowda received the event
        assets: SonyCiAssets referenced by the event
    """

    __tablename__ = 'sonyci_events'

    id: str | None = Field(primary_key=True, default=None, index=True)
    type: SonyCiEventType | None = Field(
        default=None, sa_type=Enum(SonyCiEventType), index=True
    )
    createdOn: datetime | None = Field(default=None, index=True)
    createdBy: dict[str, Any] | None = Field(default=None, sa_type=JSON)
    payload: dict[str, Any] | None = Field(default=None, sa_type=JSON)
    created_at: datetime | None = Field(
        sa_column=Column(DateTime(timezone=True), default=datetime.utcnow, index=True)
    )
    assets: list['SonyCiAsset'] = Relationship(
        back_populates='events', link_model=SonyCiEventAssetLink
    )

    async def __admin_repr__(self, request: Request):
        return f'{self.type.value if self.type else "Event"} {self.id}'

SonyCiEventType

Bases: Enum

SonyCi webhook event type

The type of notification sent by SonyCi to the webhook API.

Source code in chowda/models.py
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
class SonyCiEventType(enum.Enum):
    """SonyCi webhook event type

    The type of notification sent by SonyCi to the webhook API.
    """

    MoveAsset = 'MoveAsset'
    CopyAsset = 'CopyAsset'
    TrashAsset = 'TrashAsset'
    DeleteAsset = 'DeleteAsset'
    DeleteElement = 'DeleteElement'
    JobStatusChange = 'JobStatusChange'
    AssetProcessingFinished = 'AssetProcessingFinished'
    ElementProcessingFinished = 'ElementProcessingFinished'
    AssetArchiveStatusChange = 'AssetArchiveStatusChange'
    AssetRestoreStatusChange = 'AssetRestoreStatusChange'
    LockElement = 'LockElement'
    UnlockElement = 'UnlockElement'
    CopyAssetsToWorkspace = 'CopyAssetsToWorkspace'
    CopyAssetsToCatalog = 'CopyAssetsToCatalog'
    AssetMetadataChange = 'AssetMetadataChange'
    ElementMetadataChange = 'ElementMetadataChange'

SonyCiRestoreStatus

Bases: Enum

SonyCi restore status

Accepted values
  • Not restored
  • Restore in progress
  • Restore failed
  • Restored
Source code in chowda/models.py
221
222
223
224
225
226
227
228
229
230
231
232
233
234
class SonyCiRestoreStatus(enum.Enum):
    """SonyCi restore status

    Accepted values:
     - Not restored
     - Restore in progress
     - Restore failed
     - Restored
    """

    NotRestored = 'Not restored'
    RestoreInProgress = 'Restore in progress'
    RestoreFailed = 'Restore failed'
    Restored = 'Restored'

SonyCiTrashbin

Bases: SonyCiAssetBase

A SonyCiAsset that has been trashed in SonyCi.

Source code in chowda/models.py
420
421
422
423
424
425
426
427
428
class SonyCiTrashbin(SonyCiAssetBase, table=True):
    """A SonyCiAsset that has been trashed in SonyCi."""

    __tablename__ = 'sonyci_trashbin'

    statusDescription: dict[str, Any] | None = Field(default=None, sa_type=JSON)
    trashedOn: datetime | None = Field(default=None, index=True)

    media_files: MediaFile | None = Relationship(back_populates='trashbin')

SonyCiUploadTransferType

Bases: Enum

SonyCi upload transfer type Indicates how the asset was uploaded.

Valid values are:

  • SinglepartHttp
  • MultipartHttp
  • Aspera
  • Copy
  • FTP
  • WorkspaceSend
  • ClipFromSource
Source code in chowda/models.py
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
class SonyCiUploadTransferType(enum.Enum):
    """SonyCi upload transfer type
    Indicates how the asset was uploaded.

    Valid values are:

    - SinglepartHttp
    - MultipartHttp
    - Aspera
    - Copy
    - FTP
    - WorkspaceSend
    - ClipFromSource
    """

    SinglepartHttp = 'SinglepartHttp'
    MultipartHttp = 'MultipartHttp'
    Aspera = 'Aspera'
    Copy = 'Copy'
    FTP = 'FTP'
    WorkspaceSend = 'WorkspaceSend'
    ClipFromSource = 'ClipFromSource'

User

Bases: SQLModel

User model

Attributes:

Name Type Description
id int | None

Primary key

email EmailStr

User email

first_name str

User first name

last_name str

User last name

Source code in chowda/models.py
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
class User(SQLModel, table=True):
    """User model

    Attributes:
        id: Primary key
        email: User email
        first_name: User first name
        last_name: User last name
    """

    __tablename__ = 'users'
    id: int | None = Field(primary_key=True)
    email: EmailStr = Field(unique=True, index=True, sa_type=AutoString)
    first_name: str = Field(min_length=3, index=True)
    last_name: str = Field(min_length=3, index=True)

    async def __admin_repr__(self, request: Request):
        return f'{self.first_name} {self.last_name}'