Skip to content

Custom data models

Models provide structured, append-only data storage beyond the standard player/group/session fields. Use them for ordered collections of entries like transaction logs, auction bids, or any data that accumulates over time.

Note

The built-in chat system is built on top of models. For chat functionality, use the chat API directly.

Creating a model

Create a model in a session, typically in new_session:

from uproot.models import create_model

def new_session(session):
    session.offers = create_model(session)

create_model returns a ModelIdentifier that you store on the session (or group) for later use.

Defining entry types

Define the structure of your entries using the Entry metaclass:

from uproot.models import Entry
from uproot.types import PlayerIdentifier

class Offer(metaclass=Entry):
    player: PlayerIdentifier
    price: float
    quantity: int

Entry classes are immutable dataclasses. The id field name is reserved.

Fields with types PlayerIdentifier, SessionIdentifier, GroupIdentifier, or ModelIdentifier are auto-filled when using add_entry.

Adding entries

Use add_entry to add an entry to a model. Identifier fields matching the player's session, group, or player ID are filled automatically:

from uproot.models import add_entry

@live
async def post_offer(page, player, price: float, quantity: int):
    add_entry(player.session.offers, player, Offer, price=price, quantity=quantity)

The first argument is the model identifier, the second is the player (used for auto-filling identifier fields), and the third is the entry type. Remaining keyword arguments fill the entry's fields.

Each entry is timestamped and assigned a UUID.

Raw entries

To add an entry without auto-filling, use add_raw_entry:

from uproot.models import add_raw_entry

add_raw_entry(session.log, {"event": "started", "detail": "phase 2"})

Retrieving entries

All entries

from uproot.models import get_entries

entries = get_entries(session.offers, Offer)
for uuid, timestamp, offer in entries:
    print(f"{offer.player} offered {offer.price} at {timestamp}")

get_entries returns a list of (UUID, float, entry) tuples, ordered by time.

Filtered entries

from uproot.models import filter_entries

# Filter by field values
cheap_offers = filter_entries(session.offers, Offer, price=10.0)

# Filter by predicate
large_offers = filter_entries(
    session.offers, Offer,
    predicate=lambda o: o.quantity > 100,
)

# Filter by entry UUID
specific = filter_entries(session.offers, Offer, id=some_uuid)

Latest entry

from uproot.models import get_latest_entry

uuid, timestamp, latest = get_latest_entry(session.offers, Offer)

Single field

Read a single field from the model's storage:

from uproot.models import get_field

value = get_field(session.offers, "some_field")

Checking existence

from uproot.models import model_exists

if model_exists(session.offers):
    ...

Example: auction log

A complete example using models to track bids in an auction:

from uproot.models import Entry, create_model, add_entry, get_entries, get_latest_entry
from uproot.types import PlayerIdentifier


class Bid(metaclass=Entry):
    player: PlayerIdentifier
    amount: float


def new_session(session):
    session.auction = create_model(session)


class Auction(Page):
    @live
    async def place_bid(page, player, amount: float):
        add_entry(player.session.auction, player, Bid, amount=amount)

        # Get all bids
        bids = get_entries(player.session.auction, Bid)

        # Return current highest
        if bids:
            _, _, highest = max(bids, key=lambda b: b[2].amount)
            return {"highest": highest.amount, "total_bids": len(bids)}
        return {"highest": 0, "total_bids": 0}

Summary

Function Purpose
create_model(session) Create a new model, returns ModelIdentifier
add_entry(mid, player, EntryType, **fields) Add entry with auto-filled identifiers
add_raw_entry(mid, entry) Add entry without auto-filling
get_entries(mid, EntryType) Get all entries as (UUID, time, entry) tuples
filter_entries(mid, EntryType, **filters) Get filtered entries
get_latest_entry(mid, EntryType) Get most recent entry
get_field(mid, field_name) Read a field from model storage
model_exists(mid) Check if a model exists