How to Build Declarative ETL Pipelines with the Semantica Pipeline DSL
Semantica's pipeline DSL lets you define end-to-end ETL workflows as composable, serializable steps that handle dependency resolution, parallel execution, retries, and progress tracking automatically.
In the semantica-agi/semantica open-source repository, the pipeline DSL transforms imperative Python scripts into maintainable, reproducible data pipelines. The system separates what your pipeline does (declarative step definitions) from how it runs (parallelism, failure handling, scheduling), enabling you to build robust ETL workflows without boilerplate orchestration code.
Core Components of the Pipeline DSL
The Semantica pipeline system consists of layered components, each with a distinct responsibility:
| Component | Purpose | Source File |
|---|---|---|
PipelineBuilder |
Fluent DSL for constructing pipelines with add_step, connect_steps, and build |
semantica/pipeline/pipeline_builder.py |
PipelineStep |
Dataclass representing a single step: name, type, handler, dependencies, status, and delta mode support | semantica/pipeline/pipeline_builder.py#L55 |
PipelineValidator |
Pre-execution validation for cycles, missing handlers, and misconfigured connections | semantica/pipeline/pipeline_validator.py |
ExecutionEngine |
Runtime orchestration: scheduling, resource allocation, progress tracking, and result aggregation | semantica/pipeline/execution_engine.py |
FailureHandler |
Per-step retry policies (exponential, linear, fixed) and failure strategies (skip, retry, abort, fallback) | semantica/pipeline/failure_handler.py |
ParallelismManager |
Thread or process pool management for concurrent step execution | semantica/pipeline/parallelism_manager.py |
PipelineSerializer |
YAML serialization for pipeline reproducibility across environments | semantica/pipeline/pipeline_builder.py#L88 |
ExecutionResult |
Post-execution metadata: success status, output, timing metrics, and error details | semantica/pipeline/execution_engine.py#L62 |
Step 1: Constructing a Pipeline with PipelineBuilder
The PipelineBuilder class in semantica/pipeline/pipeline_builder.py provides a fluent interface for defining steps and their dependencies. Each add_step call creates a PipelineStep dataclass; connect_steps establishes the directed acyclic graph (DAG) that determines execution order.
from semantica.pipeline import PipelineBuilder
from semantica.ingest import FileIngestor
from semantica.parse import DocumentParser
from semantica.semantic_extract import NERExtractor
from semantica.kg import GraphBuilder
# Initialize handlers
ingestor = FileIngestor()
parser = DocumentParser()
extractor = NERExtractor(method="ml")
kg_builder = GraphBuilder(merge_entities=True)
# Build the pipeline declaratively
builder = PipelineBuilder()
builder.add_step("ingest", "file_ingest", handler=ingestor.ingest_file)
builder.add_step("parse", "document_parse", handler=parser.parse)
builder.add_step("extract", "ner_extract", handler=extractor.extract)
builder.add_step("build_kg", "graph_build", handler=kg_builder.build)
# Define data flow: ingest → parse → extract → build_kg
builder.connect_steps("ingest", "parse")
builder.connect_steps("parse", "extract")
builder.connect_steps("extract", "build_kg")
pipeline = builder.build("my_etl_pipeline")
The handler parameter accepts any Python callable, making the DSL agnostic to your implementation. Step names passed to add_step are internal identifiers; the second argument is the step type used for logging and metrics.
Step 2: Validating Pipeline Structure
Before execution, validate your pipeline with PipelineValidator to catch cycles, orphaned steps, and missing handlers early. The validator in semantica/pipeline/pipeline_validator.py returns a structured result with valid, errors, and warnings fields.
from semantica.pipeline import PipelineValidator
validator = PipelineValidator()
validation = validator.validate_pipeline(pipeline)
if not validation.valid:
for err in validation.errors:
print(f"Validation error: {err}")
raise RuntimeError("Pipeline validation failed")
for warn in validation.warnings:
print(f"Warning: {warn}")
This validation step prevents runtime failures from structural issues like circular dependencies between steps.
Step 3: Configuring Parallelism and Failure Handling
The ExecutionEngine in semantica/pipeline/execution_engine.py orchestrates runtime behavior. Control concurrency via max_workers and resilience via FailureHandler policies.
from semantica.pipeline import (
ExecutionEngine,
RetryPolicy,
RetryStrategy,
FailureHandler
)
# Initialize engine with 4 parallel workers
engine = ExecutionEngine(max_workers=4)
# Configure exponential backoff for the NER extraction step
policy = RetryPolicy(
max_retries=3,
strategy=RetryStrategy.EXPONENTIAL,
initial_delay=1.0,
backoff_factor=2.0, # 1s, 2s, 4s delays
)
handler = FailureHandler()
handler.set_retry_policy("ner_extract", policy)
# Inject custom failure handling
engine.failure_handler = handler
# Execute with input data path
result = engine.execute_pipeline(pipeline, data="data/")
print(f"Success: {result.success}")
print(f"Execution time: {result.metrics['execution_time']:.1f}s")
print(f"Steps executed: {result.metrics['steps_executed']}")
print(f"Steps failed: {result.metrics['steps_failed']}")
The ParallelismManager (semantica/pipeline/parallelism_manager.py) automatically schedules steps with no pending dependencies onto the worker pool. Steps become eligible for execution as soon as all their upstream dependencies complete successfully.
Step 4: Enabling Reproducibility with YAML Serialization
Pipelines built with the DSL are fully serializable. The PipelineSerializer enables version-controlled, environment-agnostic pipeline definitions.
# Save to YAML
yaml_path = "my_pipeline.yaml"
builder.serialize(pipeline, output_path=yaml_path)
# Load and replay on any machine
from semantica.pipeline import PipelineSerializer
loaded_pipeline = PipelineSerializer().load(yaml_path)
This capability is critical for CI/CD integration, where the same pipeline definition must run identically across development, staging, and production environments.
Step 5: Implementing Incremental Processing with Delta Mode
For large datasets, enable delta mode to process only changed records between pipeline runs. Configure delta_mode=True with version identifiers:
builder.add_step(
"extract",
"ner_extract",
handler=extractor.extract,
delta_mode=True,
base_version_id="v1.2",
target_version_id="v1.3",
)
The ExecutionEngine feeds only documents whose version changed between base_version_id and target_version_id to the handler, significantly reducing reprocessing time for incremental ETL workflows.
Execution Flow Summary
Understanding the complete lifecycle helps debug and optimize pipelines:
- Construction —
PipelineBuilder.add_step()andconnect_steps()define the DAG - Validation —
PipelineValidator.validate_pipeline()catches structural errors - Serialization (optional) —
builder.serialize()produces reproducible YAML - Execution —
ExecutionEngine.execute_pipeline()schedules steps, manages theParallelismManagerpool, and appliesFailureHandlerpolicies - Result —
ExecutionResultprovides success status, output data, timing metrics, and error details
Summary
- Use
PipelineBuilderfor declarative step definition with a fluent, chainable API - Validate pipelines with
PipelineValidatorbefore execution to catch cycles and missing handlers - Control parallelism via
ExecutionEngine(max_workers=n)and theParallelismManager - Implement fault tolerance through
FailureHandlerretry policies (exponential, linear, fixed) and strategies (skip, retry, abort, fallback) - Ensure reproducibility by serializing pipelines to YAML with
PipelineSerializer - Reduce reprocessing overhead with delta mode for incremental ETL runs
Frequently Asked Questions
What makes Semantica's pipeline DSL "declarative"?
The DSL separates what from how. You declare steps and their relationships; the ExecutionEngine handles scheduling, parallelism, retries, and resource management. This contrasts with imperative frameworks where you manually coordinate execution flow.
How does the DSL handle step failures?
The FailureHandler class in semantica/pipeline/failure_handler.py supports per-step policies. You can configure retries with exponential backoff, skip failed steps, abort the entire pipeline, or execute fallback handlers. Unhandled exceptions propagate to ExecutionResult.errors.
Can I mix thread and process parallelism?
The ParallelismManager in semantica/pipeline/parallelism_manager.py abstracts pool creation. Configure the pool type through ExecutionEngine parameters; the DSL itself remains agnostic to the underlying concurrency model. CPU-bound steps benefit from process pools; I/O-bound steps from thread pools.
Is the pipeline format portable across Python versions?
YAML-serialized pipelines are version-agnostic configurations. The serialized format contains step metadata, dependencies, and handler references—not serialized code. Handlers must exist in the target environment, but the pipeline structure transfers without compatibility issues.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →