WorkingRecord¶
WorkingRecord
¶
WorkingRecord(base: RecordLike, con: DuckDBPyConnection)
Bases: Record
A Record whose last source is a staging area, plus the edit surface.
A Record in the type as well as in the fold: what it reads is the data
with its pending edits applied, and it reads it by being one layer deeper
than its base rather than by overlaying anything of its own. Every read
member is inherited unchanged - outputs alone is overridden, results not
overlaying - so an edit reads back through the same code path a committed
layer would.
Staged rows live in connection-scoped DuckDB tables, the only place a staged row exists: the reads fold them rather than holding a copy, so what is staged is asked of the reads themselves.
The Resolver is fixed at construction and the source list never changes;
a set changes what the last source's tables hold, not which sources there
are. That is what lets the inherited members stay correct - they cache a key
set only where the fold is stable, which a staged source makes it not.
Source code in src/datarecord/mutable.py
outputs
property
¶
outputs: Frames
Staged results, keyed by attribute - what a tool handed back.
Results reach a record through set(..., kind="outputs"), so a tool can
solve against this record's pending inputs and attach what it computed
without committing first. The base's results are not included: they
were computed from inputs these edits may have changed, and results do
not overlay, so what is staged is the whole answer.
Keeping them coherent with the inputs is the caller's business - editing an input after attaching results leaves results describing a record that no longer exists, and nothing here silently discards them.
set
¶
set(
attribute: str,
value: Any,
*,
entity: Sequence[str] | None = None,
kind: Literal["inputs", "outputs"] = "inputs",
indexed_by: str | None = None,
**dims: Any,
) -> None
Stage an attribute value for a group of components.
A labelled series must say what its index holds: indexed_by="snapshot"
names the axis, and entity= implies it where the attribute has exactly
one other coordinate. Without either the index is read as entity names.
Never inferred from the labels themselves - an axis label may be a string
just like a name, so that would make one call mean different things in
two records.
value takes five forms: a scalar broadcast to every name, a sequence
aligned positionally to names, a mapping keyed by name, a long frame
supplying its own keys, and a narwhals expression - which is a function
of the current value rather than a value, so it reads before it stages
and two such calls compose.
No entity_type parameter: the type is looked up from the entity,
so one call may span types and each is validated against its own
type's spec. entity=None means every component whose type
declares attribute.
Every other coordinate goes through **dims, including a group's -
bus="north" for a connection attribute, from=/to= for a corridor.
None has a parameter of its own, since which coordinates exist is
declared rather than fixed.
kind names the destination in the format's own terms:
"outputs" stages into outputs/ instead of inputs/, which is how a
tool hands results back. Results use the same long schema; what differs
is that they do not overlay.
Two checks are skipped for "outputs", both because a result is not a
value the schema governs: the attribute need not be declared, and a
result's name need not resolve to a declared member. A solve may
produce rows for a component type it derived rather than read - PyPSA's
SubNetwork is one - and rejecting those would refuse a legitimate
result. An input for an undeclared name stays an error.
Source code in src/datarecord/mutable.py
999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 | |
add
¶
Stage new components from a wide frame.
Splits it: attributes addressed by entity alone stay in dims/entity_type/,
varying ones become inputs/ rows. Which is which comes from the
schema, so this needs no framework registry.
Not a sequence of set calls: a component exists by virtue of its
member row, so staging attribute values for a name no layer declares is
what _validate_attribute rejects. Adding a bus with no attributes makes
the point - nothing to set, yet the bus must exist.
ctype stays a parameter where set loses it: this is the call that
establishes a name's type, so there is nothing yet to look it up in. It is also where uniqueness is enforced.
Source code in src/datarecord/mutable.py
1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 | |
remove
¶
Stage a tombstone per entity.
Need not enumerate what it deletes: one row per key, and the fold applies it to every attribute. Nor scope it - a component exists or it does not, so a deletion removes it whole.
One row, on the entity axis, which is where membership lives and the only
place the fold reads a tombstone from. A member file holds values, never
a deleted (_member_columns), so no second write there keeps step
with this one. The axis row carries the type only where a group declares
the axis; the delete keys on entity alone regardless, so it lands
whatever type the add named.
Source code in src/datarecord/mutable.py
add_group
¶
Stage rows of one declared group from a frame carrying its coordinates.
The one path every group is added through, a record's connection
group included.
No component type, which is no coordinate of a group (https://energy-models.github.io/datarecord/design/format/#where-a-value-lives).
Notes
Source code in src/datarecord/mutable.py
remove_group
¶
Stage a tombstone per key, over one declared group's group_key.
An into label is no part of a key: the tuple is removed, whatever
label it carried.
Notes
Source code in src/datarecord/mutable.py
rollback
¶
Clear every staged row without writing.
Notes
Source code in src/datarecord/mutable.py
commit
¶
commit(target: Target) -> Revision | None
Write everything staged and clear it.
Returns:
| Type | Description |
|---|---|
The new child for a `NewChild` target, so the caller can read what it
|
|
just wrote without going back to the record table; `None` for a
|
|
`Directory`, which belongs to no record. Overloaded on the target, so a
|
|
caller committing to a child holds a `Revision` rather than an optional
|
|
one - which target it passed is what decides, and it is always literal
|
|
at the call site.
|
|
The layer lands in the *child*, never in the node that was branched
|
|
from - layers are write-once - so it is the returned node that
|
|
reads back the edits.
|
|
Source code in src/datarecord/mutable.py
NewChild
dataclass
¶
NewChild(record: Revision | None = None)
Write the staged rows as a new child layer of record.
Only the edits are written; the fold resolves the rest from the parent.
record defaults to the node the WorkingRecord was built over, which is
what a caller branching from a revision means every time. Passing one
explicitly is for the rarer case of re-parenting the edits elsewhere; a
WorkingRecord over a base that is not a layered node (a directory, a
framework object) has nothing to default to and must supply it.
Notes
Directory
dataclass
¶
Write a standalone record at uri: staged rows plus what the record
already reads, there being no parent to resolve against.