Platform-agnostic job routing framework for Spark ETL pipelines
Project description
SparkRouter
Platform-agnostic job routing framework for Spark ETL pipelines.
Write your ETL logic once, run it on Databricks, AWS Glue, EMR, or Docker containers.
Why SparkRouter?
- Write Once, Run Anywhere: Same job code runs on multiple Spark platforms
- Clean Architecture: Factory + Template Method patterns keep code testable
- No Mocks Needed: Dependency injection with Noop implementations for testing
- Configuration-Driven: JSON config via CLI, no code changes between environments
Installation
uv add sparkrouter
# or
pip install sparkrouter
With optional dependencies:
uv add sparkrouter[spark] # Include PySpark
uv add sparkrouter[aws] # Include boto3
uv add sparkrouter[all] # Include everything
Quick Start
1. Define Your Job
from sparkrouter import AbstractJob, AbstractJobFactory, NotificationService
class MyETLJob(AbstractJob):
"""Your ETL job with business logic."""
def __init__(self, notification_service: NotificationService):
self.notification_service = notification_service
def execute_job(self, input_path: str, output_path: str) -> dict:
# Your business logic here
print(f"Processing {input_path} -> {output_path}")
return {"records_processed": 1000}
def on_success(self, results):
self.notification_service.send_notification(
subject="Job Success",
message=f"Processed {results['records_processed']} records"
)
def on_failure(self, error_message):
self.notification_service.send_notification(
subject="Job Failed",
message=error_message
)
2. Create a Factory
from sparkrouter.testing.noop import NoopNotificationService
class MyETLJobFactory(AbstractJobFactory):
"""Factory that assembles jobs with dependencies."""
def create_job(self, **kwargs) -> MyETLJob:
config = self.parse_job_config(job_name='my_etl_job', **kwargs)
return MyETLJob(
notification_service=NoopNotificationService()
)
def main(**kwargs):
"""Entry point called by platform scripts."""
factory = MyETLJobFactory()
return factory.run(**kwargs)
3. Run It
# Local / Container
python -m sparkrouter.entry_points.container \
--module_name mypackage.my_etl_job_factory \
--my_etl_job '{}' \
--input_path "s3://bucket/input/" \
--output_path "s3://bucket/output/"
The same job runs on Databricks, Glue, or EMR - just use a different entry point.
Architecture
Entry Point (platform-specific)
│
▼
importlib.import_module(module_name).main(**kwargs)
│
▼
AbstractJobFactory.run()
│
▼
ConcreteFactory.create_job() → inject dependencies
│
▼
AbstractJob.run() → Template Method (final)
│
▼
ConcreteJob.execute_job() → Your business logic
│
▼
on_success() or on_failure()
Platform Entry Points
SparkRouter provides entry points for each platform:
| Platform | Entry Point | Service Provider |
|---|---|---|
| Databricks | sparkrouter.entry_points.databricks |
DATABRICKS |
| AWS Glue | sparkrouter.entry_points.glue |
GLUE |
| Amazon EMR | sparkrouter.entry_points.emr |
EMR |
| Container/Local | sparkrouter.entry_points.container |
CONTAINER |
Each entry point:
- Parses CLI arguments
- Adds platform context (
service_provider,has_spark) - Dynamically imports your module
- Calls
main(**kwargs)
Airflow Integration
AWS Glue
from airflow.providers.amazon.aws.operators.glue import GlueJobOperator
task = GlueJobOperator(
task_id='my_etl',
job_name='my-glue-job',
script_location='s3://bucket/scripts/glue/entry.py',
script_args={
'--module_name': 'mypackage.my_etl_job_factory',
'--my_etl_job': '{"key": "value"}',
'--input_path': 's3://bucket/input/',
'--output_path': 's3://bucket/output/',
},
)
Databricks
from airflow.providers.databricks.operators.databricks import DatabricksSubmitRunOperator
task = DatabricksSubmitRunOperator(
task_id='my_etl',
databricks_conn_id='databricks_default',
spark_python_task={
'python_file': 'dbfs:/scripts/databricks/entry.py',
'parameters': [
'--module_name', 'mypackage.my_etl_job_factory',
'--my_etl_job', '{"key": "value"}',
'--input_path', 's3://bucket/input/',
'--output_path', 's3://bucket/output/',
],
},
)
Testing Without Mocks
SparkRouter encourages testing with Noop implementations instead of mocks:
from sparkrouter.testing.noop import NoopNotificationService
def test_my_job():
notifier = NoopNotificationService()
job = MyETLJob(notification_service=notifier)
result = job.run(input_path="/in", output_path="/out")
assert result["records_processed"] == 1000
assert len(notifier.notifications) == 1
assert "Success" in notifier.notifications[0]["subject"]
Examples
See the examples directory for complete working examples:
- Simple ETL Job - Basic job with factory pattern
- Airflow DAGs - Glue and Databricks DAG examples
License
Apache License 2.0
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 sparkrouter-0.1.0.tar.gz.
File metadata
- Download URL: sparkrouter-0.1.0.tar.gz
- Upload date:
- Size: 24.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.18 {"installer":{"name":"uv","version":"0.9.18","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
897d1b95a243362668095bbfeacab2161347c48fa3884398d424d724efe556c4
|
|
| MD5 |
51acd99f07783decfcf7d1640f587927
|
|
| BLAKE2b-256 |
68a5192ad27c0650d13a20109e9f98aff9de11cb933581341ce15bdc11af040c
|
File details
Details for the file sparkrouter-0.1.0-py3-none-any.whl.
File metadata
- Download URL: sparkrouter-0.1.0-py3-none-any.whl
- Upload date:
- Size: 19.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.18 {"installer":{"name":"uv","version":"0.9.18","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d56d500f023b01e3bcffd16a9e2d898b7d5b473af2895e0aa613395a7c9cd636
|
|
| MD5 |
4d07eb90a8125b7a6cdc09cdcb203a0d
|
|
| BLAKE2b-256 |
b94d77016b2817293c53ca83c12979be439b549497f3afc05685cb0cd0074d6f
|