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 |
|
|
Attachment type |
Structure |
|
|
Structure type |
Enumeration |
|
|
Enumeration type |
Concept |
|
|
Concept type |
Path |
|
|
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.
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 runtimeFlexible, 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¶
Database - Database and CommitDatabase in detail
Blobs - Working with binary data
Types and Values - Deep dive into the type system