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