Conworld Codex 2018-06-14

By Max Woerner Chase

In this post:


My basic idea of how I want to structure Conworld Codex is slowly shaping up.

A "Codex" is a SQLite database. It's handled in memory normally, and written to disk all at once, possibly in some kind of versioned format.

Through SQLAlchemy, each in-memory database has a corresponding session. Therefore, I can say that a loaded Codex has an associated session.

The sessions have associated models, which are currently data-only. I suspect that "fat models" in Django come from trying to put the business logic near the parts of the code responsible for persistence.

For the other levels of code, access to the Codex data is mediated through an "App" (should be "Codex") object, which functions as a root to a tree-based interface, and maintains canonical copies of everything visible to consumers of that interface. Taking some terminology from Wx, we have ideas like:

Okay, this train of thought is unpleasant enough that I'm going to just start searching for stuff like "declarative GUI" and see if I find anything applicable to this.

Enaml looks interesting, though parts of the docs do seem worryingly anemic.

What's got me down right now is, I'm trying to teach myself how to put together UIs, and the corollary there is, I don't know what I'm doing. I think it would be easier with a completely fixed, single-window setup... which is not what I'm doing.

I think what I need to do if I want to get anywhere with this is, step away from the higher levels of code, and explicitly write out every transformation of data I want to model. They all need to be handled, so I should have a checklist made.

First, the data:

and relationships:

It occurs to me that a database in which the tables were sum types and the indexes were explictly of functions, would be able to express these ideas in a very concise way. But, since I don't feel like digging up the relevant old projects (... maybe if I ever feel like getting better with profiling), I'll look into joined table inheritance.

I went and rewrote all the models underlying this with all the stuff I just said in mind. Somehow, the tests all pass after I did that. I think it might be because there's no __slots__ attribute on any of the classes I wrote? Anyway.

I may have rewritten things a few more times.

For now, the most primitive types are Events, Topics, and secondarily Tellers.

To create an Event, provide its name, which must be unique.

To create a Topic, provide its name, which must be unique.

To create a Teller, provide either the name of a Topic which is not already associated with a Teller, or a name for a new Topic.

I believe relations are made just with the appropriate constructor, and then Commentary composes a Relation and a Teller.

To generalize this, I think I need a big matrix, where one side is CRUD and the other side is Event, Topic, Teller, EventRelation, EventTopic, EventContribution, EventConstitution, and Commentary.

Have a code dump:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
"""SQLAlchemy ORM classes for modeling codex data."""


from sqlalchemy import Column, Integer, String, ForeignKey, UniqueConstraint
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__ = 'event'

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


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

    __tablename__ = 'topic'

    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__ = 'teller'

    id = Column(Integer, ForeignKey('topic.id'), primary_key=True)


class EventRelation(Base):  # type: ignore
    """Base class for relations."""

    __tablename__ = 'event_relation'

    id = Column(Integer, primary_key=True)
    type = Column(String(50))

    __mapper_args__ = {
        'polymorphic_identity': 'event_relation',
        'polymorphic_on': type,
    }


class EventTopic(EventRelation):  # type: ignore
    """How a topic relates to an event."""

    __tablename__ = 'event_topic'
    __table_args__ = (
        UniqueConstraint('topic_id', 'event_id'),
    )
    __mapper_args__ = {
        'polymorphic_identity': 'event_topic',
    }

    id = Column(Integer, ForeignKey('event_relation.id'), primary_key=True)
    topic_id = Column(Integer, ForeignKey('topic.id'), nullable=False)
    event_id = Column(Integer, ForeignKey('event.id'), nullable=False)


class EventContribution(EventRelation):
    """How one event contributed to another."""

    __tablename__ = 'event_contribution'
    __table_args__ = (
        UniqueConstraint('cause', 'effect'),
    )
    __mapper_args__ = {
        'polymorphic_identity': 'event_contribution',
    }

    id = Column(Integer, ForeignKey('event_relation.id'), primary_key=True)
    cause = Column(Integer, ForeignKey('event.id'), nullable=False)
    effect = Column(Integer, ForeignKey('event.id'), nullable=False)


class EventConstitution(EventRelation):
    """How one event was part of another."""

    __tablename__ = 'event_constitution'
    __table_args__ = (
        UniqueConstraint('part', 'whole'),
    )
    __mapper_args__ = {
        'polymorphic_identity': 'event_constitution'
    }

    id = Column(Integer, ForeignKey('event_relation.id'), primary_key=True)
    part = Column(Integer, ForeignKey('event.id'), nullable=False)
    whole = Column(Integer, ForeignKey('event.id'), nullable=False)


class Commentary(Base):
    """Commentary on a relation by a Teller."""

    __tablename__ = 'commentary'
    __table_args__ = (
        UniqueConstraint('event_relation_id', 'teller_id'),
    )

    id = Column(Integer, primary_key=True)
    event_relation_id = Column(Integer, ForeignKey('event_relation.id'), nullable=False)
    teller_id = Column(Integer, ForeignKey('teller.id'), nullable=False)

    text = Column(String)

I'm very unsure whether some of this makes sense, but I'm feeling a little better about using inheritance rather than manually monomorphizing relationships like I was before.

PS: You know what category of feature would be great to think about with all of this stuff? Robust undo/redo support.


Next week, I get that matrix filled in.