feat: add a table to store award information

This commit is contained in:
ducklet 2024-05-18 23:32:10 +02:00
parent 5eb7211b59
commit f102e07256
2 changed files with 120 additions and 12 deletions

View file

@ -0,0 +1,44 @@
"""add awards table
Revision ID: 62882ef5e3ff
Revises: c08ae04dc482
Create Date: 2024-05-18 16:35:10.145964+00:00
"""
from typing import Sequence
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision: str = "62882ef5e3ff"
down_revision: str | None = "c08ae04dc482"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
"awards",
sa.Column("id", sa.String(), nullable=False),
sa.Column("movie_id", sa.String(), nullable=False),
sa.Column("category", sa.String(), nullable=False),
sa.Column("details", sa.String(), nullable=False),
sa.Column("created", sa.String(), nullable=False),
sa.Column("updated", sa.String(), nullable=False),
sa.ForeignKeyConstraint(
["movie_id"],
["movies.id"],
),
sa.PrimaryKeyConstraint("id"),
)
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table("awards")
# ### end Alembic commands ###

View file

@ -13,6 +13,8 @@ from typing import (
Mapping, Mapping,
Protocol, Protocol,
Type, Type,
TypeAlias,
TypeAliasType,
TypedDict, TypedDict,
TypeVar, TypeVar,
Union, Union,
@ -25,8 +27,9 @@ from sqlalchemy.orm import registry
from .types import ULID from .types import ULID
JSON = int | float | str | None | list["JSON"] | dict[str, "JSON"] JSONScalar: TypeAlias = int | float | str | None
JSONObject = dict[str, JSON] type JSON = JSONScalar | list["JSON"] | dict[str, "JSON"]
type JSONObject = dict[str, JSON]
T = TypeVar("T") T = TypeVar("T")
@ -126,6 +129,10 @@ def asplain(
continue continue
target: Any = f.type target: Any = f.type
if isinstance(target, TypeAliasType):
# Support type aliases.
target = target.__value__
# XXX this doesn't properly support any kind of nested types # XXX this doesn't properly support any kind of nested types
if (otype := optional_type(f.type)) is not None: if (otype := optional_type(f.type)) is not None:
target = otype target = otype
@ -150,10 +157,13 @@ def asplain(
elif target in {bool, str, int, float}: elif target in {bool, str, int, float}:
assert isinstance( assert isinstance(
v, target v, target
), f"Type mismatch: {f.name} ({target} != {type(v)})" ), f"Type mismatch: {f.name!a} ({target!a} != {type(v)!a})"
d[f.name] = v
elif target in {Literal}:
assert isinstance(v, JSONScalar)
d[f.name] = v d[f.name] = v
else: else:
raise ValueError(f"Unsupported value type: {f.name}: {type(v)}") raise ValueError(f"Unsupported value type: {f.name!a}: {type(v)!a}")
return d return d
@ -196,18 +206,24 @@ def fromplain(cls: Type[T], d: Mapping, *, serialized: bool = False) -> T:
def validate(o: object) -> None: def validate(o: object) -> None:
for f in fields(o): for f in fields(o):
vtype = type(getattr(o, f.name)) ftype = f.type
if vtype is f.type: if isinstance(ftype, TypeAliasType):
# Support type aliases.
ftype = ftype.__value__
v = getattr(o, f.name)
vtype = type(v)
if vtype is ftype:
continue continue
origin = get_origin(f.type) origin = get_origin(ftype)
if origin is vtype: if origin is vtype:
continue continue
is_union = isinstance(f.type, UnionType) or origin is Union is_union = isinstance(ftype, UnionType) or origin is Union
if is_union: if is_union:
# Support unioned types. # Support unioned types.
utypes = get_args(f.type) utypes = get_args(ftype)
if vtype in utypes: if vtype in utypes:
continue continue
@ -216,7 +232,14 @@ def validate(o: object) -> None:
if any(vtype is gtype for gtype in gtypes): if any(vtype is gtype for gtype in gtypes):
continue continue
raise ValueError(f"Invalid value type: {f.name}: {vtype}") if origin is Literal:
# Support literal types.
vals = get_args(ftype)
if v in vals:
continue
raise ValueError(f"Invalid value: {f.name!a}: {v!a}")
raise ValueError(f"Invalid value type: {f.name!a}: {vtype!a}")
def utcnow() -> datetime: def utcnow() -> datetime:
@ -365,10 +388,10 @@ dataclass containing the ID of the linked data.
The contents of the Relation are ignored or discarded when using The contents of the Relation are ignored or discarded when using
`asplain`, `fromplain`, and `validate`. `asplain`, `fromplain`, and `validate`.
""" """
Relation = Annotated[T | None, _RelationSentinel] Relation: TypeAlias = Annotated[T | None, _RelationSentinel]
Access = Literal[ type Access = Literal[
"r", # read "r", # read
"i", # index "i", # index
"w", # write "w", # write
@ -413,6 +436,9 @@ class User:
self.groups.append({"id": group_id, "access": access}) self.groups.append({"id": group_id, "access": access})
users = User.__table__
@mapper_registry.mapped @mapper_registry.mapped
@dataclass @dataclass
class Rating: class Rating:
@ -477,3 +503,41 @@ class Group:
id: ULID = field(default_factory=ULID) id: ULID = field(default_factory=ULID)
name: str = None name: str = None
users: list[GroupUser] = field(default_factory=list) users: list[GroupUser] = field(default_factory=list)
type AwardCategory = Literal[
"imdb-top-250", "imdb-bottom-100", "imdb-pop-100", "oscars"
]
@mapper_registry.mapped
@dataclass
class Award:
__table__: ClassVar[Table] = Table(
"awards",
metadata,
Column("id", String, primary_key=True), # ULID
Column("movie_id", ForeignKey("movies.id"), nullable=False), # ULID
Column(
"category", String, nullable=False
), # Enum: "imdb-top-250", "imdb-bottom-100", "imdb-pop-100", "oscars", ...
Column(
"details", String, nullable=False
), # e.g. "23" (position in list), "2024, nominee, best director", "1977, winner, best picture", ...
Column("created", String, nullable=False), # datetime
Column("updated", String, nullable=False), # datetime
)
id: ULID = field(default_factory=ULID)
movie_id: ULID = None
movie: Relation[Movie] = None
category: AwardCategory = None
details: str = None
created: datetime = field(default_factory=utcnow)
updated: datetime = field(default_factory=utcnow)
awards = Award.__table__