Pipeline¶
The Pipeline class is the main entry point for building and running ETL pipelines.
Overview¶
from pycharter import Pipeline, HTTPExtractor, FileLoader, Rename
# Build with pipe operator
pipeline = (
Pipeline(HTTPExtractor(url="https://api.example.com/data"))
| Rename({"old": "new"})
| FileLoader(path="output.json")
)
# Run
result = await pipeline.run()
API Reference¶
Pipeline
¶
DAG-based ETL pipeline.
Pipelines are constructed from YAML config files or dicts and consist of a directed acyclic graph of steps (extract, transform, join, union, load).
Example::
pipeline = Pipeline.from_file("pipeline.yaml")
result = asyncio.run(pipeline.run())
Source code in src/pycharter/pipeline_generator/pipeline.py
run
async
¶
run(*, run_id: str | None = None, fail_fast: bool = True) -> PipelineResult
Execute the pipeline.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
run_id
|
str | None
|
Optional identifier for this run. |
None
|
fail_fast
|
bool
|
Abort on the first step failure. |
True
|
Returns:
| Type | Description |
|---|---|
PipelineResult
|
Aggregated pipeline result with per-step results. |
Source code in src/pycharter/pipeline_generator/pipeline.py
from_config_dir
classmethod
¶
from_config_dir(directory: str | Path, *, extra_variables: dict[str, str] | None = None) -> Pipeline
Create a pipeline from a directory of config files.
Looks for a pipeline.yaml (or .yml) first. If not found,
assembles a section-based config from individual
extract.yaml, load.yaml, transform.yaml files.
Also loads variables.yaml and settings.yaml if present.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
directory
|
str | Path
|
Path to the config directory. |
required |
extra_variables
|
dict[str, str] | None
|
Additional variables. |
None
|
Returns:
| Type | Description |
|---|---|
Pipeline
|
A validated, ready-to-run pipeline. |
Source code in src/pycharter/pipeline_generator/pipeline.py
from_config_files
classmethod
¶
from_config_files(*paths: str | Path, extra_variables: dict[str, str] | None = None) -> Pipeline
Create a pipeline by merging multiple YAML config files.
Each file is loaded and merged (later files override earlier ones on key collisions).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
paths
|
str | Path
|
One or more YAML file paths. |
()
|
extra_variables
|
dict[str, str] | None
|
Additional variables. |
None
|
Returns:
| Type | Description |
|---|---|
Pipeline
|
A validated, ready-to-run pipeline. |
Source code in src/pycharter/pipeline_generator/pipeline.py
Factory Methods¶
from_config_dir¶
Load pipeline from a directory containing extract.yaml, transform.yaml, and load.yaml:
from_config_files¶
Load from explicit file paths:
pipeline = Pipeline.from_config_files(
extract="configs/extract.yaml",
transform="configs/transform.yaml", # Optional
load="configs/load.yaml",
variables={"API_KEY": "secret"}
)
from_config_file¶
Load from a single combined config file:
PipelineResult¶
PipelineResult
dataclass
¶
PipelineResult(success: bool = True, rows_extracted: int = 0, rows_transformed: int = 0, rows_loaded: int = 0, rows_failed: int = 0, rows_quarantined_extract: int = 0, rows_quarantined_load: int = 0, validation_errors_extract: list[str] = list(), validation_errors_load: list[str] = list(), start_time: datetime | None = None, end_time: datetime | None = None, duration_seconds: float | None = None, batches_processed: int = 0, batch_results: list[BatchResult] = list(), errors: list[str] = list(), pipeline_name: str | None = None, run_id: str | None = None, quality_report: Any = None, lineage_events: list[dict[str, Any]] = list(), step_results: list[StepResult] = list())
Complete result from running an ETL pipeline.
Examples¶
Basic Pipeline¶
import asyncio
from pycharter import Pipeline, FileExtractor, FileLoader
pipeline = (
Pipeline(FileExtractor(path="input.json"))
| FileLoader(path="output.json")
)
result = asyncio.run(pipeline.run())
print(f"Loaded {result.rows_loaded} rows")
With Transformations¶
from pycharter import Pipeline, HTTPExtractor, PostgresLoader, Rename, Filter, AddField
pipeline = (
Pipeline(HTTPExtractor(url="https://api.example.com/users"))
| Rename({"userName": "user_name"})
| Filter(lambda r: r.get("active"))
| AddField("processed_at", "now()")
| PostgresLoader(connection_string="...", table="users")
)
Error Handling¶
from pycharter.shared.errors import ErrorMode, ErrorContext
# Collect errors instead of raising
result = await pipeline.run(
error_context=ErrorContext(mode=ErrorMode.COLLECT)
)
if result.errors:
for error in result.errors:
print(f"Error: {error}")
With Variables¶
pipeline = Pipeline.from_config_dir(
"pipelines/users/",
variables={
"API_KEY": os.environ["API_KEY"],
"OUTPUT_PATH": "/data/output.json"
}
)
Message Queue Acknowledgment¶
When a pipeline uses a messaging extractor (Kafka, RabbitMQ, SQS), the pipeline automatically acknowledges each batch after loading:
from pycharter.pipeline_generator.extractors import KafkaExtractor
pipeline = (
Pipeline(KafkaExtractor(topics=["orders"], consumer_group="etl"))
| Rename({"orderId": "order_id"})
| PostgresLoader(connection_string="...", table="orders")
)
# Pipeline calls extractor.acknowledge() after each batch load
result = await pipeline.run()
# Don't forget to close the consumer
await pipeline.extractor.close()
The pipeline determines success from the LoadResult:
| Scenario | Acknowledge call |
|---|---|
| Load succeeds | acknowledge(batch_index, success=True) |
| Load fails | acknowledge(batch_index, success=False) |
| Dry run | acknowledge(batch_index, success=True) |
| No loader set | acknowledge(batch_index, success=True) |
If acknowledge() raises an exception, it is logged as a warning and the pipeline continues.
Incremental Extraction¶
Track a watermark field across runs so only new/updated records are extracted:
type: database
query: "SELECT * FROM events WHERE updated_at > '${watermark}'"
connection_string: "${DATABASE_URL}"
incremental:
enabled: true
watermark_field: updated_at
initial_value: "2024-01-01"
The pipeline persists the highest watermark value on success and injects it into the next run.
Testing Utilities¶
PyCharter provides mock classes and assertion helpers for testing pipelines without real I/O:
from pycharter import MockExtractor, MockLoader, PipelineTestHarness
# Mock extractor yields fixture data
extractor = MockExtractor(data=[
[{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}]
])
# Mock loader captures what was loaded
loader = MockLoader()
pipeline = Pipeline(extractor) | Rename({"name": "full_name"}) | loader
result = await pipeline.run()
# Assert on captured data
from pycharter import assert_record_count, assert_fields_present
assert_record_count(loader.loaded_data, 2)
assert_fields_present(loader.loaded_data, ["id", "full_name"])