Load a workspace
New in v1.0.0
To load the same workspace as syside check and the Syside VS Code Extension,
pass config=True and sysand_dir=True to load_model.
Without these arguments, your script includes files excluded by the project
configuration and cannot resolve names from Sysand dependencies.
This example explains how Syside selects workspace files and resolves dependencies. Its script compares loader settings for learning, rather than providing a task to copy into a production workflow.
The project
This example uses a brake requirements project with two requirements, a draft, and an installed dependency:
example_model.sysmldefinesstoppingDistanceandpedalTravel. Both usereqifmetadata from theSysideReqIFpackage, provided by thesensmetry/syside-reqifdependency..project.jsondeclares the dependency,sysand-lock.tomlrecords its resolved version, and.sysand/holds the installed dependency.scratch/draft.sysmlcontains a draft requirement that should be excluded:package BrakeDrafts { requirement regenerativeBraking { doc /* The vehicle should recover energy while braking. */ } }
syside.toml, which leaves the draft out of the workspace:exclude = ["scratch/**"]
Enable workspace settings
config=Truereadssyside.tomland applies itsincludeandexcludesettings. Here, it excludesscratch/draft.sysml.sysand_dir=Trueloads the dependencies installed in.sysand/. Here, it makes theSysideReqIFimport resolve.
Both arguments default to False. Setting them to True discovers the file
or folder relative to the current working directory, so the script changes
into the example folder before loading. You can also pass an explicit path to
either argument.
Dependency documents appear in the returned model as external documents. Syside does not validate them, and name resolution uses only the project’s direct dependencies.
Results
The script loads the folder with each combination of arguments. Successful loads print the document paths and requirement names. Failed loads print the error count and the first diagnostic.
|
|
Result |
|---|---|---|
|
|
Fails because |
|
|
Loads three requirements, including the draft. |
|
|
Excludes the draft, but still cannot resolve |
|
|
Loads the two intended requirements and the dependency. |
Loading successfully does not guarantee that you loaded the intended files.
With sysand_dir=True alone, the draft raises the requirement count from two
to three without producing an error.
The script then runs syside check in the same folder. The CLI reads both
workspace settings automatically, checks only example_model.sysml, and
exits with code 0. The final comparison confirms that its checked files
match the model’s project documents when both arguments are True.
Concepts used
load_modelwith itsconfigandsysand_dirargumentsModelErrorprovides diagnostics when loading failsDocumentTierdistinguishes project documents from dependency documents throughDocument.document_tierdecode_pathturns the URL of a document into a pathsyside check --stats, see Check command, lists the files it checked
Example model
package BrakeRequirements {
private import SysideReqIF::*;
requirement stoppingDistance {
doc /* The vehicle shall stop within 40 m from 100 km/h. */
metadata reqif { :>> identifier = "REQ-001"; }
}
requirement pedalTravel {
doc /* The brake pedal travel shall not exceed 150 mm. */
metadata reqif { :>> identifier = "REQ-002"; }
}
}
Example script
"""Workspace settings and Sysand dependencies must be enabled when loading a model."""
import os
import pathlib
import re
import subprocess
import sys
import syside
EXAMPLE_DIR = pathlib.Path(__file__).resolve().parent
def relative(path: str | os.PathLike[str]) -> str:
"""Return ``path`` relative to the example, so the output is portable."""
return pathlib.Path(path).resolve().relative_to(EXAMPLE_DIR).as_posix()
def project_files(model: syside.Model) -> set[str]:
"""Return the files of ``model`` that belong to the project itself."""
return {
relative(syside.decode_path(document.url))
for document in model.documents
if document.document_tier == syside.DocumentTier.Project
}
def requirement_names(model: syside.Model) -> list[str]:
"""Return requirement names in a stable order for comparison."""
return sorted(
requirement.declared_name or ""
for requirement in model.nodes(syside.RequirementUsage)
)
def load(config: bool, sysand_dir: bool) -> syside.Model | None:
"""Load the example folder, and print what the script gets to work with."""
print(f"load_model(['.'], config={config}, sysand_dir={sysand_dir})")
try:
model = syside.load_model(["."], config=config, sysand_dir=sysand_dir)
except syside.ModelError as error:
errors = sorted(
(
relative(syside.decode_path(url)),
diagnostic.line,
diagnostic.col,
str(diagnostic.code),
diagnostic.message,
)
for url, diagnostic in error.diagnostics.errors
)
path, line, col, code, message = errors[0]
print(f" {len(errors)} errors, the first: {path}:{line}:{col}")
print(f" {code}: {message}")
return None
print(" documents:")
for path in sorted(relative(path) for path in model.paths):
print(f" {path}")
print(f" requirements: {', '.join(requirement_names(model))}")
return model
def check_files() -> tuple[int, set[str]]:
"""Run ``syside check`` here, and return its exit code and checked files."""
result = subprocess.run(
[sys.executable, "-m", "syside", "check", "--stats", "--colour", "no"],
capture_output=True,
text=True,
check=False,
)
# `--stats` prints a `<path> | <diagnostics>` row for each checked file.
files = {
relative(match[1])
for match in re.finditer(
r"^(.+\.(?:sysml|kerml)) +\| +\d+$", result.stdout, re.MULTILINE
)
}
return result.returncode, files
def main() -> None:
"""Compare the four loader settings with the files checked by the CLI."""
# Both the loader and CLI discover workspace settings from this directory.
os.chdir(EXAMPLE_DIR)
loaded: dict[tuple[bool, bool], syside.Model | None] = {}
for config, sysand_dir in (
(False, False),
(False, True),
(True, False),
(True, True),
):
loaded[config, sysand_dir] = load(config=config, sysand_dir=sysand_dir)
print("syside check")
exit_code, files = check_files()
print(f" exit code: {exit_code}")
print(f" files: {', '.join(sorted(files))}")
for (config, sysand_dir), model in loaded.items():
if model is not None:
same = project_files(model) == files
print(
f" same files as config={config}, sysand_dir={sysand_dir}: "
f"{'yes' if same else 'no'}"
)
if __name__ == "__main__":
main()
Output
load_model(['.'], config=False, sysand_dir=False)
9 errors, the first: example_model.sysml:2:20
reference-error: No Namespace named 'SysideReqIF' found.
load_model(['.'], config=False, sysand_dir=True)
documents:
.sysand/lib/sensmetry-syside-reqif_1.0.0/SysideReqIF.sysml
example_model.sysml
scratch/draft.sysml
requirements: pedalTravel, regenerativeBraking, stoppingDistance
load_model(['.'], config=True, sysand_dir=False)
9 errors, the first: example_model.sysml:2:20
reference-error: No Namespace named 'SysideReqIF' found.
load_model(['.'], config=True, sysand_dir=True)
documents:
.sysand/lib/sensmetry-syside-reqif_1.0.0/SysideReqIF.sysml
example_model.sysml
requirements: pedalTravel, stoppingDistance
syside check
exit code: 0
files: example_model.sysml
same files as config=False, sysand_dir=True: no
same files as config=True, sysand_dir=True: yes
Download
Download this example as load_workspace.zip.