Conworld Codex 2018-05-10

By Max Woerner Chase

In this post:


One thing I realized recently was that I'd rather focus on in-world stories than presenting "actual events". Something, then, that would help the structure of things would be to associate commentary with different historians etc. So, instead of annotations directly on a link, each link would be eligible for commentary by each possible commentator.

I thought I was going to plan things, but then I went and cobbled together a database schema, which was maybe not the best idea.

"""SQLAlchemy ORM classes for modeling codex data."""


from sqlalchemy import Column, Integer, String, ForeignKey, UniqueConstraint
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()  # pylint: disable=invalid-name


class Event(Base):  # type: ignore
    """The events within the history of the codex."""

    __tablename__ = 'events'

    id = Column(Integer, primary_key=True)
    name = Column(String, unique=True, nullable=False)


class Teller(Base):  # type: ignore
    """The people talking about the events."""

    __tablename__ = 'tellers'

    id = Column(Integer, primary_key=True)
    name = Column(String, unique=True, nullable=False)

    topic_id = Column(Integer, ForeignKey('topics.id'), unique=True)
    topic = relationship('Topic', back_populates='teller')


class Topic(Base):  # type: ignore
    """A person, place, or thing."""

    __tablename__ = 'topics'

    id = Column(Integer, primary_key=True)
    name = Column(String, unique=True, nullable=False)

    teller = relationship('Teller', back_populates='topic', uselist=False)


class TopicRelation(Base):  # type: ignore
    """How a topic relates to an event, according to a teller."""

    __tablename__ = 'topic_relations'
    __table_args__ = (
        UniqueConstraint('topic_id', 'event_id', 'teller_id'),
    )

    id = Column(Integer, primary_key=True)
    topic_id = Column(Integer, ForeignKey('topics.id'), nullable=False)
    event_id = Column(Integer, ForeignKey('events.id'), nullable=False)
    teller_id = Column(Integer, ForeignKey('tellers.id'), nullable=False)

    text = Column(String)


class Contribution(Base):  # type: ignore
    """How one event contributed to another, according to a teller."""

    __tablename__ = 'contributions'
    __table_args__ = (
        UniqueConstraint('cause', 'effect', 'teller_id'),
    )

    id = Column(Integer, primary_key=True)
    cause = Column(Integer, ForeignKey('events.id'), nullable=False)
    effect = Column(Integer, ForeignKey('events.id'), nullable=False)
    teller_id = Column(Integer, ForeignKey('tellers.id'), nullable=False)

    text = Column(String)


class PartOf(Base):  # type: ignore
    """How one event was part of another, according to a teller."""

    __tablename__ = 'parts_of'
    __table_args__ = (
        UniqueConstraint('part', 'whole', 'teller_id'),
    )

    id = Column(Integer, primary_key=True)
    part = Column(Integer, ForeignKey('events.id'), nullable=False)
    whole = Column(Integer, ForeignKey('events.id'), nullable=False)
    teller_id = Column(Integer, ForeignKey('tellers.id'), nullable=False)

    text = Column(String)

Taken as a whole, this passes tests with full coverage, but I'm not trying to do anything yet. I'm reasonably sure this is going to go through some serious changes, and to find out what they are, I'm going to need to put together an interface for all this stuff.

First, though, a quick note: this code is using surrogate keys everywhere it can, which is a decision mainly influenced by a completely different, and somewhat outdated, technology stack. I tried to outsource the question of surrogate vs natural keys to the internet, and the main thing I learned is that you can find an article advocating for any position on that whatsoever.


Next week, I figure out what I did wrong here.