O problema
Pipeline batch tem sempre a mesma forma (ler, transformar, validar, escrever) e mesmo assim cada um é reescrito em código. Responder "o que esse job faz?" vira ler um diff de PySpark, e essa resposta não dá para versionar como contrato, lintar, gerar nem desenhar.
O pipeline é o documento
Um pipeline é um contrato JSON, executado pelo framework e interpretado por mais ninguém. Roda como está pelo Python ou pela CLI, e o mesmo arquivo abre no canvas do Studio como nós conectados.
{
"name": "customers_curated",
"input": { "format": "csv", "path": "/data/landing/customers" },
"transformations": [
{ "type": "filter", "condition": "status = 'active'" },
{ "type": "cast", "columns": { "created_at": "timestamp" } },
{ "type": "drop_duplicates", "columns": ["id"] }
],
"validations": {
"on_failure": "warn",
"rules": [
{ "type": "not_null", "columns": ["id"] },
{ "type": "unique", "columns": ["id"] }
],
"report": { "format": "csv", "path": "/dq/customers", "mode": "append" }
},
"output": {
"format": "parquet",
"path": "/data/curated/customers",
"mode": "overwrite",
"partition_by": ["created_at"]
}
}Declarativo onde costuma virar imperativo
As partes em que todo mundo volta para o código têm forma declarativa: parâmetros {param} resolvidos antes do parse, variáveis de runtime {{var}} que empurram um conjunto de chaves coletado para a leitura seguinte como um IN (...) literal (a versão declarativa do truque collect() + isin() que faz o data skipping do Delta funcionar), fragmentos reutilizáveis via $include, e vários destinos numa passada só.
{ "type": "checkpoint" },
{ "type": "collect", "column": "id_cliente", "as": "clientes_ativos" },
{ "type": "join",
"input": { "format": "delta", "path": "vendas.bronze_eventos_cliente" },
"with_transformations": [
{ "type": "filter", "condition": "id_cliente IN ({{clientes_ativos}})" },
{ "type": "distinct" }
],
"on": "id_cliente", "how": "left" }Duas metades, um contrato
O Studio é um canvas no navegador que lê e escreve exatamente esses documentos: linta enquanto você digita, gera um pipeline com o LLM que você escolher, e executa por um runner local preso em 127.0.0.1: sem conta, sem servidor, sem telemetria. Ele nunca inventa sintaxe que o framework não suporta, e o framework nunca precisa dele para rodar.
Qualidade de dados segue a mesma ideia: é o sparquet-cola, biblioteca separada, declarada como dependência e reexportada em sparquet.validation.*.
from sparquet import Sparquet
fw = Sparquet(spark={"app_name": "MyJob", "master": "local[*]"})
result = fw.run("pipeline.json", params={"dt_ref": "2026-01-01"})
print(result.summary())
fw.stop()
# or, without writing any Python at all:
# $ sparquet pipeline.jsonOnde roda
- Local, Databricks, EMR, Dataproc ou Synapse: o session manager detecta o ambiente
- Python 3.9 a 3.14, sobre PySpark 3.4+
- Extensível: registre seus próprios readers, writers, transformações e validadores
- Documentado no sparquet.dev em inglês, português e espanhol
Qualidade de dados para Spark como biblioteca, não mais um serviço para operar.