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:
// Types definitions
namespace Tuto {f529bc42-0618-4f54-a3fb-d55f95c5ad03} {
"""A user."""
concept User;
"""Login credentials for a User."""
struct Login {
string nickname;
string password;
};
"""Identity information for a User."""
struct Identity {
string firstname;
string lastname;
};
"""A small avatar image stored inline."""
struct Thumbnail {
uint16 width;
uint16 height;
blob data;
};
"""A high-resolution texture stored by reference."""
struct Texture {
uint16 width;
uint16 height;
blob_id pixels;
};
"""Lifecycle state of a User account."""
enum Status {
pending,
active,
completed
};
"""Account state for a User."""
struct Account {
Status state;
};
attachment<User, Login> login;
attachment<User, Identity> identity;
attachment<User, Thumbnail> avatar;
attachment<User, Texture> portrait;
attachment<User, Account> account;
};
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
See DSM Processing — Inject Constants for the naming convention.
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(CommitStateBuilder.initial_state(db))
>>> 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
Note
Scope of this tutorial
The single-author flow shown here does not exercise the reduction behavior described in The Dual-Layer Contract: with one author committing in sequence, mutations are never silently dropped and no LWW arbitration takes place. The contract becomes load-bearing once several authors’ writes are reduced automatically — see Modes of Use for the diagnostic and which mode applies to your application.
Step 8: Read from Database¶
Read the document back:
>>> state = CommitStateBuilder.state(db, 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(CommitStateBuilder.state(db, 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 = CommitStateBuilder.state(db, updated_id)
>>> state.attachment_getting().get(TUTO_A_USER_LOGIN, key)
Optional({nickname='zoopy', password='robust'})
>>> state = CommitStateBuilder.state(db, commit_id)
>>> state.attachment_getting().get(TUTO_A_USER_LOGIN, key)
Optional({nickname='zoop', password='robust'})
>>> state = CommitStateBuilder.initial_state(db)
>>> 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
The walkthrough above uses the dynamic API (definitions loaded at runtime). The same operations are available through the static API (typed Python package generated by Kibo). See Value Chains for the contrast.