c9e28b8135
JetStream: anatomia de streams (storage/retention/limits), consumers pull durables con ack y cursor, dedup por Nats-Msg-Id, retencion workqueue, deliver policies. Simulador: boton ipywidgets que lanza 1 publisher -> N subscribers con miles de mensajes y grafica en movimiento (acumulado + throughput instantaneo).
450 lines
59 KiB
Plaintext
450 lines
59 KiB
Plaintext
{
|
||
"cells": [
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "9cdccc58",
|
||
"metadata": {},
|
||
"source": [
|
||
"# NATS pub/sub — 01 · Core publish/subscribe y wildcards\n",
|
||
"\n",
|
||
"**NATS** es un sistema de mensajería ligero y de alto rendimiento orientado a la comunicación entre procesos (microservicios, IoT, pipelines de datos). Su modelo central es **publish/subscribe** sobre *subjects*.\n",
|
||
"\n",
|
||
"### Conceptos clave\n",
|
||
"\n",
|
||
"- **Subject**: una cadena jerárquica separada por puntos, por ejemplo `pedidos.creados` o `sensor.cocina.temp`. Es la *dirección* de un mensaje. No hay que declararlo: existe en cuanto alguien publica o se suscribe.\n",
|
||
"- **Publisher**: un proceso que envía un mensaje a un subject. No sabe quién lo va a recibir (ni si alguien lo recibirá).\n",
|
||
"- **Subscriber**: un proceso que expresa interés en uno o varios subjects. Recibe todos los mensajes publicados en ellos mientras esté conectado.\n",
|
||
"- **Broker (`nats-server`)**: el proceso central que enruta cada mensaje publicado a todos los subscribers interesados. Desacopla a publishers de subscribers: ninguno conoce la dirección de red del otro, solo el broker y el subject.\n",
|
||
"\n",
|
||
"### Garantías del *core* NATS\n",
|
||
"\n",
|
||
"El core es **fire-and-forget** (*at-most-once*): si en el momento de publicar no hay ningún subscriber conectado a ese subject, el mensaje se descarta. No hay persistencia ni reintentos. Esto lo hace extremadamente rápido. Cuando se necesita persistencia y *replay* se usa **JetStream** (notebook 02).\n",
|
||
"\n",
|
||
"En este notebook levantamos un broker en Docker y demostramos: conexión, pub/sub básico, *fan-out* a varios subscribers y *wildcards*."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "2078b7a9",
|
||
"metadata": {},
|
||
"source": [
|
||
"## 0 · Entorno: el broker NATS en Docker\n",
|
||
"\n",
|
||
"Levantamos `nats:latest` con el flag `-js` (habilita JetStream, que usaremos en el notebook 02) y publicamos el puerto estándar `4222` en el host. La función `ensure_nats` es **idempotente**: si el contenedor ya existe lo reutiliza, si está parado lo arranca, y solo lo crea si no existe. Así los tres notebooks de este análisis comparten el mismo broker."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 1,
|
||
"id": "f304bf13",
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"Broker NATS: already-running en nats://127.0.0.1:4222\n",
|
||
"nats-server version: 2.14.2 | jetstream activo: True\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"import subprocess, time, json\n",
|
||
"\n",
|
||
"NATS_CONTAINER = \"nats_demo\"\n",
|
||
"NATS_PORT = 4222\n",
|
||
"NATS_URL = f\"nats://127.0.0.1:{NATS_PORT}\"\n",
|
||
"\n",
|
||
"def _docker(*args, check=True):\n",
|
||
" return subprocess.run([\"docker\", *args], capture_output=True, text=True, check=check)\n",
|
||
"\n",
|
||
"def ensure_nats(name=NATS_CONTAINER, port=NATS_PORT):\n",
|
||
" \"\"\"Arranca un broker NATS en Docker de forma idempotente. Devuelve el estado.\"\"\"\n",
|
||
" out = _docker(\"ps\", \"-a\", \"--filter\", f\"name=^{name}$\", \"--format\", \"{{.State}}\", check=False).stdout.strip()\n",
|
||
" if out == \"running\":\n",
|
||
" state = \"already-running\"\n",
|
||
" elif out in (\"exited\", \"created\", \"paused\"):\n",
|
||
" _docker(\"start\", name)\n",
|
||
" state = \"started\"\n",
|
||
" else:\n",
|
||
" _docker(\"run\", \"-d\", \"--name\", name, \"-p\", f\"{port}:4222\", \"-p\", \"8222:8222\",\n",
|
||
" \"nats:latest\", \"-js\", \"-m\", \"8222\")\n",
|
||
" state = \"created\"\n",
|
||
" time.sleep(1.0)\n",
|
||
" return state\n",
|
||
"\n",
|
||
"state = ensure_nats()\n",
|
||
"print(f\"Broker NATS: {state} en {NATS_URL}\")\n",
|
||
"# El endpoint de monitorización (puerto 8222) expone métricas del servidor en JSON\n",
|
||
"import urllib.request\n",
|
||
"varz = json.loads(urllib.request.urlopen(\"http://127.0.0.1:8222/varz\", timeout=3).read())\n",
|
||
"print(f\"nats-server version: {varz['version']} | jetstream activo: {bool(varz.get('jetstream'))}\")"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "e708766a",
|
||
"metadata": {},
|
||
"source": [
|
||
"## 1 · Conectar un cliente\n",
|
||
"\n",
|
||
"Usamos `nats-py`, el cliente oficial para Python basado en `asyncio`. IPython permite usar `await` directamente en las celdas (*top-level await*), así que mantenemos la conexión `nc` viva en una variable global y la reutilizamos a lo largo del notebook, igual que haría un proceso real."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 2,
|
||
"id": "54b982ec",
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"Conectado.\n",
|
||
" server_id : NBMS5DILO6BHTJILLYQZCV44PQ7K5JRAAGHW76QTTQKKMFPXDKN3SYDE\n",
|
||
" max_payload: 1 MiB por mensaje\n",
|
||
" client_id : 14\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"import asyncio\n",
|
||
"import nats\n",
|
||
"\n",
|
||
"# Conexión persistente del notebook (simula el cliente de un proceso de larga vida)\n",
|
||
"nc = await nats.connect(NATS_URL, name=\"notebook-01\")\n",
|
||
"info = nc._server_info # metadata que el broker envía en el handshake\n",
|
||
"print(\"Conectado.\")\n",
|
||
"print(f\" server_id : {info['server_id']}\")\n",
|
||
"print(f\" max_payload: {info['max_payload']/1024/1024:.0f} MiB por mensaje\")\n",
|
||
"print(f\" client_id : {info['client_id']}\")"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "f83e4c5e",
|
||
"metadata": {},
|
||
"source": [
|
||
"## 2 · Publish/Subscribe básico\n",
|
||
"\n",
|
||
"El patrón mínimo: un subscriber declara interés en un subject, un publisher envía un mensaje, el broker lo entrega.\n",
|
||
"\n",
|
||
"Aquí usamos una **suscripción síncrona** (`sub.next_msg()`), cómoda para ir paso a paso en un notebook: pedimos explícitamente el siguiente mensaje. El payload siempre viaja como `bytes` — NATS no impone formato; aquí codificamos texto UTF-8."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 3,
|
||
"id": "7d5f48e1",
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"subject recibido : saludos\n",
|
||
"payload : hola desde el publisher\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"# El subscriber declara interés ANTES de que se publique (core = at-most-once)\n",
|
||
"sub = await nc.subscribe(\"saludos\")\n",
|
||
"\n",
|
||
"# El publisher envía. No sabe quién escucha; solo conoce el subject.\n",
|
||
"await nc.publish(\"saludos\", b\"hola desde el publisher\")\n",
|
||
"await nc.flush() # fuerza el envío al broker antes de seguir\n",
|
||
"\n",
|
||
"# El subscriber recoge el mensaje\n",
|
||
"msg = await sub.next_msg(timeout=2)\n",
|
||
"print(f\"subject recibido : {msg.subject}\")\n",
|
||
"print(f\"payload : {msg.data.decode()}\")\n",
|
||
"\n",
|
||
"await sub.unsubscribe()"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "544fc965",
|
||
"metadata": {},
|
||
"source": [
|
||
"## 3 · Fan-out: un publisher, N subscribers\n",
|
||
"\n",
|
||
"La potencia del pub/sub es el **fan-out**: cuando varios subscribers están interesados en el mismo subject, el broker entrega una **copia a cada uno**. El publisher hace *una* llamada `publish` y no cambia nada en su código aunque haya 1 o 1000 subscribers.\n",
|
||
"\n",
|
||
"Aquí registramos 3 subscribers al subject `noticias` mediante *callbacks* asíncronos (cada uno simula un proceso distinto) y publicamos un único mensaje."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 4,
|
||
"id": "c43d65d9",
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"sub-A <- [noticias] titular: NATS entrega a todos\n",
|
||
"sub-B <- [noticias] titular: NATS entrega a todos\n",
|
||
"sub-C <- [noticias] titular: NATS entrega a todos\n",
|
||
"\n",
|
||
"Un publish -> 3 entregas (fan-out)\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"recibidos = [] # registro de quién recibió qué\n",
|
||
"\n",
|
||
"def make_handler(nombre):\n",
|
||
" async def handler(msg):\n",
|
||
" recibidos.append({\"subscriber\": nombre, \"subject\": msg.subject, \"data\": msg.data.decode()})\n",
|
||
" return handler\n",
|
||
"\n",
|
||
"# Tres subscribers independientes al MISMO subject\n",
|
||
"subs = []\n",
|
||
"for nombre in [\"sub-A\", \"sub-B\", \"sub-C\"]:\n",
|
||
" s = await nc.subscribe(\"noticias\", cb=make_handler(nombre))\n",
|
||
" subs.append(s)\n",
|
||
"\n",
|
||
"# Un único publish...\n",
|
||
"await nc.publish(\"noticias\", b\"titular: NATS entrega a todos\")\n",
|
||
"await nc.flush()\n",
|
||
"await asyncio.sleep(0.3) # dar tiempo a los callbacks\n",
|
||
"\n",
|
||
"for r in recibidos:\n",
|
||
" print(f\"{r['subscriber']} <- [{r['subject']}] {r['data']}\")\n",
|
||
"print()\n",
|
||
"print(f\"Un publish -> {len(recibidos)} entregas (fan-out)\")\n",
|
||
"\n",
|
||
"for s in subs:\n",
|
||
" await s.unsubscribe()"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "a390783b",
|
||
"metadata": {},
|
||
"source": [
|
||
"## 4 · Wildcards: `*` y `>`\n",
|
||
"\n",
|
||
"Los subjects son jerárquicos y los subscribers pueden usar comodines para suscribirse a familias enteras de subjects:\n",
|
||
"\n",
|
||
"- **`*`** (asterisco) — comodín de **un único token**. `sensor.*.temp` casa con `sensor.cocina.temp` y `sensor.salon.temp`, pero **no** con `sensor.cocina.planta1.temp`.\n",
|
||
"- **`>`** (mayor que) — comodín de **uno o más tokens** hasta el final. `sensor.>` casa con `sensor.cocina.temp`, `sensor.salon.humedad`, `sensor.garaje.puerta.estado`...\n",
|
||
"\n",
|
||
"Esto permite que un proceso se suscriba a \"todo lo de los sensores\" o \"la temperatura de cualquier habitación\" sin conocer de antemano qué subjects concretos existirán."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 5,
|
||
"id": "c1e8d839",
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"Mensajes publicados: ['sensor.cocina.temp', 'sensor.salon.temp', 'sensor.salon.humedad', 'sensor.garaje.puerta.estado']\n",
|
||
"\n",
|
||
"[sensor.> ] casó sensor.cocina.temp\n",
|
||
"[sensor.> ] casó sensor.salon.temp\n",
|
||
"[sensor.> ] casó sensor.salon.humedad\n",
|
||
"[sensor.> ] casó sensor.garaje.puerta.estado\n",
|
||
"[sensor.*.temp ] casó sensor.cocina.temp\n",
|
||
"[sensor.*.temp ] casó sensor.salon.temp\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"wild = []\n",
|
||
"\n",
|
||
"async def on_star(msg):\n",
|
||
" wild.append({\"patron\": \"sensor.*.temp\", \"subject\": msg.subject, \"data\": msg.data.decode()})\n",
|
||
"\n",
|
||
"async def on_gt(msg):\n",
|
||
" wild.append({\"patron\": \"sensor.>\", \"subject\": msg.subject, \"data\": msg.data.decode()})\n",
|
||
"\n",
|
||
"s_star = await nc.subscribe(\"sensor.*.temp\", cb=on_star)\n",
|
||
"s_gt = await nc.subscribe(\"sensor.>\", cb=on_gt)\n",
|
||
"\n",
|
||
"# Publicamos en varios subjects concretos\n",
|
||
"publicados = {\n",
|
||
" \"sensor.cocina.temp\": \"21.5\",\n",
|
||
" \"sensor.salon.temp\": \"22.1\",\n",
|
||
" \"sensor.salon.humedad\": \"48\",\n",
|
||
" \"sensor.garaje.puerta.estado\": \"abierta\",\n",
|
||
"}\n",
|
||
"for subj, val in publicados.items():\n",
|
||
" await nc.publish(subj, val.encode())\n",
|
||
"await nc.flush()\n",
|
||
"await asyncio.sleep(0.3)\n",
|
||
"\n",
|
||
"print(\"Mensajes publicados:\", list(publicados))\n",
|
||
"print()\n",
|
||
"for w in wild:\n",
|
||
" print(f\"[{w['patron']:14}] casó {w['subject']}\")\n",
|
||
"\n",
|
||
"await s_star.unsubscribe(); await s_gt.unsubscribe()"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "1bd82c55",
|
||
"metadata": {},
|
||
"source": [
|
||
"## 5 · Visualización: qué patrón casó qué subject\n",
|
||
"\n",
|
||
"Una matriz `subject × patrón` deja claro el alcance de cada comodín: `sensor.>` captura los cuatro subjects; `sensor.*.temp` solo las dos temperaturas de un nivel (no la humedad, que no es `temp`, ni la del garaje, que tiene un token de más)."
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 6,
|
||
"id": "01cb9af2",
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"image/png": "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+umnJLuoAUl2UcuUKZNRFzVra+skHyiESG7qtdljHz58yObNm+ncuTP379+ndOnS9O3bl9GjRwMQFBREoUKF0Ol0zJ49G29vb/r378/Nmzfp3bs3e/fupVSpUixfvpzt27cbLZMA8kUvUoZXe23ExsYSEhKivWZmZsbq1au5ffs2586dY9y4cfzyyy9s2LABgLCwMEJDQ4mNjWXmzJlkzpyZ5s2bkyNHDgBy5sz56S9IiHdgKPc//vgjI0aMMHrt5s2bdO3aldy5c7N8+XKcnJwYMGAAO3bsAP7XzVIpRb58+fDx8WHr1q306dOHcuXKkStXLho3bpzkeaVSW6RmMkYvlTLUTiU1+cOrXdQcHR2BlwugG7qorV27liVLltCqVSsgcRc1Ly+vROeTG51ISQxj414fIzFx4kQWL15M1apVmT9/PvDywbVHjx506dKFatWqUa1aNaNj6tevr6VTunRpSpcuDSQ9zk+IlMDExITY2FhWrlzJ999/z6BBg8ibN6+27unQoUPZsmUL7du3x9TUFG9vbxo2bMjRo0epU6cOZ86cIU+ePNjZ2bFkyZJES4IIkRIZKtoKFiyIiYkJoaGhWqvbli1beP78OePHjwegRo0ajBs3jvLly1O5cmUsLS2NKuoaNmxIcHAwlStXZs6cOdqakUKkNTr1epW4SFViYmLo168fERER1KlTh/bt2wMvb4jt27fHzMyMoKAgSpYsSYkSJejXrx9jx45l0KBBfP3112zbto0iRYqwdOlSo9YLmWBFpAZ79uwhMjISDw8PChQoQEhICB07diR37tz8/vvvWjnOkSMHPXv25McffyQkJIT9+/ezefNm9u3bx8CBA+nZs6dRukopGWgvUgS9Xo+JiYlRedyyZQu///47sbGxDBw4kJIlSyY6TinFvXv3cHJyAqBSpUp8++23dOzYkb///pv79+9ToECBT3YdQvwbb6pwW7duHX5+fty5c4csWbIwbNgw9Ho9+fPnZ/To0URFRdG7d2+6deumHXP+/HmmT5/OxIkTsbCwoF27diQkJDB06FBy5sypTc4iRFoiT/KphHRRE5+rpLonX716lYoVK9KiRQvWrVuHp6cngYGBuLu74+3tzdOnTzl79qxWjn/66SemTp3KixcvMDc3Z8OGDbi5uREWFpYoyAOZTU2kHIaW60ePHmnbsmXLRlhYGCEh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",
|
||
"text/plain": [
|
||
"<Figure size 900x260 with 1 Axes>"
|
||
]
|
||
},
|
||
"metadata": {},
|
||
"output_type": "display_data",
|
||
"transient": {}
|
||
},
|
||
{
|
||
"data": {
|
||
"text/html": [
|
||
"<div>\n",
|
||
"<style scoped>\n",
|
||
" .dataframe tbody tr th:only-of-type {\n",
|
||
" vertical-align: middle;\n",
|
||
" }\n",
|
||
"\n",
|
||
" .dataframe tbody tr th {\n",
|
||
" vertical-align: top;\n",
|
||
" }\n",
|
||
"\n",
|
||
" .dataframe thead th {\n",
|
||
" text-align: right;\n",
|
||
" }\n",
|
||
"</style>\n",
|
||
"<table border=\"1\" class=\"dataframe\">\n",
|
||
" <thead>\n",
|
||
" <tr style=\"text-align: right;\">\n",
|
||
" <th></th>\n",
|
||
" <th>sensor.cocina.temp</th>\n",
|
||
" <th>sensor.salon.temp</th>\n",
|
||
" <th>sensor.salon.humedad</th>\n",
|
||
" <th>sensor.garaje.puerta.estado</th>\n",
|
||
" </tr>\n",
|
||
" </thead>\n",
|
||
" <tbody>\n",
|
||
" <tr>\n",
|
||
" <th>sensor.*.temp</th>\n",
|
||
" <td>1</td>\n",
|
||
" <td>1</td>\n",
|
||
" <td>0</td>\n",
|
||
" <td>0</td>\n",
|
||
" </tr>\n",
|
||
" <tr>\n",
|
||
" <th>sensor.></th>\n",
|
||
" <td>1</td>\n",
|
||
" <td>1</td>\n",
|
||
" <td>1</td>\n",
|
||
" <td>1</td>\n",
|
||
" </tr>\n",
|
||
" </tbody>\n",
|
||
"</table>\n",
|
||
"</div>"
|
||
],
|
||
"text/plain": [
|
||
" sensor.cocina.temp sensor.salon.temp sensor.salon.humedad \\\n",
|
||
"sensor.*.temp 1 1 0 \n",
|
||
"sensor.> 1 1 1 \n",
|
||
"\n",
|
||
" sensor.garaje.puerta.estado \n",
|
||
"sensor.*.temp 0 \n",
|
||
"sensor.> 1 "
|
||
]
|
||
},
|
||
"execution_count": 6,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"import pandas as pd\n",
|
||
"import matplotlib.pyplot as plt\n",
|
||
"import numpy as np\n",
|
||
"\n",
|
||
"patrones = [\"sensor.*.temp\", \"sensor.>\"]\n",
|
||
"subjects = list(publicados)\n",
|
||
"\n",
|
||
"# Construir matriz de coincidencias a partir de lo que recibió cada suscripción\n",
|
||
"M = np.zeros((len(patrones), len(subjects)), dtype=int)\n",
|
||
"for w in wild:\n",
|
||
" i = patrones.index(w[\"patron\"])\n",
|
||
" j = subjects.index(w[\"subject\"])\n",
|
||
" M[i, j] = 1\n",
|
||
"\n",
|
||
"fig, ax = plt.subplots(figsize=(9, 2.6))\n",
|
||
"ax.imshow(M, cmap=\"Greens\", vmin=0, vmax=1, aspect=\"auto\")\n",
|
||
"ax.set_xticks(range(len(subjects))); ax.set_xticklabels(subjects, rotation=25, ha=\"right\", fontsize=9)\n",
|
||
"ax.set_yticks(range(len(patrones))); ax.set_yticklabels(patrones, fontsize=10)\n",
|
||
"for i in range(len(patrones)):\n",
|
||
" for j in range(len(subjects)):\n",
|
||
" ax.text(j, i, \"OK\" if M[i, j] else \"-\", ha=\"center\", va=\"center\",\n",
|
||
" color=\"white\" if M[i, j] else \"#999\", fontsize=12)\n",
|
||
"ax.set_title(\"Coincidencia de wildcards (OK = el subscriber recibió el mensaje)\")\n",
|
||
"plt.tight_layout(); plt.show()\n",
|
||
"\n",
|
||
"pd.DataFrame(M, index=patrones, columns=subjects)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"id": "26a3785a",
|
||
"metadata": {},
|
||
"source": [
|
||
"## Resumen\n",
|
||
"\n",
|
||
"- El **broker** desacopla publishers y subscribers: solo comparten el *subject*.\n",
|
||
"- El core es **fire-and-forget**: sin subscriber conectado, el mensaje se pierde.\n",
|
||
"- **Fan-out** automático: una publicación llega a todos los subscribers interesados.\n",
|
||
"- **Wildcards** `*` (un token) y `>` (resto de la jerarquía) permiten suscribirse a familias de subjects.\n",
|
||
"\n",
|
||
"**Siguiente** → `02_queue_request_jetstream.ipynb`: repartir carga entre workers (*queue groups*), RPC (*request/reply*) y mensajería **persistente** con JetStream.\n",
|
||
"\n",
|
||
"> La conexión `nc` y el contenedor `nats_demo` siguen vivos para los siguientes notebooks."
|
||
]
|
||
}
|
||
],
|
||
"metadata": {
|
||
"kernelspec": {
|
||
"display_name": "NATS analysis",
|
||
"language": "python",
|
||
"name": "nats"
|
||
},
|
||
"language_info": {
|
||
"codemirror_mode": {
|
||
"name": "ipython",
|
||
"version": 3
|
||
},
|
||
"file_extension": ".py",
|
||
"mimetype": "text/x-python",
|
||
"name": "python",
|
||
"nbconvert_exporter": "python",
|
||
"pygments_lexer": "ipython3",
|
||
"version": "3.12.3"
|
||
}
|
||
},
|
||
"nbformat": 4,
|
||
"nbformat_minor": 5
|
||
}
|