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.sysml defines stoppingDistance and pedalTravel. Both use reqif metadata from the SysideReqIF package, provided by the sensmetry/syside-reqif dependency.

  • .project.json declares the dependency, sysand-lock.toml records its resolved version, and .sysand/ holds the installed dependency.

  • scratch/draft.sysml contains 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=True reads syside.toml and applies its include and exclude settings. Here, it excludes scratch/draft.sysml.

  • sysand_dir=True loads the dependencies installed in .sysand/. Here, it makes the SysideReqIF import 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.

config

sysand_dir

Result

False

False

Fails because SysideReqIF cannot be resolved.

False

True

Loads three requirements, including the draft.

True

False

Excludes the draft, but still cannot resolve SysideReqIF.

True

True

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_model with its config and sysand_dir arguments

  • ModelError provides diagnostics when loading fails

  • DocumentTier distinguishes project documents from dependency documents through Document.document_tier

  • decode_path turns the URL of a document into a path

  • syside 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_0.11.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_0.11.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.