Structures#
Structures manage the project registry, screen lifecycle, and release pipeline. Most are used
internally by the CLI and by Root/Screen.load(). Import from VIStk.Structures.
VINFO#
VINFO is the base class for Project and Screen. It locates the .VIS/ folder by
walking up the directory tree from the current working directory, and exposes path constants for
all project directories.
You do not instantiate VINFO directly. It is initialized automatically when Project() or
Root() is created.
If no .VIS/ folder exists when VINFO is initialized (i.e., running VIS new), it
creates the project structure and prompts for project name, company, and version.
Path attributes (available on ``Project`` and ``Screen``):
Attribute |
Description |
|---|---|
|
Absolute path to the project root |
|
Path to |
|
Path to |
|
Path to |
|
Path to |
|
Path to |
|
Path to |
|
Path to |
|
Path to |
|
Path to |
|
Path to the installed VIStk package |
|
Project name (from |
|
Project |
|
Company name (from |
|
Copyright string; defaults to |
|
Name of the default screen; |
Methods:
Method |
Description |
|---|---|
|
Undoes screen isolation — restores all screens that were temporarily set to non-releasing during a single-screen release. |
Project#
Project(VINFO) — Loads the project registry from project.json and provides screen
management. Automatically attached to Root as root.Project.
from VIStk.Structures import Project
project = Project()
Attributes:
Attribute |
Type |
Description |
|---|---|---|
|
|
All registered screens |
|
|
The currently active screen (set by |
|
|
Application settings backed by |
|
|
Default icon name |
|
|
Optional project-level window title-bar icon (Windows |
|
|
Output folder for releases |
|
|
PyInstaller hidden imports |
|
|
Copyright string from |
|
|
Filename of the Host entry-point script |
|
|
Name of the default screen; |
Methods:
Method |
Returns |
Description |
|---|---|---|
|
|
Checks if a screen with the given name is registered |
|
|
Returns the |
|
|
Returns the screen if it exists, or creates it via |
|
|
Sets |
|
|
Calls |
|
|
Unified navigation — routes through the Host if one is running (deferred via the
active TabManager’s action queue), else falls back to |
|
|
Reloads the currently active screen |
|
|
Returns |
|
|
Interactively creates a new screen (CLI use) |
|
|
Sets the default screen and persists to |
|
|
Renames a screen throughout the project; returns |
|
|
Sets any attribute in a screen’s entry with type coercion; returns |
open(screen, target=None, args=None)#
Preferred navigation method when a Host may be running. Routing rules:
Host running + target is tabbed — opens a new tab in the active TabManager’s window. A single-instance tab is focused instead of duplicated.
Host running + target is standalone — opens a new chromeless
DetachedWindowin the same Host process.No Host — falls back to
Screen.load(), which spawns a Host subprocess (a no-op in a compiled build, where the exe is the Host).
When a Host is active the call is deferred through the active TabManager’s action queue so
it runs safely from the main loop; pass target to use a specific TabManager’s queue. To
replace the current pane instead of opening a new tab, call tab_manager.navigate(screen)
directly.
# Prefer open() over load() for portable navigation
root.Project.open("WorkOrders")
root.Project.open("WorkOrders", args=["--won", "21930"])
ProjectSettings#
ProjectSettings holds per-project application settings (0.6.0), persisted to
.VIS/settings.json — the path exposed as VINFO.p_settings. You never construct it;
it is attached to every Project as project.Settings.
Settings are flat key -> value pairs. The dotted keys ("window.min_width",
"appearance.font_family") are a grouping convention only — they are treated as opaque
strings, so an app can store its own keys in any namespace it likes.
Unlike project.json, which is shared with screens and groups, settings.json is wholly
owned by this object.
from VIStk.Structures import Project
settings = Project().Settings
settings.get("notifications.duration_ms") # 5000 — framework default
settings.get("my.custom.key", "fallback") # explicit per-call default
settings.set("appearance.font_family", "Consolas")
settings.save() # persist to .VIS/settings.json
Methods:
Member |
Returns |
Description |
|---|---|---|
|
value |
Resolution order: stored override → explicit |
|
|
Sets the override in memory. Call |
|
|
Writes the full resolved set ( |
|
|
The full resolved map ( |
|
|
Drops the override for |
|
|
Materialises a default |
|
|
|
|
|
|
|
|
Class-level table of framework defaults for every known key (see below). |
File lifecycle#
A full default settings.json — every key at its default value — is generated at
VIS new scaffolding and on the first Host launch via ensure_file(), so every
available option is visible and hand-editable rather than hidden behind an empty file.
In memory only genuine overrides are tracked: values that differ from DEFAULTS, plus
any unknown custom keys. That is what makes reset() and in mean “explicitly
customised” even though the file on disk is complete. save() then writes the complete
resolved set back, keeping the file whole.
A missing file is normal (no overrides yet). A corrupt or unreadable file is non-fatal: the failure is warned to stderr and the store falls back to defaults so the app still launches.
All DEFAULTS values are immutable (None / bool / int / str) because
get() and effective() hand the default object back by reference — a mutable default
could be mutated in place by one caller and corrupt every later read. Represent an “empty
list” default as None and let callers coalesce with or [].
The Host saves settings automatically on shutdown, on both the quit_host and
last-window-close paths, and only when dirty — so an app that touched no settings never
bumps the file’s mtime. The session capture (host.last_tabs, window.last_geometry)
is taken while the windows are still open but commits only after every window has closed
without veto, so a vetoed quit leaves settings untouched. Any other caller must invoke
save() itself.
Built-in settings#
Key |
Default |
Meaning |
|---|---|---|
|
|
First-window size. |
|
|
Placement of the first window: |
|
|
Minimum window size; |
|
|
Restore the primary window’s last size and position. |
|
|
Open the first window maximized. |
|
|
Framework-written. Last primary-window geometry as |
|
|
Launch the Host at login (Windows). |
|
|
Reopen the previous session’s screens as tabs. |
|
|
Framework-written. List of tab base names from the last session. |
|
|
Default font family, applied to Tk’s named fonts at launch. |
|
|
Default font size, applied at launch. |
|
|
Stored placeholder for the styles system; no effect yet. |
|
|
Global notification toggle. |
|
|
Default banner/notification duration in milliseconds. |
Appearance settings are applied once at launch — live re-styling of already-open windows is deferred.
Keys absent from DEFAULTS are unknown to the framework and resolve to None (or the
caller’s explicit default), which is what lets an app store its own settings here without
registering them.
The framework surfaces these keys in a built-in Settings window reached from the
Settings entry on every window’s HostMenu; apps contribute their own tabs with
host.register_settings_panel(name, setup_fn). Both are documented alongside the widget
and the Host object.
Screen#
Screen(VINFO) — Represents one screen in the project. Stores metadata and provides the
load() method that switches to this screen.
Attributes:
Attribute |
Type |
Description |
|---|---|---|
|
|
Screen name |
|
|
Python script filename (e.g. |
|
|
Whether this screen is compiled to its own binary |
|
|
Icon name for this screen |
|
|
Optional window title-bar icon for this screen. Only honored when the
screen owns a standalone (chromeless) window — a tabbed screen shares a
chromed window that may host other screens, so its |
|
|
Screen description |
|
|
Screen-specific version number |
|
|
Absolute path to |
|
|
Absolute path to |
|
|
If |
|
|
If |
Methods:
Method |
Description |
|---|---|
|
Loads this screen. Routes through the Host if one is running in-process; otherwise spawns a Host subprocess (skipped in a compiled build). |
|
Asks the Host to close this screen via IPC. Returns |
|
Creates |
|
Creates |
|
Rewrites import blocks in the screen script to include all |
|
Returns all |
|
Temporarily disables release for all other screens |
|
Sends a desktop notification for this app/screen |
Host hooks#
When screen.tabbed is True, the Host imports the screen module and calls the following
functions. All hooks have default no-op stubs in the template.
Lookup priority: If modules/<screen>/m_<screen>.py exists, the Host looks for hooks
there first. The screen script is used as a fallback.
Hook |
Signature |
When called |
|---|---|---|
|
|
Once, when the tab is first opened. Build all widgets into |
|
|
Each time the tab gains focus. |
|
|
Each time the tab gains focus. |
|
|
Each time the tab loses focus or is closed. |
def setup(parent):
Label(parent, text="Hello from Tab").pack()
def configure_menu(menubar):
menubar.set_screen_items([
{"label": "Refresh", "command": refresh},
{"separator": True},
{"label": "Export", "command": export},
], label="MyScreen")
def on_focused():
start_polling()
def on_unfocused():
stop_polling()
IPC — send_to_host#
When the Host is running, any script in the same project can open a screen or send control
messages by calling send_to_host() directly.
from VIStk.Structures import send_to_host
# Open a screen in the running Host
send_to_host("MyApp", "WorkOrders")
# Send the quit signal to stop the Host
send_to_host("MyApp", "__VIS_QUIT__")
# Close a specific screen
send_to_host("MyApp", "__VIS_CLOSE__:Settings")
Parameters:
Parameter |
Type |
Description |
|---|---|---|
|
|
The project |
|
|
Screen name to open, or a reserved control message |
Returns True if the message was delivered, False if no Host port file was found or the
connection failed.
Reserved control messages:
Message |
Effect |
|---|---|
|
Gracefully shuts down the Host |
|
Asks the Host to close the named tab or Toplevel |
Screen.close() is a convenience wrapper around the __VIS_CLOSE__ message:
project = Project()
project.getScreen("Settings").close()
How it works: The Host writes its TCP port number to %TEMP%/<ProjectTitle>_vis_host.port
on startup and deletes it on shutdown. send_to_host() reads that file, connects to
127.0.0.1:<port>, and sends the message as UTF-8 text.
Version#
Version stores a semantic version number as major.minor.patch.
from VIStk.Structures import Version
v = Version("1.3.2")
print(v) # "1.3.2"
v.minor()
print(v) # "1.4.0"
Methods:
Method |
Description |
|---|---|
|
Increments major, resets minor and patch to 0 |
|
Increments minor, resets patch to 0 |
|
Increments patch |
Release#
Release(Project) — Manages the build and release pipeline. Used internally by
VIS release. You do not normally instantiate this directly.
from VIStk.Structures import Release
rel = Release(flag="beta", type="Minor")
rel.release() # compile with Nuitka, bundle assets, create installer
rel.restoreAll() # undo any screen isolation
The release pipeline uses Nuitka for compilation (not PyInstaller). The installer and uninstaller are built with PyInstaller and cached between releases.
Choosing the C compiler#
Nuitka translates each module to C and then hands it to a real C compiler.
release_info.compiler in project.json picks which one:
"release_info": {
"location": "./dist/",
"hidden_imports": [],
"compiler": "clang"
}
Platform |
Accepted values |
Notes |
|---|---|---|
Windows |
|
|
Linux |
|
|
macOS |
|
The platform-native compiler; no flag needed. |
Omitting the key (or leaving it empty) selects the platform default, which emits exactly the flags the pipeline used before the key existed — existing projects build identically.
MinGW64 gcc is deliberately not offered on Windows. It is a different C runtime rather than just a different compiler, and Nuitka silently falling back to a non-MSVC Windows toolchain is what produced the corrupt frozen-bytecode binaries in #35.
Release._check_compiler() validates the value before any compilation starts and
aborts with the accepted set on a typo or a platform mismatch. On Windows it locates
the Visual Studio installation with vswhere.exe (not $PATH — cl.exe is
only on PATH inside a Developer Command Prompt), and when clang is selected it
additionally requires clang-cl.exe under VC/Tools/Llvm. That second check
matters because the pipeline passes --assume-yes-for-downloads: without it, a
selected compiler Nuitka cannot find turns into a silent toolchain download.
Note
clang-cl.exe comes from the C++ Clang Compiler for Windows individual
component in the Visual Studio Installer. A VS install can already have a
VC/Tools/Llvm directory holding only clang-format.exe /
clang-tidy.exe — those ship with unrelated components and are not a compiler,
which is why the check probes for clang-cl.exe by name.
Warning
The component has to be installed in the Visual Studio installation Nuitka
selects, which is not necessarily the one you reach for. On a machine with
several installations, _list_msvc() ranks them the way SCons (and therefore
--msvc=latest) does: highest version first, then Enterprise > Professional >
Community > BuildTools on a version tie.
So a machine carrying both a Community and a BuildTools 2022 install at the same
version compiles with Community, even though vswhere lists BuildTools
first. Installing clang into BuildTools alone leaves the build failing in the
Scons backend with Visual Studio has no Clang component found at .... Run
VIS release (or the pre-flight directly) to see which installation is
selected and whether it has clang-cl:
from VIStk.Structures._Release import Release
for version, product, path in Release._list_msvc():
print(product, version, bool(Release._find_clang_cl(path)), path)
print("selected:", Release._find_msvc())
$PATH is deliberately not consulted — Nuitka derives the clang directory from
wherever cl.exe resolved, so a standalone LLVM on PATH would be a false pass.
Object files and Nuitka’s compile cache are compiler-specific, so a non-default
compiler builds under its own root — build/<pendix>-<compiler>/ instead of
build/<pendix>/. Switching between msvc and clang therefore keeps both
caches warm rather than forcing a cold rebuild each way. Deliverable paths
(dist/<pendix>/) are unaffected; the compiler is a build detail, not part of the
release name.
Compilation order#
Compilations are grouped into three categories and executed in this order:
Required Packages — Shared libraries (e.g.
pywomlib,VIStk) compiled as.pydmodules intoshared/.Screens — Every tabbed screen compiled as a
.pydmodule intoScreens/. The default screen is included.Binaries — Standalone screens (
tabbed=false, release=true) compiled as.exefiles, plus the Host binary.
The Host is compiled last with --standalone --follow-imports and bundles the modules/
and Screens/ packages so that screen .pyd files can resolve their imports at runtime.
Progress is displayed on a single overwriting line:
PYWOM Release - 19 Compilations
[5/19] Screens 3/14 - WOMServant — C 12/45
If any compilation fails, the release aborts immediately with a clear error message.
No .py source files are ever included in the release — if a .pyd compilation fails,
the release fails.
Methods:
Method |
Description |
|---|---|
|
Runs the full pipeline: version bump → Nuitka compilation → asset bundling → installer assembly. Warns if no default screen is set. |
|
Compiles top-level packages from |
|
Compiles screens. |
|
Compiles the Host as a standalone Nuitka executable. |
|
Copies assets (Icons, Images, |
|
Increments the project version number in |