scistudio.blocks.app
Canonical import root: from scistudio.blocks.app import ...
Self-contained public-API reference — 7 symbols from this module's __all__, with signatures and docstrings inlined (ADR-052 §7). Generated; do not hand-edit.
AppBlock — class
Stability: provisional · Since 0.3.1
class AppBlock(Block)
Hand a workflow step off to an external GUI application.
Subclass this (or configure one directly) to wrap a desktop program — an image viewer, an analysis GUI, a file converter — as a workflow block. You point it at an executable; the block writes its inputs into a shared "exchange" folder on disk, launches the program, waits for the program to write result files into an output folder, and packs those files back into the block's output ports.
While the external program is open, the block reports that it is paused and waiting, so the rest of the workflow knows it is blocked on you (or on the program) rather than stuck. Once result files appear and stop changing, the block finishes on its own.
Config-driven tools: some command-line tools do not read the exchange
directory themselves — they expect a generated config/parameter file listing
each input by path and a non-default command line (for example
tool --config config.xml). Override prepare_launch to generate
that file from the staged inputs and return the launch arguments; the
default implementation is a no-op and keeps the standard behavior.
Ports and config:
- Reads an optional input port named data by default; you can add or
rename input and output ports in the port editor. Each output port may
declare a file extension so that result files are sorted into ports
by their type (for example .csv files to one port, .png to
another).
- Emits one output Collection per output port. With no ports declared it
falls back to emitting one
scistudio.core.types.artifact.Artifact per output file.
- The app_command config field (the path to the executable) is
required; the optional output_dir field chooses where results are
saved.
Example: >>> class FijiBlock(AppBlock): ... app_command = "/Applications/Fiji.app" ... output_patterns = ["*.tif"]
Members
prepare_launch(self, exchange_dir: 'Path', output_dir: 'Path', config: 'BlockConfig') -> 'list[str] | None'—provisional· Since0.3.2— Customise the launch of the external tool after inputs are staged.run(self, inputs: 'dict[str, Collection]', config: 'BlockConfig') -> 'dict[str, Collection]'—provisional· Since0.3.1— Serialise inputs, launch the external app, and collect its outputs.
BlockCancelledByAppError — exception
Stability: provisional · Since 0.3.1
class BlockCancelledByAppError(Exception)
Raised when a block's external application exits without producing output.
A block that launches a separate desktop application raises this from its
run method when the application closes before writing any result. The
worker reports it to the engine as a cancellation, so the run is recorded as
cancelled rather than failed.
ExternalAppBridge — protocol
Stability: provisional · Since 0.3.1
class ExternalAppBridge(Protocol)
ExternalAppBridge(*args, **kwargs)
The contract an AppBlock uses to talk to an external program.
A bridge is the four-step adapter between a block and a desktop application:
write the inputs somewhere the app can read them, launch the app, watch for
the files it produces, and load those files back as results. Provide your
own implementation to support an app that needs a different on-disk layout
or launch convention; the default is FileExchangeBridge.
Because this is a runtime-checkable protocol, any object with matching
prepare / launch / watch / collect methods satisfies it
without subclassing.
Members
prepare(self, inputs: 'dict[str, Any]', exchange_dir: 'Path') -> 'None'— unmarked — see the module source / ADR-052 — Write inputs into exchange_dir so the external app can read them.launch(self, command: 'str', exchange_dir: 'Path') -> 'Any'— unmarked — see the module source / ADR-052 — Start the external application and return a handle to its process.watch(self, exchange_dir: 'Path', patterns: 'list[str]') -> 'list[Path]'— unmarked — see the module source / ADR-052 — Wait for output files matching patterns and return their paths.collect(self, output_files: 'list[Path]') -> 'dict[str, Any]'— unmarked — see the module source / ADR-052 — Load output_files into a mapping of result name to value.
FileExchangeBridge — class
Stability: provisional · Since 0.3.1
class FileExchangeBridge
Default ExternalAppBridge: swap data with an app through files.
This is the bridge AppBlock uses unless you supply your own. It
serialises each input to a file (or to JSON for scalars and unknown values)
under an exchange directory, launches the executable with that directory as
its working directory, watches an outputs subfolder for files the app
writes, and turns each output file into an
scistudio.core.types.artifact.Artifact.
Example:
A block rarely builds this directly — AppBlock constructs and
drives the bridge. Manual use looks like::
bridge = FileExchangeBridge()
bridge.prepare({"image": my_array}, exchange_dir)
proc = bridge.launch("/Applications/Fiji.app", exchange_dir)
Members
prepare(self, inputs: 'dict[str, Any]', exchange_dir: 'Path', *, input_ports: 'list[dict[str, Any]] | None' = None, registry: 'Any | None' = None) -> 'None'—provisional· Since0.3.1— Write each input into exchange_dir and record a manifest.launch(self, command: 'str | list[str]', exchange_dir: 'Path', argv_override: 'list[str] | None' = None) -> 'subprocess.Popen[bytes]'—provisional· Since0.3.1— Start the external application and return its process handle.watch(self, exchange_dir: 'Path', patterns: 'list[str]') -> 'list[Path]'—provisional· Since0.3.1— Wait for output files in exchange_dir and return their paths.collect(self, output_files: 'list[Path]') -> 'dict[str, Any]'—provisional· Since0.3.1— Wrap each output file as an Artifact, keyed by its file stem.
FileWatcher — class
Stability: provisional · Since 0.3.1
class FileWatcher
FileWatcher(directory: 'Path', patterns: 'list[str]', timeout: 'int | None' = None, poll_interval: 'float' = 0.5, process_handle: 'Any | None' = None, stability_period: 'float' = 2.0, done_marker: 'str | None' = None) -> 'None'
Watch a directory for new or changed files matching glob patterns.
AppBlock uses this to tell when an external application has finished
writing its results. After start takes a snapshot of the directory,
wait_for_output blocks until files that appear or change settle
(their modification time stops moving), then returns them. If a process
handle is supplied and that process exits before producing any output, it
raises ProcessExitedWithoutOutputError.
A process handle is any object whose liveness can be probed: a plain
subprocess.Popen (alive while poll() returns None) or any
object exposing an is_alive() method.
Example: >>> from pathlib import Path >>> watcher = FileWatcher(Path("/tmp/outputs"), patterns=[".csv"]) >>> watcher.start() >>> files = watcher.wait_for_output() # blocks until .csv files settle >>> watcher.stop()
Members
start(self) -> 'None'—provisional· Since0.3.1— Begin watching the directory for changes.wait_for_output(self) -> 'list[Path]'—provisional· Since0.3.1— Block until output files are detected and return their paths.stop(self) -> 'None'—provisional· Since0.3.1— Stop watching and release resources.
ProcessExitedWithoutOutputError — exception
Stability: provisional · Since 0.3.1
class ProcessExitedWithoutOutputError(RuntimeError)
The external app quit before writing any output files.
Raised by FileWatcher.wait_for_output when the watched process ends
while no expected output has appeared. AppBlock treats this as the
step being cancelled rather than failing.
validate_app_command — function
Stability: provisional · Since 0.3.1
validate_app_command(command: 'str | list[str]') -> 'list[str]'
Check an app command is safe to launch and split it into arguments.
Use this before handing a user-supplied command to subprocess so a
malicious or malformed entry cannot inject shell behaviour. It rejects any
command containing shell metacharacters, confirms the executable can be
found (on PATH or as an absolute path, including a macOS .app
bundle), and returns the command as a clean argument list.
Args: command: The command to validate, either a single string (an executable name or path, optionally with arguments) or a pre-split argument list.
Returns:
The validated command split into a list of arguments safe to pass to
subprocess.Popen.
Raises: ValueError: If the command is empty, contains shell metacharacters, or names an executable that cannot be resolved.
Example: >>> validate_app_command("python --version") ['python', '--version']