Migrate Python scripts

If your Python scripts use an earlier version of Syside, some imports, calls, and attributes may need updating for v1. Use the changes below to update your scripts.

Load a model

load_model and try_load_model now return a locked model that you can read directly. load_model raises syside.ModelError when the model has errors, so to read the diagnostics of a model that may have errors, use try_load_model, which returns the model either way. Get its diagnostics from model.diagnostics:

# before
model, diagnostics = syside.load_model(["model.sysml"])

# after
model = syside.load_model(["model.sysml"])
diagnostics = model.diagnostics

The model of a syside.ModelError is an UnlockedModel. Call lock() on it to read the model.

You can still unpack the return value into model, diagnostics, but a type checker infers both as Model | Diagnostics. Use model.diagnostics to avoid that ambiguity. Given a list of sources, syside.json.loads also returns a locked model, together with a report for each document it deserialized.

The kerml_source and sysml_source arguments are deprecated. To load source text, pass (name, source) tuples in the source list, and the file extension of each name sets its language. You can mix these tuples with files:

# before
model, diagnostics = syside.load_model(sysml_source=sysml)

# after
model = syside.load_model([("model.sysml", sysml)])

load_model and try_load_model can now find project settings and dependencies the same way the CLI does. Pass config=True to find syside.toml, or sysand_dir=True to find .sysand/, relative to the current working directory. With sysand_dir=True, you don’t need to list each Sysand dependency yourself. The load workspace example shows both options.

If you need to control when the model is locked, use load_model_unlocked or try_load_model_unlocked. Both return an UnlockedModel. Call lock() when you’re ready to read it.

Read documents

UnlockedModel and LockedModel replace BaseModel, so a check such as isinstance(model, syside.BaseModel) is now false. Locking a model locks all its documents together, so documents have no lock() method: remove each per-document lock. For a model returned by load_model, update document access as follows:

  • model.documents and model.user_docs become model.documents.

  • model.stdlib_docs becomes model.environment.documents.

  • To replace model.all_docs, lock the model with its environment. See Choose which documents to read.

  • Replace model.uris() with model.uris for Url values, or model.paths for pathlib.Path values. Neither property is available on an unlocked model.

# before
for path_str in model.uris():
    ...

# after
for uri in model.uris:
    path_str = syside.decode_path(uri)

for path in model.paths:
    path_str = str(path)

Choose which documents to read

DocumentKind and the considered_document_kinds argument of uris, nodes, and elements have been removed. Choose which documents to read when you lock the model:

unlocked = syside.load_model_unlocked(["model.sysml"])

# DocumentKind.MODEL
with unlocked.lock() as model:
    for node in model.nodes(...):
        ...

# DocumentKind.ENVIRONMENT
with unlocked.environment.lock() as model:
    for node in model.nodes(...):
        ...

# DocumentKind.ALL
with unlocked.lock(with_environment=True) as model:
    for node in model.nodes(...):
        ...

When you include the environment, the model’s documents come first, followed by the environment’s documents. lock only locks the environment documents when with_environment=True. If you run without the global interpreter lock (GIL), make sure nothing modifies the environment documents while the model is locked.

Read diagnostics

DiagnosticMessage has been removed. Each diagnostic is now a (Url, Diagnostic) tuple. Unpack it to get the source URL and the diagnostic:

# before
for diagnostic in diagnostics.validation:
    filename = diagnostic.filename or "<source>"
    full_message = diagnostic.full_message

# after
for url, diagnostic in diagnostics.validation:
    filename = syside.decode_path(url)
    full_message = f"{filename}{str(diagnostic).removeprefix('<source>')}"

Diagnostic also provides line and col properties, both counted from 1. The message no longer includes the source excerpt. To print the diagnostics with their excerpts, use str(model.diagnostics).

Diagnostics.results holds one DiagnosticResults for each document, with its parser, sema and validation diagnostics. To add diagnostics, use the document’s DiagnosticResults rather than Diagnostics itself. The properties of DiagnosticResults are now mutable buffers.

Evaluate with a context

The Compiler evaluation methods and the CompilerFilter constructor now require a syside.vm.Context instead of an Stdlib. Create the context from the model’s standard library, then pass it as the first argument to the Compiler evaluation methods:

ctx = syside.vm.Context(model.lib)
value, report = compiler.evaluate_feature(ctx, feature, scope)
value, report = compiler.evaluate(ctx, expr, scope=scope)

A call in the old form raises TypeError. Scripts often wrap evaluation in an except that treats a failure as a value that can’t be evaluated; make sure it doesn’t catch the TypeError, or every value reads as unevaluable.

Read the target of a feature chain

The fixes for feature reference expression access rewrite references such as Battery::capacity as feature chains such as battery.capacity. A chain is a FeatureChainExpression, which has no referent. Read its target_feature instead.

# before
target = expression.referent

# after
target = expression.target_feature

Read solver results

The JSON that syside solver prints reports each result as attainable and unattainable values. An assertion no longer has a status of satisfied, violated or unanalyzable, and values listed under values or proven are now in the attainable listing. Update scripts that read those fields, using the mapping in Read the solver’s output.

Replace removed members

  • Iterating a LazyIterator directly (for x in it): use .collect() or .for_each.

  • LazyIterator.__getitem__: use .get to access an item, or .collect or .for_each to iterate.

  • The deprecated Sema.resolve overload: use .resolve with locked documents.

  • Feature.is_read_only: use .is_constant.

  • .transition_step, .trigger_step, .trigger_steps, .effect_step, .effect_steps, and .guard_expression on succession types: these have been removed from the SysML v2 specification and have no replacement. TransitionUsage keeps .guard_expression and .guard_expressions.

  • The mutation methods of Url have been removed.

  • collect_files_recursively: use pathlib.Path.rglob.

  • The syside.preview module: load models with the load_model family instead, load_model, try_load_model and their _unlocked forms.

  • CstNode.grammar_type, grammar_symbol, has_changes, parse_state, next_parse_state, field_name_for_child, child_count and named_child_count, and StateId: these have been removed to allow future improvements.

Renamed and moved

  • ContainerView is now BufferView. The old name still works in type annotations, but not in isinstance checks.

  • Import interpreter symbols from syside.vm. Their aliases in the root module are temporary.

Run each updated script on a model with known results and check that the output matches what you expect.