# Python You defined a data model in DSM and ran Kibo over it. What you got back — and what you work against every day — is a small typed Python package named after your model. You almost never call the `dsviper` runtime directly: you import two of its modules and let them delegate to the runtime for you. This page is that daily surface, end to end. For *why* the generated code exists (the adapter/Dual Reality pattern), see {doc}`../kibo-template-viper/index`; for the raw runtime this layer sits on, see the {doc}`dsviper for Python <../dsviper-python/index>` chapter — starting at {doc}`../dsviper-python/types_values`. ## Anatomy of a generated package A generated package — say `model/` — has a handful of modules: ```text model/ ├── data.py ← the types: one class per concept, struct, enum, container ├── attachments.py ← the verbs on a commit state (get / set / enumerate) ├── database_attachments.py ← the same verbs for a standard (non-commit) Database ├── definitions.py ← the runtime type catalog (plumbing — you rarely import this) ├── value_type.py ← Viper type handles used by data.py / attachments.py (plumbing) ├── path.py ← field paths used by the fine-grained setters (plumbing) ├── resources.py ← the packed definitions blob (plumbing) └── __init__.py ← re-exports data.py ``` A model that declares **function pools** also gets `function_pools.py` (and RPC variants). This guide uses the versioned **commit database** throughout; `database_attachments.py` is the twin of `attachments.py` for the plain, non-versioned `Database` store — the same verbs, called directly on a `Database` instead of a commit state (see {doc}`../dsviper-python/database`). Every file carries this header — it is generated, and regenerated on every model change: ```python # Generated by kibo from . Do not edit by hand. ``` Treat the package as a build artifact: never hand-edit it, never commit a local tweak to it — the next generation overwrites you. If something in the generated code is wrong, the fix belongs in the **DSM model** or the **template**, not the output. ### The two modules you actually import ```python import model.data as md # the types import model.attachments as ma # the verbs ``` Everything else is plumbing these two lean on. (`attachments` re-exports `data`, so a symbol like `Tuto_UserKey` is reachable through either — but importing both under `md` / `ma` keeps *types* and *verbs* legible at the call site.) A first program touches nothing but `md`, `ma`, and a `CommitDatabase`: ```python from dsviper import CommitDatabase, CommitStateBuilder, CommitMutableState import model.data as md import model.attachments as ma db = CommitDatabase.open("model.cdb") # a typed key + a typed struct — from `data`, never a raw dsviper.Value user = md.Tuto_UserKey.create() login = md.Tuto_Login() login.nickname = "alice" # a mutation verb — from `attachments`, applied to the mutating state state = CommitMutableState(CommitStateBuilder.initial_state(db)) ma.tuto_user_login_set(state.attachment_mutating(), user, login) db.commit_mutations("Add user", state) ``` That is the whole shape of daily work: **types come from `data`, changes go through `attachments`, the commit loop lives on `CommitDatabase`.** The rest of this chapter is those three ideas in detail. ### Where each name comes from The generated names are mechanical — which is what makes a stack trace legible (see [Reading a repro](#reading-a-repro)): | You see | It came from | DSM | |---|---|---| | `md.Tuto_UserKey` | a **concept** | `concept User;` in `namespace Tuto` | | `md.Tuto_Login`, `.nickname` | a **struct** and its field | `struct Login { string nickname; … }` | | `md.Tuto_Status.ACTIVE` | an **enum** case | `enum Status { …, active, … }` | | `ma.tuto_user_login_set` | an **attachment** verb | `attachment login;` | The prefix is always `_` for types and `___` for verbs — so a symbol in an error message points straight back to a line of DSM. ## The proxy posture The generated classes — `Tuto_UserKey`, `Tuto_Login`, `Set_Tuto_UserKey` — are **proxies**. It is tempting to treat them as ordinary Python objects and drop them into ordinary Python containers. Don't. Understanding *why* is the difference between code that fails at the mistake and code that fails three layers away. ### One value, two realities Viper is a **strongly-typed** data model. The type catalog, built from your DSM at runtime, is the single source of truth — a value either conforms to its type or it is rejected, at the moment it is built. That safety lives in the runtime. Python is a **permissive** environment. `set[Tuto_UserKey]` is a *hint*: at runtime it is `set[object]`, and nothing stops you adding a string to it. A proxy is how the runtime's strong typing is expressed *inside* Python's permissive one — the same value seen two ways (the {term}`Dual Reality`). The proxy does not *add* type safety; the safety already lives in the runtime value. The proxy makes it visible and ergonomic at edit time. ### What `vpr_value` is Every proxy holds exactly one runtime value in an attribute named `vpr_value`: ```python user = md.Tuto_UserKey.create() user.vpr_value # the underlying dsviper.ValueKey — the runtime reality ``` `vpr_value` is the **seam** between the two realities. The generated code reaches through it to delegate every operation to the runtime — that is its job. It is an implementation detail of the proxy, **not an interchange format for your code.** When you find yourself writing `.vpr_value` in your own logic, it is a signal that you dropped the proxy posture somewhere upstream and are now patching the boundary by hand. ### Native container vs. proxy container The two look interchangeable and are not: ```{doctest} >>> a = md.Tuto_UserKey.create() >>> raw = {a} # to the type system this is set[object]… >>> raw.add("this is a bug") # …so this is silently accepted — a deferred bug >>> strict = md.Set_Tuto_UserKey() # a set of Tuto_UserKey, enforced at insertion >>> strict.add(a) >>> strict.add("this is a bug") # rejected here, at the mistake — fail-fast Traceback (most recent call last): ... AttributeError: ... ``` The native set carries anything; the annotation is a comment. The proxy set delegates each `add` to the runtime through `vpr_value`, so a wrong element is rejected *at the line that inserts it*, with a real stack frame. Fail-fast is not a side effect — it is the whole point of holding the proxy. ### The request that looks reasonable and isn't A recurring ask: *"let the container constructor accept a native set of proxies, so I can skip the unwrapping."* Given `attachment> bbb;`, the wish is: ```python def generate() -> set[Foo_BKey]: # really set[object] return {Foo_BKey.create(), Foo_BKey.create()} data = md.Set_Foo_BKey(generate()) # convenience wanted # instead of today's: data = md.Set_Foo_BKey({b.vpr_value for b in generate()}) ``` It will not be added, on purpose. The constructor already accepts native **scalars and PODs** — that is Viper's seamless decode, a genuine input boundary (`md.Set_uint32({1, 2, 3})` works because integers *are* their own runtime form). But a set of proxies is not a bag of PODs; it is a bag of Developer-Reality objects whose only runtime-meaningful content is hidden behind `vpr_value`. Teaching the constructor to peek into those objects would bless `set[object]`-of-proxies as an interchange format — exactly the permissive, deferred-bug posture the proxies exist to replace. The manual `{b.vpr_value for b in …}` is not a missing convenience; it is the friction that tells you the value was produced in the wrong reality. ### The rule: fix the output, not the boundary **The problem is never the input — it is your output.** Don't produce a native container and reconcile it at the edge; produce the typed container where the value is *created*: ```python def generate() -> md.Set_Foo_BKey: # a set of Foo_BKey, enforced result = md.Set_Foo_BKey() result.add(Foo_BKey.create()) result.add(Foo_BKey.create()) return result data = generate() # no unwrapping, no boundary ``` Pick the all-proxy posture and stay in it. The generated SDK expresses the runtime's type discipline; native Python objects suspend it. Mixing the two does not buy convenience — it defers the moment a wrong value is caught from the line that created it to some later line that cannot explain it. ## Living in the proxies Everything in `data` follows a few shapes. Once you know them, a new model reads itself. ### Keys — concepts and clubs A concept or club becomes a `…Key` proxy. A key is an identity, not data: ```{doctest} >>> user = md.Tuto_UserKey.create() # a fresh identity >>> user.description() # ":Tuto::UserKey" — human-readable '...:Tuto::UserKey' >>> user == user # identity comparison, via the runtime True ``` Keys are hashable and orderable (through `vpr_value`), so a `Set_Tuto_UserKey` or a `Map_Tuto_UserKey_…` keyed by them behaves as you expect. ### Structs — fields as properties A struct becomes a proxy whose fields are properties: read and assign them directly, and the runtime type-checks each assignment: ```{doctest} >>> login = md.Tuto_Login() >>> login.nickname = "alice" >>> login.password = "s3cret" >>> login.nickname 'alice' >>> login.nickname = 42 # rejected by the runtime — wrong type Traceback (most recent call last): ... dsviper.ViperError ``` (`login.copy()` returns a deep, independent copy.) ### Enums — cases as attributes An enum becomes a proxy with one attribute per case (upper-cased): ```{doctest} >>> account = md.Tuto_Account() >>> account.state = md.Tuto_Status.ACTIVE >>> account.state.name() 'active' >>> md.Tuto_Status.from_str("pending").name() # parse from a name 'pending' ``` ### Containers — Pythonic, but still typed `vector` / `set` / `map` / `xarray` / `optional` become container proxies with the idiomatic Python surface — iteration, `in`, indexing, `len`, set operators — all delegating to the runtime: ```{doctest} >>> keys = md.Set_Tuto_UserKey() >>> keys.add(md.Tuto_UserKey.create()) >>> keys.add(md.Tuto_UserKey.create()) >>> len(keys) 2 >>> all(isinstance(k, md.Tuto_UserKey) for k in keys) # iteration yields proxies True ``` The one rule from [the proxy posture](#the-proxy-posture) applies throughout: **elements are proxies too.** Build and pass the typed container; never assemble a native `set`/`list` of proxies and unwrap it at the boundary. ## Mutating: calling an attachment verb Data does not live *in* a key — it hangs off it as **attachments**. You read and write attachments through the generated free functions in `attachments`, against a **state**. A state is a point in the commit DAG (see {doc}`../commit/index` for the model); here is the loop. ### Open a state ```python db = CommitDatabase.open("model.cdb") # a fresh, empty state: state = CommitMutableState(CommitStateBuilder.initial_state(db)) # …or the latest committed state of an existing database: state = CommitMutableState(CommitStateBuilder.state(db, db.last_commit_id())) ``` ### Write — through `attachment_mutating()`, then commit Setters take an `AttachmentMutating`; nothing is persisted until you commit the state. Here is the full loop against a fresh in-memory database — a real app opens a `.cdb` instead, but the shape is identical (the generated `definitions` module seeds the schema): ```{doctest} >>> from dsviper import CommitDatabase, CommitStateBuilder, CommitMutableState >>> import model.definitions as mdef >>> db = CommitDatabase.create_in_memory() >>> _ = db.extend_definitions(mdef.definitions()) >>> user = md.Tuto_UserKey.create() >>> login = md.Tuto_Login() >>> login.nickname = "alice" >>> state = CommitMutableState(CommitStateBuilder.initial_state(db)) >>> ma.tuto_user_login_set(state.attachment_mutating(), user, login) >>> ma.tuto_user_login_set_nickname(state.attachment_mutating(), user, "alice2") # fine-grained field set >>> _ = db.commit_mutations("Register alice", state) # one typed commit ``` ### Read — through `attachment_getting()` Every attachment gives you `…_get`, `…_keys`, `…_enumerate`, each taking an `AttachmentGetting` drawn from a state: ```{doctest} >>> read = CommitMutableState(CommitStateBuilder.state(db, db.last_commit_id())) >>> getting = read.attachment_getting() >>> len(ma.tuto_user_login_keys(getting)) # Set_Tuto_UserKey 1 >>> ma.tuto_user_login_get(getting, user).unwrap().nickname # Optional_Tuto_Login 'alice2' >>> [login.nickname for _key, login in ma.tuto_user_login_enumerate(getting)] ['alice2'] ``` The mental model is uniform: **a verb is a free function taking a getting/mutating handle, a proxy key, and (for setters) a proxy value.** Collection-valued attachments add the expected verbs — `…_union_`, `…_subtract_`, `…_insert_`, `…_remove_` — each mutating in place through the same state. The commit itself, and everything about heads, history and merges, is the {doc}`../commit/index` subsystem's job, not the SDK's. ## Reading a repro Because every generated name is mechanical, an error or a bug report written in generated symbols maps straight back to your model — no guessing. Given a traceback that mentions `ma.tuto_user_login_set` or `md.Tuto_LoginKey`, read the name: - **`Tuto_Login`** → type `Login` in `namespace Tuto` → `data.py`, and the DSM line `struct Login { … }`. - **`tuto_user_login_set`** → `___` → namespace `Tuto`, key concept `User`, attachment `login`, verb `set` → the DSM line `attachment login;`, and the function in `attachments.py`. So a report like *"`tuto_user_login_set` raises on this input"* localises itself: the type is `Tuto_Login`, the attachment is `login`, the code is the generated `attachments.py`. From there: 1. The generated file says **"Do not edit by hand."** The defect is upstream — in the DSM model, or in the template that produced the file — never a patch to the output. 2. Regenerate after any model change (see {doc}`../dsviper-tools/dsm_util`); the package is a build artifact and a stale one will lie about the model. That legibility is the payoff of the proxy layer: the names you debug are the names you declared.