from collections import defaultdict from sqlalchemy import ForeignKey from sqlalchemy.orm import Mapped, mapped_column, relationship from ttfrog.db.base import BaseObject, SavingThrowsMixin, SkillsMixin __all__ = [ "ClassAttributeMap", "ClassAttribute", "ClassAttributeOption", "CharacterClass", ] class ClassAttributeMap(BaseObject): __tablename__ = "class_attribute_map" class_attribute_id: Mapped[int] = mapped_column(ForeignKey("class_attribute.id"), primary_key=True) character_class_id: Mapped[int] = mapped_column(ForeignKey("character_class.id"), primary_key=True) level: Mapped[int] = mapped_column(nullable=False, info={"min": 1, "max": 20}, default=1) attribute = relationship("ClassAttribute", uselist=False, viewonly=True, lazy="immediate") class ClassAttribute(BaseObject): __tablename__ = "class_attribute" id: Mapped[int] = mapped_column(init=False, primary_key=True, autoincrement=True) name: Mapped[str] = mapped_column(nullable=False) options = relationship("ClassAttributeOption", cascade="all,delete,delete-orphan", lazy="immediate") def __repr__(self): return f"{self.id}: {self.name}" class ClassAttributeOption(BaseObject): __tablename__ = "class_attribute_option" id: Mapped[int] = mapped_column(init=False, primary_key=True, autoincrement=True) name: Mapped[str] = mapped_column(nullable=False) attribute_id: Mapped[int] = mapped_column(ForeignKey("class_attribute.id"), nullable=False) class CharacterClass(BaseObject, SavingThrowsMixin, SkillsMixin): __tablename__ = "character_class" id: Mapped[int] = mapped_column(init=False, primary_key=True, autoincrement=True) name: Mapped[str] = mapped_column(index=True, unique=True) hit_dice: Mapped[str] = mapped_column(default="1d6") hit_dice_stat: Mapped[str] = mapped_column(default="") proficiencies: Mapped[str] = mapped_column(default="") attributes = relationship("ClassAttributeMap", cascade="all,delete,delete-orphan", lazy="immediate") @property def attributes_by_level(self): by_level = defaultdict(list) for mapping in self.attributes: by_level[mapping.level] = {mapping.attribute.name: mapping.attribute} return by_level