Tilebox languages and SDKs

Tilebox supports multiple languages and SDKs for running workflows. All Tilebox SDKs and workflows are designed to be interoperable, which means it’s possible to have a workflow where individual tasks are executed in different languages. Check out Languages & SDKs to learn more about currently available programming SDKs.

Why multi-language workflows?

You might need to use multiple languages in a single workflow for many reasons, such as:

  • You want to use a language that is better suited for a specific task (for example Python for data processing, Go for a backend API)
  • You want to use a library that is only available in a specific language (for example xarray in Python)
  • You started prototyping in Python, but need to start migrating the compute-intensive parts of your workflow to a different language for performance reasons

Multi-language workflow example

This guide will tackle the first use case: you have a tasking server in Go and want to offload some of the processing to Python.

Defining tasks in Python and Go

Python
class ScheduleImageCapture(Task):
    # The input parameters must match the ones defined in the Go task
    location: tuple[float, float]  # lat_lon
    resolution_m: int
    spectral_bands: list[float]  # spectral bands in nm

    def execute(self, context: ExecutionContext) -> None:
        # Here you can implement your task logic, submit subtasks, etc.
        print(f"Image captured for {self.location} with {self.resolution_m}m resolution and bands {self.spectral_bands}")

    @staticmethod
    def identifier() -> tuple[str, str]:
        # The identifier must match the one defined in the Go task
        return "tilebox.com/schedule_image_capture", "v1.0"
Go
type ScheduleImageCapture struct {
    // json tags must match the Python task definition
    Location      [2]float64 `json:"location"` // lat_lon
    ResolutionM   int        `json:"resolution_m"`
    SpectralBands []float64  `json:"spectral_bands"` // spectral bands in nm
}

// No need to define the Execute method since we're only submitting the task

// Identifier must match with the task identifier in the Python runner
func (t *ScheduleImageCapture) Identifier() workflows.TaskIdentifier {
    return workflows.NewTaskIdentifier("tilebox.com/schedule_image_capture", "v1.0")
}

A couple important points to note:

Creating a Go server that submits jobs

Write a simple HTTP tasking server in Go with a /submit endpoint that accepts requests to submit a ScheduleImageCapture job.

Both Go and Python code are using test-cluster-tZD9Ca2qsqt4V as the cluster slug. You should replace it with your own cluster slug, which you can create in the Tilebox Console.

Go
func main() {
    log.Println("Server starting on http://localhost:8080")

    client := workflows.NewClient()
    http.HandleFunc("/submit", submitHandler(client))
    log.Fatal(http.ListenAndServe(":8080", nil))
}

// Submit a job based on some query parameters
func submitHandler(client *workflows.Client) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        latArg := r.URL.Query().Get("lat")
        lonArg := r.URL.Query().Get("lon")
        resolutionArg := r.URL.Query().Get("resolution")
        bandsArg := r.URL.Query().Get("bands[]")

        latFloat, err := strconv.ParseFloat(latArg, 64)
        if err != nil {
            http.Error(w, err.Error(), http.StatusBadRequest)
            return
        }
        lonFloat, err := strconv.ParseFloat(lonArg, 64)
        if err != nil {
            http.Error(w, err.Error(), http.StatusBadRequest)
            return
        }

        resolutionM, err := strconv.Atoi(resolutionArg)
        if err != nil {
            http.Error(w, err.Error(), http.StatusBadRequest)
            return
        }

        var spectralBands []float64
        for _, bandArg := range strings.Split(bandsArg, ",") {
            band, err := strconv.ParseFloat(bandArg, 64)
            if err != nil {
                http.Error(w, err.Error(), http.StatusBadRequest)
                return
            }
            spectralBands = append(spectralBands, band)
        }

        job, err := client.Jobs.Submit(r.Context(), "Schedule Image capture",
            []workflows.Task{
                &ScheduleImageCapture{
                    Location:      [2]float64{latFloat, lonFloat},
                    ResolutionM:   resolutionM,
                    SpectralBands: spectralBands,
                },
            },
        )
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }

        _, _ = io.WriteString(w, fmt.Sprintf("Job submitted: %s\n", job.ID))
    }
}

In the same way that you can submit jobs across languages you can also submit subtasks across languages.

Creating a Python runner

Write a Python script that starts a task runner and registers the ScheduleImageCapture task.

Python
from tilebox.workflows import Client

def main():
    client = Client()
    runner = client.runner(tasks=[ScheduleImageCapture])
    runner.run_forever()

if __name__ == "__main__":
    main()

Testing it

Start the Go server.

Shell
go run .

In another terminal, start the Python runner.

Shell
uv run runner.py

Submit a job to the Go server.

Shell
curl http://localhost:8080/submit?lat=40.75&lon=-73.98&resolution=30&bands[]=489.0,560.6,666.5

Check the Python runner output, it should print the following line:

Output
Image captured for [40.75, -73.98] with 30m resolution and bands [489, 560.6, 666.5]

Next Steps

As a learning exercise, you can try to change the News API Workflow to replace the FetchNews task with a Go task and keep all the other tasks in Python. You’ll learn how to submit a subtask in another language than what the current task is executed in.