BrickTracker/bricktracker/metadata_list.py

146 lines
4.0 KiB
Python
Raw Normal View History

2025-01-30 16:23:47 +01:00
import logging
2025-02-03 16:46:45 +01:00
from typing import List, overload, Self, Type, TypeVar
from flask import url_for
2025-01-30 16:23:47 +01:00
from .exceptions import NotFoundException
from .fields import BrickRecordFields
from .record_list import BrickRecordList
2025-01-31 16:34:52 +01:00
from .set_owner import BrickSetOwner
2025-01-30 16:23:47 +01:00
from .set_status import BrickSetStatus
2025-02-03 16:46:45 +01:00
from .set_storage import BrickSetStorage
2025-01-31 18:08:53 +01:00
from .set_tag import BrickSetTag
2025-01-30 16:23:47 +01:00
logger = logging.getLogger(__name__)
2025-02-03 16:46:45 +01:00
T = TypeVar('T', BrickSetOwner, BrickSetStatus, BrickSetStorage, BrickSetTag)
2025-01-30 16:23:47 +01:00
# Lego sets metadata list
class BrickMetadataList(BrickRecordList[T]):
kind: str
mapping: dict[str, T]
model: Type[T]
# Database table
table: str
# Queries
select_query: str
2025-02-03 16:46:45 +01:00
# Set state endpoint
set_state_endpoint: str
def __init__(
self,
model: Type[T],
/,
*,
force: bool = False,
records: list[T] | None = None
):
self.model = model
# Records override (masking the class variables with instance ones)
if records is not None:
self.records = []
self.mapping = {}
for metadata in records:
self.records.append(metadata)
self.mapping[metadata.fields.id] = metadata
else:
# Load metadata only if there is none already loaded
records = getattr(self, 'records', None)
if records is None or force:
# Don't use super()__init__ as it would mask class variables
self.fields = BrickRecordFields()
logger.info('Loading {kind} list'.format(
kind=self.kind
))
self.__class__.records = []
self.__class__.mapping = {}
# Load the metadata from the database
for record in self.select():
metadata = model(record=record)
self.__class__.records.append(metadata)
self.__class__.mapping[metadata.fields.id] = metadata
# HTML prefix name
def as_prefix(self, /) -> str:
return self.kind.replace(' ', '-')
2025-01-30 16:23:47 +01:00
2025-02-03 16:46:45 +01:00
# Filter the list of records (this one does nothing)
def filter(self) -> list[T]:
return self.records
2025-01-30 16:23:47 +01:00
# Return the items as columns for a select
2025-02-03 16:46:45 +01:00
@classmethod
def as_columns(cls, /, **kwargs) -> str:
new = cls.new()
2025-01-30 16:23:47 +01:00
return ', '.join([
'"{table}"."{column}"'.format(
2025-02-03 16:46:45 +01:00
table=cls.table,
2025-01-30 16:23:47 +01:00
column=record.as_column(),
)
for record
2025-02-03 16:46:45 +01:00
in new.filter(**kwargs)
2025-01-30 16:23:47 +01:00
])
# Grab a specific status
2025-02-03 16:46:45 +01:00
@classmethod
def get(cls, id: str, /, *, allow_none: bool = False) -> T:
new = cls.new()
if allow_none and id == '':
return new.model()
if id not in new.mapping:
2025-01-30 16:23:47 +01:00
raise NotFoundException(
'{kind} with ID {id} was not found in the database'.format(
2025-02-03 16:46:45 +01:00
kind=new.kind.capitalize(),
2025-01-30 16:23:47 +01:00
id=id,
),
)
2025-02-03 16:46:45 +01:00
return new.mapping[id]
2025-01-30 16:23:47 +01:00
# Get the list of statuses depending on the context
2025-02-03 16:46:45 +01:00
@overload
@classmethod
def list(cls, /, **kwargs) -> List[T]: ...
@overload
@classmethod
def list(cls, /, as_class: bool = False, **kwargs) -> Self: ...
@classmethod
def list(cls, /, as_class: bool = False, **kwargs) -> List[T] | Self:
new = cls.new()
list = new.filter(**kwargs)
if as_class:
# Return a copy of the metadata list with overriden records
return cls(new.model, records=list)
else:
return list
# Instantiate the list with the proper class
@classmethod
def new(cls, /, *, force: bool = False) -> Self:
raise Exception('new() is not implemented for BrickMetadataList')
# URL to change the selected state of this metadata item for a set
@classmethod
def url_for_set_state(cls, id: str, /) -> str:
return url_for(
cls.set_state_endpoint,
id=id,
)