cursus.core.base.step_interface¶
StepInterface — the single Pydantic-validated data structure for a .step.yaml.
Combines what was previously ScriptContract + StepSpecification into one validated model. This is the single message passed between dep resolver, builder, and assembler.
It is intentionally a superset of the legacy data classes so it can stand in for all of them during migration:
StepInterface.contract(ContractSection) is a drop-in forScriptContract/StepContract: it exposesentry_point,expected_input_paths,expected_output_paths,expected_arguments,required_env_vars,optional_env_vars,framework_requirementsanddescription.StepInterface/StepInterface.spec(SpecSection) are a drop-in forStepSpecification:step_type,node_type,dependencies,outputs,get_dependency(),get_output(),get_output_by_name_or_alias(),list_required_dependencies(),list_optional_dependencies(),list_all_output_names(),validate_specification(),validate_contract_alignment()andscript_contract.Each
DependencyDecl/OutputDeclis a drop-in forDependencySpec/OutputSpec: it carrieslogical_name(auto-populated from the dict key), exposesdependency_type/output_type(aliases oftype), and supportsmatches_name_or_alias().
Validation rules: - Contract inputs and spec dependencies must have matching keys. - Contract outputs and spec outputs must have matching keys. - Paths (when present) must be valid SageMaker paths (processing OR training). - entry_point (when present) must be a .py file.
entry_point and the port path fields are Optional: script-less SageMaker
steps (CreateModel / Transform — e.g. xgboost_model, pytorch_model, batch_transform)
declare them as null in YAML.
- class InputPort(*, path=None, required=True, channels=<factory>)[source]¶
Bases:
BaseModelOne contract input declaration.
- channels: List[str]¶
Optional SageMaker training sub-channels this single input fans out into (e.g.
[train, val, test]). When set, the TrainingHandler creates oneTrainingInputper sub-channel under<path>/<channel>/instead of a single channel. This is per-step DATA (the channel layout the script expects), so it lives in the.step.yaml— not hardcoded in the handler. Empty/None ⇒ the input maps to a single channel (see TrainingHandler).
- model_config: ClassVar[ConfigDict] = {}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class OutputPort(*, path=None)[source]¶
Bases:
BaseModelOne contract output declaration.
- model_config: ClassVar[ConfigDict] = {}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class EnvVars(*, required=<factory>, optional=<factory>)[source]¶
Bases:
BaseModelEnvironment variable declarations.
- model_config: ClassVar[ConfigDict] = {}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class ComputeSpec(*, kind=None, framework_version_field=None, framework_version_default=None, py_version_field=None, sdk_class=None, instance_size_mode='large_or_small', kms_network=False, retrieve_image=False, framework_name=None, lock_training_region=False, locked_region='us-east-1', requires='none')[source]¶
Bases:
BaseModelDeclarative spec for the step’s COMPUTE object — the processor/estimator/model/transformer the builder constructs (FZ 31e1d3k).
Every VALUE is a config field, so the compute object is fully constructable from config + this descriptor — which says WHICH SDK class and WHICH config fields. Lives in the
.step.yamlso it is surfaced to users (thesteps patternsview) and lets the builder template build the compute generically, replacing the near-identical per-step_create_processor/_create_estimatorfactories. Empty (kind=None) ⇒ the step keeps its own factory.- kind: str | None¶
sklearn | xgboost | framework | script (processors) · estimator · model · transformer · None.
- framework_version_field: str | None¶
The config attr holding the framework version (e.g.
processing_framework_version).
- framework_version_default: str | None¶
Default framework version when the field is absent on the config — several steps used
getattr(config, "processing_framework_version", "<default>")(defaults vary per step, e.g.1.0-1/1.2-1).None⇒ the field must be present.
- py_version_field: str | None¶
The config attr holding the py version (framework processors / estimators).
- sdk_class: str | None¶
the SDK class NAME to use as the estimator_cls / model class (e.g.
PyTorch,SKLearn,XGBoost,PyTorchModel).- Type:
For
frameworkprocessors / estimators / models
- instance_size_mode: str¶
large_or_small(theuse_large_processing_instanceternary) orfixed(a single field).- Type:
How the processing instance type is chosen
- kms_network: bool¶
set the KMS volume key + shared network config + the ECR-from-role image. A genuinely special, declared-once deviation.
- Type:
scriptkind only (EdxUploading)
- retrieve_image: bool¶
explicitly retrieve the training image_uri (PyTorch-for-LightGBM).
- Type:
estimatorkind only
- framework_name: str | None¶
the framework NAME passed to
image_uris.retrievefor the INFERENCE image (xgboost/pytorch). Distinct fromsdk_class(the model CLASS, e.g.XGBoostModel): the class instantiates the model, this names the container image to retrieve.- Type:
modelkind
- lock_training_region: bool¶
training images/jobs are forced to a fixed region (
us-east-1) — an explicit platform constraint, NOT a bug. This is a TOGGLEABLE pattern: a step opts into locking (lock_training_region: true); to run in standard (unlocked) mode it setslock_training_region: falsehere (or via config) — no code change. When False, the region comes fromconfig.aws_region(the normal region).- Type:
estimatorimage-retrieval region locking. SAIS RESTRICTION
- requires: str¶
DEPENDENCY AXIS — the 3rd-party package this COMPUTE pattern needs at BUILD time (FZ 31e1d3l).
nonefor the sagemaker-only kinds (sklearn/xgboost/framework/estimator/model/transformer);mods_workflow_coreONLY for thescriptkind withkms_network(the EdxUploading ScriptProcessor, which lazily importsKMS_ENCRYPTION_KEY_PARAM/PROCESSING_JOB_SHARED_NETWORK_CONFIGinbuilder_base._create_compute). This is a CONSEQUENCE ofkms_network— the validator keeps it consistent so it can’t drift, and it is declared in the.step.yamlso the mods-vs-native split is visible (steps patterns).
- model_config: ClassVar[ConfigDict] = {}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- model_post_init(context, /)¶
This function is meant to behave like a BaseModel method to initialize private attributes.
It takes context as an argument since that’s what pydantic-core passes when calling it.
- Parameters:
self (BaseModel) – The BaseModel instance.
context (Any) – The context.
- class JobArgDecl(*, flag, source='')[source]¶
Bases:
BaseModelDECLARATIVE record of one CLI argument the step’s script accepts (FZ 31e1d3h).
Documentation / alignment / introspection only — the TRUE argument list is built at runtime by
config.get_job_arguments()(config is the single source). This just makes the script’s argument surface visible in the.step.yaml(the analog ofenv_varsdeclaring names).- source: str¶
The config attribute the value comes from (e.g.
job_type). Empty for a bare boolean flag.
- model_config: ClassVar[ConfigDict] = {}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class RegistrySection(*, sagemaker_step_type=None, step_assembly=None, requires='none', description='')[source]¶
Bases:
BaseModelThe ‘registry’ section of a .step.yaml — the construction binding + its 3rd-party footprint.
Previously this YAML block was silently dropped (StepInterface had no field for it), so the
step_assemblyand the create-step dependency had no declaration home. It is now a real section:sagemaker_step_type+step_assemblyselect the PatternHandler, andrequiresdeclares the create_step axis’s BUILD-time 3rd-party dependency.- sagemaker_step_type: str | None¶
The SageMaker verb that selects the PatternHandler (Processing / Training / CreateModel / Transform / the SAIS verbs). Mirrors the registry’s
sagemaker_step_type.
- step_assembly: str | None¶
DEPRECATED — moved to
patterns.step_assembly(FZ 31e1d3f1). Kept only as a back-compat read for any not-yet-migrated YAML;_auto_bind_handler+io_viewpreferpatterns.step_assembly. No .step.yaml in this package declares it here anymore.
- requires: str¶
DEPENDENCY AXIS — the 3rd-party package the CREATE_STEP pattern needs at BUILD time (FZ 31e1d3l).
nonefor the native (sagemaker-only) handlers;secure_ai_sandbox_workflow_python_sdkfor the SDKDelegation steps whose builder module imports a SAIS Step class at module level (Registration / CradleDataLoading / DataUploading / RedshiftDataLoading — fatal-on-load if the SDK is absent). Declared here so the mods/SAIS-vs-native split is authored data in the.step.yamland visible insteps patterns; a conformance gate keeps it equal to the builders’ actual module-level SAIS imports.
- model_config: ClassVar[ConfigDict] = {}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- model_post_init(context, /)¶
This function is meant to behave like a BaseModel method to initialize private attributes.
It takes context as an argument since that’s what pydantic-core passes when calling it.
- Parameters:
self (BaseModel) – The BaseModel instance.
context (Any) – The context.
- class PatternsSection(*, step_assembly=None, include_job_type_in_path=True, direct_input_keys=<factory>)[source]¶
Bases:
BaseModelThe ‘patterns’ section of a .step.yaml — the per-axis STRATEGY-SELECTION knobs (FZ 31e1d3f1).
This is the BLUEPRINT that guides how the handlers combine/inject behavior per axis, so a step’s implementation is NOT hard-wired in its builder shell. Distinct from
contract(script-shaped I/O data),compute(the SDK compute object),registry(discovery + 3rd-party deps), andspec(DAG wiring)._auto_bind_handlerreads these into the bound handler’s knobs so editing the YAML steers the build with no Python change.use_step_argsis intentionally NOT a field here: it is DERIVED fromstep_assembly(thestep_argsstrategy preset setsuse_step_args: True,codesets False) — so it can never disagree with the routing verb.- step_assembly: str | None¶
Processing sub-verb that joins
registry.sagemaker_step_typeto pick the handler:code(2A,ProcessingStep(code=...)) |step_args(2B,processor.run()) |delegation(SDKDelegation).None⇒ the handler’s default (codefor Processing).
- include_job_type_in_path: bool¶
output_path_tokenwas REMOVED (FZ 31e1d3f1b). The output-destination S3 prefix is DERIVED from the step name —canonical_to_snake(step_type)— not a declarable field: it corresponds to the step name by convention, and the historical deviations were non-standard. Whetherconfig.job_typeis a segment of the synthesized output destination.- Type:
NOTE
- direct_input_keys: List[str]¶
Logical input names passed straight through to the processor (not spec×contract joined) — the template-provided direct input allowlist.
- as_knobs()[source]¶
The HANDLER_KNOBS the bound handler reads — only NON-DEFAULT entries, so an unset field falls through to the strategy preset/contract default exactly as the old class-attr knobs did.
step_assemblyis routing (passed separately toresolve_handler), not a knob.
- model_config: ClassVar[ConfigDict] = {}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- model_post_init(context, /)¶
This function is meant to behave like a BaseModel method to initialize private attributes.
It takes context as an argument since that’s what pydantic-core passes when calling it.
- Parameters:
self (BaseModel) – The BaseModel instance.
context (Any) – The context.
- class ContractSection(*, entry_point=None, source_dir=False, output_path_token=None, include_job_type_in_path=True, inputs=<factory>, outputs=<factory>, circular_ref_check=False, skip_inputs=<factory>, input_source_overrides=<factory>, sink=False, compute=<factory>, arguments=<factory>, job_arguments=<factory>, env_vars=<factory>, computed_env_paths=<factory>, framework_requirements=<factory>, runtime_requires=<factory>, description='')[source]¶
Bases:
BaseModelThe ‘contract’ section of a .step.yaml — script execution requirements.
Drop-in for the legacy ScriptContract / StepContract: the
expected_*/required_env_vars/optional_env_varsaccessors flatten the structured ports back to theDict[str, str]/List[str]shapes consumers expect.- source_dir: bool¶
Whether this step’s script needs its whole directory uploaded (sibling modules) — i.e.
processor.run(code=entry_point, source_dir=<dir>)— vs a self-contained single scriptprocessor.run(code=<full_script_path>). This is per-step DATA (a script-packaging fact), so it lives in the.step.yamlrather than being inferred from the processor class. The ProcessingHandler reads it as thesplit_source_dirswitch. Default False (self-contained). NOTE: a True value requires a FrameworkProcessor (ScriptProcessor.runhas nosource_dir).
- output_path_token: str | None¶
OPT-IN override for the output-destination S3 prefix segment. Default
None⇒ the segment is DERIVED from the step name (canonical_to_snake(step_type)), the convention for ~all steps. When set to a non-empty string it is used VERBATIM as that segment instead of the derived token — needed when an EXTERNAL consumer keys off a fixed S3 folder name that does not match the cursus step name (e.g. PIPER scans<pipeline>/Model_Metric_Generation_Step/for.metricfiles). This re-introduces the field removed in FZ 31e1d3f1b, but as an explicit escape hatch (default-off) rather than a routinely-set knob — the derived convention still holds by default.
- include_job_type_in_path: bool¶
Whether
config.job_typeis a segment of the synthesized output destination. The other_get_outputsaxis: some steps put job_type in the path, some don’t. Default True. The ProcessingHandler reads this as theinclude_job_type_in_pathknob.
- outputs: Dict[str, OutputPort]¶
- circular_ref_check: bool¶
Per-step input-resolution deviations from the standard spec×contract loop (FZ 31e1d3i), read by ProcessingHandler.get_inputs so the step needs no _get_inputs override:
circular_ref_check — run the PipelineVariable circular-reference guard before mapping. skip_inputs — declared dependencies the script loads internally (not mounted as inputs). input_source_overrides {logical_name: config_attr} — take the input SOURCE from a config
attr/method (config is the value source) instead of the resolved dependency value.
- sink: bool¶
A SINK step produces no outputs — ProcessingHandler.get_outputs returns
[](FZ 31e1d3i), so a sink step (e.g. an uploader) needs no _get_outputs override.
- compute: ComputeSpec¶
BACK-COMPAT MIRROR of the top-level
StepInterface.compute(FZ 31e1d3k). The compute descriptor was promoted to a top-level.step.yamlsection (peer ofcontract/spec) because it describes the BUILDER’s compute object, not the script contract — script-less steps (CreateModel/Transform) have a near-empty contract but a full compute. This field is kept and kept in sync byStepInterface._sync_and_alignso existingb.contract.computeread sites still work; authors declarecompute:at the top level now.
- job_arguments: List['JobArgDecl']¶
DECLARATIVE record of the CLI arguments the step’s script accepts (FZ 31e1d3h) — each entry is
{flag, source}(the--flagemitted and the config attribute it comes from). This is documentation / alignment / introspection ONLY: the TRUE values are produced at build time byconfig.get_job_arguments()(config is the single source). Mirrors howenv_varsdeclares names while the config supplies values. Not used to drive_get_job_arguments.
- computed_env_paths: Dict[str, List[str]]¶
env vars whose VALUE is an S3 sub-path under the pipeline’s execution prefix (
base_output_path), not a config field — e.g. a script that reads/writes an extra staging location. MapsENV_VAR -> [segment, ...]; the base_get_environment_variablessetsENV_VAR = Join(base_output_path, *segments). This is the declarative form of the formerly-hand-written_get_environment_variablesoverrides (e.g. BedrockBatchProcessing’s BEDROCK_BATCH_INPUT/OUTPUT_S3_PATH) — the env analog of the output-destination token, so a step needs no Python to compute a runtime S3 env path.- Type:
COMPUTED-S3-ENV pattern (FZ 31e1d3g3 Phase A3)
- runtime_requires: List[str]¶
DEPENDENCY AXIS (runtime) — 3rd-party packages the step’s SCRIPT imports at CONTAINER runtime (FZ 31e1d3l). This is ORTHOGONAL to build-time deps (
compute.requires/registry.requires): these imports live insteps/scripts/<entry_point>and execute inside the SAIS Docker image, NOT during pipeline construction — they never affect offline import of cursus/builders. Kept on a separate descriptor so build-time vs runtime deps are never conflated. E.g. EdxUploading + RedshiftDataLoading scripts importsecure_ai_sandbox_python_lib(a runtime, not build, dep).
- property input_channels: Dict[str, List[str]]¶
Per-input declared training sub-channels (
logical_name -> [channel, ...]).Only inputs that declare a non-empty
channelslist appear. The TrainingHandler reads this to fan a single input into<path>/<channel>/sub-channels — the channel layout is per-step DATA in the.step.yaml, not a handler constant.
- model_config: ClassVar[ConfigDict] = {}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class DependencyDecl(*, logical_name='', type=DependencyType.PROCESSING_OUTPUT, required=True, compatible_sources=<factory>, semantic_keywords=<factory>, data_type='S3Uri', description='')[source]¶
Bases:
BaseModelOne spec dependency declaration. Drop-in for the legacy DependencySpec.
logical_nameis auto-populated from the dict key by SpecSection’s validator.dependency_typeis exposed as an alias oftypefor the resolver/assembler.- type: DependencyType¶
- property dependency_type: DependencyType¶
Legacy DependencySpec field name.
- model_config: ClassVar[ConfigDict] = {}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class OutputDecl(*, logical_name='', type=DependencyType.PROCESSING_OUTPUT, property_path='', aliases=<factory>, semantic_keywords=<factory>, data_type='S3Uri', description='')[source]¶
Bases:
BaseModelOne spec output declaration. Drop-in for the legacy OutputSpec.
logical_nameis auto-populated from the dict key by SpecSection’s validator.output_typeis exposed as an alias oftype.- type: DependencyType¶
- property output_type: DependencyType¶
Legacy OutputSpec field name.
- matches_name_or_alias(name)[source]¶
Check if name matches the logical name or any alias (case-insensitive).
- model_config: ClassVar[ConfigDict] = {}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class SpecSection(*, dependencies=<factory>, outputs=<factory>, step_type='', node_type=NodeType.INTERNAL)[source]¶
Bases:
BaseModelThe ‘spec’ section of a .step.yaml — dependency resolution metadata.
Drop-in for the legacy StepSpecification’s dependency/output surface. The enclosing StepInterface mirrors
step_type/node_typehere so this object can be registered/consumed standalone where a StepSpecification was expected.- dependencies: Dict[str, DependencyDecl]¶
- outputs: Dict[str, OutputDecl]¶
- get_output_by_name_or_alias(name)[source]¶
Get output by logical name or alias (case-insensitive on aliases).
- validate_specification()[source]¶
Consistency check (legacy StepSpecification.validate_specification).
- model_config: ClassVar[ConfigDict] = {}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class VariantDecl(*, spec=<factory>, contract=<factory>)[source]¶
Bases:
BaseModelA job_type variant block from a .step.yaml (e.g. training / calibration).
Holds the spec/contract overrides that are merged over the base when a builder requests a specific job_type. Stored as raw dicts because they are partial overrides, not standalone sections.
- spec: Dict¶
- contract: Dict¶
- model_config: ClassVar[ConfigDict] = {}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class StepInterface(*, step_type, node_type=NodeType.INTERNAL, registry=<factory>, compute=<factory>, patterns=<factory>, contract, spec=<factory>, variants=<factory>)[source]¶
Bases:
BaseModelValidated representation of a .step.yaml file.
This is the single message passed among dep resolver, builder, and assembler. Replaces the previous (ScriptContract|StepContract, StepSpecification) tuple.
Build it from a parsed YAML dict via
from_yaml(), which applies any requestedjob_typevariant before validation.- registry: RegistrySection¶
- compute: ComputeSpec¶
Declarative COMPUTE descriptor (FZ 31e1d3k) — a TOP-LEVEL section (peer of contract/spec) because it describes the BUILDER’s compute object (processor/estimator/model/transformer), not the script contract: script-less steps (CreateModel/Transform) carry a near-empty contract but a full compute.
_sync_and_alignmirrors it ontocontract.computefor back-compat. Empty (kind=None) ⇒ the step keeps its own factory.
- patterns: PatternsSection¶
Per-axis STRATEGY-SELECTION knobs (FZ 31e1d3f1) — the blueprint that wires pattern injection (step_assembly / include_job_type_in_path / direct_input_keys), read into the bound handler by
_auto_bind_handlerso the YAML steers the build, not a builder shell.
- contract: ContractSection¶
- spec: SpecSection¶
- variants: Dict[str, VariantDecl]¶
- classmethod from_yaml(data, job_type=None)[source]¶
Build a StepInterface from a parsed
.step.yamldict, resolving variants.When
job_typenames a variant, that variant’sspec/contractoverrides are deep-merged over the base sections before validation. The merge is recursive: a variant that lists only a subset ofspec.dependencies(oroutputs/ contractinputs) overrides just those ports’ fields and leaves the rest of the base set intact — it does not replace the whole nested dict. Steps without a matching variant fall back to the base sections unchanged.A shallow merge here was a latent bug: because variants routinely restate only the ports they tweak,
{**base, **variant}at the section level dropped every base port the variant happened to omit (e.g. it droppedhyperparameters_s3_urifromRiskTableMapping’s variants, which then violated the contract↔spec alignment invariant and raised at construction).
- property script_contract: ContractSection¶
Legacy StepSpecification.script_contract accessor.
- property dependencies: Dict[str, DependencyDecl]¶
- property outputs: Dict[str, OutputDecl]¶
- validate_contract_alignment()[source]¶
Validate that the contract aligns with the spec.
Mirrors legacy StepSpecification.validate_contract_alignment: every contract input must have a matching spec dependency, and every contract output a matching spec output (extra spec deps/outputs and output aliases allowed). Returns a ValidationResult (is_valid / errors).
- model_config: ClassVar[ConfigDict] = {}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].