cursus.core.base.builder_templates¶
Template-based step builder + construction-verb handlers (Phase 0c of the simplification).
This module introduces a single TemplateStepBuilder facade and a set of construction-verb
PatternHandler strategies, selected at build time by resolve_handler(sagemaker_step_type,
step_assembly). The goal is to replace the 44 hand-written <Name>StepBuilder classes with
thin shells that declare only STEP_NAME and inherit everything else.
Status: Phase S3 in progress — wired, 2/45 builders are live shells. This facade IS now
routed: TabularPreprocessingStepBuilder and BatchTransformStepBuilder are live shells that
construct through it; the remaining 43 stay hand-written until their byte-diff-gated batch migrates
(see the plan’s Phase S3). All 5 construction-verb handlers (ProcessingHandler, TrainingHandler,
ModelCreationHandler, TransformHandler, SDKDelegationHandler) are fully implemented + covered by
session-independent parity suites in tests/core/base/.
- Design notes (verified against source):
6 construction verbs, but Processing-2A and Processing-2B collapse to ONE
ProcessingHandlerwithuse_step_args/split_source_dirknobs (NI-1) — they share get_inputs/get_outputs verbatim and differ only in build_step.The facade does NOT impose a fixed make_compute→inputs→outputs→build order — it hands the merged inputs/outputs to
handler.build_stepand lets the handler orchestrate, because Transform runs make_compute last (NI-3) and ModelCreation drops caching (NI-4).sagemaker_step_typealone selects the handler for 5 verbs; onlyProcessingneeds thestep_assemblysub-discriminator (code | step_args | delegation).
- class TemplateStepBuilder(config, sagemaker_session=None, role=None, registry_manager=None, dependency_resolver=None, spec=None)[source]¶
Bases:
StepBuilderBaseRouted builder facade — the single concrete parent of the (future) 2-line shells.
A shell declares only its registry key:
class TabularPreprocessingStepBuilder(TemplateStepBuilder): STEP_NAME = "TabularPreprocessing"
The facade keeps the exact 5-kwarg
__init__contract thePipelineAssemblercalls (builder_base.__init__), binds a handler viaresolve_handlerfrom the step’ssagemaker_step_type(+step_assembly), and implements the abstract methods (_get_inputs/_get_outputs/create_step) by delegating to that handler. It is aStepBuilderBasesubclass, so the assembler/catalog contract and the discovery hierarchy hold.Wired and live (Phase S3):
TabularPreprocessingStepBuilderandBatchTransformStepBuilderare real shells routing through this facade; the rest migrate per Phase S3 (byte-diff-gated).- STEP_NAME: str | None = None¶
Subclasses set this to their canonical registry step name (drives spec load + routing). The slot itself is declared on StepBuilderBase (the root that reads it in _get_step_name, FZ 31e1d3g3 Phase C1); re-stated here for local readability of the shell contract.
- STEP_ASSEMBLY: str | None = None¶
Optional explicit step_assembly for Processing steps (code | step_args | delegation). If None, defaults to “code” for Processing (see resolve_handler).
- HANDLER_KNOBS: Dict[str, Any] = {}¶
Per-step handler knobs (direct_input_keys, split_source_dir, include_job_type_in_path, …).
- validate_configuration()[source]¶
Validate builder-context configuration requirements (optional hook).
No-op by default. The Pydantic config class is the authority for config validation — required fields,
@field_validator/@model_validatorconstraints, and defaults are all enforced at config construction, BEFORE the builder runs. A config that constructs is valid by definition, so most builders need no override here (FZ 31e1d3e). Override ONLY to assert an invariant the config genuinely cannot express — one involving builder context (self.role/self.session/self.spec/ resolved dependencies) or a cross-field rule not yet on the config model.
- create_step(**kwargs)[source]¶
Create pipeline step.
This method should be implemented by all step builders to create a SageMaker pipeline step. It accepts a dictionary of keyword arguments that can be used to configure the step.
Common parameters that all step builders should handle: - dependencies: Optional list of steps that this step depends on - enable_caching: Whether to enable caching for this step (default: True)
Step-specific parameters should be extracted from kwargs as needed.
- Parameters:
**kwargs (Any) – Keyword arguments for configuring the step
- Returns:
SageMaker pipeline step
- Return type:
Step
- class PatternHandler(knobs=None)[source]¶
Bases:
ABCBase strategy for one construction verb.
A handler is stateless config: it holds per-step declarative
knobsand receives the owningTemplateStepBuilder(b) on each call, readingb.config/b.spec/b.contract/b.role/b.sessionand calling base helpers (b._get_step_name(),b._get_base_output_path(),b._get_cache_config(),b.extract_inputs_from_dependencies(),b.log_info…).- requires_config_fields: tuple = ()¶
Config attributes this handler reads off
b.configat BUILD time that are NOT expressible as a.step.yamldescriptor (FZ 31e1d3g3 Phase D1 / OQ 31e1d3h1). The B3 RegistryBinding validator unions this with the descriptor-derived attrs (compute*_fieldnames,contract.input_source_overridesvalues) to check config-field COVERAGE — i.e. that the resolved config class actually supplies every field the bound handler willgetattrat build time. Declared as DATA (not scraped from source) because some reads use a runtime attr name (getattr(b.config, attr)withattrfrom the contract) that is statically undecidable. Empty for handlers that read only spec/contract, not config.
- class ProcessingHandler(knobs=None)[source]¶
Bases:
PatternHandlerProcessing verb — covers both 2A (
code=) and 2B (processor.run()→step_args).- Knobs:
use_step_args(bool): 2B if True (processor.run()thenProcessingStep(step_args=...)), else 2A (ProcessingStep(code=...)).split_source_dir(bool): for 2B, splitget_script_path()intosource_dir+code=entry_point.include_job_type_in_path(bool): whetherconfig.job_typeis a path segment. (The output-prefix segment itself is DERIVED from the step name —canonical_to_snake— not a knob; FZ 31e1d3f1b.)
NOTE:
make_compute(the processor factory) andget_environment_variablesare per-step and currently still live on each builder; the migration re-homes them via amake_computeknob/hook. Until then a routed Processing step must supply its processor factory. This handler implements the shared get_inputs/get_outputs join and the build_step shape; the processor-factory wiring is completed per-step in the Phase-2 Batch-A/C work.
- class TrainingHandler(knobs=None)[source]¶
Bases:
PatternHandlerTraining verb — builds a
TrainingStep(estimator, inputs=channels).Re-homed from
builder_xgboost_training_step.py(3 of 4 builders use the path-parts channel parser; PyTorch keeps its own_get_inputsoverride). Distinctive:get_inputsreturnsDict[str, TrainingInput]keyed by channel name;input_pathfans out to train/val/test;hyperparameters_s3_uriskipped whenconfig.skip_hyperparameters_s3_uri.get_outputsreturns a single str/Joinoutput_path(not a list).build_stepORDER: get_inputs → empty-guard → get_outputs → make_compute (the estimator is created WITHoutput_paththreaded in, so outputs run BEFORE compute).caching is STANDARD (
cache_config=_get_cache_config(enable_caching)).
- KNOBS = (KnobSpec(name='make_compute', type='callable', default=None, required=False, doc='estimator factory; defaults to builder._create_estimator'), KnobSpec(name='direct_input_keys', type='list', default=['input_path'], required=False, doc='direct input keys (input_path threads in)'))¶
- DEFAULT_FANOUT_CHANNELS = ('train', 'val', 'test')¶
Fallback sub-channels for a fan-out input when the .step.yaml does not declare
contract.inputs.<name>.channels(back-compat for not-yet-annotated interfaces). The source of truth is the YAMLchannelslist — seechannels_for.
- classmethod channels_for(logical_name, container_path, declared_channels=None)[source]¶
The SageMaker training channel name(s) a dependency maps to — the SINGLE SOURCE of the channel rule, shared by
get_inputs(runtime) and thesteps iotool (static).Priority: (1) the
channelsdeclared on the input in the.step.yaml(per-step DATA); (2) for the conventionalinput_pathwith no declaration, the back-compatDEFAULT_FANOUT_CHANNELS; (3) theparts[5]of an/opt/ml/input/data/<channel>container path; (4) the logical name itself. No resolved input value is needed.
- class ModelCreationHandler(knobs=None)[source]¶
Bases:
PatternHandlerModelCreation verb — builds a
CreateModelStep(model=...).- Distinctive (re-homed from
builder_xgboost_model_step.py/builder_pytorch_model_step.py): get_inputsis a single-key{"model_data": ...}passthrough (NOT a spec×contract join, NOT the DummyTraining 3-tier resolution); raises ifmodel_dataabsent.get_outputsreturnsNone(CreateModelStep auto-exposesproperties.ModelName).make_compute(the model factory) runs LAST, consumingmodel_data.caching is DROPPED — CreateModelStep takes no
cache_config; warn onenable_caching=Trueand pass no cache config (the inverse of every other handler).
- Distinctive (re-homed from
- class TransformHandler(knobs=None)[source]¶
Bases:
PatternHandlerTransform verb — builds a
TransformStep(transformer, inputs=TransformInput).- Distinctive (re-homed from
builder_batch_transform_step.py): get_inputsreturns a 2-tuple(TransformInput, model_name)— spec-only (no contract), with hard-codedmodel_name/processed_datalogical-name dispatch.get_outputsreturns a single str/Join (not a list).make_compute(the Transformer factory) runs LAST — it consumes bothmodel_name(from inputs) andoutput_path(from outputs).caching is guarded (
cache_config=... if enable_caching else None).
- requires_config_fields: tuple = ('job_type',)¶
Config fields the Transform build genuinely DEPENDS on. The Transform read-sites (builder_templates.py:691-695) also touch content_type/split_type/join_source/input_filter/ output_filter, but those are all OPTIONAL on BatchTransformStepConfig (they carry defaults, so a missing value can’t break the build), so they are NOT coverage requirements. Only
job_typeis required (no default) and is read at :718 — its absence WOULD break the build.
- Distinctive (re-homed from
- class SDKDelegationHandler(knobs=None)[source]¶
Bases:
PatternHandlerSDKDelegation verb — instantiate a SAIS SDK
MODSPredefinedProcessingStepsubclass directly.Covers Cradle / Redshift / DataUploading / Registration (re-homed from their builders). The SDK step builds its own processor/inputs internally, so there is no
make_compute. The SDK step class is injected via thesdk_step_classknob (the SAIS SDK can’t be imported at registration time). Threeinput_mode``s: ``none([] — Cradle/Redshift),resolve_s3(DataUploading — resolve the input S3, pass asinput_s3_location=),mims_ordered(Registration — an orderedProcessingInputlist, PackagedModel first).get_inputsreturns(processing_inputs, resolved_s3).
- resolve_handler(sagemaker_step_type, step_assembly=None, knobs=None)[source]¶
Select and instantiate the construction-verb handler for a step.
Routing is by
sagemaker_step_typeONLY (never by step name —DummyTrainingisProcessingand must route as Processing).Processingis sub-discriminated bystep_assembly(code|step_args|delegation, defaultcode). All routing data lives in thestrategy_registry; this function only maps the runtime args onto an (axis, name) lookup and merges the registry’s preset knobs under the caller’s knobs.
- exception NoBuilderError[source]¶
Bases:
ValueErrorRaised when an (axis, name) has no routable strategy (abstract / builder-less type).