Skip to main content

Python Database API

The Python Database API is currently in Beta. Methods, parameters, and behavior are subject to change as we refine the interface.

Overview

The Python Database API provides live read and write access to model objects from Davinci Python code. It is available through the global database object and the equivalent davinci.database alias. The API supports:
  • Searching model objects by name, type, fields, path, and graph relationships
  • Loading full objects or selected fields
  • Creating objects under a parent, including full nested trees
  • Updating editable fields on matching objects
  • Saving objects with create-or-update behavior
  • Deleting objects, optionally recursively
  • Reading and writing table cells with A1 notation
  • Adding and removing relationships between objects
  • Retrieving project-level configuration
The API is implemented in shared runtime code and is available in both Pyodide and backend execution.

Runtime Use

You can write code in either of these styles:
For stored Davinci code objects, prefer top-level await or result = await main(). Do not use asyncio.run(main()) there, because the runtime already provides an active event loop. Both the global database object and davinci.database point to the same API.

Object References

Anywhere an object reference is expected, bracketed UUID strings are the preferred style:
Plain UUID strings are still accepted for compatibility:
Named path references are also supported in many places:

Common Return Shape

When fields="common" is used (the default), objects are projected to a standardised set of fields. The exact fields depend on the object type. Base fields (all types): Type-specific fields included in common: resolvedValue is included as a read-only nested object representing the system-computed result:
  • For attribute/constraint/state: { value, error, errorMsg, equationString } — the inner value is the computed numeric or string result.
  • For task: { startDate, endDate, duration, progression, error, errorMsg }.

Writable Fields

database.update() and database.save() enforce a per-type allowlist. Attempting to write a read-only field raises an error. Writable on all types: name, shortName, documentation Guidance:
  • Use name for the human-readable object label
  • Use shortName for numbering, identifiers, diagram numbers, and compact codes
  • Example: name="Mission Analysis" and shortName="1.2.3"
Type-specific writable fields:

Methods

Search returns a list of matching objects.
Supported parameters:
  • path="Package.Part.Attribute" — dot-path resolution
  • type="attribute" or type=["attribute", "constraint"]
  • where={...} — field filters
  • graph={...} — graph filters
  • fields="common" or fields=[...]
  • limit=50 — max results (default 50, max 500)

where operators

Hierarchy search:

database.load(...)

Load returns full object data or selected fields. If no table cell selector is used:
  • one match returns a single object dict
  • multiple matches return a list
  • if the returned object is a table, it is a dict-like table object that also supports A1 reads such as await table["B2"]
Load by path:
Load by id:
Load reference data:

database.create(...)

Create is idempotent by default for named objects. If an object with the same parent + name + type already exists, that existing object is reused and updated instead of creating a duplicate with a new UUID.
Notes:
  • Use type, not objectType
  • parent is a top-level call argument on database.create(...), not a field inside each object
  • Good: await database.create(parent=package_id, objects=[{"type": "item", "name": "X"}])
  • Bad: await database.create(objects=[{"type": "item", "name": "X", "parent": package_id}])
  • If an object has a number/code/hierarchy label, put that in shortName
  • Example: {"type": "item", "name": "Mission Analysis", "shortName": "1.2.3"}
  • Writable fields may be supplied either directly on the object or under fields
  • Example: {"type": "item", "name": "X", "shortName": "1.2"}
  • Example: {"type": "item", "name": "X", "fields": {"shortName": "1.2"}}
  • If both are provided, values inside fields win
  • Nested creation is supported through children
  • Re-running the same payload under the same parent will generally reuse the same object IDs
  • Explicit id still wins when provided
  • For reference objects, this reuse applies to the metadata object. Separately stored reference file/blob content remains attached to the reused reference ID rather than creating a duplicate metadata object.

Creating a parts tree

The children key on any node is recursively processed. The entire tree is created in a single operation with all parent/child relationships wired automatically.
You can also build trees programmatically:

database.update(...)

Update applies field changes to matching objects. Only writable fields are accepted (see Writable Fields above). Important:
  • Use fields={...}, not updates={...}
  • target={"id": ...} expects a single id string, not a list of ids
Update by search target:

database.save(...)

