Block Development Libraries

Spade provides official libraries for five languages. Each library handles the runtime details — reading inputs, loading parameters, writing outputs — so you can focus entirely on your processing logic.

LanguageRuntimeInstall
PythonPython 3.12+ with uvpip install spadelib
RR 4.0+ with pakpak::pkg_install("krbundy/spadelib-cran")
TypeScriptBun 1.0+bun add spadelib
GoGo 1.25+go get github.com/krbundy/spadelib
RustRust stablecargo add spadelib

All five libraries share the same concepts and workflow:

  1. Define a handler function that accepts typed inputs and parameters
  2. Process the data using your domain tools and libraries
  3. Return a typed output — the library writes it to the correct location
  4. Call run(handler) — the library wires everything together

The libraries also provide a manifest builder that generates block manifest YAML from your handler's type annotations, keeping the manifest and implementation in sync.

Optional inputs and any inputs🔗

From spadelib 0.3, a block can declare a wired input optional -- it may be left unconnected -- and an input of type any, which takes whatever output is connected to it (a file, a JSON result, a directory or a collection). Both are declared in the manifest:

inputs:
  raster:    { type: file, format: GeoTIFF }
  hillshade: { type: file, format: GeoTIFF, optional: true }
  member:    { type: any, optional: true }

An optional input that nothing is connected to is absent: there is no inputs/<name>/ directory at all, and every library hands the handler its language's null. That is different from a connected collection that happens to be empty, which arrives as an empty collection.

An any input arrives as an AnyInput: kind (file, json, directory or collection) and format say what was connected; path is the file itself (or the directory, for a directory or collection); paths lists the top-level files.

LanguageOptional inputany input
Pythonannotate Optional[RasterFile] (or RasterFile | None); receives Noneannotate AnyInput
Rgive the parameter a default (hillshade = NULL) and a wired type in spade_types; receives NULLspade_types(h) <- list(member = "AnyInput")
TypeScriptinputs: { hillshade: optional(RasterFile) }; receives undefinedinputs: { member: AnyInput }
Gospadelib.OptionalInput[*spadelib.RasterFile](args, "hillshade"); returns nilspadelib.InputAny(args, "member") / OptionalInputAny
Rustargs.optional_input::<RasterFile>("hillshade")?; returns Noneargs.any_input("member")? / optional_any_input
from typing import Optional
from spadelib import RasterFile, run

def handler(raster: RasterFile, hillshade: Optional[RasterFile]) -> RasterFile:
    if hillshade is None:
        ...

Because several optional or any inputs of the same type are interchangeable to type matching, a pipeline connecting into them must name the input with as (see Input References); the web editor does this for you.