Skip to content

Commit

Permalink
✊ Representation (#23)
Browse files Browse the repository at this point in the history
Updates Starlette Admin fields to more user-friendly values

* Uses __admin__repr function to customize model representations in Starlette admin to more user-friendly values.
* Adds UserFactory and better faker sample data.
* Adds custom CLAMSProvider faker class.
  • Loading branch information
mrharpo authored Apr 14, 2023
1 parent b346248 commit f72558c
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 25 deletions.
34 changes: 32 additions & 2 deletions chowda/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,26 @@

from typing import Any, Dict, List, Optional
from pydantic import AnyHttpUrl, EmailStr, stricturl
from sqlalchemy import JSON, Column

from sqlalchemy import JSON, Column, DateTime, String, Text
from starlette.requests import Request
from sqlmodel import Field, Relationship, SQLModel

from enum import Enum

MediaUrl = stricturl(allowed_schemes=['video', 'audio', 'text'], tld_required=False)
"""Media url validator. Must have prefix of video, audio, or text. No TLD required.
Example:
video://*
"""


class AppStatus(Enum):
PENDING = 'pending'
RUNNING = 'running'
COMPLETE = 'complete'
FAILED = 'failed'


class User(SQLModel, table=True):
"""User model
Expand All @@ -32,6 +41,9 @@ class User(SQLModel, table=True):
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}'


class MediaFileCollectionLink(SQLModel, table=True):
media_file_id: Optional[int] = Field(
Expand Down Expand Up @@ -64,6 +76,9 @@ class MediaFile(SQLModel, table=True):
)
clams_events: List['ClamsEvent'] = Relationship(back_populates='media_file')

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


class Collection(SQLModel, table=True):
__tablename__ = 'collections'
Expand All @@ -74,6 +89,9 @@ class Collection(SQLModel, table=True):
back_populates='collections', link_model=MediaFileCollectionLink
)

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


class Batch(SQLModel, table=True):
__tablename__ = 'batches'
Expand All @@ -87,6 +105,9 @@ class Batch(SQLModel, table=True):
)
clams_events: List['ClamsEvent'] = Relationship(back_populates='batch')

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


class ClamsAppPipelineLink(SQLModel, table=True):
clams_app_id: Optional[int] = Field(
Expand All @@ -108,6 +129,9 @@ class ClamsApp(SQLModel, table=True):
)
clams_events: List['ClamsEvent'] = Relationship(back_populates='clams_app')

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


class Pipeline(SQLModel, table=True):
__tablename__ = 'pipelines'
Expand All @@ -119,6 +143,9 @@ class Pipeline(SQLModel, table=True):
)
batches: List[Batch] = Relationship(back_populates='pipeline')

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


class ClamsEvent(SQLModel, table=True):
__tablename__ = 'clams_events'
Expand All @@ -131,3 +158,6 @@ class ClamsEvent(SQLModel, table=True):
clams_app: Optional[ClamsApp] = Relationship(back_populates='clams_events')
media_file_id: Optional[int] = Field(default=None, foreign_key='media_files.id')
media_file: Optional[MediaFile] = Relationship(back_populates='clams_events')

async def __admin_repr__(self, request: Request):
return f'{self.batch.name}: {self.clams_app.name}: {self.status}'
75 changes: 58 additions & 17 deletions tests/factories.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,69 @@
from chowda.models import MediaFile, Batch, Collection, ClamsApp, Pipeline, ClamsEvent
from chowda.models import (
MediaFile,
Batch,
Collection,
ClamsApp,
Pipeline,
ClamsEvent,
User,
AppStatus,
)
import factory
import secrets
from sqlalchemy import orm
from faker import Faker
from chowda.db import engine
from faker.providers import BaseProvider


class CLAMSProvider(BaseProvider):
'''A custom Faker provider for generating CLAMS data'''

def app_name(self):
return f'app-{self.generator.word(part_of_speech="noun")}'

def guid(self):
return f'cpb-aacip-{str(self.generator.random_int())}-{self.generator.hexify(8*"^")}'

def collection_name(self):
if self.generator.random.choice([True, False]):
return self.title() + ' Collection'
else:
return self.generator.name() + ' Collection'

def batch_name(self):
return f'Batch {self.random_int()}: {self.title()}'

def title(self):
num_words = self.generator.random.randint(1, 10)
return self.generator.sentence(nb_words=num_words).title()[:-1]


factory.Faker.add_provider(CLAMSProvider)

# Create a factory-specific engine for factory data. This can be used to modify
# factory-generated data (see seeds.py)
factory_session = orm.scoped_session(orm.sessionmaker(engine))

fake = Faker()


class ChowdaFactory(factory.alchemy.SQLAlchemyModelFactory):
class Meta:
sqlalchemy_session = factory_session
sqlalchemy_session_persistence = 'commit'


class UserFactory(ChowdaFactory):
class Meta:
model = User

first_name = factory.Faker('first_name')
last_name = factory.Faker('last_name')
email = factory.Faker('email')


class MediaFileFactory(ChowdaFactory):
class Meta:
model = MediaFile

guid = 'cpb-aacip-' + secrets.token_hex(6)[:-1]
guid = factory.Faker('guid')

@factory.post_generation
def batches(self, create, extracted, **kwargs):
Expand All @@ -47,16 +88,16 @@ class CollectionFactory(ChowdaFactory):
class Meta:
model = Collection

name = factory.Sequence(lambda n: 'Batch %d' % n)
description = factory.Sequence(lambda n: 'Batch %d Description' % n)
name = factory.Faker('collection_name')
description = factory.Faker('text')


class BatchFactory(ChowdaFactory):
class Meta:
model = Batch

name = factory.Sequence(lambda n: 'Batch %d' % n)
description = factory.Sequence(lambda n: 'Batch %d Description' % n)
name = factory.Faker('batch_name')
description = factory.Faker('text')

@factory.post_generation
def media_files(self, create, extracted, **kwargs):
Expand All @@ -72,9 +113,9 @@ class ClamsAppFactory(ChowdaFactory):
class Meta:
model = ClamsApp

name = factory.Sequence(lambda n: 'Clams App %d' % n)
description = factory.Sequence(lambda n: 'Clams App %d Description' % n)
endpoint = fake.url()
name = factory.Faker('app_name')
description = factory.Faker('text')
endpoint = factory.Faker('url')

@factory.post_generation
def pipelines(self, create, extracted, **kwargs):
Expand All @@ -90,8 +131,8 @@ class PipelineFactory(ChowdaFactory):
class Meta:
model = Pipeline

name = factory.Sequence(lambda n: 'Pipeline %d' % n)
description = factory.Sequence(lambda n: 'Pipeline %d Description' % n)
name = factory.Faker('bs')
description = factory.Faker('catch_phrase')

@factory.post_generation
def clams_apps(self, create, extracted, **kwargs):
Expand All @@ -107,5 +148,5 @@ class ClamsEventFactory(ChowdaFactory):
class Meta:
model = ClamsEvent

status: str = "TODO: REPLACE WITH ENUM VAL"
response_json: dict = {"TODO": "REPLACE WITH EXPECTED RESPONSE"}
status: str = factory.Faker('random_element', elements=AppStatus)
response_json: dict = factory.Faker('json')
27 changes: 22 additions & 5 deletions tests/seeds.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,44 +6,61 @@
PipelineFactory,
ClamsEventFactory,
CollectionFactory,
UserFactory,
)
from chowda.models import AppStatus
from random import sample, randint, choice

status = list(AppStatus)


def seed(
num_media_files: int = 1000,
num_collections: int = 100,
num_batches: int = 100,
num_clams_apps: int = 10,
num_pipelines: int = 10,
num_clams_events: int = 800,
num_clams_events: int = 1000,
num_users: int = 10,
):
"""Seed the database with sample data."""
# Create some sample Users
UserFactory.create_batch(num_users)

# Create some sample CLAMS Apps and Pipelines
clams_apps = ClamsAppFactory.create_batch(num_clams_apps)
pipelines = PipelineFactory.create_batch(num_pipelines)

# Randomly assign CLAMS Apps to Pipelines
for pipeline in pipelines:
pipeline.clams_apps = sample(clams_apps, randint(1, 4))
pipeline.clams_apps = sample(clams_apps, randint(1, num_clams_apps))

# Create the sample MediaFiles, Collections, and Batches
media_files = MediaFileFactory.create_batch(num_media_files)
collections = CollectionFactory.create_batch(num_collections)
batches = BatchFactory.create_batch(num_batches)

# Assign each batch to a random pipeline
for batch in batches:
batch.pipeline = choice(pipelines)

# Randomly assign all media files to 0-3 batches and to 1 collection
# Randomly assign all MediaFiles to 0-3 batches and to 1 collection
for media_file in media_files:
media_file.batches = sample(batches, randint(0, 3))
media_file.collections = [choice(collections)]

# Create some sample ClamsEvents on random batches, media files, and clams apps from the pipeline
for _ in range(num_clams_events):
batch = choice(batches)
ClamsEventFactory.create(
batch=choice(batches),
batch=batch,
media_file=choice(batch.media_files),
clams_app=choice(batch.pipeline.clams_apps),
status=choice(status).value,
)

factory_session.commit()


if __name__ == "__main__":
# If we're running directly, then call seed() function.
seed()
5 changes: 4 additions & 1 deletion tests/test_factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
ClamsApp,
Pipeline,
ClamsEvent,
AppStatus,
)
from factories import (
MediaFileFactory,
Expand All @@ -15,6 +16,7 @@
PipelineFactory,
ClamsEventFactory,
)
from random import choice


def test_media_file_factory():
Expand Down Expand Up @@ -61,8 +63,9 @@ def test_clams_event_factory():
pipeline = PipelineFactory.create(clams_apps=[clams_app])
media_file = MediaFileFactory.create()
batch = BatchFactory.create(media_files=[media_file], pipeline=pipeline)
status = choice(list(AppStatus)).value
clams_event = ClamsEventFactory.create(
media_file=media_file, batch=batch, clams_app=clams_app
media_file=media_file, batch=batch, clams_app=clams_app, status=status
)
assert type(clams_event) is ClamsEvent
assert_related(clams_event, batch=batch, media_file=media_file, clams_app=clams_app)
Expand Down

0 comments on commit f72558c

Please sign in to comment.