scistudio.tutorials
Canonical import root: from scistudio.tutorials import ...
Self-contained public-API reference — 16 symbols from this module's __all__, with signatures and docstrings inlined (ADR-052 §7). Generated; do not hand-edit.
Action — type-alias
Stability: unmarked — see the module source / ADR-052
type-alias — see the module source for the value.
Condition — class
Stability: provisional · Since 0.3.4
class Condition
Condition(term: 'str', args: 'Mapping[str, Any]' = <factory>, operands: 'tuple[Condition, ...]' = ()) -> None
A parsed done_when: one vocabulary term, or an all/any of them.
Build one with parse_condition rather than by hand — that is where a
term outside VOCABULARY, or an argument a term does not accept, is
rejected with a message naming the offending field. Judge one with
evaluate.
A driver only needs this type when it defers part of a step to the core
vocabulary; a step whose condition the vocabulary cannot express has no
Condition at all, which is why DeclaresConditions.condition may
return None.
Members
is_combinator(self) -> 'bool'— unmarked — see the module source / ADR-052 — True when this node is anallor anany.terms(self) -> 'frozenset[str]'— unmarked — see the module source / ADR-052 — Every vocabulary term this condition uses, recursively.
ConditionValidationError — exception
Stability: provisional · Since 0.3.4
class ConditionValidationError(ValueError)
A done_when was rejected at manifest validation (FR-049).
Raised for an unknown term, a malformed shape, or missing term arguments.
scistudio.tutorials.manifest re-raises it as a
ManifestValidationError naming the file.
CopyAction — class
Stability: provisional · Since 0.3.4
class CopyAction
CopyAction(source: 'str', destination: 'str') -> None
Copy one asset directory into the tutorial project.
Existing files under the destination are overwritten, for the same reason
WriteAction overwrites.
DeclaresConditions — protocol
Stability: provisional · Since 0.3.4
class DeclaresConditions(Protocol)
DeclaresConditions(*args, **kwargs)
An optional capability: a driver that can name a step's condition.
Not part of TutorialDriver, because FR-038 fixes that interface at
four members and a package driver must be able to implement conditions the
vocabulary cannot express — which by definition have no
scistudio.tutorials.conditions.Condition to return.
A driver that can answer lets the session skip re-evaluating on an event that maps to none of the step's terms (FR-050). A driver that cannot is re-evaluated on every mapped event instead. The two produce identical responses, which is what FR-040 constrains; only the number of evaluations differs, and evaluation is side-effect free (FR-055).
Members
condition(self, context: 'DriverContext') -> 'Condition | None'— unmarked — see the module source / ADR-052 — Return the conditioncontext.step_idis judged by, if it has one.
DriverContext — class
Stability: provisional · Since 0.3.4
class DriverContext
DriverContext(key: 'TutorialKey', tutorial_dir: 'Path', project_dir: 'Path | None', step_id: 'str | None', satisfied_step_ids: 'tuple[str, ...]' = ()) -> None
Everything a driver is told about the session asking the question.
Deliberately not the session object: a driver reads position and location and nothing else, so it cannot advance the session, end it, or start another one. That is what keeps FR-043's one-session rule and FR-044's containment properties of the runtime rather than of every driver.
ProductState — protocol
Stability: provisional · Since 0.3.4
class ProductState(Protocol)
ProductState(*args, **kwargs)
The one injected port through which conditions read product truth.
FR-046: judging is a backend concern, evaluated against the registries, the
workflow definition, the run records, git, and the filesystem. This module
reaches all of that through this protocol rather than by importing the API
runtime, which keeps api -> tutorials a one-way edge and keeps the
package testable without a FastAPI app (checklist §6.1.2, §6.1.3).
Every member is a pure read (FR-055).
Members
workflow(self) -> 'WorkflowDefinition | None'— unmarked — see the module source / ADR-052 — The active workflow definition, orNonewhen none is open.block_type_names(self) -> 'frozenset[str]'— unmarked — see the module source / ADR-052data_type_names(self) -> 'frozenset[str]'— unmarked — see the module source / ADR-052previewer_type_ids(self) -> 'frozenset[str]'— unmarked — see the module source / ADR-052plot_bindings(self) -> 'tuple[tuple[str, str, str], ...]'— unmarked — see the module source / ADR-052 —(plot_id, node_id, output_port)for every plot that exists.run_records(self) -> 'tuple[RunSummary, ...]'— unmarked — see the module source / ADR-052 — The recent runs, most recent first.port_has_output(self, node_id: 'str', port: 'str') -> 'bool'— unmarked — see the module source / ADR-052git_branches(self) -> 'frozenset[str]'— unmarked — see the module source / ADR-052git_current_branch(self) -> 'str | None'— unmarked — see the module source / ADR-052library_entries(self) -> 'frozenset[tuple[str, str]]'— unmarked — see the module source / ADR-052 —(kind, name)for every entry in the tutorial-scoped library.interactions_completed(self) -> 'frozenset[str]'— unmarked — see the module source / ADR-052pages_reached(self) -> 'frozenset[str]'— unmarked — see the module source / ADR-052ui_events(self) -> 'frozenset[str]'— unmarked — see the module source / ADR-052
ReplayAction — class
Stability: provisional · Since 0.3.4
class ReplayAction
ReplayAction(surface: 'str', segments: 'tuple[ReplaySegment, ...]') -> None
Replay scripted material into one named surface (FR-061, FR-061a, FR-061b).
How a tutorial shows a conversation that would otherwise need a live model: the material is scripted, and the surface renders it as though it had just arrived. A segment may bind to the file actions that must land first, so text claiming a file was written cannot be read before the file exists.
RunSummary — class
Stability: provisional · Since 0.3.4
class RunSummary
RunSummary(run_id: 'str', workflow_id: 'str', succeeded: 'bool', succeeded_node_ids: 'frozenset[str]' = frozenset()) -> None
The read-only view of one recorded run that run_succeeded needs.
Deliberately not scistudio.core.lineage.record.RunRecord: this module
must not depend on the lineage schema, and the only facts a condition can
ask about are which workflow the run belonged to, whether it succeeded, and
which nodes within it succeeded. The layer that satisfies
ProductState projects the lineage rows into this.
StepView — class
Stability: provisional · Since 0.3.4
class StepView
StepView(id: 'str', index: 'int', total: 'int', title: 'str | None' = None, say: 'str | None' = None, highlight: 'Mapping[str, Any] | None' = None, route_to: 'str | None' = None, prefill: 'tuple[Mapping[str, Any], ...]' = (), awaiting_continue: 'bool' = False, satisfied: 'bool' = False) -> None
What one step looks like, for every driver alike (FR-040, FR-041).
The closed set of fields a driver may influence, and the return type of
TutorialDriver.step_view. A driver may return this class, any object
carrying these attributes, or a mapping of them; of reduces all
three to a plain StepView and drops anything else, which is what makes a
package driver and core's ManifestDriver indistinguishable to
everything downstream.
Only id, index and total are required. The rest
describe what the step shows and what it is waiting for:
title,say— the step's heading and body text.highlight— the product surface to point at; the manifest'sHIGHLIGHT_TARGETSnames what can be addressed.route_to— the surface to open before the step is readable.prefill— values to seed into a form or editor.awaiting_continue— the step ends on the reader acknowledging it rather than on a condition.
satisfied is not a driver's field. A driver reports what a step
is; whether it currently holds is the runtime's answer, attached after
the driver's view has been reduced.
Members
of(cls, raw: 'Any') -> 'StepView'— unmarked — see the module source / ADR-052 — Return raw as a plainStepView, dropping anything else.
TutorialDriver — protocol
Stability: provisional · Since 0.3.4
class TutorialDriver(Protocol)
TutorialDriver(*args, **kwargs)
What a package implements to own its tutorial's logic (FR-038, FR-040).
Most tutorials need none of this. A tutorial.yaml written against the
published schema is run by core's ManifestDriver, and that is the
path to prefer. Implement this protocol only when the tutorial's logic
cannot be expressed as a manifest — a step judged by a condition the
vocabulary has no term for, or a sequence that depends on what the reader
did earlier.
The protocol is four methods and stays four: the runtime asks what the current step looks like, whether it is satisfied, what to do on entering it, and which step comes next. There is no fifth question and no hook to add one.
Name the class from the manifest, and it is imported only when a reader starts that tutorial (FR-021); an import failure ends that one session and leaves every other tutorial listed and startable (FR-044).
Three properties are worth knowing before writing one, because each removes work rather than adding it:
- Core owns rendering. Whatever
step_viewreturns is normalised throughStepView.ofat the boundary, so extra attributes and extra mapping keys are dropped rather than reaching a response (FR-041). A driver cannot introduce a display primitive or ship a frontend asset, and correspondingly never has to describe one. - The session holds the cursor. A driver is asked about the step a
DriverContextnames, not about "its" current step, so it persists nothing and survives a backend restart without any state of its own (FR-037). - The core evaluator is available.
scistudio.tutorials.conditions.evaluateandscistudio.tutorials.conditions.parse_conditionare public, sois_satisfiedcan defer every term the vocabulary already covers and implement only the remainder (FR-042).
A driver that answers most steps from its manifest and one step itself::
from scistudio.tutorials import (
Condition, DriverContext, ProductState, StepView, evaluate,
)
class MyDriver:
def __init__(self, manifest, key):
self._steps = list(manifest.steps)
def step_view(self, context: DriverContext) -> StepView:
step = self._step(context.step_id)
index = self._steps.index(step)
return StepView(
id=step.id, index=index, total=len(self._steps),
title=step.title, say=step.say,
)
def is_satisfied(self, context: DriverContext, product: ProductState) -> bool:
step = self._step(context.step_id)
if step.id == "calibration-converged":
return self._converged(product) # no vocabulary term for this
return step.done_when is None or evaluate(step.done_when, product)
def entry_actions(self, context: DriverContext):
return self._step(context.step_id).actions
def advance(self, context: DriverContext) -> str | None:
if context.step_id is None:
return self._steps[0].id
nxt = self._steps.index(self._step(context.step_id)) + 1
return self._steps[nxt].id if nxt < len(self._steps) else None
Optionally also implement DeclaresConditions, which lets the
session skip re-evaluating a step on events that cannot affect it. It
changes how often is_satisfied is called and never what the reader
sees.
Members
step_view(self, context: 'DriverContext') -> 'Any'— unmarked — see the module source / ADR-052 — Return the view ofcontext.step_id.is_satisfied(self, context: 'DriverContext', product: 'ProductState') -> 'bool'— unmarked — see the module source / ADR-052 — Return whethercontext.step_idis satisfied by current product state.entry_actions(self, context: 'DriverContext') -> 'Sequence[Action]'— unmarked — see the module source / ADR-052 — Return the actions to perform on enteringcontext.step_id(FR-056).advance(self, context: 'DriverContext') -> 'str | None'— unmarked — see the module source / ADR-052 — Return the id of the step to enter aftercontext.step_id.
TutorialKey — class
Stability: provisional · Since 0.3.4
class TutorialKey
TutorialKey(source_kind: 'str', source_id: 'str', tutorial_id: 'str') -> None
A tutorial's identity: its source and its id (FR-019, FR-075).
source_id is "" for core, the distribution name for a package, and
"user" or "project" for the two local tiers — the same spelling the
HTTP contract and the progress groups use, so one value travels from
discovery through the project marker to the progress record without being
re-encoded at each boundary.
Members
core(cls, tutorial_id: 'str') -> 'TutorialKey'— unmarked — see the module source / ADR-052 — Return the key of a core tutorial, whose source id is empty.group(self) -> 'tuple[str, str]'— unmarked — see the module source / ADR-052 — Return the progress group this tutorial belongs to (FR-076).
VOCABULARY — constant
Stability: unmarked — see the module source / ADR-052
constant — see the module source for the value.
WriteAction — class
Stability: provisional · Since 0.3.4
class WriteAction
WriteAction(source: 'str', destination: 'str') -> None
Write one asset file into the tutorial project.
Available at any step, not only at bootstrap (FR-058). An existing destination is overwritten (spec §2 Edge Cases).
evaluate — function
Stability: provisional · Since 0.3.4
evaluate(condition: 'Condition', state: 'ProductState') -> 'bool'
Judge condition against state.
Side-effect free (FR-055): no file is created, no registry is mutated, no run is triggered. Called on step entry (FR-054), on a mapped event (FR-050), and on an explicit request (FR-053) — never on a timer (FR-051).
parse_condition — function
Stability: provisional · Since 0.3.4
parse_condition(raw: 'Any', *, field_name: 'str' = 'done_when') -> 'Condition'
Parse a done_when mapping, rejecting anything outside the vocabulary.
The accepted shape is a single-key mapping: {term: {args}} for a term,
{all: [condition, ...]} or {any: [...]} for a combinator. Called at
manifest validation (FR-049), never at evaluation.