Ir al contenido
synsema ¿por qué?
← Todos los casos
Caso 3 · Medido el 2026-10-16 · Synsema 0.6.44 · mismos datos y mismo modelo de los dos lados

Qué puede hacer una dependencia

Tu ETL importa un módulo auxiliar. ¿Qué puede hacer ese módulo con los permisos de tu programa?

14 vs 0

paquetes de terceros en el mismo ETL de 29 líneas

El mismo ETL: 14 paquetes y 169 MB de código ajeno en Python, cero en Synsema.

ETL en Python14ETL en Synsema0
Paquetes de terceros instalados para el mismo ETLCada cuadrado es un paquete, directo o transitivo. 169 MB, 2156 archivos .py y 99 binarios nativos del lado de Python.

El problema

Los paquetes envenenados en npm y PyPI que roban tokens y secretos de CI ya son cosa de todos los meses.

En Python o Node, cualquier módulo importado corre con todos los permisos del proceso. Un ETL chico trae una docena de paquetes que nadie lee.

Qué construimos

  • Un ETL real, escrito dos veces en 29 líneas: clima diario de cinco ciudades desde Open-Meteo a Parquet más un resumen CSV. Los dos dan los mismos números.
  • Cada versión importa un módulo auxiliar que representa cualquier dependencia. Formatea números y además informa qué podría hacer: leer .env, abrir una conexión a un host no declarado.
  • Cantidad de dependencias, tamaño y vulnerabilidades conocidas (pip-audit) del lado de Python; synsema code caps y synsema build del lado de Synsema.

Resultados

El mismo ETL, lado a lado
PythonSynsema
Paquetes, directos y transitivos140
Código de terceros en disco169 MB · 2156 archivos .py · 99 binarios nativosninguno
Vulnerabilidades conocidas (pip-audit)3 avisos en requests, pyarrow, python-dotenvnada que auditar
El módulo importado puede leer .envsíno
El módulo importado puede ir a un host no declaradosíno
Saber qué toca antes de ejecutarno, hay que leer los 14 paquetessí, code caps marca MISSING file.read(".env") y net("example.com")
Distribuciónintérprete + venv + 14 paquetesun binario de 45,9 MB
Tiempo de ejecución6,8 a 9,3 s (1,3 s solo en importar; ciudades en serie)1,4 s (ciudades en paralelo)

El código, lado a lado

Lo habitual
ETL en Pythonrequests, pandas, pyarrow, tenacity, python-dotenv.
import datetime as dt, json, sys
import requests, pandas as pd
from tenacity import retry, stop_after_attempt, wait_exponential
from dotenv import load_dotenv
import helpers

load_dotenv()
CITIES = {"Buenos Aires": (-34.61, -58.38), "Santiago": (-33.45, -70.67), "Lima": (-12.05, -77.04), "Bogotá": (4.71, -74.07), "Madrid": (40.42, -3.70)}

@retry(stop=stop_after_attempt(4), wait=wait_exponential(min=1, max=10))
def fetch(lat, lon, start, end):
    r = requests.get("https://archive-api.open-meteo.com/v1/archive", params={"latitude": lat, "longitude": lon, "start_date": start,
                     "end_date": end, "daily": "temperature_2m_max,precipitation_sum", "timezone": "auto"}, timeout=30)
    r.raise_for_status()
    return r.json()["daily"]

end = dt.date(2026, 10, 10); start = end - dt.timedelta(days=29)
rows = []
for city, (lat, lon) in CITIES.items():
    d = fetch(lat, lon, start.isoformat(), end.isoformat())
    rows += [{"city": city, "date": f, "tmax": t, "rain": p} for f, t, p in zip(d["time"], d["temperature_2m_max"], d["precipitation_sum"])]
df = pd.DataFrame(rows)
df.to_parquet(sys.argv[1] + "/weather_python.parquet", index=False)
summary = df.groupby("city").agg(tmax_mean=("tmax", "mean"), rain_total=("rain", "sum")).reset_index()
summary["tmax_mean"] = summary["tmax_mean"].map(helpers.round1)
summary.to_csv(sys.argv[1] + "/summary_python.csv", index=False)
print(summary.to_string(index=False))
print("what the imported module can do:", json.dumps(helpers.capabilities(), ensure_ascii=False))
cases/03-supply-chain/python/etl.py · desde la línea 2
Synsema
El mismo ETL en SynsemaDos require: un host y una carpeta de salida. Nada más.
use "./helpers.syn" as helpers
require net("archive-api.open-meteo.com")
require file.write("./cases/03-supply-chain/results/*")

let CITIES be [{"c": "Buenos Aires", "lat": -34.61, "lon": -58.38}, {"c": "Santiago", "lat": -33.45, "lon": -70.67},
               {"c": "Lima", "lat": -12.05, "lon": -77.04}, {"c": "Bogotá", "lat": 4.71, "lon": -74.07}, {"c": "Madrid", "lat": 40.42, "lon": -3.70}]
let END be date("2026-10-10")
let START be END - duration(days = 29)

task fetch(x)
    let n be 0
    while n < 4
        let r be http_get("https://archive-api.open-meteo.com/v1/archive", {}, {"latitude": text(x.lat), "longitude": text(x.lon),
            "start_date": text(START), "end_date": text(END), "daily": "temperature_2m_max,precipitation_sum", "timezone": "auto"}, 30)
        when r.ok
            let d be (json of r).daily
            give apply((e) => {"city": x.c, "date": e.item, "tmax": d.temperature_2m_max[e.index], "rain": d.precipitation_sum[e.index]}, enumerate(d.time))
        set n to n + 1
    raise(`could not fetch {x.c}`)

let rows be flatten(parallel_map(fetch, CITIES, 5))
write_file("./cases/03-supply-chain/results/weather_synsema.parquet", parquet_write(rows))
let summary be apply((r) => merge(r, {"tmax_mean": helpers.round1(r.tmax_mean)}), summarize(rows, "city", {"tmax_mean": mean_of("tmax"), "rain_total": sum_of("rain")}))
write_file("./cases/03-supply-chain/results/summary_synsema.csv", csv_encode(summary))
each r in summary
    print(`{r.city}: {r.tmax_mean} °C, {round_to(r.rain_total, 1)} mm`)
print(`what the imported module can do: {helpers.capabilities()}`)
cases/03-supply-chain/synsema/etl.syn · desde la línea 3

Por qué Synsema

  • No hay gestor de paquetes ni código de terceros. Los módulos son archivos locales.
  • Un módulo recibe solo las capabilities que declaró el archivo de entrada. Si el ETL declara un host, ningún módulo puede hablar con otro.
  • synsema code caps es una revisión estática: muestra qué necesita cada task y qué falta declarar, antes de ejecutar.

Lo que no resuelve

  • Los avisos de pip-audit se van actualizando, que es justamente el mantenimiento continuo que pide Python.
  • Un módulo local de Synsema hereda las capabilities del programa. Si el programa declara de más, el módulo también las tiene.
  • La diferencia de tiempo viene sobre todo de bajar en serie contra en paralelo, no del lenguaje.

Reproducirlo

git clone https://github.com/synsema/why-synsema && cd why-synsema
# cases/03-supply-chain
synsema run       cases/03-supply-chain/synsema/etl.syn
synsema code caps cases/03-supply-chain/synsema/etl.syn
uv run --with-requirements cases/03-supply-chain/python/requirements.txt python cases/03-supply-chain/python/etl.py cases/03-supply-chain/results
uvx pip-audit -r cases/03-supply-chain/python/requirements.txt

github.com/synsema/why-synsema/tree/main/cases/03-supply-chain

Compartir