Skip to content
Back to top
View this page

# DSM Processing

This chapter covers loading and processing DSM (Digital Substrate Model) files in Python.

## The DSM Workflow

Processing DSM files follows three steps:

    ┌─────────────┐     ┌─────────────┐     ┌─────────────┐
    │  assemble   │ ──► │    parse    │ ──► │  introspect │
    │  (.dsm)     │     │  (validate) │     │   (visit)   │
    └─────────────┘     └─────────────┘     └─────────────┘

## Step 1: Assemble

`DSMBuilder.assemble()` reads DSM files. The path can be a single `.dsm` file or a directory containing `.dsm` files; in the directory case, every file is concatenated and parsed as a single unit. The doctests below reuse the bundled `Tuto` fixture builder pre-loaded by the test harness:

    >>> builder = _builder
    >>> [p.source().split('/')[-1] for p in builder.parts()]
    ['model.dsm']

In production code you would assemble from your own paths:

    >>> builder = DSMBuilder.assemble("model.dsm")

    >>> builder = DSMBuilder.assemble("models")

    >>> for part in builder.parts():
    ...     print(part.source())

## Step 2: Parse

`parse()` validates syntax and semantics, returning three values:

    >>> report, dsm_defs, defs = builder.parse()
    >>> report.has_error()
    False
    >>> type(dsm_defs).__name__
    'DSMDefinitions'
    >>> type(defs).__name__
    'DefinitionsConst'

Return Value | Type | Description
--- | --- | ---
`report` | `DSMParseReport` | Errors and warnings
`dsm_defs` | `DSMDefinitions` | Structured DSM data (or None if errors)
`defs` | `DefinitionsConst` | Runtime definitions (or None if errors)

### Handling Errors

    >>> report, dsm_defs, defs = builder.parse()
    >>> if report.has_error():
    ...     for error in report.errors():
    ...         print(f"{error.source()}:{error.line()}: {error.message()}")
    ... else:
    ...     print("Parse successful!")

## Step 3: DSM Introspection

`DSMDefinitions` provides structured access to inspect the parsed model.

### DSMDefinitions

The root container for all DSM elements:

    >>> dsm_defs.concepts()
    [Tuto::User]

    >>> sorted(str(s) for s in dsm_defs.structures())
    ['Tuto::Account', 'Tuto::Identity', 'Tuto::Login', 'Tuto::Texture', 'Tuto::Thumbnail']

    >>> dsm_defs.enumerations()
    [Tuto::Status]

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

### DSMConcept

Inspect concept definitions:

    >>> concept = dsm_defs.concepts()[0]
    >>> concept.type_name()
    Tuto::User

    >>> isinstance(concept.runtime_id(), ValueUUId)
    True

    >>> concept.documentation()
    'A user.'

    >>> concept.parent() is None
    True

For a model with concept inheritance, `parent()` returns the parent concept, e.g. `Tuto::Admin`.

### DSMStructure

Inspect structure definitions and their fields:

    >>> struct = [s for s in dsm_defs.structures() if str(s.type_name()) == 'Tuto::Login'][0]
    >>> struct.type_name()
    Tuto::Login

    >>> [(f.name(), str(f.type())) for f in struct.fields()]
    [('nickname', 'string'), ('password', 'string')]

    >>> struct.fields()[0].documentation()
    ''

### DSMEnumeration

Inspect enumeration definitions and their cases:

    >>> enum = dsm_defs.enumerations()[0]
    >>> enum.type_name()
    Tuto::Status

    >>> [case.name() for case in enum.members()]
    ['pending', 'active', 'completed']

The Tuto fixture ties `Status` into a struct used by the `account` attachment, so the enum can be exercised end-to-end through the runtime:

    >>> account = TUTO_A_USER_ACCOUNT.create_document()
    >>> account
    {state=.pending}

    >>> account.state = ValueEnumeration(TUTO_E_STATUS, "active")
    >>> account
    {state=.active}

### DSMAttachment

Inspect attachment definitions. `identifier()` returns the fully qualified name `<concept>.<attachment>`:

    >>> att = dsm_defs.attachments()[0]
    >>> att.identifier()
    'Tuto::User.login'

    >>> att.key_type()
    Tuto::User

    >>> att.document_type()
    Tuto::Login

### Generate DSM Text

Reconstruct DSM source from definitions. The output groups types by attachment and adds explanatory comments:

    >>> source = dsm_defs.to_dsm()
    >>> 'namespace Tuto' in source and 'concept User' in source
    True
    >>> 'attachment<User, Login> login' in source
    True

Pass `show_runtime_id=True` to append runtime IDs.

## Using Runtime Definitions

The `DefinitionsConst` from parse enables runtime operations.

### Inject Constants

`defs.inject()` makes generated constants available in the namespace. The `Tuto` constants are already in scope thanks to the doctest fixture:

    >>> TUTO_S_LOGIN
    Tuto::Login

    >>> TUTO_A_USER_LOGIN
    attachment<User, 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

### Query Types

    >>> types = defs.query_types("Login")
    >>> types
    [Tuto::Login]
    >>> Value.create(types[0])
    {nickname='', password=''}

### Access Attachments

    >>> for att in defs.attachments():
    ...     print(att.description())

## Serialization

DSMDefinitions can be serialized for distribution.

### Binary (DSMB)

    >>> blob = dsm_defs.encode()
    >>> blob
    blob(...)

    >>> restored = DSMDefinitions.decode(blob)
    >>> type(restored).__name__
    'DSMDefinitions'

### JSON

    >>> json_str = dsm_defs.json_encode()
    >>> 'concepts' in json_str and 'attachments' in json_str
    True

    >>> restored = DSMDefinitions.json_decode(json_str)
    >>> type(restored).__name__
    'DSMDefinitions'

## What’s Next

- Tutorial - Complete workflow using DSM with CommitDatabase

- Database - Persistence with Database and CommitDatabase

- Serialization - Binary and JSON encoding