Structures and Enumerations¶
Structures and enumerations are user-defined types that require registration
with Definitions before use. This chapter covers creating and working with both.
Creating Structures with Definitions¶
Structures are defined within a Definitions context:
>>> from dsviper import *
>>> defs = Definitions()
>>> ns = NameSpace(ValueUUId("f529bc42-0618-4f54-a3fb-d55f95c5ad03"), "Tuto")
Define a structure with a descriptor:
>>> desc = TypeStructureDescriptor("Login")
>>> desc.add_field("nickname", Type.STRING)
>>> desc.add_field("password", Type.STRING)
>>> t_login = defs.create_structure(ns, desc)
>>> t_login
Tuto::Login
Instantiating Structures¶
Create structure instances with Value.create(). Default values use the
type’s “zero”:
>>> login = Value.create(t_login)
>>> login
{nickname='', password=''}
Initialize from a dict:
>>> login = Value.create(t_login, {"nickname": "zoop"})
>>> login
{nickname='zoop', password=''}
Field Access¶
Access fields by name:
>>> login.nickname
'zoop'
>>> login.password = "secret"
>>> login
{nickname='zoop', password='secret'}
Type Checking¶
Field assignments are type-checked:
>>> login.nickname = 42
Traceback (most recent call last):
...
dsviper.ViperError: ...expected type 'str', got 'int'...
Default Values¶
Specify default values when defining fields:
>>> desc = TypeStructureDescriptor("Config")
>>> desc.add_field("scale", ValueFloat(1.0))
>>> desc.add_field("items", Value.create(TypeVector(Type.INT64), [1, 2, 3]))
>>> t_config = defs.create_structure(ns, desc)
>>> Value.create(t_config)
{scale=1.0, items=[1, 2, 3]}
Nested Structures¶
Structures can contain other structures:
>>> desc_pos = TypeStructureDescriptor("Position")
>>> desc_pos.add_field("x", Type.FLOAT)
>>> desc_pos.add_field("y", Type.FLOAT)
>>> t_position = defs.create_structure(ns, desc_pos)
>>> desc_vertex = TypeStructureDescriptor("Vertex")
>>> desc_vertex.add_field("position", t_position)
>>> desc_vertex.add_field("label", Type.STRING)
>>> t_vertex = defs.create_structure(ns, desc_vertex)
>>> vertex = Value.create(t_vertex)
>>> vertex
{position={x=0.0, y=0.0}, label=''}
Mutate a nested field:
>>> vertex.position.x = 10.0
>>> vertex.position.y = 20.0
>>> vertex.label = "A"
>>> vertex
{position={x=10.0, y=20.0}, label='A'}
Paths¶
A Path locates a piece of information within a value:
>>> p = Path.from_field("nickname").const()
>>> p
.nickname
Apply a path to read or write the targeted field:
>>> p.at(login)
'zoop'
>>> p.set(login, "new_nick")
>>> login.nickname
'new_nick'
Complex Paths¶
Paths can traverse nested structures:
>>> p = Path.from_field("position").field("x").const()
>>> p
.position.x
>>> p.at(vertex)
10.0
>>> p.set(vertex, 15.0)
Path Components¶
Paths can include:
Field access:
.field("name")Index access:
.index(0)Unwrap:
.unwrap()Map key:
.key(key_value)
>>> p = Path.from_field("items").index(0).const()
>>> p
.items[0]
>>> p.components()
[{type: Field, value: 'items':string}, {type: Index, value: 0:uint64}]
Enumerations¶
Enumerations are types with a fixed set of named cases.
Defining an Enumeration¶
>>> desc = TypeEnumerationDescriptor("Status", documentation="Task status")
>>> desc.add_case("pending", "Waiting to start")
>>> desc.add_case("active", "In progress")
>>> desc.add_case("completed", "Finished")
>>> t_status = defs.create_enumeration(ns, desc)
>>> t_status
Tuto::Status
Creating Enumeration Values¶
By case name (most common):
>>> status = ValueEnumeration(t_status, "active")
>>> status.name()
'active'
By index (0-based):
>>> status = ValueEnumeration(t_status, 0)
>>> status.name()
'pending'
Default (first case):
>>> Value.create(t_status)
.pending
Specify case with Value.create():
>>> Value.create(t_status, "completed")
.completed
Properties¶
>>> status = ValueEnumeration(t_status, "active")
>>> status.name()
'active'
>>> status.index()
1
>>> status.type_enumeration()
Tuto::Status
Type Introspection¶
Query available cases from the type:
>>> cases = t_status.cases()
>>> [c.name() for c in cases]
['pending', 'active', 'completed']
Query a case by name:
>>> case = t_status.query("active")
>>> case.documentation()
'In progress'
Check existence (raises on invalid):
>>> t_status.check("unknown")
Traceback (most recent call last):
...
dsviper.ViperError: ...the case unknown is not defined for enum Tuto::Status...
Comparison¶
Enumerations compare lexicographically by name, not by index:
>>> pending = ValueEnumeration(t_status, "pending")
>>> active = ValueEnumeration(t_status, "active")
>>> active < pending
True
Constraints¶
Maximum 256 cases per enumeration (uint8 index)
Case names must be unique within an enumeration
Empty enumerations are not allowed
Type Information¶
Introspect structure types. Field reprs include the field type:
>>> t_login.fields()
[nickname:string, password:string]
>>> field = t_login.fields()[0]
>>> field.name()
'nickname'
>>> field.type()
string
Using Structures with Databases¶
Structures are typically stored via attachments. See DSM for defining attachments and Database for persistence patterns.
Note
The “Quick preview” snippet below depends on a DSM model compiled by Kibo
(TUTO_A_USER_LOGIN is generated). It is illustrative — see the
Tutorial for a self-contained working example.
# With DSM definitions loaded
>>> key = TUTO_A_USER_LOGIN.create_key()
>>> login = TUTO_A_USER_LOGIN.create_document()
>>> login.nickname = "alice"
# Store in database
>>> db.set(TUTO_A_USER_LOGIN, key, login)
What’s Next¶
DSM - Define data models with attachments
Database - Persisting structures
Serialization - JSON and binary encoding