Self-hosted data ingestion framework โ extract, transform, and load data from anywhere.
Project description
๐ชท Lotos
Self-hosted data ingestion framework โ extract, transform, and load data from anywhere.
Lotos is a lightweight, YAML-driven ETL framework built with Python. Define pipelines as configuration, not code.
Features
- YAML-based pipelines โ declarative, version-controlled, easy to read
- Pluggable architecture โ connectors, transforms, and sinks are auto-discovered via a registry
- Source connectors โ SQL databases, REST APIs, local/remote files (CSV, JSON, Parquet)
- Transform chain โ select, rename, cast, filter, deduplicate, flatten, SQL transforms, computed columns, custom expressions, and schema validation
- Destination sinks โ local files, Azure Blob Storage, Azure Data Lake Storage Gen2
- Full & incremental loading โ watermark-based extraction with SQLite-persisted state
- DAG orchestration โ dependency-aware parallel execution of pipeline graphs with retries and backoff
- Run history โ every execution is tracked (status, row counts, duration, errors)
- State management โ inspect, reset, and clear watermarks and history via CLI
- Secret resolution โ reference environment variables or
.envvalues with${SECRET:KEY}syntax - Structured logging โ powered by
structlogwith JSON or console output - Monitoring โ Prometheus metrics endpoint + pre-built Grafana dashboard
- Data quality checks โ row count, null %, freshness, uniqueness, value range, regex match
- CLI interface โ run, validate, inspect, orchestrate, monitor, and scaffold pipelines from the terminal
Architecture
Installation
pip install -e .
With optional dependencies:
pip install -e ".[azure]" # Azure Blob / ADLS Gen2
pip install -e ".[postgres]" # PostgreSQL
pip install -e ".[mysql]" # MySQL
pip install -e ".[monitoring]" # Prometheus metrics + Grafana
pip install -e ".[all]" # Everything
pip install -e ".[dev]" # Dev tools (pytest, ruff)
Requires Python 3.11+
Quick Start
1. Generate a pipeline template
lotos init my_pipeline --source sql
2. Edit the YAML
pipeline:
name: my_pipeline
description: "Extract customers and clean data"
version: "1.0"
source:
connector: sql
load_mode: incremental # full (default) or incremental
config:
connection_string: "mysql+pymysql://user:pass@localhost:3306/mydb"
query: "SELECT * FROM customers"
transforms:
- type: select_columns
config:
columns: [id, name, email, created_at]
- type: deduplicate
config:
subset: [id]
keep: last
sink:
connector: file
config:
path: "output/customers.parquet"
format: parquet
write_mode: overwrite
watermark:
field: updated_at
initial_value: "2020-01-01"
3. Run it
lotos run pipelines/my_pipeline.yaml --log-level INFO
Load Modes
Lotos supports two extraction strategies, configured per-pipeline via source.load_mode:
Full Load (full)
Extracts all rows from the source on every run. The watermark is ignored for filtering but still tracked for informational purposes.
source:
connector: sql
load_mode: full
config:
query: "SELECT * FROM orders"
Incremental Load (incremental)
Extracts only new/changed rows since the last run, using a watermark column. The watermark value is persisted in a local SQLite state store (.lotos/state.db) and automatically updated after each successful run.
source:
connector: sql
load_mode: incremental
config:
connection_string: "${SECRET:DB_CONN}"
query: "SELECT * FROM orders WHERE updated_at > :watermark"
watermark:
field: updated_at
initial_value: "2024-01-01"
Watermark resolution order:
- Explicit CLI override (
--watermark) - Full mode โ always
None(no filter) - Incremental โ persisted value from SQLite state store
- Incremental, no persisted value โ
watermark.initial_valuefrom YAML
Pipeline Orchestration
Orchestrate multiple pipelines as a directed acyclic graph (DAG). Independent pipelines run in parallel; dependent pipelines wait for their upstream dependencies.
Declaring dependencies
pipeline:
name: load_orders
depends_on:
- load_customers
- load_products
Running the DAG
lotos orchestrate pipelines/ --workers 4 --log-level INFO
Lotos will:
- Discover all
.yaml/.ymlfiles in the directory - Build a dependency graph from
depends_ondeclarations - Topologically sort into execution layers (Kahn's algorithm)
- Execute each layer in parallel with a thread pool
- Skip downstream pipelines if an upstream dependency fails (marked
cancelled) - Retry failed pipelines with exponential backoff (
2^attemptseconds, capped at 120s) - Track every run in the state store
Example DAG
pipelines/
โโโ 01_customers.yaml # depends_on: []
โโโ 02_products.yaml # depends_on: []
โโโ 03_orders.yaml # depends_on: [customers, products]
โโโ 04_analytics.yaml # depends_on: [orders]
Execution layers:
Layer 1: customers, products (parallel)
Layer 2: orders (waits for layer 1)
Layer 3: analytics (waits for layer 2)
Retry & schedule config
Configure retries and timeouts per pipeline:
schedule:
max_retries: 3 # 1 initial + 3 retries = 4 attempts
retry_backoff: exponential # 2s, 4s, 8s, โฆ, capped at 120s
timeout_seconds: 3600 # per-pipeline timeout warning
State Management
All pipeline state is stored in .lotos/state.db (SQLite with WAL mode).
Watermarks
lotos state show # List all watermarks
lotos state reset my_pipeline # Clear watermark (forces full reload)
Run History
lotos history # Show all runs (last 20)
lotos history my_pipeline # Filter by pipeline
lotos history --status failed -n 50 # Filter by status, show 50
lotos state clear-history # Delete all history
lotos state clear-history my_pipeline # Delete history for one pipeline
CLI Commands
| Command | Description |
|---|---|
lotos run <file> |
Run a single pipeline |
lotos orchestrate <dir> |
Run all pipelines as a DAG |
lotos validate <file> |
Validate YAML without running |
lotos inspect <file> |
Show pipeline details |
lotos init <name> |
Generate a pipeline template |
lotos monitor |
Start Prometheus metrics server |
lotos quality <file> |
Run quality checks only (no sink) |
lotos list connectors |
List available source connectors |
lotos list transforms |
List available transforms |
lotos list sinks |
List available destination sinks |
lotos list all |
List everything |
lotos history [pipeline] |
Show run history |
lotos state show |
Show all stored watermarks |
lotos state reset <name> |
Clear watermark for a pipeline |
lotos state clear-history |
Delete run history |
Run options
--output, -o Save result to file (csv/json/parquet)
--format, -f Output format (default: parquet)
--dry-run Extract + transform only, skip sink
--log-level, -l DEBUG, INFO, WARNING, ERROR
--log-format console or json
Orchestrate options
--workers, -w Max parallel pipelines (default: 4)
--dry-run Extract + transform only
--log-level, -l Log level
--log-format Log format
Monitor options
--port, -p Prometheus HTTP port (default: 9090)
--addr Bind address (default: 0.0.0.0)
--backfill Backfill metrics from state store history
Monitoring & Data Quality
Prometheus metrics
Install with pip install -e ".[monitoring]", then:
lotos monitor --port 9090 --backfill # start metrics server
Exposed metrics at http://localhost:9090/metrics:
| Metric | Type | Labels |
|---|---|---|
lotos_pipeline_runs_total |
Counter | pipeline, status |
lotos_pipeline_duration_seconds |
Histogram | pipeline |
lotos_pipeline_rows_extracted_total |
Counter | pipeline |
lotos_pipeline_rows_loaded_total |
Counter | pipeline |
lotos_pipeline_last_success_timestamp |
Gauge | pipeline |
lotos_pipeline_errors_total |
Counter | pipeline |
lotos_quality_checks_total |
Counter | pipeline, check, status |
Grafana dashboard
Import the pre-built dashboard from lotos/monitoring/grafana_dashboard.json into Grafana.
It includes 14 panels covering pipeline runs, duration percentiles, data volume, quality checks, and error trends.
Data quality checks
Add a quality_checks section to any pipeline YAML:
quality_checks:
- type: row_count
min: 100
action: block # block | warn | alert
- type: null_percentage
column: email
max_percentage: 5.0
action: warn
- type: freshness
column: updated_at
max_age_hours: 24
action: alert
- type: uniqueness
columns: [id]
action: block
- type: value_range
column: age
min: 0
max: 150
action: warn
- type: regex_match
column: email
pattern: "^[\\w.+-]+@[\\w-]+\\.[a-zA-Z]{2,}$"
action: alert
Run checks without loading:
lotos quality pipelines/my_pipeline.yaml
Connectors
| Name | Type | Description |
|---|---|---|
sql |
Source | Any SQLAlchemy-compatible database |
rest_api |
Source | REST APIs with pagination & auth |
file |
Source | CSV, JSON, Parquet (local or Azure Blob) |
file |
Sink | Write to local files or Azure Blob |
adls_gen2 |
Sink | Azure Data Lake Storage Gen2 / Blob Storage |
ADLS Gen2 Sink
The adls_gen2 sink supports both Azure storage modes via the hierarchical_namespace option:
sink:
connector: adls_gen2
config:
account_name: "mystorageaccount"
container_name: "mycontainer"
directory_path: "data/output"
file_name: "results"
format: parquet
hierarchical_namespace: false # false = Blob API, true = Data Lake (DFS) API
account_key: "${SECRET:ADLS_KEY}"
write_mode: overwrite
Authentication methods (in priority order):
account_keyโ storage account keyconnection_stringโ full connection stringsas_tokenโ shared access signatureDefaultAzureCredentialโ automatic (managed identity, env vars, CLI login)
Transforms
| Name | Description |
|---|---|
select_columns |
Keep only specified columns |
rename_columns |
Rename columns via a mapping |
cast_types |
Cast column data types |
filter_rows |
Filter rows by conditions |
deduplicate |
Remove duplicate rows |
flatten |
Flatten nested structs/lists |
sql |
Run SQL queries (Polars SQL โ no DB needed) |
computed |
Add computed columns with expressions |
custom |
Apply a custom Python expression |
validate_schema |
Validate column types and constraints |
Project Structure
lotos/
โโโ cli.py # CLI (Typer)
โโโ config/ # Settings, logging, secrets
โโโ connectors/ # Source connectors (SQL, REST, file)
โโโ core/
โ โโโ models.py # Pydantic models (YAML schema)
โ โโโ pipeline.py # Pipeline execution engine
โ โโโ registry.py # Plugin auto-discovery
โ โโโ state.py # SQLite state store
โ โโโ exceptions.py # Custom exceptions
โโโ orchestration/
โ โโโ base.py # DAG orchestrator
โโโ sinks/ # Destination sinks (file, ADLS Gen2)
โโโ transforms/ # Transform plugins
โโโ monitoring/ # Monitoring & quality
โโโ base.py # Monitor interfaces (Log / Prometheus)
โโโ metrics.py # Prometheus metrics collector & server
โโโ quality.py # Data quality checker (6 check types)
โโโ grafana_dashboard.json # Pre-built Grafana dashboard
pipelines/ # Pipeline YAML definitions
tests/ # Test suite
Development
pip install -e ".[dev]"
pytest
ruff check .
License
Artefact
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file lotos-1.0.0.tar.gz.
File metadata
- Download URL: lotos-1.0.0.tar.gz
- Upload date:
- Size: 598.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7548d810f9782f7082b1605d735d171f683abb3274275a7f91ecd52ab973bcb2
|
|
| MD5 |
a2777288b6447bc14822ef3b5038a5cc
|
|
| BLAKE2b-256 |
bde4a2ffd1c6ac61d0f0061081d7cc03d3fdd69baa81e10c40bec313478199aa
|
File details
Details for the file lotos-1.0.0-py3-none-any.whl.
File metadata
- Download URL: lotos-1.0.0-py3-none-any.whl
- Upload date:
- Size: 64.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cad27ed0f73c9b2db287db3bfd99d5e30acbc36900b17e098b17e0e409c4ac2b
|
|
| MD5 |
2d408da26bd1e2376555d3c6b9a083f6
|
|
| BLAKE2b-256 |
40438ed701c4e5887a07047ad6f3ffe5010c5b01e762be7443f51834eedec23e
|