Workspaces & caching¶
This guide covers loading models from files and directories, how
multi-file merges behave, and how the content-addressed model cache
works. The API reference for everything here is
longeron.workspace.
load() dispatches on the path¶
load() is the universal entry point. It
inspects the path and picks the loader:
Input |
Behavior |
|---|---|
|
Parse the text and build the model. |
|
Import a JSON export losslessly ( |
a directory |
Load every |
import longeron
model = longeron.load("models/") # a whole workspace
model = longeron.load_many(["lib.sysml", "app.json"]) # an explicit set
load_many() loads an explicit list of .sysml
and .json files and merges them the same way.
Directory loads merge under one root¶
A directory load produces one Model whose root
namespace holds the top-level members of every file. Cross-file imports
(private import Units::*;) and qualified references therefore resolve,
because every package lives under the same root.
Three rules keep directory loads deterministic:
Files load in sorted path order, so the merged member order never depends on the filesystem.
.kermlfiles are ignored. KerML is parse-and-validate only in this package, so KerML sources never contribute model elements.If the directory contains no
.sysmlfile,loadraisesBuildErrorinstead of returning an empty model.
To merge already-loaded models, use
merge_models().
Saving a workspace back, file by file¶
A directory load remembers which file every top-level member came from
(member.source_file), and save_workspace()
uses those breadcrumbs to write edits back to the files they belong
to – the save path behind the model app’s Save button for
directory-loaded entries:
import longeron
from longeron import edit, export
model = longeron.load("models/")
tracker = edit.track(model)
edit.set_attribute_value(model, "Parts::Motor::mass", "0.075")
export.save_workspace(model, tracker.changes) # -> [Path("models/parts.sysml")]
The rules, honestly enforced:
Every tracked edit (
longeron.edit) maps to the top-level member(s) it touched; a rename maps to every file whose references its cascade rewrote.Only mapped files whose regenerated text differs from disk are rewritten – untouched files keep their bytes (and comments; a rewritten file is regenerated in
to_sysml()canonical form, exactly like a single-file save).Anything unmappable refuses with
SysMLErrorand nothing is written: a top-level member with no recorded source file (added after the load), or a change record without a usable breadcrumb. The escape hatch is an explicit-path save-as (save()), which writes one merged file.
workspace_plan() returns what a save would
write ({path: new_text}) without touching disk – the model app uses
it to put the file names on the Save button’s tooltip.
The model cache¶
Parsing is the slow step: the ANTLR Python runtime takes seconds per file, and minutes for the standard library. Built models are therefore cached on disk. A warm directory load is roughly 1000x faster than a cold parse.
What a cache entry is¶
A cache entry is plain JSON in the same lossless schema as
to_json(). There are no pickles anywhere, so a
cache entry is inspectable text and never executes code on load.
Entries are written atomically, so concurrent processes cannot corrupt
the cache.
How entries are keyed¶
The entry key combines two fingerprints:
the SHA-256 of the source text, and
a fingerprint of the generated parser, the builder, the model classes, the expression AST, and the package version.
Editing a source file, regenerating the grammar, or upgrading the
package each produce a new key, so stale entries are never read. Stale
entries are not an error. They sit unused until
clear_cache() removes them.
Where the cache lives¶
The cache directory is resolved in this order:
Priority |
Source |
Value |
|---|---|---|
1 |
|
that directory |
2 |
|
|
3 |
default |
|
cache_dir() returns the resolved directory,
and clear_cache() deletes every entry.
When caching is on¶
Caching defaults to on – for single files as well as directories. The dominant cold-load cost (ANTLR ATN warmup) is paid per process either way, so a warm cache hit turns a multi-second CLI invocation into milliseconds:
longeron.load("models/") # cached
longeron.load("one.sysml") # cached too
longeron.load("one.sysml", cache=False) # parse from source
Pass cache=False to opt out. On the command line,
--no-cache bypasses the cache for any model-consuming subcommand
(see the CLI reference).
Caching is best-effort: if the cache directory is not writable, load
still returns the built model and simply skips the store.
The standard library uses the same machinery¶
standard_library_model() ships a prebuilt JSON
snapshot of the vendored standard library, fingerprinted against the
library sources and the model classes. The snapshot loads in
milliseconds. If the fingerprint is stale, the library rebuilds from its
.sysml sources through the same workspace cache.