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")
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 |
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 |
...
|
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 |
...
|
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 |
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]
|
|
Result[Self, str]
|
error as text. |
Result[Self, str]
|
|
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]
|
|
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',)