Tutorial

This tutorial walks through a complete example: creating a data model, setting up a database, and performing operations with CommitDatabase.

Prerequisites: This tutorial assumes you have read DSM and understand the basics of DSM syntax and the assemble → parse → introspect workflow.

The User/Login Model

We’ll create a simple model with Users who have Login credentials and Identity information.

Step 1: Define the Data Model

Create a file model.dsm:

namespace Tuto {f529bc42-0618-4f54-a3fb-d55f95c5ad03} {

concept User;

struct Login {
    string nickname;
    string password;
};

struct Identity {
    string firstname;
    string lastname;
};

attachment<User, Login> login;
attachment<User, Identity> identity;

};

Step 2: Validate the Model

Check the DSM syntax:

python3 tools/dsm_util.py check model.dsm

Step 3: Create a Database

Create a Commit database that embeds the definitions:

python3 tools/dsm_util.py create_commit_database model.dsm model.cdb

Step 4: Open and Explore

Open the database in Python and list the types and attachments it carries:

>>> sorted(str(t) for t in db.definitions().types())
['Tuto::Account', 'Tuto::Identity', 'Tuto::Login', 'Tuto::Status', 'Tuto::Texture', 'Tuto::Thumbnail', 'Tuto::User']

>>> sorted(str(a).split()[-1] for a in db.definitions().attachments())
['Tuto::account', 'Tuto::avatar', 'Tuto::identity', 'Tuto::login', 'Tuto::portrait']

Step 5: Inject Constants

db.definitions().inject() makes types accessible as constants in the caller’s namespace (already done in this tutorial’s setup):

>>> TUTO_A_USER_LOGIN
attachment<User, Login> Tuto::login

>>> TUTO_S_LOGIN
Tuto::Login

Naming convention: Constants follow the pattern {NAMESPACE}_{KIND}_{NAME}:

Kind

Prefix

Example

Description

Attachment

_A_

TUTO_A_USER_LOGIN

Attachment type

Structure

_S_

TUTO_S_LOGIN

Structure type

Enumeration

_E_

TUTO_E_STATUS

Enumeration type

Concept

_C_

TUTO_C_USER

Concept type

Path

_P_

TUTO_P_LOGIN_NICKNAME

Path to field

Step 6: Create a Key and Document

Create a new User key. instance_id() returns the underlying UUID (randomly generated, so we just check the type here):

>>> key = TUTO_A_USER_LOGIN.create_key()
>>> isinstance(key.instance_id(), ValueUUId)
True

Create a Login document:

>>> login = TUTO_A_USER_LOGIN.create_document()
>>> login
{nickname='', password=''}

>>> login.nickname = "zoop"
>>> login.password = "robust"
>>> login
{nickname='zoop', password='robust'}

Step 7: Commit to Database

Create a mutable state, associate the document with the key, and commit:

>>> mutable_state = CommitMutableState(db.initial_state())

>>> mutable_state.attachment_mutating().set(TUTO_A_USER_LOGIN, key, login)

>>> commit_id = db.commit_mutations("First Commit", mutable_state)
>>> isinstance(commit_id, ValueCommitId) and len(str(commit_id)) == 40
True

Important

The Dual-Layer Contract

CommitDatabase guarantees structural integrity but NOT semantic integrity. When concurrent streams converge, mutations on non-existent documents or unresolved paths are silently ignored. Your application must validate business rules when consuming state.

See The Dual-Layer Contract.

Step 8: Read from Database

Read the document back:

>>> state = db.state(commit_id)
>>> result = state.attachment_getting().get(TUTO_A_USER_LOGIN, key)
>>> result
Optional({nickname='zoop', password='robust'})

>>> result.unwrap()
{nickname='zoop', password='robust'}

Step 9: Update a Field

Update using a path-based setter. Capture the new commit id returned by commit_mutations — the database’s mutating APIs don’t auto-advance an implicit “current commit” pointer, so chaining commits requires the explicit id:

>>> mutable_state = CommitMutableState(db.state(commit_id))
>>> mutable_state.attachment_mutating().update(TUTO_A_USER_LOGIN, key, TUTO_P_LOGIN_NICKNAME, "zoopy")
>>> updated_id = db.commit_mutations("Update Nickname", mutable_state)

Step 10: View History

Read from different commits:

>>> state = db.state(updated_id)
>>> state.attachment_getting().get(TUTO_A_USER_LOGIN, key)
Optional({nickname='zoopy', password='robust'})

>>> state = db.state(commit_id)
>>> state.attachment_getting().get(TUTO_A_USER_LOGIN, key)
Optional({nickname='zoop', password='robust'})

>>> state = db.initial_state()
>>> state.attachment_getting().get(TUTO_A_USER_LOGIN, key)
nil

Step 11: Inspect Commit Headers

>>> header = db.commit_header(updated_id)
>>> header.label()
'Update Nickname'
>>> header.parent_commit_id() == commit_id
True

Two Approaches

Viper supports two ways to work with your data model:

Dynamic API (Above)

Use Viper’s runtime metadata directly:

  • DSMBuilder.assemble(path).parse() → work with types at runtime

  • Flexible, interpretive, Python-friendly

  • No code generation needed

Static API (Generated)

Generate infrastructure code from your DSM:

python3 tools/dsm_util.py create_python_package model.dsm

This creates a Python package with:

  • Type-safe classes for concepts and structures

  • Generated accessors for attachments

  • IDE autocompletion support

>>> import model.attachments as ma

# Use generated types
>>> key = ma.Tuto_UserKey.create()
>>> login = ma.Tuto_Login()
>>> login.nickname = "user"

# Use generated accessors
>>> state = CommitMutableState(db.state(db.last_commit_id()))
>>> ma.tuto_user_login_set(state.attachment_mutating(), key, login)

Note

The Static API snippet above requires running Kibo first to generate the model.attachments package. It is illustrative — see the Kibo chapter for the full code-generation workflow.

Both approaches use the same underlying Viper runtime.

What’s Next