Skip to content

Fact types

Fact

Bases: BaseModel

The base class of a type of fact. Declare fields as on any Pydantic model.

A fact is frozen. It cannot change after you make it. Use but to make a changed copy. Mark the fields that identify the fact with Key[...]. Mark an enum field that may only move forward with Forward[...].

The log names a type by type_name(). The default is the class name in lower case. Write class NoteBefore(Fact, name="note") to use another name. Then 2 classes can describe 1 type before and after a change.

Example
from scriptt.world import Fact, Key


class Task(Fact):
    id: Key[str]
    title: str


Task(id="t9", title="Buy tea")

model_config class-attribute instance-attribute

model_config = ConfigDict(frozen=True)

identity property

identity: tuple

The values of the key fields as a tuple.

2 facts with the same identity are 2 versions of 1 thing.

Example
today.one(Task, id="t5").identity  # -> ('t5',)

type_name classmethod

type_name() -> str

Give the name that the log uses for this type.

Returns:

Type Description
str

The name= option of the class. If there is none, the class name in lower

str

case.

Example
Task.type_name()  # -> 'task'

key_fields classmethod

key_fields() -> tuple[str, ...]

Give the names of the key fields.

Returns:

Type Description
str

The fields marked Key[...] in declaration order. A type with no mark gives

...

its first field.

Example
Task.key_fields()  # -> ('id',)
Note.key_fields()  # -> ('task',)

refs classmethod

refs() -> dict[str, type[Fact]]

Give the fields marked Ref[...] and the type that each one points to.

Returns:

Type Description
dict[str, type[Fact]]

A dict from field name to type in declaration order.

Example
from scriptt.world import Fact, Key, Ref


class TodoList(Fact):
    name: Key[str]


class Task(Fact):
    id: Key[str]
    list: Ref[TodoList]


Task.refs()  # -> {'list': <class 'TodoList'>}

forward_fields classmethod

forward_fields() -> tuple[str, ...]

Give the names of the fields that may only move forward.

Returns:

Type Description
str

The fields marked Forward[...] in declaration order. It is an empty tuple

...

if there is no such field.

Example
Task.forward_fields()  # -> ()

but

but(**changes: Any) -> Self

Make a copy of the fact with some fields changed. The type checks the copy again.

Parameters:

Name Type Description Default
**changes Any

The fields to change as keyword arguments.

{}

Returns:

Type Description
Self

A new fact of the same type. The first fact does not change.

Raises:

Type Description
ValidationError

A new value does not fit its field or a rule of the type fails.

Example
plants = today.one(Task, id="t5")
plants.but(title="Water the garden").title  # -> 'Water the garden'
plants.title  # -> 'Water the plants'

__describe__

__describe__() -> Template

Give the t-string that say uses to name this fact. It has the type and the key. An example is "note t1".

A type can define its own __describe__ that returns a different t-string.

Returns:

Type Description
Template

A t-string.

Example
from scriptt.world import say

say(t"{today.one(Note, task='t1')}")  # -> 'note t1'

label classmethod

label(field: str) -> str

Give the words for 1 field in text.

Parameters:

Name Type Description Default
field str

The name of a field of the type.

required

Returns:

Type Description
str

The description of the field (`p: float =

str

Field(description="probability")`). If there is none, it is the name with

str

each underscore as a space.

Raises:

Type Description
KeyError

The type has no such field.

Example
Task.label("title")  # -> 'title'

check classmethod

check(data: dict[str, Any]) -> Result[Self, str]

Make a fact from a dictionary or give the first reason that the data does not make one.

Rules are Pydantic validators on the type. The constructor raises an error. This method does not.

Parameters:

Name Type Description Default
data dict[str, Any]

The fields as a dictionary from field name to value.

required

Returns:

Type Description
Result[Self, str]

Success(fact) or Failure(reason). The reason is the field and the first

Result[Self, str]

error as text. Success and

Result[Self, str]

Failure come from the library returns.

Example
data = {"id": "t9", "list": "home", "title": "Buy tea", "due": "soon"}
Task.check(data).failure()
# -> 'due: Input should be a valid date or datetime, input is too short'
Task.check(data | {"due": "2026-11-05"}).unwrap().due.day  # -> 5

grammar classmethod

grammar() -> str

Give a Lark grammar for 1 line of text of the form <type> field=value .... parse uses it.

The grammar comes from the fields of the type. The fields may come in any order. An enum or Literal field accepts only its values. A date may be ISO text (2026-10-08) or quoted text.

Returns:

Type Description
str

The grammar as text in Lark syntax.

Example
Task.grammar().splitlines()[0]
# -> 'start: "task" (f_id | f_list | f_title | f_status | f_due)*'

parse classmethod

parse(line: str) -> Result[Self, str]

Read 1 line of text as a fact or give the reason that it is not one.

The line is the type name and then field=value pairs. A text value is in double quotes. A number can be a percent. 70% is 0.7. A wrong value gives the field and the values that it accepts.

Parameters:

Name Type Description Default
line str

The line of text. Spaces at the start and the end do not matter.

required

Returns:

Type Description
Result[Self, str]

Success(fact) or Failure(reason) from the library returns.

Example
Task.parse(
    'task id="t9" list="home" title="Buy tea" status=done'
).unwrap().status.value  # -> 'done'
Task.parse(
    'task id="t9" list="home" title="Buy tea" status=waiting'
).failure()  # -> 'status must be one of: open, done'

to_text

to_text() -> str

Write the fact as 1 line of text. parse reads the line back into this fact.

A field that is None is left out.

Returns:

Type Description
str

The line. An example is `task id="t5" list="home"

str

title="Water the plants" status=open due=2026-11-04`.

Example
plants = today.one(Task, id="t5")
plants.to_text()[:24]  # -> 'task id="t5" list="home"'
Task.parse(plants.to_text()).unwrap() == plants  # -> True

Key

Key = Annotated[T, _KEY]

Mark a field as part of the key of a fact: id: Key[str].

The key fields identify the fact. Two facts of 1 type with the same key values are 2 versions of 1 thing. A type with no Key field uses its first field as the key. Key[T] works with | None too.

Example
from scriptt.world import Fact, Key

class Reminder(Fact):
    list: Key[str]
    title: Key[str]
    minutes: int

Reminder.key_fields()   # -> ('list', 'title')

Ref

Ref = Annotated[str, _REF]

Mark a text field that contains the key of a fact of another type: list: Ref[TodoList].

The value is the key of the other fact (the other type has 1 key field). A list of keys works too: candidates: tuple[Ref[Print], ...]. World.problems reports a Ref that points to no fact, World.about follows Ref fields back to a fact, and diagram draws them as arrows. Ref works inside Key[...] and with | None.

Example
from scriptt.world import Fact, Key, Ref

class TodoList(Fact):
    name: Key[str]

class Task(Fact):
    id: Key[str]
    list: Ref[TodoList]

Task.refs()   # -> {'list': <class 'TodoList'>}

Forward

Forward = Annotated[T, _FORWARD]

Mark an enum field whose value may only move forward, in the order that the enum declares its members: state: Forward[State].

World.add refuses a new version of a fact that moves the field back.

Example
from enum import StrEnum
from scriptt.world import Fact, Forward, Key

class Stage(StrEnum):
    PLANNED = "planned"
    STARTED = "started"
    FINISHED = "finished"

class Project(Fact):
    id: Key[str]
    stage: Forward[Stage]

Project.forward_fields()   # -> ('stage',)