R, Python and command-line API¶
yamaa has the same small API in R and Python. Use it to create, verify and save SDTM and ADaM datasets and define.xml files. Both packages run the same Rust engine, so the same files give the same results, printed in the same words, provided that host-specific project functions are equivalent. Each language calls its own implementation, and the function file's tests check both on the same cases (Project configuration). The command line runs the same engine from a shell, except for project functions.
Quick reference¶
| Task | R | Python |
|---|---|---|
| Open a specification or a project | yamaa_spec(path), yamaa_project(path) |
yamaa.spec(path), yamaa.project(path) |
| One specification of a project | project$spec("DM") |
project.spec("DM") |
| A project's specifications | project$names |
project.names |
| Check without data | spec$check()$issues |
spec.check().issues |
| Build one dataset in memory | run <- spec$run() |
run = spec.run() |
| Did it succeed? | run$status |
run.status |
| Save it, and get the paths written | paths <- run$save() |
paths = run.save() |
| Build every SDTM dataset | project$run("sdtm")$save() |
project.run("sdtm").save() |
| One dataset of a project run | runs[["ADSL"]]$output |
runs["ADSL"].output |
| Run report | run$report |
run.report |
| A table in Polars or pandas | run$output is a data frame |
run.output.to_polars(), run.output.to_pandas() |
| Read an output as a stream | spec$run(stream = TRUE)$output |
spec.run(stream=True).output |
| Follow a run, and see where it spent its time | spec$run(progress = print)$timings |
spec.run(progress=print).timings |
| define.xml and transport files | project$submission("sdtm")$save() |
project.submission("sdtm").save() |
| See the resolved specification | spec$resolve() |
spec.resolve() |
| Read ODM XML | yamaa_read_odm(path) |
yamaa.read_odm(path) |
| Lint, and fix the layout | yamaa_lint(paths, fix = TRUE) |
yamaa.lint(paths, fix=True) |
| JSON Schema of a file type | yamaa_schema("spec") |
yamaa.schema("spec") |
| Conditions and their history | yamaa_conditions() |
yamaa.conditions() |
| Engine version and platform | yamaa_info() |
yamaa.info() |
| Check an installation | yamaa_selftest() |
yamaa.selftest() |
| Run a command line as this host | yamaa_cli(arguments) |
yamaa.cli(arguments), python -m yamaa |
Examples in R and Python are shown in tabs, R first; pick a language once and every tab on the page follows. From 1.0.0, every 1.x release keeps the functions, arguments, members and printed forms on this page, as the stability contract says.
Input files¶
Every yamaa file is YAML whose first two fields are yamaa, the oldest
language version the file needs, always quoted, as in yamaa: "1.0", and
type, which names one of four file types.
- A specification (
type: spec) describes one dataset: its inputs, keys, columns, checks andoutput. - A project (
type: project) describes a study. It lists the specifications of each section,sdtm,adamorsend, the function and codelists files they share, the lock of each host language, and the metadata define.xml needs. - A function file (
type: function) declares one project function: its parameters, return type and tests, and the callable that implements it in each host language. - A codelists file (
type: codelists) holds the codelists of one terminology source (Codelists and terminology).
An engine reads a file that declares its own language version or an older 1.x
one, and refuses a newer file as unsupported_language_version, naming the
version the file needs. The schema fields
list every field of each file type.
We recommend using AI to help you create these files. Human review and sign-off are necessary steps.
Dataset specification¶
For example, sdtm/dm.yaml builds SDTM DM, one row per subject, from the
study's ODM XML export (Read ODM XML).
# sdtm/dm.yaml
yamaa: "1.0"
type: spec
model: SDTM
domain: DM
keys: [STUDYID, USUBJID]
input:
ODM: ../edc/odm.xml
output:
path: dm.parquet
columns: [STUDYID, DOMAIN, USUBJID, SUBJID, AGE, AGEU, SEX]
report: dm-report.parquet
columns:
- name: STUDYID
type: str
label: Study Identifier
derivation: ODM.StudyOID
- name: DOMAIN
type: str
label: Domain Abbreviation
derivation: "'DM'"
- name: USUBJID
type: str
label: Unique Subject Identifier
derivation: {str_template: "{ODM.StudyOID}-{ODM.SubjectKey}"}
- name: SUBJID
type: str
label: Subject Identifier for the Study
derivation: ODM.SubjectKey
# An age outside 18 to 85 is listed in the report and does not stop the run.
- name: AGE
type: int
label: Age
derivation: {odm: ODM.IT.DM.AGE}
verifications:
- range: {min: 18, max: 85, severity: warning}
- name: AGEU
type: str
label: Age Units
derivation:
case:
- when: "AGE IS NOT NULL"
then: "'YEARS'"
- otherwise: null
# define.xml lists each origin of a column.
- name: SEX
type: str
label: Sex
derivation: {odm: ODM.IT.DM.SEX}
submission:
origins:
- type: Collected
source: Investigator
model names the CDISC model, SDTM, ADaM or SEND; a specification
that declares submission metadata needs it. keys identify the rows, and
each column's derivation says how its value is made. A column's
submission.origins is a list, since define.xml can give a variable several
origins.
yamaa_spec()andyamaa.spec()point to a specification file and read nothing yet.run()builds the dataset in memory. The firstrun()compiles the specification and keeps it. A laterrun()of the same object reuses it if its paths still name the same files, even after a change of working directory, and every file it was compiled from is unchanged. Otherwise it compiles again. Everyrun()reads the input data afresh. A copied, pickled or restored object starts with nothing kept.save()writes the run report tooutput.report, then the dataset tooutput.path, and returns the absolute paths it wrote, in that order (R returns them invisibly). A failed run writes only its run report, then raisesyamaa.Error(in R, signalsyamaa_error) with the run's issues; the error'spathslists what the save wrote first.
spec <- yamaa_spec("sdtm/dm.yaml")
run <- spec$run()
run$status # "succeeded" or "failed"
run$output # generated dataset
run$issues # identified errors
run$report # run report
paths <- run$save() # save run report and dataset
spec = yamaa.spec("sdtm/dm.yaml")
run = spec.run()
run.status # "succeeded" or "failed"
run.output # generated dataset
run.issues # identified errors
run.report # run report
paths = run.save() # save run report and dataset
Python operation refusals use the native yamaa.Error class. Handle and state
refusals use its yamaa.StateError subclass; R uses yamaa_error and its
yamaa_state_error subclass. Each carries issues and paths, with an empty
issues table when no diagnostic explains the refusal, including an R refusal
whose original diagnostics could not be converted to a data frame.
Tables¶
Python yamaa.Table and yamaa.Error objects can be pickled and sent between
processes. Tables keep their column types, missing values and list columns;
errors keep their message, issues, written paths and chained cause. As with
ordinary Python exceptions, the cause and any added state must be picklable.
An unpicklable callback exception raises its normal pickle error when the
refusal is pickled; the original cause stays attached. Pickling an unread streamed
output reads it whole, as to_pylist() does. An output stream already taken by
a reader keeps its usual refusal on a whole-table read.
Every table that yamaa returns is a data frame in R, except the output of a
run read as a stream (Read an output as a stream).
In Python it is a yamaa.Table, which needs no other package: printing it
shows its column names and types and its first and last rows, len() and
num_rows count its rows, column_names names its columns, and to_pylist()
gives one dict per row. to_polars(), to_pandas() and to_arrow() convert
it to the table library you use, which must be installed: Polars, pandas with
PyArrow, or PyArrow. A library that reads the Arrow PyCapsule interface, such
as polars.DataFrame(table) or pyarrow.table(table), reads it directly.
| Column type | R | Python value | Arrow |
|---|---|---|---|
str |
character | str |
Utf8 |
int |
double; an error beyond 2^53 | int |
Int64 |
float |
double | float |
Float64 |
date |
Date |
datetime.date |
Date32 |
datetime |
POSIXct in UTC |
datetime.datetime, without a time zone |
Timestamp, in seconds |
time |
hms, whole seconds since midnight |
datetime.time, without a time zone |
Time32, in seconds |
list, in issues and conditions() |
list column | list |
List |
A missing value is NA in R, NULL for a missing list, and None in
Python. to_pandas() keeps each column's Arrow type as a pandas.ArrowDtype,
so an integer column with a missing value keeps its exact integers.
dm <- run$output
nrow(dm)
dm = run.output # prints a preview
dm.num_rows
rows = dm.to_pylist() # [{"STUDYID": "STUDY01", ...}, ...]
frame = dm.to_polars() # or dm.to_pandas(), dm.to_arrow()
Supply in-memory inputs¶
Python can replace declared input files by alias for one run:
run = yamaa.spec("adam/adsl.yaml").run(inputs={"DM": frame})
frame exports the Arrow PyCapsule stream protocol, as PyArrow and Polars
frames do; a pandas data frame is converted through PyArrow, with its index
excluded. Values are copied into an owned snapshot during the call, including
for a deferred stream. Aliases may come from a parent layer. Other inputs,
metadata and save targets keep reading and writing their files. Declared input
types still apply, and the report identifies these inputs as memory:<alias>
with no byte count. An unknown alias fails the run before study data is read.
Frames admit UTF-8 strings, 64-bit integers and floats, date32, zone-free
second timestamps and second time32 values. Convert other physical types preserving their values before
exporting; an unsupported type raises yamaa.Error with an input diagnostic.
R takes the same alias overrides as a named list of data frames:
run <- yamaa_spec("adam/adsl.yaml")$run(inputs = list(DM = frame))
R characters and factors become UTF-8 strings, integers become 64-bit integers,
numeric vectors become floats, Date values become whole days, POSIXct
values become whole UTC seconds and hms values become times of day. Missing
values stay missing. Fractional dates, timestamps or times are refused. No R Arrow package is needed for input conversion.
Project runs and the command line continue reading files.
Read an output as a stream¶
run(stream = TRUE) in R and run(stream=True) in Python build a dataset as
its output is read. The output's whole table, which a run without stream
keeps, is never built; the run still holds the accepted rows that each batch
is built from. The call reads no study data and runs no project code. The
output is an Arrow stream: in Python a yamaa.Table, which a library that
reads the Arrow PyCapsule stream interface pulls batch by batch, such as
pyarrow.RecordBatchReader.from_stream(run.output) or
polars.DataFrame(run.output); in R an Arrow C stream, an external pointer of
class nanoarrow_array_stream, which nanoarrow and arrow read, such as
arrow::RecordBatchReader$import_from_c(run$output).
The first batch builds the run through every check and the declared order, so
it exists only once the whole output is accepted. Each later batch, of at most
65,536 rows, is made as it is read and not kept. The batches hold the values,
order and column types of the table a run without stream returns, and the
run's save() writes the same files.
Once the stream has ended, status, issues, report, save() and printing
give the run's result. A run that fails ends the stream with an error that
names its first issue, and it then holds its issues and saves only its report
file. Before anything reads the output, asking for the result runs the
dataset whole, as a run without stream does, and the output is then the
table it keeps, which can be read as often as you like. In Python, len(),
num_rows, printing, to_pylist() and the to_*() conversions of the
output read it whole in the same way.
A stream is read once. Reading it again, or asking for the result while a
reader has not reached its end, raises yamaa.StateError in Python and signals
yamaa_error in R. Stopping before the last batch, by dropping the reader or
with Ctrl+C, cancels the run: nothing more is built, and the run has no
result, so it saves nothing, and asking for its result reports cannot read
<dataset>: operation cancelled. Through another library's reader, Ctrl+C, or
a project function's KeyboardInterrupt in Python and interrupt in R, ends
the stream with that library's error. A run whose project functions run in
the host is built only on the thread that called run(). Read on another
thread, its stream refuses the read with that library's error, whose message
is cannot read <dataset>: a run that can call project functions is built only
on the thread that started it [host_thread]. The refusal holds no issue and
fails nothing: the thread that called run() can still read the stream.
run <- yamaa_spec("adam/adlb.yaml")$run(stream = TRUE)
reader <- arrow::RecordBatchReader$import_from_c(run$output)
while (!is.null(batch <- reader$read_next_batch())) {
# write or summarize the batch
}
run$status
run$save()
import pyarrow as pa
run = yamaa.spec("adam/adlb.yaml").run(stream=True)
for batch in pa.RecordBatchReader.from_stream(run.output):
... # write or summarize the batch
run.status
run.save()
The command line saves whole runs; it has no streaming flag.
Progress and timings¶
check(), run() and submission() take a progress function, called
with one event as each phase starts and as it ends. An event is a named list
in R and a dict in Python, with name, phase, kind, outcome, number
and rows:
| Field | Value |
|---|---|
name |
The specification's path as given; a project run member's name; a submission's section |
phase |
preparation, activation, input, admission, derivation, output_keys, verification, output or composition |
kind |
start or end of the phase; partition for each partition of its input a partitioned run derives, in order, in derivation, as the run enters it or, with partitions deriving at once on several threads, as it takes the partition's work back; batch as a streamed run hands out a batch of its output, after output |
outcome |
For an end, succeeded or failed; otherwise missing |
number, rows |
For a partition, its number from 1 and its records; for a batch, its number and its rows; otherwise missing |
A run passes the phases from preparation to output in that order, with
activation only when its specification calls a project function; a run of
a plan an earlier run() of the object kept starts at input. A check is
one preparation of each specification it checks, and a submission one
composition of each section. A project run reports its specifications in
run order, each one's events together; with specifications building at once
on several threads, a specification's events arrive once it has built. The
function runs on the thread that called
the method, so it can show progress, and it may call yamaa itself, on the
same object too. In Python, a call releases the global interpreter lock while
the engine works and takes it again to call the function or a project
function, so other threads run meanwhile: two threads can run two
specifications at once.
A progress function that signals an error in R or raises in Python is
called no more: the method finishes its work, and its result then fails with
one issue, engine_rejected with the code host_progress_failure. Its
context includes host_error and host_message, and its message names the
exception. A refused save chains the original exception as Python's
__cause__ or R's parent condition. A failed
run keeps no output and saves nothing; a project run builds no later
specification, and a submission composes no later section, and either
refuses its save. An interrupt in the function cancels the call, as Ctrl+C
does (Interrupts).
Every check, run, project run, member of a project run and submission has
timings: a table of each phase it ran, in the order the phases started,
with the columns name, phase, started_ms (from the start of the call),
elapsed_ms and outcome. Timings are facts of one call, so no saved file
holds them: the saved output, run report, Dataset-JSON and define.xml are the
same with or without them. The command line prints both under --verbose
(Output).
run <- yamaa_spec("adam/adlb.yaml")$run(progress = function(event) {
if (identical(event$kind, "start")) message(event$phase, " ...")
})
run$timings
def report(event):
if event["kind"] == "start":
print(event["phase"], "...")
run = yamaa.spec("adam/adlb.yaml").run(progress=report)
run.timings.to_polars()
Check a specification without data¶
check() reports problems in a specification without reading study data or
running project code. It does not open the input datasets, so some problems,
such as a column name that no input has, appear only when you call run().
The pattern of a str_extract or str_replace is one of them when only the
data gives its source's type, such as a field of a Parquet, SAS or ODM input
that types does not declare: run() then reports either the source's type
or the pattern.
Each problem is one row of issues: its severity, phase and
condition, the rule it breaks, the spec_paths it concerns, a message
to read, and its facts in context as JSON text. Its file, line and
column say where to edit: the YAML file that wrote the first spec path,
which is the parent layer when the specification inherits that value, and
the line and column of that entry. A problem with no place in a YAML file,
such as a project section that does not exist, leaves them missing.
yamaa_spec("sdtm/dm.yaml")$check()$issues
yamaa.spec("sdtm/dm.yaml").check().issues
context is canonical JSON text (RFC 8785): one object per issue, such as
{"actual":"int","expected":"str","source":"SRC.NUM"}, whose keys depend on
the condition. Parse it with json.loads in Python and jsonlite::fromJSON
in R, and read it by key, since a release may add keys. jsonlite is in the R
package's Suggests, so install it if you have not; it reads an integer beyond
2^53 as the nearest double unless you pass bigint_as_char = TRUE.
issues <- yamaa_spec("sdtm/dm.yaml")$check()$issues
# A named list per issue; an integer beyond 2^53 stays text.
contexts <- lapply(issues$context, jsonlite::fromJSON,
bigint_as_char = TRUE)
import json
issues = yamaa.spec("sdtm/dm.yaml").check().issues
contexts = [json.loads(row["context"]) for row in issues.to_pylist()] # a dict per issue
The column is text, not a nested value, so that a host needs no JSON library
to give it to you (#3030 D28). A nested value would not reach you whole
either: an R table refuses an integer beyond 2^53 (Tables), which
a context may hold; and Arrow, through which a Python table converts to
Polars, pandas or PyArrow, holds JSON only as text, so those libraries would
receive text anyway. The command line's --format json gives the same facts
as an object (JSON documents).
A fragment, a specification that a layer lists under include, names no
template of its own, so a check of the fragment alone fails at the first
value its template would give. check(parent = ) checks it as if the
template you name were its parent, before any study file lists it: the
fragment composes as it would in a file that names that template as its
parent, lists the fragment under include and writes nothing else, and the
check reports what that file's check would report. Each issue is located in
the fragment or in a layer of the template. parent is a path you give, like
the specification's own: one that cannot be read is an issue that names it
as you gave it, at no field. A specification that declares parent or
include is no fragment, and a parent given for it raises yamaa.Error,
as value_not_permitted at parent
(LAYER-4). run(), resolve() and a
project's check() take no parent.
yamaa_spec("fragments/ae-duration.yaml")$check(parent = "templates/adae.yaml")$issues
yamaa.spec("fragments/ae-duration.yaml").check(parent="templates/adae.yaml").issues
Status and printed results¶
Every result has a status. A check succeeds when it finds no issue, and a
run when it builds its dataset with no issue. A project run succeeds when
every specification in it succeeds, and a submission when every section
composes with no issue; anything else is failed. A specification of a
project run that did not run, because a specification it reads from did not
complete, is skipped.
Printing a result shows its status, its size, its issue count and its first issue with its location. The engine writes the text, so R, Python and the command line print the same words. A project run lists each specification on a line of its own, and one of its runs prints like any run:
runs <- yamaa_project("project.yaml")$run()
runs$status # "succeeded"
runs # the summary
runs[["ADSL"]] # one specification's run
runs = yamaa.project("project.yaml").run()
runs.status # "succeeded"
print(runs) # the summary
print(runs["ADSL"])
In a study whose ADAE fails, so that ADTTE, which reads ADAE, is skipped, the
run of its adam section and ADTTE's run print:
yamaa project run of project.yaml, section adam: failed, 3 specifications, 2 issues
ADSL succeeded 254 rows x 48 columns, 0 issues
ADAE failed no output, 1 issue
ADTTE skipped no output, 1 issue
first issue: adam/adae.yaml:57:9: column ASTDY reads TRTSDT, which is declared after it [forward_reference, COLUMN-5]
yamaa run of ADTTE: skipped, no output, 1 issue
first issue: adam/adtte.yaml:9:5: this specification did not run because the producer of its input ADAE did not complete [producer_not_completed, PROJECT-11]
yamaa_spec(), yamaa.spec() and the project functions read nothing, so
their objects print only the path you gave, such as yamaa spec
sdtm/dm.yaml. A check prints yamaa check of <path> with its status and
issue count, and a submission counts its define.xml and transport files.
In R, every result also has S3 methods: print() writes the lines and
returns the result invisibly, format() returns them, names() lists the
result's fields and methods, starting with status, as names(run) gives
"status" "output" "issues" "report" "timings" "save", and length() counts
them. A project run's names() are its specifications, as $names gives
them. In Python, repr() and str() return the printed text.
The labels of each printed line, their order and separators, the layout of
the first issue: line and of a project run's member lines, and a table
preview's yamaa.Table: header are frozen from 1.0.0. The messages are not: a
release may improve them, so a program reads status, and an issue's
condition and rule, never the printed text.
Errors¶
check() and run() return problems as values. A call that cannot complete,
such as a save() of a failed run, a resolve() of a specification that
cannot be composed, or a name the project does not have, raises yamaa.Error
in Python or signals a yamaa_error condition in R. Its issues hold every
issue, in the columns of the issues table, and its paths list the files a
refused save wrote first. Its message says what the call could not do, then
gives the first issue with its location and counts the others:
cannot save ADAE: adam/adae.yaml:57:9: column ASTDY reads TRTSDT, which is declared after it [forward_reference, COLUMN-5] (and 2 more issues)
The message starts with cannot save, cannot resolve, cannot select,
cannot read, cannot lint or cannot give the schema of, and what the
call named.
result <- tryCatch(
yamaa_project("project.yaml")$run("send"),
yamaa_error = function(error) error
)
conditionMessage(result)
result$issues
try:
yamaa.project("project.yaml").run("send")
except yamaa.Error as error:
print(error)
error.issues
See the resolved specification¶
resolve() returns the specification after its layers are composed: the
complete normalized model, where each field came from, and where each resource
is. A producing specification linked through schema is a resource of its
consumer, not a separate entry. R returns a named list and Python a dict. It
reads no study data and runs no project code. Each entry records its
language_version, the newest version any of its layers declares in
yamaa.
Each entry's origins maps a JSON pointer into the model to where that value
came from. A value a layer wrote names that layer as declaring_source, with
the line and column where the layer wrote it, counted from 1, as an
issue's file, line and column are. end_line and end_column give the
exclusive end of the written node. Earlier version 1 artifacts may omit the
end pair, or all location fields. A schema default names its schema
module and no location, and a value composition made, such as the model's
root, names neither.
Each entry's pruned lists what composition wrote that the resolved model no
longer holds, since nothing the artifact needs reads it
(LAYER-11): each pruned input, named
intermediate, column or row template's derivation by its spec_path, then
each value inside it that another layer wrote, such as a label a study layer
gives a template column it leaves out of output.columns. Each names its
declaring_source with the line, column, end_line and end_column
where that layer wrote it. lint() reports, as unused_declaration, only
what the linted file itself writes; pruned also shows what it inherits.
Artifacts written before 1.0.0 have no pruned.
resolved <- yamaa_spec("sdtm/dm.yaml")$resolve()
names(resolved)
resolved = yamaa.spec("sdtm/dm.yaml").resolve()
resolved.keys()
Project configuration¶
A project file lists each section's specifications in specs, with what they
share:
# project.yaml
yamaa: "1.0"
type: project
functions: [functions/bmi.yaml]
environments:
python: {lock: uv.lock}
r: {lock: renv.lock}
study:
id: STUDY01
name: STUDY01
description: An example study
protocol_name: STUDY01
sdtm:
specs: [sdtm/dm.yaml]
standards:
- {name: SDTMIG, version: "3.4", status: Final}
define: sdtm/define.xml
transport: xpt
adam:
specs: [adam/adsl.yaml]
standards:
- {name: ADaMIG, version: "1.3", status: Final}
define: adam/define.xml
transport: dataset_json
functionslists the function files that declare the project functions the specifications call.environmentsnames the packaging lock of each host language: a uv lock for Python and an renv lock for R. By design, one language runs the whole study, and each package reads only its own entry. Before a build that calls a project function, the package checks the installed versions of yamaa and of each called package against its lock, and the build fails withruntime_artifact_mismatchwhen one differs. Without a lock, the installed callables run unchecked. The lock pins yamaa too, since the engine is part of what the functions were tested with, so after you upgrade yamaa every build that calls a project function fails this way until its lock is made again: regenerate eachenvironments.<host>.lock, for example withuv lock --upgrade-package yamaaorrenv::snapshot(), and re-run each function package's own tests.codelists, not used here, lists the codelists files the specifications bind (Codelists and terminology).study, and each section'sstandardsanddefine, are the metadata define.xml needs, andtransportwrites one Dataset-JSON (dataset_json, ordataset_ndjsonfor its newline-delimited form) or SAS Transport (xpt) file for each dataset. Undercontext: Submission, the default, a section that composes define.xml must name itstransport(Create define.xml).
The submission packages of one study can share one project file and write
only what each package changes. A package's project file names the study's
as its parent:
# package/project.yaml
yamaa: "1.0"
type: project
parent: ../study/project.yaml
clear: [adam.originator]
adam:
define: define.xml
The package keeps everything its parent declares and changes only the fields
it writes: study, environments and each section merge field by field, so
this adam keeps the parent's specs and standards, and a list such as
functions, codelists or a section's specs replaces the inherited one
whole. clear removes an inherited value. Each path resolves from the file
that writes it, so the specs this package inherits name the specifications
beside ../study/project.yaml. A project file names at most one parent,
which may name its own, and you open the package by its own project file
(project layering).
A function file declares one project function: its parameters, what it
returns, the callable that implements it in each host language, and its
tests. Every build that calls the function runs its tests first, in whichever
language runs the study, before it reads any study data. The tests cover the
cases the contract implies: an ordinary value, a boundary, a missing value of
each parameter, and a decimal comparison for a float result.
# functions/bmi.yaml
yamaa: "1.0"
type: function
name: bmi
description: Body mass index from a weight in kg and a height in cm.
params:
- {name: weight_kg, type: float}
- {name: height_cm, type: float}
returns: float
implementations: {python: projectbmi.bmi, r: projectbmi::bmi}
tests:
- id: normal
covers: [normal, numeric-comparison]
args: {weight_kg: 70.0, height_cm: 175.0}
result: 22.857142857142858
- id: zero-weight
covers: [boundary, numeric-comparison]
args: {weight_kg: 0.0, height_cm: 175.0}
result: 0.0
- id: missing-weight
covers: ["short-circuit-missing:weight_kg"]
args: {weight_kg: null, height_cm: 175.0}
result: null
- id: missing-height
covers: ["short-circuit-missing:height_cm"]
args: {weight_kg: 70.0, height_cm: null}
result: null
Read another dataset¶
adam/adsl.yaml builds ADSL from the DM dataset that sdtm/dm.yaml writes,
and calls bmi:
# adam/adsl.yaml
yamaa: "1.0"
type: spec
model: ADaM
domain: ADSL
keys: [STUDYID, USUBJID]
input:
DM: {path: ../sdtm/dm.parquet, schema: ../sdtm/dm.yaml}
VS:
path: ../edc/vitals.csv
types: {WEIGHT: float, HEIGHT: float}
key: [USUBJID]
base: DM
output:
path: adsl.parquet
columns: [STUDYID, USUBJID, AGE, SEX, BMIBL]
report: adsl-report.parquet
columns:
# A copied column takes its type and label from sdtm/dm.yaml.
- copy:
dataset: DM
columns: [STUDYID, USUBJID, AGE, SEX]
# VS.WEIGHT and VS.HEIGHT join on VS's key, USUBJID.
- name: BMIBL
type: float
label: Baseline Body Mass Index (kg/m2)
derivation:
function:
name: bmi
args: {weight_kg: VS.WEIGHT, height_cm: VS.HEIGHT}
DMnames the dataset file and, throughschema, the specification that writes it. The copy entry takes four of its columns, each with its type and label, and with a Predecessor origin that namesDM's column (SDTM to ADaM). The link reads only whatdm.yamldeclares; it never runs DM.DMis thebasethat builds the rows, so a read ofDMneeds no join. A read of another input, such asVS.WEIGHT, is a join on that input's declaredkey, each column matched against the current row's column of the same name; yamaa never infers a join from the columns two datasets happen to share (Declared join keys).functioncalls a project function with its arguments by name. A specification that calls one, or binds a codelist, runs with its project: open it with the project'sspec(name).
Work with a project¶
yamaa_project() and yamaa.project() read nothing until you call a method.
spec(name) returns one of the project's specifications by its domain name,
with the project's codelists and functions. Open a specification this way
when it binds a codelist or an inventory vocabulary, or calls a project
function: a specification that does so, checked or run without its project,
fails before any data is read with project_binding_missing at each such
binding.
project <- yamaa_project("project.yaml")
project$names # every specification, in run order
adsl <- project$spec("ADSL")
adsl$run()$save()
project$check()$issues # every specification, no data read
project = yamaa.project("project.yaml")
project.names # every specification, in run order
adsl = project.spec("ADSL")
adsl.run().save()
project.check().issues # every specification, no data read
Only specifications listed in the project have names, and each name is unique
across the project. An unknown name raises an error whose issue lists the
names the project has. names lists them in the order a run of every section
builds them, each specification after the ones it reads from, and reads no
study data; it raises when the project file cannot be read or lists a name
twice.
The project's check() checks every listed specification as spec(name)
would. It also reports two listings that share a name, specifications that
would write the same file, and an input that reads another listed
specification's dataset without linking it through schema.
Project execution¶
run() builds every specification in the section you name, "sdtm", "adam"
or "send", in dependency order. Without a name, it builds every section.
Naming a section the project does not declare, in run() or submission(),
raises an error whose issue lists the sections it declares. As with spec(),
a later run() reuses what earlier runs of the same project object compiled,
and compiles again any specification whose files changed.
Its save() writes each specification's run report and dataset in run order,
or only the run reports when any specification failed, and returns the paths it
wrote. It checks every target before it writes anything.
project$run("sdtm")$save() # all SDTM datasets
project$run("adam")$save() # all ADaM datasets
runs <- project$run() # all SDTM and ADaM datasets
runs$names # the specifications, in run order
length(runs) # how many; names(runs) lists them too
runs[["ADSL"]]$output # one specification's dataset
runs$report # all run reports, in run order
runs$save() # save every dataset and run report
project.run("sdtm").save() # all SDTM datasets
project.run("adam").save() # all ADaM datasets
runs = project.run() # all SDTM and ADaM datasets
runs.names # the specifications, in run order
len(runs) # how many; "ADSL" in runs, list(runs)
runs["ADSL"].output # one specification's dataset
runs.report # all run reports, in run order
runs.save() # save every dataset and run report
Create define.xml and submission datasets¶
Set define_version on a submission section to select 2.0.0 or any
published 2.1 release from 2.1.0 through 2.1.11. The generated document
retains that exact version; omission selects 2.1.11.
submission() composes one define.xml for the selected section, or one for
every section that names define. Each specification it covers declares the
dataset's submission metadata, and each of its output columns a label and
submission metadata (the full mapping table
lists them), as the
sdtm-dm-metadata
benchmark does; the short sdtm/dm.yaml above declares too little, and its
submission fails with submission_metadata_missing. Each section also names
its transport, dataset_json, dataset_ndjson or xpt, and the submission
writes each saved dataset as one Dataset-JSON file, in its single-object or
its newline-delimited form, or one SAS Transport version 5 file next to
define.xml. A section without transport fails with missing_required_field
at it, unless it sets context: Other for a document that is not a
submission, which then writes define.xml alone.
Its save() writes each section's transport files, then its define.xml, and
returns the paths it wrote. Its created is the creation time that the
documents carry: the created you gave, or else the time the submission read
once and gave every section.
project$run("sdtm")$save()
submission <- project$submission("sdtm", created = "2026-10-21T12:00:00")
submission$status
submission$issues
submission$save()
project$submission()$save()
project.run("sdtm").save()
submission = project.submission("sdtm", created="2026-10-21T12:00:00")
submission.status
submission.issues
submission.save()
project.submission().save()
Interrupts¶
Press Ctrl+C while a call runs, and it stops at the next point where the
engine looks for an interrupt: as it starts, before each specification of a
project and between the steps that build a dataset, before each batch of
project function calls, before each section of a submission, and once in a
save, after it has prepared every file and before it writes the first. The
call returns no result and keeps nothing it built. Python raises the
KeyboardInterrupt itself, and R signals its interrupt condition, as any
interrupted R code does. An interrupt never stops a save partway: one
stopped this way writes nothing and leaves the result unsaved, so you can
call save() again. A project function that raises KeyboardInterrupt or
SystemExit in Python ends the whole call the same way. A run read as a
stream also stops before each batch
(Read an output as a stream).
Read ODM XML¶
A 1.0 specification reads an ODM 1.3 or 2.0 XML file directly: an input whose
path ends in .xml, such as ODM: ../edc/odm.xml in sdtm/dm.yaml, reads one
row per clinical item in file order. Only a snapshot reads: a transactional
file, and one without clinical data such as a define.xml, are refused with
their issue, and audit records, signatures and annotations are skipped
(ODM-1).
ODM limits must be non-missing whole numbers, with at least one member or byte and at least eight levels. Negative, fractional or Boolean limits refuse as value_not_permitted at their argument, carrying the supplied value in the issues table.
read_odm() reads the same table in R or Python. Use it for an export inside
a TAR archive, which an input does not open, or to look at the items
yourself.
odm <- yamaa_read_odm("edc/export.tar.gz", archive_member = "odm.xml")
odm = yamaa.read_odm("edc/export.tar.gz", archive_member="odm.xml")
Lint specifications¶
lint() checks yamaa files against the style guide and returns
a result. Its findings table has one row per finding: the file, the line and column where the departure
starts, the finding, its spec path and a message. A directory names the
yamaa files beneath it, of every file type. With fix, it first applies
the proved fixes, replacing a file only when it reads as the same values
with the same comments, or as those values with exactly the canonical
rewrite a finding names, and returns what remains.
result <- yamaa_lint(c("project.yaml", "sdtm/", "adam/"))
remaining <- yamaa_lint("sdtm/dm.yaml", fix = TRUE)
result = yamaa.lint(["project.yaml", "sdtm/", "adam/"])
remaining = yamaa.lint("sdtm/dm.yaml", fix=True)
The result has status, findings and paths. Read result.findings in Python or result$findings in R; paths lists the files a fix rewrote. Status is failed while any finding remains, and printing the result gives the engine summary.
JSON Schema of a file type¶
schema() returns the JSON Schema of a file type, spec, project,
codelists or function, as canonical JSON text: one line, with its members
in key order. Save it, and an editor that validates YAML against a JSON Schema
checks and completes yamaa files as you type. The schema is part of the
installed engine, so every installation of one release gives the same text. A
specification file may be one layer of an inheritance chain, so the spec
schema checks what one layer may write. Any other file type raises
yamaa.Error in Python or signals a yamaa_error condition in R, whose issue
names the condition unexpected_file_type.
writeLines(yamaa_schema("spec"), "spec.schema.json")
from pathlib import Path
Path("spec.schema.json").write_text(yamaa.schema("spec") + "\n")
Conditions and their history¶
conditions() returns the condition catalogue: every condition the engine
may report in an issue, and every condition a release retired, one row per
condition in name order. condition is the name that an issue's condition
column gives, severity the severity of its issues, phases the phases that
may report it, rules the rule cards that its issues cite in their rule
column, in card order, and message the template that its issues' messages
are written from. renamed_from gives the name a condition had before a
release renamed it, and retired_in the release that retired a condition,
which keeps its row with no phase, rule or message. So a program that
matches conditions by name, such as one that explains them, finds every name
it knows: a condition of the catalogue, the renamed_from of one, or a
retired one. phases and rules are list columns, and a message,
renamed_from or retired_in that a condition lacks is NA in R and None
in Python. The catalogue is part of the installed engine, so every
installation of one release gives the same table, and yaml/conditions.yaml
at the release's tag holds it too.
catalogue <- yamaa_conditions()
catalogue[!is.na(catalogue$renamed_from), c("condition", "renamed_from")]
rows = yamaa.conditions().to_pylist()
renamed = {row["renamed_from"]: row["condition"] for row in rows if row["renamed_from"]}
Check an installation¶
selftest() runs the benchmark cases built into the engine, in memory, and
compares each result with the case's independently authored expected files;
it reads and writes no file and uses no network, so every host runs the same
cases on the same bytes. status is failed when a case failed, and
succeeded otherwise. cases has one row per case, with its case,
status and message; a case that needs a feature this build does not
compile is skipped. info names the engine, its language versions and
features, the host and the platform. Printing the result gives its report,
the installation and operational qualification evidence of the installation:
the summary line, the first failure when a case failed, a not covered: line
naming each feature, as info() names it, that no case exercised, and the
information line.
yamaa_selftest()
print(yamaa.selftest())
yamaa selftest: succeeded, 5 of 5 cases passed
engine 0.8.0, language 1.0, python 3.12.4, linux-x86_64
Engine information¶
info() returns what the installed engine is and where it runs: its
version, the language_versions it reads, the optional features it was
built with, the host, the host's own host_version, and the platform,
such as linux-x86_64. R returns a named list and Python a dict, in that
order. A selftest's info holds the same facts, and the command line prints
them for yamaa --version. The engine version is the same in every host;
packageVersion("yamaa") and yamaa.__version__ stay the package versions.
yamaa_info()$version
yamaa.info()["version"]
Inputs, outputs and paths¶
Inputs can be CSV, Parquet, SAS datasets (.sas7bdat), SAS transport v5/v8
files (.xpt), Dataset-JSON 1.1 files (.json, or .ndjson for the
newline-delimited form) or ODM XML files (.xml). A SAS input declares its
encoding, for example
{path: input/dm.xpt, encoding: windows-1252, types: {SEQ: int}}. A
Dataset-JSON input takes each field's type from its column's dataType, so
types only narrows one, as in {path: input/vs.ndjson, types: {VISITNUM: int}}. Outputs are
CSV or Parquet; Dataset-JSON and SAS Transport version 5 files come from
submission(). Every run divides its input into partitions of whole subjects,
so a CSV or Parquet input that is a specification's only input may hold more
than 10,000,000 records when the windows and aggregates group by the first two
keys, such as STUDYID and USUBJID
(LIMIT-3).
A run derives up to four of its partitions at once, each on a thread of its
own, or fewer when the process may use fewer cores. check(), run() and
submission() take threads to set that number, a positive whole number, as
in run(threads = 1) in R and run(threads=1) in Python, and the command
line takes --threads N. A project run also builds its specifications at
once on those threads: a specification starts once every specification whose
output it reads has finished, and the specifications building at once and
their partitions share the threads, so a project run never uses more. A
Parquet output is encoded on the same threads, that many row groups at once. A
specification that calls a project function runs on the thread that called
the method. A threads of 1 derives one partition and builds one
specification at a time, and a value that is neither a positive whole number
nor the default, NULL in R and None in Python, fails the call. The
output, its issues, its report and the progress events are the same for every
value; only the time and the peak memory change.
A relative path resolves from the YAML file that writes it, so
../edc/odm.xml in sdtm/dm.yaml is the study's edc/odm.xml. A path you
pass to a function is a character string in R, and a string or any
os.PathLike, such as a pathlib.Path, in Python.
On Windows, a specification, project or ODM file you pass may sit on a network
share (\\server\share\study\project.yaml) or a mapped drive, and the paths
its YAML writes resolve on that share. Paths inside YAML keep forward slashes.
Below the directory you pass, symbolic links, junctions and other reparse
points are refused on every drive. On a share, a link is refused when the file
server reports it as one, as Windows servers do. A server that follows links
itself, as Samba does with its default follow symlinks = yes, shows the
link's target instead, and that target is read like any other file.
A parent specification's relative input and output paths resolve beside the
parent, and an included fragment's beside the fragment, even when it sits on
another drive than the specification you pass. A
parent on a network share that the specification you pass is not on cannot
declare relative input or output paths, because a path in YAML cannot name a
share: such a parent is refused with parent_on_another_share, naming the
parent, the path and both roots. Keep it on the same share, or write those
paths with relative_to: entry.
While a save replaces a file, it locks the file's folder, and another save on the same computer that reaches that folder meanwhile fails rather than waits. The lock does not reach other computers: saves from two computers into one network folder are not serialized, so save into a shared folder from one computer at a time.
Command line¶
The yamaa command line runs the same engine as the R and Python packages,
as one standalone binary that needs no Python, R or Rust. Each command makes
the calls described above and prints the engine's own text, so a result, an
issue or an error reads as it does in R and Python. Only its arguments change
what it does: no configuration file, environment variable or colour does.
Each package also runs the same commands as itself
(Run the command line as a host), which is
how a command reaches project functions.
yamaa [--format text|json] COMMAND [ARGUMENTS]
yamaa --version [--format text|json]
yamaa --help
--format selects what a command prints: text, the default, or json, one
JSON document. It may come before or after the command. --version prints
the engine information for the host cli, one field
per line, or with --format json one object with the six keys of info(),
whose host_version is null. --help, given anywhere before --, prints the
usage whatever else is given. Both exit 0.
Commands¶
yamaa check PATH [--spec NAME | --parent PATH] [--threads N] [--verbose]
yamaa run PATH [--spec NAME | --section SECTION] [--host python|r] [--interpreter PATH] [--threads N] [--verbose]
yamaa submission PATH [--section SECTION] [--created TIME] [--threads N] [--verbose]
yamaa resolve PATH [--spec NAME]
yamaa read-odm SOURCE OUT [--archive-member NAME] [--max-archive-members N] [--max-expanded-bytes N] [--max-xml-depth N]
yamaa lint [--fix] PATH...
yamaa schema TYPE
yamaa conditions
yamaa selftest
| Command | What it does | In R and Python |
|---|---|---|
check |
Checks a specification or a project without reading data, or with --parent a fragment on a template |
check(), check(parent = ) |
run |
Builds a specification or a project, then saves what it built and prints each path written; with --host or a project that names one language, calls its project functions through that language's worker |
run(), then save() |
submission |
Composes a project's define.xml and transport files, then saves them and prints each path written | submission(), then save() |
resolve |
Prints the resolved specification as canonical JSON | resolve() |
read-odm |
Reads SOURCE, an ODM XML file or a TAR archive that holds one, and saves its items to OUT | read_odm() |
lint |
Lints each PATH, a yamaa file or a directory of them; with --fix, first applies the proved fixes in place |
lint() |
schema |
Prints the JSON Schema of TYPE: spec, project, codelists or function |
schema() |
conditions |
Prints the condition catalogue | conditions() |
selftest |
Runs the cases built into the engine and compares each with its expected files | selftest() |
check, run and resolve take a specification or a project, whichever
PATH declares in its type:, and submission takes a project. run's
--host LANGUAGE and --interpreter PATH settle the project functions a
project run calls (Project functions from the standalone binary). For a
project, --spec NAME selects one of its specifications, as spec(name)
does, and --section SECTION one of its sections, sdtm, adam or send;
without them, check and run cover the whole project, and submission
every section that names define. run takes --spec or --section, not
both, and resolve needs --spec for a project, which has no resolved form
of its own. For a specification, check --parent checks it as a fragment
whose parent is the template the flag names, as check(parent = ) does, so
check takes --spec or --parent, not both. --created TIME gives the ISO 8601 creation time that a
submission's documents carry; without it, the submission reads the clock once
for every section and prints the time it read. --threads N sets how many
partitions of one run derive at once, and how many threads a project run's
specifications and their partitions share, as threads does in R and Python
(Inputs, outputs and paths); an N of 0 is a
usage error. A command's flags follow it,
before or after its arguments, each at most once, and -- ends the flags, so
a PATH that starts with - can follow it. A PATH whose type the command does
not take, such as a specification with --spec, is a usage error.
read-odm reads SOURCE as read_odm() does, then saves the items to OUT,
whose extension, .csv or .parquet, selects the format; any other
extension is refused before anything is written. Its flags are
read_odm()'s arguments, with the same defaults: --archive-member NAME
names the archive member to read, and --max-archive-members N (default
10000), --max-expanded-bytes N (default 268435456) and --max-xml-depth N
(default 64) bound the members an archive holds, the bytes read or expanded,
and the depth of element nesting
(ODM-1). Each N is a whole number.
The standalone binary runs on no interpreter, so a run of a project that
calls project functions starts one, a worker of the host package, and runs
them there (Project functions from the standalone binary).
--host names the language, python or r, and --interpreter the
interpreter; without --host, the one language the project names runs
them. A project that names no one language, or a run of a specification
alone, runs no project function: it reads no project's environments
entry, so it checks no lock, and a run of a specification that calls a
project function fails before it reads any input, with
unsupported_operation at the first derivation that calls one, in the
order a formatted file writes them (the intermediates', then the columns',
then the row templates'). Every other build runs as it does in R and
Python, and check checks every specification, since it runs no project
code.
yamaa check project.yaml
yamaa check fragments/ae-duration.yaml --parent templates/adae.yaml
yamaa run sdtm/dm.yaml
yamaa run project.yaml --section sdtm
yamaa --format json run project.yaml --spec DM
yamaa submission project.yaml --section sdtm --created 2026-10-21T12:00:00
yamaa read-odm edc/export.tar.gz edc/odm.parquet --archive-member odm.xml
yamaa lint --fix project.yaml sdtm/ adam/
yamaa schema spec > spec.schema.json
Project functions from the standalone binary¶
A run of a project whose specifications call project functions needs the
language they run in and an interpreter with the host package and the
function packages installed, the ones the project's lock names. The binary
settles the language first: --host python or --host r, or, without the
flag, the one language the project declares an environments entry for,
or, when it declares none, the one language its function files implement.
A project that names both languages is ambiguous, so without --host the
run calls no project function and refuses a call of one as
unsupported_operation; --interpreter alone is then a usage error. The
JSON document's host is the language settled on, or null.
The functions run in a worker of the host package, python -m yamaa._worker
or Rscript running the R package's worker, which the binary starts the
first time a run asks for a function and ends with the run. --interpreter
PATH names the interpreter; without it, the binary starts python3, or
python where that is not found, or Rscript, from PATH, so a virtual
environment or an renv project that is active in the shell is the one used.
The worker verifies the lock, binds and calls each function as the
package's own run() does, so the result, the issues and the activation
tests are the host's, and a function's warnings reach the terminal through
the worker's standard error. Its standard output carries the binary's
protocol: the Python worker moves sys.stdout to standard error before any
project code runs, and an R function must not write to standard output.
The lock pins yamaa because the engine is part of what the functions were
tested with (Project configuration), so a declared
lock is verified against the worker's installation, and the worker's
package must carry the binary's engine version: a difference fails the run
with runtime_artifact_mismatch for the package yamaa, naming both
versions. Without a lock, the installed functions run unchecked, as they do
in the hosts. A worker that cannot start, or that ends, fails the run
before it reads data; the engine keeps such a host failure opaque in the
issue, so the reason is written to standard error as yamaa: <message>.
yamaa run project.yaml # the one language the project names
yamaa run project.yaml --host python --interpreter .venv/bin/python
yamaa run project.yaml --host r --spec ADSL
Run the command line as a host¶
yamaa_cli(arguments) in R and yamaa.cli(arguments) in Python run one
command of the command line as that package: arguments are the words after
yamaa, as the binary takes them. The command runs over the package's own
engine, so it verifies the project's lock and calls the project functions
installed in that R or Python, as run() does, and it prints what the binary
prints on the package's standard output and standard error, then returns the
binary's exit status, 0, 1 or 2, invisibly in R. --version and selftest
name the host, r or python, with its version on a host version: line.
Under --verbose, each progress line reaches standard error as it happens.
Ctrl+C raises KeyboardInterrupt in Python and signals R's interrupt, as
every call of the package does (Interrupts).
python -m yamaa runs the Python entry from a shell, with the binary's
arguments and exit status, from the interpreter whose packages the project's
lock names; R has no such entry point, so a shell runs Rscript with the
arguments after the expression. A host runs its own functions, so --host
and --interpreter are usage errors there:
python -m yamaa run project.yaml --spec ADSL
Rscript -e 'quit(status = yamaa::yamaa_cli(commandArgs(TRUE)))' run project.yaml --spec ADSL
status <- yamaa_cli(c("--format", "json", "run", "project.yaml", "--spec", "ADSL"))
status = yamaa.cli(["--format", "json", "run", "project.yaml", "--spec", "ADSL"])
Output¶
Standard output carries what a command prints, each line ending in a line
feed. As text, check, run and submission print the summary line and
member lines that R and Python print
(Status and printed results), then each issue
on a line of its own, as the first issue: line shows the first. run and
submission then print the absolute path of each file the save wrote, in the
order written, and a submission made without --created prints
created: <time> after its summary line. resolve and schema print
canonical JSON in either format, and read-odm prints the path it wrote.
lint prints each finding as <file>:<line>:<column>: <finding> <message>,
then a summary line such as 3 lint findings in 2 of 5 files, to which
--fix adds ; fixed 1 file. conditions prints the catalogue as a table: a
line of its column names, then one line per condition, each cell separated by
a tab, a list's items joined with , and a missing cell empty. selftest
prints the report that R and Python print. A call that cannot complete prints
the message of the error that R and Python raise (Errors), which
carries the first issue, then each other issue's line and the paths written
before it.
With --format json, a command prints one canonical JSON document instead:
one line, with its members in key order (JSON documents).
Standard error carries only the refusals of exit status 2, each as
yamaa: <message> and then run 'yamaa --help' for usage, which a failed
write to standard output leaves out. There are three exceptions. A run's
worker writes there what the functions it runs write to standard error,
and the binary writes yamaa: <message> there for a worker that cannot
start or that ends. A selftest
case that panics fails, and the panic message, which shows where the engine
panicked, goes to standard error. And check, run and submission with
--verbose write each progress event there as it happens, as
<name>: <phase> <kind> with a partition's number and records or an end's
outcome, such as sdtm/dm.yaml: derivation end: succeeded. Then, for each
run, they write how it divided its driver input, as
<name>: partitions <strategy> <count>, where the strategy is
whole_table, key_bounded or consecutive, such as
sdtm/dm.yaml: partitions key_bounded 1. Last comes the timings table of
the result, its column names and one line per phase, each cell separated by
a tab (Progress and timings).
Standard output is the same with or without --verbose.
Exit status¶
| Status | Meaning |
|---|---|
| 0 | The command completed and succeeded: its status is succeeded and any save wrote everything, or resolve, schema, --version or --help printed its output. |
| 1 | The command completed with findings: a status of failed or skipped, a refused save, remaining lint findings, a failed selftest case, or a refusal that carries issues, such as an unknown section, an unknown TYPE or a PATH that cannot be read. |
| 2 | The command could not run as asked: a usage error, an argument the engine cannot take, such as text that is not UTF-8, a PATH whose declared type the command does not take, or a failed write to standard output. |
A file problem the engine reports as an issue, such as a missing input, exits 1, since R and Python return the same issue.
Press Ctrl+C, and check, run and submission stop at the engine's next
checkpoint, as a call in R or Python does (Interrupts): the
command prints nothing, and an interrupt never stops a save partway. The
other commands stop at once, so an interrupted lint --fix, which replaces
each file whole, can leave some files fixed and others not. Either way, the
process then ends as an interrupted program does, with none of the statuses
above: on Linux and macOS by the interrupt signal, which a shell reports as
status 130, and on Windows with the exit code STATUS_CONTROL_C_EXIT.
JSON documents¶
With --format json, each command prints one object with the keys below, and
resolve and schema print the JSON they print as text. A refusal prints the
same keys, except where the table names a refusal's.
| Command | Keys |
|---|---|
check |
command, path, spec, status, issues |
run |
command, path, spec, section, host, status, members, issues, paths |
submission |
command, path, section, created, status, issues, paths |
resolve |
the resolved specification; a refusal: command, path, spec, status, issues |
read-odm |
command, path, rows, status, issues, paths |
lint |
command, status, findings, paths; a refusal: command, status, findings, issues, paths |
schema |
the JSON Schema; a refusal: command, type, status, issues |
conditions |
command, status, conditions |
selftest |
command, status, cases, info |
--version |
version, language_versions, features, host, host_version, platform |
commandis the command's name.path,spec,sectionandtypeare its arguments as given, null for a flag not given;read-odm'spathis SOURCE, and a check with--parentprints the same keys, itspaththe fragment. A run'shostis the language the run settled on for its project functions,pythonorr, as--hostgave it or the project named it, whether or not a function was called, and null when it settled on none: a specification run alone, a project that names no one language, or a host package's entry.statusis the result's status, which a refused save keeps, andfailedfor a call refused before it has a result.lint's isfailedwhen a finding remains, andselftest's when a case failed;conditions' is alwayssucceeded.issuesholds every issue as an object with the twelve columns ofissues:severity,phase,condition,rule,spec_paths,file,line,column,message,context,source_spanandoperand_route, whosecontextis an object rather than the JSON text that the Python and R tables hold (Check a specification without data).pathslists the files written, as the text prints them.membershas one object for each specification of a project run, in run order, with itsname,status,rows,columnsandissue_count;rowsandcolumnsare null without output, and a listing that could not be read is named by its path. A specification's own run has none.createdis the creation time that a submission's documents carry; a refused submission has none, so it is--createdas given, or null.rowsis the number of itemsread-odmread, null only when the read is refused. Itsstatusfollows the read, as a run's follows the run: a read that completed issucceededeven when the save of OUT is then refused, which itsissuesand exit status 1 report.findingshas one object for each lint finding, with itsfile,line,column,finding,spec_pathandmessage.caseshas one object for each selftest case, with itscase,statusandmessage, andinfoholds the engine information with the keys that--versionprints.conditionshas one object for each condition of the catalogue, in name order, with itscondition,severity,phases,rules,message,renamed_fromandretired_in;phasesandrulesare lists, and a missing cell is null.
From 1.0.0, every 1.x release keeps the commands, flags, exit statuses and
JSON keys on this page, and may add commands, flags and keys, as the
stability contract says. The messages
are not frozen: a program reads status, and an issue's condition and
rule, never its message.