Skip to main content

A minimal DAG execution engine with pluggable executors

Project description

yray

A lightweight DAG execution framework for building reusable data, machine learning, and batch-processing pipelines.

yray separates workflow definition, planning, and execution, allowing the same pipeline to run across different execution environments without modification.


Installation

Requirements

  • Python 3.12+
  • uv (recommended)

Install uv:

curl -LsSf https://astral.sh/uv/install.sh | sh

Install yray (development)

Clone the repository:

git clone https://github.com/dplusj/yray.git
cd yray

Install dependencies:

uv sync

Install Ray support (optional)

uv sync --extra ray

or

pip install "yray[ray]"

Running Examples

yray includes two example pipelines:

examples/
├── local_pipeline.py
└── ray_pipeline.py

Local Executor Example

Run pipeline locally without any distributed system:

uv run python examples/local_pipeline.py

This uses:

  • LocalExecutor
  • In-process execution
  • Deterministic DAG evaluation

Ray Executor Example

Run the same pipeline on Ray:

uv sync --extra ray
uv run python examples/ray_pipeline.py

This uses:

  • RayExecutor
  • Distributed task scheduling
  • Remote execution

Run Tests

uv run pytest

Build Package

uv build

Motivation

Most workflow systems tightly couple DAG definition and execution.

yray treats a workflow as a reusable intermediate representation (IR):

Task
  ↓
DagNode
  ↓
Pipeline
  ↓
ExecutionPlan
  ↓
Executor

A pipeline is defined once and can be executed repeatedly across different runtime contexts:

  • Dates
  • Symbols
  • Regions
  • Experiments
  • Hyperparameter configurations
  • Any custom runtime dimension

Core Architecture

                     Context
                         │
                         ▼

+---------+     +---------+     +------------+
|  Task   | --> | DagNode | --> |  Pipeline  |
+---------+     +---------+     +------------+
                                         │
                                         ▼

                                +----------------+
                                |    Planner     |
                                +----------------+
                                         │
                                         ▼

                                +----------------+
                                | ExecutionPlan  |
                                +----------------+
                                         │
                                         ▼

                                +----------------+
                                |   Executor     |
                                +----------------+
                                         │
              ┌──────────────────────────┼──────────────────────────┐
              ▼                          ▼                          ▼

       LocalExecutor             RayExecutor              SlurmExecutor

Core Concepts

Task

A Task defines a unit of computation.

from yray import task, Context

@task
def load_features(*, context: Context):
    return [1, 2, 3, 4]

Tasks:

  • Define computation logic
  • Are backend-independent
  • Receive runtime Context

DagNode

A DagNode wraps a Task and defines dependencies.

features = load_features()

normalized = normalize_features(features)

Represents:

load_features
      ↓
normalize_features

Pipeline

A Pipeline defines final outputs of a workflow.

from yray import Pipeline

pipeline = Pipeline(
    outputs={
        "model": model,
        "metrics": metrics,
    }
)

Context

Runtime information injected by the executor.

from yray import Context

ctx = Context(
    date="2024-01-01",
    params={
        "symbol": "AAPL",
        "region": "US",
    },
)

Used inside tasks:

@task
def load_features(*, context: Context):
    symbol = context.params["symbol"]
    return symbol

Planner

Converts Pipeline → ExecutionPlan.

from yray import Planner

plan = Planner.compile(pipeline)

Plans are reusable across contexts.


Executor

Executes a plan on a backend.

Available:

  • LocalExecutor
  • RayExecutor (optional)
from yray import Engine, LocalExecutor

engine = Engine(executor=LocalExecutor())

Quick Start

Define Tasks

from yray import task, Context

@task
def load_features(*, context: Context):
    return [1, 2, 3, 4, 5]


@task
def normalize_features(data, *, context: Context):
    total = sum(data)
    return [x / total for x in data]


@task
def train_model(data, *, hidden_size: int, lr: float, epochs: int, context: Context):
    return {
        "hidden_size": hidden_size,
        "lr": lr,
        "epochs": epochs,
    }

Build DAG

features = load_features()

normalized = normalize_features(features)

model = train_model(
    normalized,
    hidden_size=64,
    lr=1e-3,
    epochs=10,
)

Define Pipeline

from yray import Pipeline

pipeline = Pipeline(
    outputs={
        "model": model,
    }
)

Compile Plan

from yray import Planner

plan = Planner.compile(pipeline)

Execute (Local)

from yray import Engine, LocalExecutor, Context

engine = Engine(
    executor=LocalExecutor(),
)

result = engine.gather(
    engine.run(
        plan,
        Context(
            date="2024-01-01",
            params={
                "symbol": "AAPL",
            },
        ),
    )
)

print(result)

Execute (Ray)

from yray import Engine, Planner, Context
from yray.executors import RayExecutor

plan = Planner.compile(pipeline)

engine = Engine(
    executor=RayExecutor(),
)

result = engine.gather(
    engine.run(
        plan,
        Context(
            date="2024-01-01",
            params={
                "symbol": "AAPL",
            },
        ),
    )
)

Multi-Model Example

features = load_features()

normalized = normalize_features(features)

small = train_model(normalized, hidden_size=16, lr=1e-3, epochs=5)

medium = train_model(normalized, hidden_size=64, lr=1e-3, epochs=5)

large = train_model(normalized, hidden_size=128, lr=1e-4, epochs=5)

Graph:

                 small
                /
load → normalize
                \
                 medium
                \
                 large

Multi-Context Execution

from yray import Context

contexts = [
    Context(date="2024-01-01"),
    Context(date="2024-01-02"),
    Context(date="2024-01-03"),
]

results = [
    engine.run(plan, ctx)
    for ctx in contexts
]

final = engine.gather(results)

Backend Extension

class Executor:
    def submit(self, node, deps, kwargs, context):
        ...

    def gather(self, handle):
        ...

Example:

class SlurmExecutor(Executor):
    def submit(self, node, deps, kwargs, context):
        ...

No changes required to DAG or Planner.


Features

  • Typed DAG IR
  • Context-aware execution
  • Planner-based execution separation
  • Local + Ray backends
  • Fan-out DAG support
  • Reusable execution plans
  • Multi-context execution

Roadmap

  • Persistent caching
  • DAG visualization
  • Incremental execution
  • Slurm backend
  • Kubernetes backend
  • Failure recovery
  • Distributed tracing

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

yray-0.1.0.tar.gz (61.0 kB view details)

Uploaded Source

Built Distribution

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

yray-0.1.0-py3-none-any.whl (8.6 kB view details)

Uploaded Python 3

File details

Details for the file yray-0.1.0.tar.gz.

File metadata

  • Download URL: yray-0.1.0.tar.gz
  • Upload date:
  • Size: 61.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.5

File hashes

Hashes for yray-0.1.0.tar.gz
Algorithm Hash digest
SHA256 91a1c12eb54c63fb920a1456240ca3aeb9733dd56115c510806555a7f10b3065
MD5 0a9ab656f267b17271a70cd1a062a569
BLAKE2b-256 e22898d16c5ee139aca0b1ff979da9f88cf4d00882e226820b7065ea46d2dccf

See more details on using hashes here.

File details

Details for the file yray-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: yray-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 8.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.5

File hashes

Hashes for yray-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 1048b81863334d4aed35316a8379517bb6ed4aa06a37293d4d9b16cf11ee51d4
MD5 51e108f5493fc855865c1f6337fbd83e
BLAKE2b-256 f823a2b2c97c835228f9de29738171f3fc9181702af7694acb286ef821f1f1f2

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