Save performs create-or-update behavior and returns the saved objects. Default identity behavior:
  • If id is provided and exists, that object is updated
  • Otherwise, if an object with the same parent + name + type already exists, that object is reused and updated
  • Otherwise, a new object is created
  • For reference objects, the metadata object is reused by identity; separately stored reference content stays associated with the reused reference ID unless updated through the file-specific save path
  • parent is a top-level call argument on database.save(...), not a field inside each object
  • Good: await database.save(parent=package_id, objects=[{"type": "item", "name": "X"}])
  • Bad: await database.save(objects=[{"type": "item", "name": "X", "parent": package_id}])
  • Writable fields may be supplied either directly on the object or under fields
  • If both are provided, values inside fields win
Save new object under a parent:
Save by id:
Save by path:

database.delete(...)

Delete removes matching objects. Important:
  • target={"id": ...} expects a single id string
  • If you have many ids, do not pass a list into target.id; use another supported target form or issue separate batched deletes at a higher level
Recursive delete:

database.move(...)

Move re-parents existing objects into a different package or parent object. Use move(...) for hierarchy changes. Do not try to move objects by writing parent through database.update(...). Basic move:
Move a batch selected by search criteria:
Move a single object relative to a neighbour:
Important:
  • parent is required and is the destination parent/package
  • target={"id": ...} expects a single id string
  • neighbour ordering is currently for single-target moves only
  • return value is a list of moved objects
  • database.move(...) raises an error if the selector matches 0 objects instead of silently succeeding
  • when debugging a move, print the selected ids or names before the move call and compare them to the returned moved objects
Debug example:

database.relate(...)

Add or remove a relationship between two objects.
Standard relationship types include: uses, satisfies, verify, allocate, subject, realize, derive, trace, performs, actor, mitigation, source, impact, result, resource, dependency. Custom relationship types defined in the project are also accepted. Both source and target accept UUIDs or dot-path strings. They may also be arrays for batched relationship updates.
Batch relate from one source to many targets:
Batch relate many sources to many targets:
If both source and target are arrays, database.relate(...) applies the Cartesian product in one batched call. The return value is:
  • a single dict for one source + one target
  • a list of dicts for batched array input

database.get_project_info()

Returns project-level configuration as a dict.
Use this to discover what relationship types are defined in the current project:

Tables

Tables can be loaded and updated through the same database API.

Read cells with A1 notation

Cell queries return a 2D list (grid) for easy processing. Each entry is the cell content value. Read one cell:
You can also load the table object once and then use A1 indexing directly:
If the cell is empty, printing the result may show a blank line. That still means the read succeeded and the cell value is "". Read a range:
The same range can be read from the loaded table object:
Read a row:
Read a column:
Read the whole table:
If you need cell metadata (type, format, etc.) alongside content, pass cell_fields:
The loaded table object supports the same metadata reads:

Use table data to build model objects

Tables are a valid input source for model generation. A common pattern is:
  1. load a table range with A1 notation
  2. transform the rows into object payloads
  3. create or save the objects in one batched call
If a cell contains newline-delimited text, split it normally in Python:

Read table structure

The common projection for a table now includes rowCount and colCount directly:

Update cells

Update a single cell:
Update a range with a 2D list:

References

Reference loading works through database.load(..., include_reference_data=True).
Reference creation is still primarily done through davinci_save_file(...). A database-native reference save method is planned.

Current Notes and Constraints

  • The API is async. Use await.
  • fields="common" is the default and returns the standardised field set described above.
  • parent for create/save is a string reference, not {"id": ...}.
  • Both "uuid" and "[uuid]" forms are accepted where object references are expected.
  • Path-based loads hydrate the full object before projection, so nested fields like rows and columns work when explicitly requested.
  • Database writes are validated and applied through the shared operations pipeline.
  • Attempting to write a read-only field raises: Field 'resolvedValue' is read-only and cannot be updated on type 'attribute'.

Quick Examples

Search by name:
Create a full parts tree in one call:
Read attribute value and its resolved result:
Link two objects with a relationship:
Read a table as a 2D grid:
Update an existing attribute:
Delete a created object:
Discover relationship types defined in the project: