Skip to main content

Platform-agnostic job routing framework for Spark ETL pipelines

Project description

SparkRouter

PyPI version Python License

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
  • Configuration-Driven: JSON config via CLI, no code changes between environments

Installation

uv add sparkrouter
# or
pip install sparkrouter

Quick Start

How It Works

flowchart LR
    A["Airflow DAG"] --> B["Entry Script"] --> C["Your Job Factory"] --> D["Your Job"]
  1. Airflow (or any orchestrator) triggers a Spark platform job
  2. The entry script routes to your code via --module_name
  3. Your factory creates the job with dependencies
  4. Your job runs the business logic

Step 1: Write Your Job

# my_etl_job.py
from sparkrouter import AbstractJob

class MyETLJob(AbstractJob):
    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):
        print(f"Done: {results['records_processed']} records")

    def on_failure(self, error_message):
        print(f"Failed: {error_message}")

Step 2: Write Your Factory

# my_etl_job_factory.py
from sparkrouter import AbstractJobFactory
from my_etl_job import MyETLJob

class MyETLJobFactory(AbstractJobFactory):
    def create_job(self, **kwargs) -> MyETLJob:
        return MyETLJob()

def main(**kwargs):
    """Entry point called by SparkRouter."""
    factory = MyETLJobFactory()
    return factory.run(**kwargs)

Step 3: Run Locally

No entry script needed - run directly from the installed package:

python -m sparkrouter.entry_points.container \
    --module_name my_etl_job_factory \
    --input_path "/data/input" \
    --output_path "/data/output"

Platform Deployment

When deploying to Glue, Databricks, or EMR, those platforms require a script file at an S3/DBFS location. SparkRouter uses a single entry script per platform that routes to any of your jobs via --module_name. You create this script once, upload it once, and reuse it for all jobs.

AWS Glue

1. Create the entry script (one time):

# glue_entry.py
from sparkrouter.entry_points.glue import main

if __name__ == "__main__":
    main()

2. Upload to S3 (one time):

aws s3 cp glue_entry.py s3://my-bucket/scripts/glue_entry.py

3. Run any job by specifying --module_name:

GlueJobOperator(
    script_location='s3://my-bucket/scripts/glue_entry.py',
    script_args={
        '--module_name': 'mypackage.jobs.my_etl_job_factory',
        '--input_path': 's3://data/input/',
        '--output_path': 's3://data/output/',
    },
    default_arguments={
        '--additional-python-modules': 'sparkrouter,mypackage',
    },
)

Databricks

1. Create the entry script (one time):

# databricks_entry.py
from sparkrouter.entry_points.container import ContainerEntryPoint

class DatabricksEntryPoint(ContainerEntryPoint):
    @property
    def service_provider(self) -> str:
        return "DATABRICKS"

    def detect_spark(self) -> bool:
        return True  # Databricks always has Spark

def main(argv=None):
    return DatabricksEntryPoint().run(argv)

if __name__ == "__main__":
    main()

2. Upload to DBFS (one time):

databricks fs cp databricks_entry.py dbfs:/scripts/databricks_entry.py

3. Run any job by specifying --module_name:

DatabricksSubmitRunOperator(
    spark_python_task={
        'python_file': 'dbfs:/scripts/databricks_entry.py',
        'parameters': [
            '--module_name', 'mypackage.jobs.my_etl_job_factory',
            '--input_path', 's3://data/input/',
            '--output_path', 's3://data/output/',
        ],
    },
    libraries=[
        {'pypi': {'package': 'sparkrouter'}},
        {'pypi': {'package': 'mypackage'}},
    ],
)

EMR

1. Create the entry script (one time):

# emr_entry.py
import os
from sparkrouter.entry_points.container import ContainerEntryPoint

class EMREntryPoint(ContainerEntryPoint):
    @property
    def service_provider(self) -> str:
        return "EMR"

    def add_platform_context(self, args):
        args = super().add_platform_context(args)
        args['region'] = os.environ.get('AWS_REGION')
        return args

    def detect_spark(self) -> bool:
        return True  # EMR always has Spark

def main(argv=None):
    return EMREntryPoint().run(argv)

if __name__ == "__main__":
    main()

2. Upload to S3 (one time):

aws s3 cp emr_entry.py s3://my-bucket/scripts/emr_entry.py

3. Run any job via spark-submit:

spark-submit s3://my-bucket/scripts/emr_entry.py \
    --module_name mypackage.jobs.my_etl_job_factory \
    --input_path s3://data/input/ \
    --output_path s3://data/output/

Examples

See the examples directory:

License

Apache License 2.0

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

sparkrouter-0.2.0.tar.gz (24.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

sparkrouter-0.2.0-py3-none-any.whl (15.6 kB view details)

Uploaded Python 3

File details

Details for the file sparkrouter-0.2.0.tar.gz.

File metadata

  • Download URL: sparkrouter-0.2.0.tar.gz
  • Upload date:
  • Size: 24.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.8

File hashes

Hashes for sparkrouter-0.2.0.tar.gz
Algorithm Hash digest
SHA256 595c18acc59ac2732a94a3a77e0d363669582c665a281d4158c4ffafc1048c3c
MD5 316003efe4c4f116e1ef227f672190ca
BLAKE2b-256 334c55f90b1cdac9896520079a9e553efbfae5df021df4752926ed95c409dcd0

See more details on using hashes here.

File details

Details for the file sparkrouter-0.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for sparkrouter-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 ec44258fd80996cf4f8e6245fbdc3d80e49b639a85abe740d3c7fbd4bef88242
MD5 a189f822f9340bd59d20ccdf95f9d85e
BLAKE2b-256 499e693143f0815fbba5119e010f0dbd4c7bdf4de36c4da65b2e41f094a39a03

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page