frontend añadido y backend de creacion de notas

This commit is contained in:
2025-09-18 00:31:23 +02:00
parent 1deabb8a26
commit a76b13ec33
75 changed files with 20364 additions and 317 deletions
-4
View File
@@ -6,11 +6,7 @@ ENV POSTGRES_USER=postgres \
RUN apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
gdal-bin \
postgresql-15-pgvector \
postgresql-15-postgis-3 \
postgresql-15-postgis-3-scripts \
proj-bin \
&& rm -rf /var/lib/apt/lists/*
# Copiamos scripts de inicialización
+220
View File
@@ -0,0 +1,220 @@
import marimo
__generated_with = "0.15.5"
app = marimo.App(width="columns")
@app.cell
def _():
import os
import textwrap
import yaml
import subprocess
from pathlib import Path
# ==== Parámetros ====
PUERTO_POSTGRES = 55455
SERVICIO = "postgres_ext"
PASSWORD = "mipassword"
USER = "postgres"
DBNAME = "basededatos"
# Lista de extensiones que quieres habilitar
EXTENSIONES = [
# builtin
"hstore", "citext", "uuid-ossp", "pg_trgm",
# requieren paquetes
# "postgis",
"pgvector",
# "timescaledb" # <- si quieres usar base de timescaledb, activa la bandera abajo
]
# Usa imagen base de timescaledb cuando la extensión 'timescaledb' esté en la lista
timescaledb_base_image = False # pon True si quieres usar la imagen base de TimescaleDB
RUTA_PROYECTO = Path(".").resolve()
return (
DBNAME,
EXTENSIONES,
PASSWORD,
PUERTO_POSTGRES,
Path,
RUTA_PROYECTO,
SERVICIO,
USER,
subprocess,
textwrap,
timescaledb_base_image,
yaml,
)
@app.function
def pkgs_para_extensiones(exts, pg_major=15, use_timescale_base=False):
"""
Devuelve (pkgs_apt, builtins) para las extensiones solicitadas.
builtins = extensiones que no requieren apt
pkgs_apt = paquetes apt necesarios para otras extensiones
"""
builtins = []
pkgs_apt = []
for e in exts:
e_low = e.lower()
if e_low in {"hstore", "citext", "uuid-ossp", "pg_trgm"}:
builtins.append(e_low)
elif e_low == "postgis":
pkgs_apt += [
f"postgresql-{pg_major}-postgis-3",
f"postgresql-{pg_major}-postgis-3-scripts",
"gdal-bin",
"proj-bin",
]
elif e_low == "pgvector":
pkgs_apt += [f"postgresql-{pg_major}-pgvector"]
elif e_low == "timescaledb":
if not use_timescale_base:
# Para instalar timescaledb necesitarías repos adicionales
pass
else:
raise ValueError(f"Extensión no soportada en este helper: {e}")
pkgs_apt = sorted(set(pkgs_apt))
return pkgs_apt, builtins
@app.cell
def _(DBNAME, Path, USER, textwrap):
def generar_dockerfile(ruta: Path, exts, use_timescale_base=False, pg_major=15):
ruta.mkdir(parents=True, exist_ok=True)
pkgs_apt, builtins = pkgs_para_extensiones(exts, pg_major, use_timescale_base)
base_image = (
f"timescale/timescaledb:2.16-pg{pg_major}" if use_timescale_base else f"postgres:{pg_major}"
)
apt_block = ""
if pkgs_apt:
apt_lines = [
"RUN apt-get update && \\",
" DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \\",
]
# 👇 Backslash en **todas** las líneas de paquetes
for pkg in pkgs_apt:
apt_lines.append(f" {pkg} \\")
# y ya colgamos el rm del último paquete
apt_lines.append(" && rm -rf /var/lib/apt/lists/*")
apt_block = "\n".join(apt_lines)
dockerfile = f"""
FROM {base_image}
# Variables de entorno útiles
ENV POSTGRES_USER={USER} \\
POSTGRES_DB={DBNAME}
{apt_block}
# Copiamos scripts de inicialización
COPY docker-entrypoint-initdb.d/ /docker-entrypoint-initdb.d/
"""
(ruta / "Dockerfile").write_text(textwrap.dedent(dockerfile).strip() + "\n", encoding="utf-8")
return (generar_dockerfile,)
@app.cell
def _(Path):
def generar_init_sql(ruta: Path, exts):
init_dir = ruta / "docker-entrypoint-initdb.d"
init_dir.mkdir(parents=True, exist_ok=True)
lines = ["-- Habilitar extensiones solicitadas"]
for e in exts:
e_low = e.lower()
ext_name = {
"pgvector": "vector",
"postgis": "postgis",
"hstore": "hstore",
"citext": "citext",
"uuid-ossp": "\"uuid-ossp\"",
"pg_trgm": "pg_trgm",
"timescaledb": "timescaledb",
}.get(e_low, e_low)
lines.append(f"CREATE EXTENSION IF NOT EXISTS {ext_name};")
sql = "\n".join(lines) + "\n"
(init_dir / "10-extensions.sql").write_text(sql, encoding="utf-8")
return (generar_init_sql,)
@app.cell
def _(DBNAME, Path, USER, yaml):
def crear_docker_compose(ruta: Path, servicio: str, puerto_host: int, password: str):
compose = {
"version": "3.8",
"services": {
servicio: {
"build": {"context": "."},
"restart": "always",
"ports": [f"{puerto_host}:5432"],
"environment": {
"POSTGRES_PASSWORD": password,
"POSTGRES_USER": USER,
"POSTGRES_DB": DBNAME,
},
"healthcheck": {
"test": ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"],
"interval": "10s",
"timeout": "5s",
"retries": 5,
},
"volumes": [
"postgres_data:/var/lib/postgresql/data"
],
}
},
"volumes": {"postgres_data": {}}
}
(ruta / "docker-compose.yml").write_text(yaml.dump(compose, sort_keys=False), encoding="utf-8")
return (crear_docker_compose,)
@app.cell
def _(Path, subprocess):
def construir_y_levantar(ruta: Path):
def _run(cmd):
subprocess.run(cmd, cwd=ruta, check=True)
try:
_run(["docker", "compose", "build"])
_run(["docker", "compose", "up", "-d"])
except Exception:
_run(["docker-compose", "build"])
_run(["docker-compose", "up", "-d"])
return (construir_y_levantar,)
@app.cell
def _(
EXTENSIONES,
PASSWORD,
PUERTO_POSTGRES,
RUTA_PROYECTO,
SERVICIO,
construir_y_levantar,
crear_docker_compose,
generar_dockerfile,
generar_init_sql,
timescaledb_base_image,
):
if __name__ == "__main__":
RUTA_PROYECTO.mkdir(parents=True, exist_ok=True)
generar_dockerfile(RUTA_PROYECTO, EXTENSIONES, timescaledb_base_image)
generar_init_sql(RUTA_PROYECTO, EXTENSIONES)
crear_docker_compose(RUTA_PROYECTO, SERVICIO, PUERTO_POSTGRES, PASSWORD)
construir_y_levantar(RUTA_PROYECTO)
return
if __name__ == "__main__":
app.run()
File diff suppressed because one or more lines are too long
@@ -0,0 +1,170 @@
{
"version": "1",
"metadata": {
"marimo_version": "0.15.5"
},
"cells": [
{
"id": "Hbol",
"code_hash": "4bc2f9bcc1b0d2dd0c6466f381371b75",
"outputs": [
{
"type": "data",
"data": {
"text/plain": ""
}
}
],
"console": []
},
{
"id": "MJUe",
"code_hash": "b2c1f26f82d1f970b9d43928212acde9",
"outputs": [
{
"type": "data",
"data": {
"text/plain": ""
}
}
],
"console": []
},
{
"id": "vblA",
"code_hash": "b30b5e29cb002700bc3faa054f631b30",
"outputs": [
{
"type": "data",
"data": {
"text/plain": ""
}
}
],
"console": []
},
{
"id": "bkHC",
"code_hash": "7ef814b3ab4f0f8f69a5cbf3e71e683f",
"outputs": [
{
"type": "data",
"data": {
"text/plain": ""
}
}
],
"console": []
},
{
"id": "lEQa",
"code_hash": "6b2fb5e4f49b15856ce86f923d9fc12f",
"outputs": [
{
"type": "data",
"data": {
"text/plain": ""
}
}
],
"console": []
},
{
"id": "PKri",
"code_hash": "4d9a7cdeb028ab0e0df782ba3a18ec56",
"outputs": [
{
"type": "data",
"data": {
"text/plain": ""
}
}
],
"console": []
},
{
"id": "Xref",
"code_hash": "a5bd6b98ad6169496877f80431f7c095",
"outputs": [
{
"type": "data",
"data": {
"text/plain": ""
}
}
],
"console": [
{
"type": "stream",
"name": "stderr",
"text": "time=\"2025-09-17T22:50:29+02:00\" level=warning msg=\"/home/lucas/DataProyects/myrag/docker-compose.yml: the attribute `version` is obsolete, it will be ignored, please remove it to avoid potential confusion\"\n"
},
{
"type": "stream",
"name": "stdout",
"text": "#1 [internal] load local bake definitions\n"
},
{
"type": "stream",
"name": "stdout",
"text": "#1 reading from stdin 533B done\n#1 DONE 0.0s\n\n#2 [internal] load build definition from Dockerfile\n#2 transferring dockerfile: 407B done\n#2 DONE 0.0s\n"
},
{
"type": "stream",
"name": "stdout",
"text": "\n#3 [internal] load metadata for docker.io/library/postgres:15\n#3 DONE 0.0s\n\n#4 [internal] load .dockerignore\n#4 transferring context: 2B done\n#4 DONE 0.0s\n\n#5 [internal] load build context\n#5 transferring context: 361B done\n#5 DONE 0.0s\n\n#6 [1/3] FROM docker.io/library/postgres:15@sha256:bc51cf4f1fe02cce7ed2370b20128a9b00b4eb804573a77d2a0d877aaa9c82b1\n#6 resolve docker.io/library/postgres:15@sha256:bc51cf4f1fe02cce7ed2370b20128a9b00b4eb804573a77d2a0d877aaa9c82b1 0.1s done\n"
},
{
"type": "stream",
"name": "stdout",
"text": "#6 DONE 0.1s\n\n#7 [2/3] RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends postgresql-15-pgvector && rm -rf /var/lib/apt/lists/*\n#7 CACHED\n\n#8 [3/3] COPY docker-entrypoint-initdb.d/ /docker-entrypoint-initdb.d/\n#8 CACHED\n\n#9 exporting to image\n#9 exporting layers done\n#9 exporting manifest sha256:c9b19eb6c84d8f896e3f9e824ef00a31a839070940367592ad6673f5e0989030 done\n#9 exporting config sha256:93741b0438170d04f8d5b9d7af1207d1e7ebda9f538e82dadfcd50ff79f9a375 done\n#9 exporting attestation manifest sha256:ac2e7c14e0c77fa10de7c775f9f42de2fce0574b00746a2839bf3ea23005c2f0 0.0s done\n#9 exporting manifest list sha256:c6e32cf5a7f69dd47dbfcbbd2ce7da2be5ba947dcfc5d35679d702823b8380b6 0.0s done\n#9 naming to docker.io/library/myrag-postgres_ext:latest done\n#9 unpacking to docker.io/library/myrag-postgres_ext:latest 0.0s done\n#9 DONE 0.1s\n"
},
{
"type": "stream",
"name": "stdout",
"text": "\n#10 resolving provenance for metadata file\n#10 DONE 0.0s\n"
},
{
"type": "stream",
"name": "stderr",
"text": " myrag-postgres_ext Built\n"
},
{
"type": "stream",
"name": "stderr",
"text": "time=\"2025-09-17T22:50:30+02:00\" level=warning msg=\"/home/lucas/DataProyects/myrag/docker-compose.yml: the attribute `version` is obsolete, it will be ignored, please remove it to avoid potential confusion\"\n"
},
{
"type": "stream",
"name": "stderr",
"text": " Network myrag_default Creating\n"
},
{
"type": "stream",
"name": "stderr",
"text": " Network myrag_default Created\n Volume \"myrag_postgres_data\" Creating\n Volume \"myrag_postgres_data\" Created\n"
},
{
"type": "stream",
"name": "stderr",
"text": " Container myrag-postgres_ext-1 Creating\n"
},
{
"type": "stream",
"name": "stderr",
"text": " Container myrag-postgres_ext-1 Created\n"
},
{
"type": "stream",
"name": "stderr",
"text": " Container myrag-postgres_ext-1 Starting\n"
},
{
"type": "stream",
"name": "stderr",
"text": " Container myrag-postgres_ext-1 Started\n"
}
]
}
]
}
@@ -0,0 +1,27 @@
{
"version": "1",
"metadata": {
"marimo_version": "0.15.5"
},
"cells": [
{
"id": "Hbol",
"code_hash": "1d0db38904205bec4d6f6f6a1f6cec3e",
"outputs": [
{
"type": "data",
"data": {
"text/plain": ""
}
}
],
"console": []
},
{
"id": "nsKB",
"code_hash": null,
"outputs": [],
"console": []
}
]
}
@@ -7,12 +7,195 @@
{
"id": "Hbol",
"code_hash": "1d0db38904205bec4d6f6f6a1f6cec3e",
"outputs": [
{
"type": "data",
"data": {
"text/plain": ""
}
}
],
"console": []
},
{
"id": "BYtC",
"code_hash": "aa046ea533e2c24f4855a198a08d72d2",
"outputs": [
{
"type": "data",
"data": {
"text/html": "<pre style='font-size: 12px'>&#x27;/home/lucas/DataProyects/myrag/.model/nomic-embed-text-v1.5&#x27;</pre>"
}
}
],
"console": [
{
"type": "stream",
"name": "stderr",
"text": "\rFetching 20 files: 0%| | 0/20 [00:00<?, ?it/s]"
},
{
"type": "stream",
"name": "stderr",
"text": "\rFetching 20 files: 5%|\u2588\u2588\u2588\u2588\u258d | 1/20 [00:00<00:06, 2.80it/s]"
},
{
"type": "stream",
"name": "stderr",
"text": "\rFetching 20 files: 30%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u258d | 6/20 [00:03<00:07, 1.78it/s]\rFetching 20 files: 100%|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588| 20/20 [00:03<00:00, 6.03it/s]\n"
}
]
},
{
"id": "RGSE",
"code_hash": "e64c8b56fa9ae281a52adbd60011ac93",
"outputs": [
{
"type": "data",
"data": {
"text/plain": ""
}
}
],
"console": [
{
"type": "stream",
"name": "stderr",
"text": "<All keys matched successfully>\n"
}
]
},
{
"id": "zTot",
"code_hash": "ff7286115d2791a2e3fd3a7c1e5d12d7",
"outputs": [
{
"type": "data",
"data": {
"text/plain": ""
}
}
],
"console": [
{
"type": "stream",
"name": "stdout",
"text": "Texto: La inteligencia artificial est\u00e1 transformando el mundo.\nDimensi\u00f3n: 768\nPrimeros valores: [0.7112516164779663, 1.4581586122512817, -3.3529951572418213, -0.17522235214710236, 1.0074328184127808]\n\nTexto: Los embeddings convierten texto en vectores num\u00e9ricos.\nDimensi\u00f3n: 768\nPrimeros valores: [1.0517628192901611, 0.7358752489089966, -3.375025749206543, -0.5610738396644592, 0.42669662833213806]\n\n"
}
]
},
{
"id": "Bksw",
"code_hash": "b2327ba2e99ea713ebb77b2abff4ef78",
"outputs": [
{
"type": "data",
"data": {
"text/plain": ""
}
}
],
"console": []
},
{
"id": "Kclp",
"code_hash": "185b6270311f793d93ca5c16393c1377",
"outputs": [
{
"type": "data",
"data": {
"text/plain": ""
}
}
],
"console": [
{
"type": "stream",
"name": "stdout",
"text": "\u2705 Se insertaron 20 textos con sus embeddings en la tabla 'nota'.\n"
}
]
},
{
"id": "emfo",
"code_hash": null,
"outputs": [
{
"type": "data",
"data": {
"text/plain": ""
}
}
],
"console": []
},
{
"id": "Hstk",
"code_hash": "ed8923d1de8e16f4d26ef4ba4f5b8052",
"outputs": [
{
"type": "data",
"data": {
"text/plain": ""
}
}
],
"console": []
},
{
"id": "nWHF",
"code_hash": "5d30b9180a1e189ad154c7e279b90158",
"outputs": [
{
"type": "data",
"data": {
"text/plain": ""
}
}
],
"console": []
},
{
"id": "WYEk",
"code_hash": null,
"outputs": [],
"console": []
},
{
"id": "iLit",
"code_hash": "26b7bc9a5b3e0e95fafc0927665959a4",
"outputs": [
{
"type": "error",
"ename": "exception",
"evalue": "name 'conn2' is not defined",
"traceback": []
}
],
"console": [
{
"type": "stream",
"name": "stderr",
"text": "<span class=\"codehilite\"><div class=\"highlight\"><pre><span></span><span class=\"gt\">Traceback (most recent call last):</span>\n File <span class=\"nb\">&quot;/home/lucas/DataProyects/myrag/.venv/lib/python3.10/site-packages/marimo/_runtime/executor.py&quot;</span>, line <span class=\"m\">138</span>, in <span class=\"n\">execute_cell</span>\n<span class=\"w\"> </span><span class=\"n\">exec</span><span class=\"p\">(</span><span class=\"n\">cell</span><span class=\"o\">.</span><span class=\"n\">body</span><span class=\"p\">,</span> <span class=\"n\">glbls</span><span class=\"p\">)</span>\n File <span class=\"nb\">&quot;/tmp/marimo_87281/__marimo__cell_iLit_.py&quot;</span>, line <span class=\"m\">5</span>, in <span class=\"n\">&lt;module&gt;</span>\n<span class=\"w\"> </span><span class=\"n\">engine</span><span class=\"o\">=</span><span class=\"n\">conn2</span>\n<span class=\"gr\">NameError</span>: <span class=\"n\">name &#39;conn2&#39; is not defined. Did you mean: &#39;conn&#39;?</span>\n</pre></div>\n</span>"
}
]
},
{
"id": "nlcQ",
"code_hash": "5a69e974fc8098fd051fa96adc48490e",
"outputs": [
{
"type": "data",
"data": {
"text/plain": ""
}
}
],
"console": []
},
{
"id": "MJUe",
"code_hash": "1f8586344a5957d1bdc12a7a52b71734",
"code_hash": "b4f98a698fa556ceba5f8d6f2a22ee66",
"outputs": [],
"console": []
},
@@ -52,100 +235,88 @@
"outputs": [],
"console": []
},
{
"id": "BYtC",
"code_hash": "aa046ea533e2c24f4855a198a08d72d2",
"outputs": [],
"console": []
},
{
"id": "RGSE",
"code_hash": "84f17564cd89f8d5871bd5d014d9591f",
"outputs": [],
"console": []
},
{
"id": "Kclp",
"code_hash": "185b6270311f793d93ca5c16393c1377",
"outputs": [],
"console": []
},
{
"id": "emfo",
"code_hash": null,
"outputs": [],
"console": []
},
{
"id": "Hstk",
"code_hash": "ed8923d1de8e16f4d26ef4ba4f5b8052",
"outputs": [],
"console": []
},
{
"id": "nWHF",
"code_hash": "5d30b9180a1e189ad154c7e279b90158",
"outputs": [],
"console": []
},
{
"id": "iLit",
"code_hash": "26b7bc9a5b3e0e95fafc0927665959a4",
"outputs": [],
"console": []
},
{
"id": "ZHCJ",
"code_hash": "be4d2e78ea7c4df1905803b0aa4e2634",
"outputs": [],
"console": []
},
{
"id": "ROlb",
"code_hash": "5a69e974fc8098fd051fa96adc48490e",
"outputs": [],
"console": []
},
{
"id": "qnkX",
"code_hash": "0df54b622e1697ee572493a908ce8db3",
"outputs": [],
"console": []
},
{
"id": "TqIu",
"code_hash": "744f972cb53daa52449b041ba42407d8",
"outputs": [],
"console": []
"outputs": [
{
"type": "data",
"data": {
"text/plain": ""
}
}
],
"console": [
{
"type": "stream",
"name": "stdout",
"text": "\u2705 Tabla 'nota' creada con pgvector.\n"
}
]
},
{
"id": "Vxnm",
"code_hash": null,
"outputs": [],
"outputs": [
{
"type": "data",
"data": {
"text/plain": ""
}
}
],
"console": []
},
{
"id": "DnEU",
"code_hash": "057c1dc1d7e6987dfe189d0c5885621d",
"outputs": [],
"outputs": [
{
"type": "data",
"data": {
"text/html": "<marimo-ui-element object-id='DnEU-0' random-id='b3ed3f7d-3db4-56da-4546-28e6578f93f0'><marimo-text data-initial-value='&quot;&quot;' data-label='&quot;&lt;span class=&#92;&quot;markdown prose dark:prose-invert&#92;&quot;&gt;&lt;span class=&#92;&quot;paragraph&#92;&quot;&gt;Texto de b\u00fasqueda&lt;/span&gt;&lt;/span&gt;&quot;' data-placeholder='&quot;&quot;' data-kind='&quot;text&quot;' data-full-width='true' data-disabled='false' data-debounce='true'></marimo-text></marimo-ui-element>"
}
}
],
"console": []
},
{
"id": "ulZA",
"code_hash": "171370e936570dc14ae77ef27bf67f17",
"outputs": [],
"outputs": [
{
"type": "data",
"data": {
"text/plain": ""
}
}
],
"console": []
},
{
"id": "ecfG",
"code_hash": "b353ed28b6fb1a40454312bac397e1e6",
"outputs": [],
"outputs": [
{
"type": "data",
"data": {
"text/html": "<marimo-ui-element object-id='ecfG-0' random-id='16317371-ed91-592f-e580-aba3fc4408bd'><marimo-table data-initial-value='[]' data-label='null' data-data='&quot;[]&quot;' data-total-rows='0' data-total-columns='3' data-max-columns='50' data-banner-text='&quot;&quot;' data-pagination='true' data-page-size='10' data-field-types='[[&quot;ID&quot;,[&quot;string&quot;,&quot;object&quot;]],[&quot;T\u00edtulo&quot;,[&quot;string&quot;,&quot;object&quot;]],[&quot;Distancia&quot;,[&quot;string&quot;,&quot;object&quot;]]]' data-show-filters='true' data-show-download='true' data-show-column-summaries='false' data-show-data-types='true' data-show-page-size-selector='false' data-show-column-explorer='true' data-show-chart-builder='true' data-row-headers='[]' data-has-stable-row-id='false' data-lazy='false' data-preload='false'></marimo-table></marimo-ui-element>"
}
}
],
"console": []
},
{
"id": "Pvdt",
"code_hash": null,
"outputs": [],
"outputs": [
{
"type": "data",
"data": {
"text/plain": ""
}
}
],
"console": []
}
]
+359
View File
@@ -0,0 +1,359 @@
{
"version": "1",
"metadata": {
"marimo_version": "0.15.5"
},
"cells": [
{
"id": "Hbol",
"code_hash": "1d0db38904205bec4d6f6f6a1f6cec3e",
"outputs": [
{
"type": "data",
"data": {
"text/plain": ""
}
}
],
"console": []
},
{
"id": "MJUe",
"code_hash": "a140d2922a2ce53cb099f6bde8d0809d",
"outputs": [
{
"type": "data",
"data": {
"text/plain": ""
}
}
],
"console": []
},
{
"id": "vblA",
"code_hash": "fc11529c9e3d0dc8f21af0544866b1d9",
"outputs": [
{
"type": "data",
"data": {
"text/html": "<marimo-ui-element object-id='vblA-0' random-id='c861c370-36b6-04eb-a3d6-06f105a64396'><marimo-dropdown data-initial-value='[&quot;nota&quot;]' data-label='&quot;&lt;span class=&#92;&quot;markdown prose dark:prose-invert&#92;&quot;&gt;&lt;span class=&#92;&quot;paragraph&#92;&quot;&gt;Selecciona una tabla&lt;/span&gt;&lt;/span&gt;&quot;' data-options='[&quot;nota&quot;,&quot;prueba_simple&quot;]' data-allow-select-none='false' data-searchable='false' data-full-width='false'></marimo-dropdown></marimo-ui-element>"
}
}
],
"console": []
},
{
"id": "bkHC",
"code_hash": "a47ac438791b89042e2abdbc7cf12608",
"outputs": [
{
"type": "data",
"data": {
"text/html": "<span class=\"markdown prose dark:prose-invert\"><h2 id=\"leer-datos\">Leer Datos</h2></span>"
}
}
],
"console": []
},
{
"id": "lEQa",
"code_hash": "d6d9505cb9812d5cf8ac2c50adca02dd",
"outputs": [
{
"type": "data",
"data": {
"text/html": "<marimo-ui-element object-id='lEQa-0' random-id='dcb09e0f-a0fa-af56-5e10-12cfe075373c'><marimo-table data-initial-value='[]' data-label='null' data-data='&quot;[]&quot;' data-total-rows='0' data-total-columns='14' data-max-columns='50' data-banner-text='&quot;&quot;' data-pagination='false' data-page-size='10' data-field-types='[[&quot;titulo&quot;,[&quot;string&quot;,&quot;object&quot;]],[&quot;contenido&quot;,[&quot;string&quot;,&quot;object&quot;]],[&quot;embedder&quot;,[&quot;string&quot;,&quot;object&quot;]],[&quot;tags&quot;,[&quot;string&quot;,&quot;object&quot;]],[&quot;relaciones&quot;,[&quot;string&quot;,&quot;object&quot;]],[&quot;propiedades&quot;,[&quot;string&quot;,&quot;object&quot;]],[&quot;id&quot;,[&quot;string&quot;,&quot;object&quot;]],[&quot;sys_created_at&quot;,[&quot;string&quot;,&quot;object&quot;]],[&quot;sys_created_by&quot;,[&quot;string&quot;,&quot;object&quot;]],[&quot;sys_updated_at&quot;,[&quot;string&quot;,&quot;object&quot;]],[&quot;sys_updated_by&quot;,[&quot;string&quot;,&quot;object&quot;]],[&quot;sys_version&quot;,[&quot;string&quot;,&quot;object&quot;]],[&quot;sys_notes&quot;,[&quot;string&quot;,&quot;object&quot;]],[&quot;sys_deleted_at&quot;,[&quot;string&quot;,&quot;object&quot;]]]' data-selection='&quot;multi&quot;' data-show-filters='true' data-show-download='true' data-show-column-summaries='false' data-show-data-types='true' data-show-page-size-selector='false' data-show-column-explorer='true' data-show-chart-builder='true' data-row-headers='[]' data-has-stable-row-id='true' data-lazy='false' data-preload='false'></marimo-table></marimo-ui-element>"
}
}
],
"console": []
},
{
"id": "PKri",
"code_hash": "efee8978c4cc896329f5d545979e1cae",
"outputs": [
{
"type": "data",
"data": {
"text/html": "<div style='display: flex;flex: 1;flex-direction: row;justify-content: space-between;align-items: normal;flex-wrap: nowrap;gap: 0.5rem'><marimo-ui-element object-id='PKri-0' random-id='69fecbc2-934e-c429-98c5-6fdf1b97f10d'><marimo-number data-initial-value='1' data-label='&quot;&lt;span class=&#92;&quot;markdown prose dark:prose-invert&#92;&quot;&gt;&lt;span class=&#92;&quot;paragraph&#92;&quot;&gt;ID a recuperar&lt;/span&gt;&lt;/span&gt;&quot;' data-debounce='false' data-full-width='false' data-disabled='false'></marimo-number></marimo-ui-element><marimo-ui-element object-id='PKri-1' random-id='d546b821-73ee-b36e-3541-58b977adc173'><marimo-button data-initial-value='0' data-label='&quot;&lt;span class=&#92;&quot;markdown prose dark:prose-invert&#92;&quot;&gt;&lt;span class=&#92;&quot;paragraph&#92;&quot;&gt;Recuperar objeto&lt;/span&gt;&lt;/span&gt;&quot;' data-kind='&quot;neutral&quot;' data-disabled='false' data-full-width='false'></marimo-button></marimo-ui-element></div>"
}
}
],
"console": []
},
{
"id": "Xref",
"code_hash": "dbd655fa8172a7a0bdc15d8136654a54",
"outputs": [
{
"type": "data",
"data": {
"text/html": "<div style='display: flex;flex: 1;flex-direction: column;justify-content: flex-start;align-items: normal;flex-wrap: nowrap;gap: 0.5rem'><span class=\"markdown prose dark:prose-invert\"><span class=\"paragraph\"><strong>Estado:</strong> \u2139\ufe0f Introduce un ID y pulsa <em>Recuperar</em>.</span></span><div style='border-radius: 6px; overflow: hidden; background-color: var(--slate-1); display: inline-block; min-width: 0; max-width: 100%;'><div style='padding: 10px 12px 8px 12px; display: flex; align-items: center;'><span style='background-color: var(--crimson-3); color: var(--crimson-11);padding: 2px 8px; border-radius: 4px; font-family: monospace; font-size: 0.75rem; font-weight: 600; margin-right: 8px; display: inline-block;'>instance</span><span style='font-family: monospace; font-size: 0.875rem; color: var(--slate-12);'>builtins.NoneType</span></div><div style='height: 1px; background-color: var(--slate-3); margin: 0 12px 8px 12px;'></div><div style='background-color: var(--background); border: 1px solid var(--slate-3); border-radius: 4px; padding: 8px 10px; margin: 0 12px 8px 12px; overflow-x: auto; overflow-y: hidden;'><span>None</span></div></div></div>"
}
}
],
"console": []
},
{
"id": "SFPL",
"code_hash": "afa3b5e8e9cb8430be7308e6277f262f",
"outputs": [
{
"type": "data",
"data": {
"text/html": "<span class=\"markdown prose dark:prose-invert\"><h2 id=\"crear-datos\">Crear datos</h2></span>"
}
}
],
"console": []
},
{
"id": "BYtC",
"code_hash": "bb06bed12045257025c88f5965778806",
"outputs": [
{
"type": "data",
"data": {
"text/plain": ""
}
}
],
"console": []
},
{
"id": "RGSE",
"code_hash": "091792b8fca71a2927f9fd134032d180",
"outputs": [
{
"type": "data",
"data": {
"text/html": "<div style='display: flex;flex: 1;flex-direction: column;justify-content: flex-start;align-items: normal;flex-wrap: nowrap;gap: 0.5rem'><marimo-ui-element object-id='RGSE-0' random-id='d81572e7-d6ef-d09f-0bc5-39ce8dc23108'><marimo-text data-initial-value='&quot;&quot;' data-label='&quot;&lt;span class=&#92;&quot;markdown prose dark:prose-invert&#92;&quot;&gt;&lt;span class=&#92;&quot;paragraph&#92;&quot;&gt;titulo&lt;/span&gt;&lt;/span&gt;&quot;' data-placeholder='&quot;&quot;' data-kind='&quot;text&quot;' data-full-width='false' data-disabled='false' data-debounce='true'></marimo-text></marimo-ui-element><marimo-ui-element object-id='RGSE-1' random-id='0a3bce1c-03ba-c0f4-a10b-2b1e60ef3ca9'><marimo-text data-initial-value='&quot;&quot;' data-label='&quot;&lt;span class=&#92;&quot;markdown prose dark:prose-invert&#92;&quot;&gt;&lt;span class=&#92;&quot;paragraph&#92;&quot;&gt;contenido&lt;/span&gt;&lt;/span&gt;&quot;' data-placeholder='&quot;&quot;' data-kind='&quot;text&quot;' data-full-width='false' data-disabled='false' data-debounce='true'></marimo-text></marimo-ui-element><marimo-ui-element object-id='RGSE-2' random-id='b0166af8-382a-621d-999b-8477a6589cd5'><marimo-text data-initial-value='&quot;&quot;' data-label='&quot;&lt;span class=&#92;&quot;markdown prose dark:prose-invert&#92;&quot;&gt;&lt;span class=&#92;&quot;paragraph&#92;&quot;&gt;embedder&lt;/span&gt;&lt;/span&gt;&quot;' data-placeholder='&quot;&quot;' data-kind='&quot;text&quot;' data-full-width='false' data-disabled='false' data-debounce='true'></marimo-text></marimo-ui-element><marimo-ui-element object-id='RGSE-3' random-id='a3112d4f-a1b3-b4e5-20cf-b0f66ae97dfa'><marimo-text data-initial-value='&quot;&quot;' data-label='&quot;&lt;span class=&#92;&quot;markdown prose dark:prose-invert&#92;&quot;&gt;&lt;span class=&#92;&quot;paragraph&#92;&quot;&gt;tags&lt;/span&gt;&lt;/span&gt;&quot;' data-placeholder='&quot;&quot;' data-kind='&quot;text&quot;' data-full-width='false' data-disabled='false' data-debounce='true'></marimo-text></marimo-ui-element><marimo-ui-element object-id='RGSE-4' random-id='f45cc46c-7291-e545-5555-3f7e185444e3'><marimo-text data-initial-value='&quot;&quot;' data-label='&quot;&lt;span class=&#92;&quot;markdown prose dark:prose-invert&#92;&quot;&gt;&lt;span class=&#92;&quot;paragraph&#92;&quot;&gt;relaciones&lt;/span&gt;&lt;/span&gt;&quot;' data-placeholder='&quot;&quot;' data-kind='&quot;text&quot;' data-full-width='false' data-disabled='false' data-debounce='true'></marimo-text></marimo-ui-element><marimo-ui-element object-id='RGSE-5' random-id='f7a04960-08c6-eb37-4aef-f53bf76bf476'><marimo-text data-initial-value='&quot;&quot;' data-label='&quot;&lt;span class=&#92;&quot;markdown prose dark:prose-invert&#92;&quot;&gt;&lt;span class=&#92;&quot;paragraph&#92;&quot;&gt;propiedades&lt;/span&gt;&lt;/span&gt;&quot;' data-placeholder='&quot;&quot;' data-kind='&quot;text&quot;' data-full-width='false' data-disabled='false' data-debounce='true'></marimo-text></marimo-ui-element><marimo-ui-element object-id='RGSE-6' random-id='a9bcb810-83d7-a331-441c-156479ab8cd4'><marimo-button data-initial-value='0' data-label='&quot;&lt;span class=&#92;&quot;markdown prose dark:prose-invert&#92;&quot;&gt;&lt;span class=&#92;&quot;paragraph&#92;&quot;&gt;Agregar fila a nota&lt;/span&gt;&lt;/span&gt;&quot;' data-kind='&quot;neutral&quot;' data-disabled='false' data-full-width='false'></marimo-button></marimo-ui-element></div>"
}
}
],
"console": []
},
{
"id": "Kclp",
"code_hash": "08b6a92e6023c45e2a1faa991cca1465",
"outputs": [
{
"type": "data",
"data": {
"text/html": "<div style='display: flex;flex: 1;flex-direction: column;justify-content: flex-start;align-items: normal;flex-wrap: nowrap;gap: 0.5rem'><span class=\"markdown prose dark:prose-invert\"><span class=\"paragraph\"><strong>Estado:</strong> \u2139\ufe0f Completa los campos y pulsa <em>Agregar fila</em>.</span></span><span class=\"markdown prose dark:prose-invert\"></span></div>"
}
}
],
"console": []
},
{
"id": "emfo",
"code_hash": "515e2a1ee6f6bb4e70db428cdab232ef",
"outputs": [
{
"type": "data",
"data": {
"text/html": "<span class=\"markdown prose dark:prose-invert\"><span class=\"paragraph\">Para a\u00f1adir un objeto dentro de la base de datos:</span></span>"
}
}
],
"console": []
},
{
"id": "Hstk",
"code_hash": "30cfa4d73e93ebb8d87cff0080dbe588",
"outputs": [
{
"type": "data",
"data": {
"text/html": "<div style='display: flex;flex: 1;flex-direction: column;justify-content: flex-start;align-items: normal;flex-wrap: nowrap;gap: 0.5rem'><marimo-ui-element object-id='Hstk-0' random-id='29554cd3-1c21-364b-20c1-33d8f3abba4a'><marimo-button data-initial-value='0' data-label='&quot;&lt;span class=&#92;&quot;markdown prose dark:prose-invert&#92;&quot;&gt;&lt;span class=&#92;&quot;paragraph&#92;&quot;&gt;Insertar objeto en la bbdd&lt;/span&gt;&lt;/span&gt;&quot;' data-kind='&quot;neutral&quot;' data-disabled='false' data-full-width='false'></marimo-button></marimo-ui-element></div>"
}
}
],
"console": []
},
{
"id": "nWHF",
"code_hash": "ec621b22f37f5ac499733f8e9a9a5690",
"outputs": [
{
"type": "data",
"data": {
"text/html": "<span class=\"markdown prose dark:prose-invert\"><span class=\"paragraph\">\u2139\ufe0f Pulsa el bot\u00f3n para insertar el objeto.</span></span>"
}
}
],
"console": []
},
{
"id": "iLit",
"code_hash": "cc58ebb0b0434b75a91d49ebf58dbe35",
"outputs": [
{
"type": "data",
"data": {
"text/html": "<span class=\"markdown prose dark:prose-invert\"><h2 id=\"actualizar-datos\">Actualizar datos</h2></span>"
}
}
],
"console": []
},
{
"id": "ZHCJ",
"code_hash": "32e14415e9ab6e0fbe53b67cb8d64005",
"outputs": [
{
"type": "data",
"data": {
"text/html": "<div style='display: flex;flex: 1;flex-direction: row;justify-content: space-between;align-items: normal;flex-wrap: nowrap;gap: 0.5rem'><marimo-ui-element object-id='ZHCJ-0' random-id='bdbf71f8-99f2-9446-7831-24b7bfe579da'><marimo-number data-initial-value='1' data-label='&quot;&lt;span class=&#92;&quot;markdown prose dark:prose-invert&#92;&quot;&gt;&lt;span class=&#92;&quot;paragraph&#92;&quot;&gt;ID a actualizar&lt;/span&gt;&lt;/span&gt;&quot;' data-debounce='false' data-full-width='false' data-disabled='false'></marimo-number></marimo-ui-element><marimo-ui-element object-id='ZHCJ-1' random-id='23ec135c-41be-39da-8ac9-253323513080'><marimo-button data-initial-value='0' data-label='&quot;&lt;span class=&#92;&quot;markdown prose dark:prose-invert&#92;&quot;&gt;&lt;span class=&#92;&quot;paragraph&#92;&quot;&gt;Buscar registro&lt;/span&gt;&lt;/span&gt;&quot;' data-kind='&quot;neutral&quot;' data-disabled='false' data-full-width='false'></marimo-button></marimo-ui-element><marimo-ui-element object-id='ZHCJ-2' random-id='3e1c601b-b92f-8e7c-e9fc-640a84049b26'><marimo-button data-initial-value='0' data-label='&quot;&lt;span class=&#92;&quot;markdown prose dark:prose-invert&#92;&quot;&gt;&lt;span class=&#92;&quot;paragraph&#92;&quot;&gt;Confirmar actualizaci\u00f3n&lt;/span&gt;&lt;/span&gt;&quot;' data-kind='&quot;success&quot;' data-disabled='false' data-full-width='false'></marimo-button></marimo-ui-element></div>"
}
}
],
"console": []
},
{
"id": "ROlb",
"code_hash": "6d12c71c1c4ac237c5e0c82fb931d1a4",
"outputs": [
{
"type": "data",
"data": {
"text/html": "<div style='display: flex;flex: 1;flex-direction: column;justify-content: flex-start;align-items: normal;flex-wrap: nowrap;gap: 0.5rem'><span class=\"markdown prose dark:prose-invert\"><span class=\"paragraph\"><strong>Estado:</strong> \u2139\ufe0f Introduce un ID y pulsa <em>Buscar</em>.</span></span><span class=\"markdown prose dark:prose-invert\"></span></div>"
}
}
],
"console": []
},
{
"id": "qnkX",
"code_hash": "9dc30c75899af32ea77e419cc735061b",
"outputs": [
{
"type": "data",
"data": {
"text/html": "<span class=\"markdown prose dark:prose-invert\"><span class=\"paragraph\"><strong>Estado final:</strong> \u2139\ufe0f Esperando confirmaci\u00f3n de actualizaci\u00f3n.</span></span>"
}
}
],
"console": []
},
{
"id": "TqIu",
"code_hash": "8c918601d587b1c6e350302e7b9069e0",
"outputs": [
{
"type": "data",
"data": {
"text/html": "<span class=\"markdown prose dark:prose-invert\"><h2 id=\"eliminar-datos\">Eliminar datos</h2></span>"
}
}
],
"console": []
},
{
"id": "Vxnm",
"code_hash": "c0214118b582809003e3039a0341057a",
"outputs": [
{
"type": "data",
"data": {
"text/html": "<div style='display: flex;flex: 1;flex-direction: row;justify-content: space-between;align-items: normal;flex-wrap: nowrap;gap: 0.5rem'><marimo-ui-element object-id='Vxnm-0' random-id='dc2f9c49-4c2b-9859-b678-f83c79c3402b'><marimo-number data-initial-value='1' data-label='&quot;&lt;span class=&#92;&quot;markdown prose dark:prose-invert&#92;&quot;&gt;&lt;span class=&#92;&quot;paragraph&#92;&quot;&gt;ID a eliminar&lt;/span&gt;&lt;/span&gt;&quot;' data-debounce='false' data-full-width='false' data-disabled='false'></marimo-number></marimo-ui-element><marimo-ui-element object-id='Vxnm-1' random-id='277cff69-08c7-70d7-f5cd-c497b74f6287'><marimo-button data-initial-value='0' data-label='&quot;&lt;span class=&#92;&quot;markdown prose dark:prose-invert&#92;&quot;&gt;&lt;span class=&#92;&quot;paragraph&#92;&quot;&gt;Hard delete&lt;/span&gt;&lt;/span&gt;&quot;' data-kind='&quot;danger&quot;' data-disabled='false' data-full-width='false'></marimo-button></marimo-ui-element></div>"
}
}
],
"console": []
},
{
"id": "DnEU",
"code_hash": "00884625b81ef34105cef03ff14fc88e",
"outputs": [
{
"type": "data",
"data": {
"text/html": "<span class=\"markdown prose dark:prose-invert\"><span class=\"paragraph\"><strong>Estado:</strong> \u2139\ufe0f Introduce un ID y pulsa <em>Eliminar</em>.</span></span>"
}
}
],
"console": []
},
{
"id": "ulZA",
"code_hash": "d89cdd832d925cffca734778040f2e4a",
"outputs": [
{
"type": "data",
"data": {
"text/html": "<div style='display: flex;flex: 1;flex-direction: row;justify-content: space-between;align-items: normal;flex-wrap: nowrap;gap: 0.5rem'><marimo-ui-element object-id='ulZA-0' random-id='3f07edfc-97c3-071f-86a7-7d342918cfa3'><marimo-number data-initial-value='1' data-label='&quot;&lt;span class=&#92;&quot;markdown prose dark:prose-invert&#92;&quot;&gt;&lt;span class=&#92;&quot;paragraph&#92;&quot;&gt;ID a eliminar (soft)&lt;/span&gt;&lt;/span&gt;&quot;' data-debounce='false' data-full-width='false' data-disabled='false'></marimo-number></marimo-ui-element><marimo-ui-element object-id='ulZA-1' random-id='8507ea99-794f-903c-cb6b-fa4f16a471e4'><marimo-button data-initial-value='0' data-label='&quot;&lt;span class=&#92;&quot;markdown prose dark:prose-invert&#92;&quot;&gt;&lt;span class=&#92;&quot;paragraph&#92;&quot;&gt;Soft delete&lt;/span&gt;&lt;/span&gt;&quot;' data-kind='&quot;warn&quot;' data-disabled='false' data-full-width='false'></marimo-button></marimo-ui-element></div>"
}
}
],
"console": []
},
{
"id": "ecfG",
"code_hash": "c6127f223f7e2b14d957a78930d717a5",
"outputs": [
{
"type": "data",
"data": {
"text/html": "<span class=\"markdown prose dark:prose-invert\"><span class=\"paragraph\"><strong>Estado:</strong> \u2139\ufe0f Introduce un ID y pulsa <em>Soft delete</em>.</span></span>"
}
}
],
"console": []
},
{
"id": "Pvdt",
"code_hash": "19d503fb42af4a3d9f0ed03cc9b5ea4a",
"outputs": [
{
"type": "data",
"data": {
"text/html": "<div style='display: flex;flex: 1;flex-direction: row;justify-content: space-between;align-items: normal;flex-wrap: nowrap;gap: 0.5rem'><marimo-ui-element object-id='Pvdt-0' random-id='9ec434b9-ac5f-0852-6ef5-b238bb1859f7'><marimo-number data-initial-value='1' data-label='&quot;&lt;span class=&#92;&quot;markdown prose dark:prose-invert&#92;&quot;&gt;&lt;span class=&#92;&quot;paragraph&#92;&quot;&gt;ID a restaurar (soft)&lt;/span&gt;&lt;/span&gt;&quot;' data-debounce='false' data-full-width='false' data-disabled='false'></marimo-number></marimo-ui-element><marimo-ui-element object-id='Pvdt-1' random-id='67587b59-8f1e-eee5-b7c9-0ae66174438c'><marimo-button data-initial-value='0' data-label='&quot;&lt;span class=&#92;&quot;markdown prose dark:prose-invert&#92;&quot;&gt;&lt;span class=&#92;&quot;paragraph&#92;&quot;&gt;Restaurar registro&lt;/span&gt;&lt;/span&gt;&quot;' data-kind='&quot;success&quot;' data-disabled='false' data-full-width='false'></marimo-button></marimo-ui-element></div>"
}
}
],
"console": []
},
{
"id": "ZBYS",
"code_hash": "9060810de67ce9024b4371e9589f0912",
"outputs": [
{
"type": "data",
"data": {
"text/html": "<span class=\"markdown prose dark:prose-invert\"><span class=\"paragraph\"><strong>Estado:</strong> \u2139\ufe0f Introduce un ID y pulsa <em>Restaurar</em>.</span></span>"
}
}
],
"console": []
},
{
"id": "aLJB",
"code_hash": "7f5d14e9bea4cf343e656f2f50b35466",
"outputs": [
{
"type": "data",
"data": {
"text/plain": ""
}
}
],
"console": []
},
{
"id": "nHfw",
"code_hash": "49c5a314d8c46512d86821c15a3c9373",
"outputs": [
{
"type": "data",
"data": {
"text/plain": ""
}
}
],
"console": []
}
]
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,158 @@
{
"version": "1",
"metadata": {
"marimo_version": "0.15.5"
},
"cells": [
{
"id": "Hbol",
"code_hash": "1d0db38904205bec4d6f6f6a1f6cec3e",
"outputs": [],
"console": []
},
{
"id": "MJUe",
"code_hash": "aa046ea533e2c24f4855a198a08d72d2",
"outputs": [],
"console": []
},
{
"id": "vblA",
"code_hash": "f915decadb13df1db264e9a806ab99be",
"outputs": [],
"console": []
},
{
"id": "bkHC",
"code_hash": "ff7286115d2791a2e3fd3a7c1e5d12d7",
"outputs": [],
"console": []
},
{
"id": "lEQa",
"code_hash": "b2327ba2e99ea713ebb77b2abff4ef78",
"outputs": [],
"console": []
},
{
"id": "PKri",
"code_hash": "185b6270311f793d93ca5c16393c1377",
"outputs": [],
"console": []
},
{
"id": "Xref",
"code_hash": null,
"outputs": [],
"console": []
},
{
"id": "SFPL",
"code_hash": "ed8923d1de8e16f4d26ef4ba4f5b8052",
"outputs": [],
"console": []
},
{
"id": "BYtC",
"code_hash": "5d30b9180a1e189ad154c7e279b90158",
"outputs": [],
"console": []
},
{
"id": "RGSE",
"code_hash": "7fd319edea57cfe1ff5cfed25b9fe418",
"outputs": [],
"console": []
},
{
"id": "Kclp",
"code_hash": "26b7bc9a5b3e0e95fafc0927665959a4",
"outputs": [],
"console": []
},
{
"id": "emfo",
"code_hash": "5a69e974fc8098fd051fa96adc48490e",
"outputs": [],
"console": []
},
{
"id": "Hstk",
"code_hash": "b4f98a698fa556ceba5f8d6f2a22ee66",
"outputs": [],
"console": []
},
{
"id": "nWHF",
"code_hash": "b2c1f26f82d1f970b9d43928212acde9",
"outputs": [],
"console": []
},
{
"id": "iLit",
"code_hash": "b30b5e29cb002700bc3faa054f631b30",
"outputs": [],
"console": []
},
{
"id": "ZHCJ",
"code_hash": "7ef814b3ab4f0f8f69a5cbf3e71e683f",
"outputs": [],
"console": []
},
{
"id": "ROlb",
"code_hash": "6b2fb5e4f49b15856ce86f923d9fc12f",
"outputs": [],
"console": []
},
{
"id": "qnkX",
"code_hash": "4d9a7cdeb028ab0e0df782ba3a18ec56",
"outputs": [],
"console": []
},
{
"id": "TqIu",
"code_hash": "a5bd6b98ad6169496877f80431f7c095",
"outputs": [],
"console": []
},
{
"id": "Vxnm",
"code_hash": "744f972cb53daa52449b041ba42407d8",
"outputs": [],
"console": []
},
{
"id": "DnEU",
"code_hash": null,
"outputs": [],
"console": []
},
{
"id": "ulZA",
"code_hash": "057c1dc1d7e6987dfe189d0c5885621d",
"outputs": [],
"console": []
},
{
"id": "ecfG",
"code_hash": "171370e936570dc14ae77ef27bf67f17",
"outputs": [],
"console": []
},
{
"id": "Pvdt",
"code_hash": "b353ed28b6fb1a40454312bac397e1e6",
"outputs": [],
"console": []
},
{
"id": "ZBYS",
"code_hash": null,
"outputs": [],
"console": []
}
]
}
View File
View File
View File
+45
View File
@@ -0,0 +1,45 @@
from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
from backend.services.notaService import NotaService
from backend.schemas.notaSchema import NotaCreate, NotaUpdate, NotaOut, SearchQuery, HybridSearchQuery
from backend.db.dependencies import get_db # tu función para obtener la sesión
router = APIRouter(prefix="/notas", tags=["Notas"])
@router.post("/", response_model=str)
def create_nota(data: NotaCreate, db: Session = Depends(get_db)):
return NotaService(db).create(**data.dict())
@router.get("/{id_}", response_model=NotaOut)
def get_nota(id_: str, db: Session = Depends(get_db)):
return NotaService(db).get(id_)
@router.get("/", response_model=list[NotaOut])
def list_notas(db: Session = Depends(get_db)):
return NotaService(db).list_all()
@router.put("/{id_}", response_model=bool)
def update_nota(id_: str, data: NotaUpdate, db: Session = Depends(get_db)):
return NotaService(db).update(id_, **data.dict(exclude_none=True))
@router.delete("/{id_}", response_model=bool)
def delete_nota(id_: str, db: Session = Depends(get_db)):
return NotaService(db).delete(id_)
# ---------------------
# RAG Endpoints
# ---------------------
@router.post("/search", response_model=list[NotaOut])
def search_nota(query: SearchQuery, db: Session = Depends(get_db)):
return NotaService(db).search_by_text(query.texto, query.top_k)
@router.post("/hybrid-search", response_model=list[NotaOut])
def hybrid_search(query: HybridSearchQuery, db: Session = Depends(get_db)):
return NotaService(db).hybrid_search(query.texto, query.tags, query.top_k)
+7
View File
@@ -0,0 +1,7 @@
from fastapi import APIRouter
from backend.api.endpoints.notaEndpoint import router as nota_router
api_router = APIRouter()
api_router.include_router(nota_router)
View File
+5
View File
@@ -0,0 +1,5 @@
# backend/db/base.py
from sqlalchemy.orm import declarative_base
# Declarative Base común para todos los modelos ORM
Base = declarative_base()
+8
View File
@@ -0,0 +1,8 @@
from backend.db.session import SessionLocal
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
+36
View File
@@ -0,0 +1,36 @@
# backend/db/session.py
import os
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from urllib.parse import quote_plus
from dotenv import load_dotenv
# Cargar variables de entorno (.env) desde backend/.env
load_dotenv(dotenv_path=os.path.join(os.path.dirname(__file__), "..", ".env"))
DB_USER = os.getenv("DB_USER")
DB_PASSWORD = quote_plus(os.getenv("DB_PASSWORD"))
DB_HOST = os.getenv("DB_HOST")
DB_PORT = os.getenv("DB_PORT", "55455")
DB_NAME = os.getenv("DB_NAME")
DATABASE_URL = (
f"postgresql+psycopg2://{DB_USER}:{DB_PASSWORD}@{DB_HOST}:{DB_PORT}/{DB_NAME}"
)
# Crear engine
engine = create_engine(
DATABASE_URL,
pool_size=5,
max_overflow=10,
future=True,
echo=False, # pon True para ver las queries en consola
)
# Fábrica de sesiones
SessionLocal = sessionmaker(
bind=engine,
autoflush=False,
expire_on_commit=False,
future=True,
)
+7
View File
@@ -0,0 +1,7 @@
from fastapi import FastAPI
from backend.api.router import api_router
app = FastAPI(title="Notes")
# attach router
app.include_router(api_router)
+52
View File
@@ -0,0 +1,52 @@
from pydantic import BaseModel, Field
from typing import Optional, List, Dict
# ---------------------
# Base
# ---------------------
class NotaBase(BaseModel):
titulo: str = Field(..., description="Título de la nota")
contenido: str = Field(..., description="Contenido de la nota")
tags: Optional[Dict] = Field(None, description="Tags de la nota")
relaciones: Optional[Dict] = Field(None, description="Relaciones")
propiedades: Optional[Dict] = Field(None, description="Propiedades adicionales")
# ---------------------
# Create & Update
# ---------------------
class NotaCreate(NotaBase):
pass
class NotaUpdate(BaseModel):
contenido: Optional[str]
tags: Optional[Dict]
relaciones: Optional[Dict]
propiedades: Optional[Dict]
# ---------------------
# Response
# ---------------------
class NotaOut(NotaBase):
id: str
embedder: List[float] = Field(..., description="Vector embedding de la nota")
class Config:
from_attributes = True
# ---------------------
# Search Inputs
# ---------------------
class SearchQuery(BaseModel):
texto: str
top_k: int = 5
class HybridSearchQuery(BaseModel):
texto: str
tags: Optional[Dict] = None
top_k: int = 5
+75
View File
@@ -0,0 +1,75 @@
from sqlalchemy.orm import Session
from typing import List, Optional
from domains.nota import notaDom, notaRepo
from domains.embedder import NomicEmbedder
class NotaService:
def __init__(self, session: Session):
self.repo = notaRepo(session)
self.emb = NomicEmbedder.get_instance()
# -------------------
# CRUD
# -------------------
def create(self, titulo: str, contenido: str, tags: Optional[dict] = None,
relaciones: Optional[dict] = None, propiedades: Optional[dict] = None) -> str:
embedding = self.emb.embed(contenido)
nota = notaDom(titulo, contenido, embedding, tags, relaciones, propiedades)
return self.repo.add(nota)
def get(self, id_: str) -> Optional[notaDom]:
return self.repo.get_by_id(id_)
def list_all(self) -> List[notaDom]:
return self.repo.get_all()
def update(self, id_: str, nuevo_contenido: Optional[str] = None,
tags: Optional[dict] = None, relaciones: Optional[dict] = None,
propiedades: Optional[dict] = None) -> bool:
new_data = {}
if nuevo_contenido:
new_data["contenido"] = nuevo_contenido
new_data["embedder"] = self.emb.embed(nuevo_contenido)
if tags: new_data["tags"] = tags
if relaciones: new_data["relaciones"] = relaciones
if propiedades: new_data["propiedades"] = propiedades
return self.repo.update(id_, new_data)
def delete(self, id_: str) -> bool:
return self.repo.soft_delete(id_)
# -------------------
# RAG: búsquedas
# -------------------
def search_by_text(self, texto: str, top_k: int = 5) -> List[notaDom]:
emb = self.emb.embed(texto)
sql = """
SELECT * FROM public.nota
WHERE sys_deleted_at IS NULL
ORDER BY embedder <-> :query_emb
LIMIT :top_k
"""
rows = self.repo.session.execute(sql, {"query_emb": emb, "top_k": top_k}).fetchall()
return [self.repo.Mapper.from_model(r) for r in rows]
def search_by_tags(self, tags: dict, offset: int = 0, limit: int = 10) -> List[notaDom]:
return self.repo.get_paginated_by_tags(tags, offset, limit)
def hybrid_search(self, texto: str, tags: Optional[dict] = None, top_k: int = 5) -> List[notaDom]:
emb = self.emb.embed(texto)
sql = """
SELECT * FROM public.nota
WHERE sys_deleted_at IS NULL
{tags_filter}
ORDER BY embedder <-> :query_emb
LIMIT :top_k
"""
tags_filter = "AND tags @> :tags" if tags else ""
query = sql.format(tags_filter=tags_filter)
params = {"query_emb": emb, "top_k": top_k}
if tags:
params["tags"] = tags
rows = self.repo.session.execute(query, params).fetchall()
return [self.repo.Mapper.from_model(r) for r in rows]
+521
View File
@@ -0,0 +1,521 @@
import marimo
__generated_with = "0.15.5"
app = marimo.App(width="columns")
@app.cell
def _():
import marimo as mo
return (mo,)
@app.cell
def _():
from backend.db.session import engine
import pandas as pd
return engine, pd
@app.cell
def _(engine, mo, pd):
# Query directo a information_schema
query_tables = """
SELECT table_name
FROM information_schema.tables
WHERE table_schema = 'public'
ORDER BY table_name;
"""
tables_df = pd.read_sql(query_tables, engine)
# Dropdown para elegir tabla
table_selector = mo.ui.dropdown(
options=tables_df["table_name"].tolist(),
label="Selecciona una tabla",
allow_select_none= False,
value=tables_df["table_name"][0]
)
table_selector
return (table_selector,)
@app.cell(hide_code=True)
def _(mo):
mo.md(r"""## Leer Datos""")
return
@app.cell
def _(
delete_status,
engine,
final_status,
insert_button,
inserted_data,
mo,
pd,
soft_delete_status,
soft_restore_status,
table_selector,
):
inserted_data
final_status
delete_status
soft_delete_status
soft_restore_status
insert_button
query_data = f'SELECT * FROM "{table_selector.value}" LIMIT 1000;'
df_preview = pd.read_sql(query_data, engine)
mo.ui.table(df_preview) # editor visual
return
@app.cell
def _(mo):
get_obj_id_input = mo.ui.number(label="ID a recuperar", value=1)
get_obj_button = mo.ui.run_button(label="Recuperar objeto", kind="neutral")
mo.hstack([get_obj_id_input, get_obj_button])
return get_obj_button, get_obj_id_input
@app.cell
def _(get_obj_button, get_obj_id_input, mo, repo, session, table_selector):
obj_status = "️ Introduce un ID y pulsa *Recuperar*."
obj_python = None
if get_obj_button.value and table_selector.value:
try:
# Usamos el repo → devuelve un dominio (no un dict)
_fetched_obj = repo.get_by_id(get_obj_id_input.value)
if _fetched_obj:
obj_status = f"✅ Objeto recuperado con ID {get_obj_id_input.value}"
obj_python = _fetched_obj
else:
obj_status = f"⚠️ No se encontró registro con ID {get_obj_id_input.value}"
except Exception as exc:
session.rollback()
obj_status = f"❌ Error al recuperar: {exc!s}"
mo.vstack([
mo.md(f"**Estado:** {obj_status}"),
mo.inspect(obj_python)
])
return
@app.cell(hide_code=True)
def _(mo):
mo.md(r"""## Crear datos""")
return
@app.cell(hide_code=True)
def _(engine, pd, table_selector):
from sqlalchemy import text
table_selector
query_columns = text("""
SELECT column_name, data_type, column_default
FROM information_schema.columns
WHERE table_schema = 'public'
AND table_name = :table_name
AND column_name NOT LIKE 'sys_%'
ORDER BY ordinal_position;
""")
columns_df = pd.read_sql(query_columns, engine, params={"table_name": table_selector.value})
columns_df = columns_df[
~(
(columns_df["column_name"] == "id") &
(columns_df["column_default"].notnull()) &
(columns_df["column_default"].str.contains("nextval"))
)
]
return (columns_df,)
@app.cell
def _(columns_df, mo, table_selector):
# Generamos inputs dinámicamente con soporte para fechas
ui_inputs = {}
for _, row in columns_df.iterrows():
col, dtype = row["column_name"], row["data_type"]
if dtype in ("integer", "numeric", "bigint", "smallint"):
ui_inputs[col] = mo.ui.number(label=col)
elif dtype in ("boolean",):
ui_inputs[col] = mo.ui.checkbox(label=col)
elif dtype in ("date", "timestamp", "timestamp without time zone", "timestamp with time zone"):
ui_inputs[col] = mo.ui.date(label=col) # 👉 usar date picker de Marimo
else:
ui_inputs[col] = mo.ui.text(label=col)
# Botón
run_button_add = mo.ui.run_button(
label=f"Agregar fila a {table_selector.value}",
kind="neutral"
)
# Layout de inputs + botón
mo.vstack(list(ui_inputs.values()) + [run_button_add])
return run_button_add, ui_inputs
@app.cell
def _(MapperCls, mo, repo, run_button_add, table_selector, ui_inputs):
_status_message = "️ Completa los campos y pulsa *Agregar fila*."
inserted_id = None
inserted_data = None
if run_button_add.value and table_selector.value:
try:
# Recoger inputs y normalizar fechas a ISO
raw_data = {}
for _col, ui in ui_inputs.items():
value = ui.value
if hasattr(value, "isoformat"): # 👉 si es date/datetime
raw_data[_col] = value.isoformat()
elif value == "" or value is None: # 👉 convertir '' → None
raw_data[_col] = None
else:
raw_data[_col] = value
# Convertir dict → dominio con el mapper correcto
dominio_obj = MapperCls.from_dict(raw_data)
# Insertar en la tabla usando el repo correcto
inserted_id = repo.add(dominio_obj, created_by="admin")
_status_message = f"✅ Registro insertado en {table_selector.value} con ID: {inserted_id}"
inserted_data = raw_data
except Exception as exc:
_status_message = f"❌ Error al insertar: {exc!s}"
mo.vstack([
mo.md(f"**Estado:** {_status_message}"),
mo.json(inserted_data) if inserted_data else mo.md("")
])
return (inserted_data,)
@app.cell
def _(mo):
mo.md(r"""Para añadir un objeto dentro de la base de datos:""")
return
@app.cell
def _(mo):
from domains.nota import notaDom
# Aqui genera tu objeto de python
# objeto_python = notaDom(
# nombre="Prueba",
# descripcion="Registro de prueba insertado desde Python",
# activo=True,
# )
insert_button = mo.ui.run_button(label="Insertar objeto en la bbdd", kind="neutral")
mo.vstack([
# mo.inspect(objeto_python),
insert_button])
return (insert_button,)
@app.cell
def _(ejemploRepo, insert_button, mo, objeto_python, session):
insert_status = "️ Pulsa el botón para insertar el objeto."
nuevo_id = None
if insert_button.value:
try:
# Instancia del repo
repo_objeto = ejemploRepo(session)
# Inserción en la BD
nuevo_id = repo_objeto.add(
objeto_python,
created_by="admin",
notes="Inserción desde código Python"
)
insert_status = f"✅ Registro insertado con ID: {nuevo_id}"
except Exception as exc:
session.rollback()
insert_status = f"❌ Error al insertar: {exc!s}"
mo.md(insert_status)
return
@app.cell(hide_code=True)
def _(mo):
mo.md(r"""## Actualizar datos""")
return
@app.cell(hide_code=True)
def _(mo):
update_id_input = mo.ui.number(label="ID a actualizar", value=1)
update_fetch_button = mo.ui.run_button(label="Buscar registro", kind="neutral")
confirm_update_button = mo.ui.run_button(label="Confirmar actualización", kind="success")
mo.hstack([update_id_input, update_fetch_button, confirm_update_button])
return confirm_update_button, update_fetch_button, update_id_input
@app.cell
def _(
MapperCls,
mo,
repo,
session,
table_selector,
update_fetch_button,
update_id_input,
):
# Valores iniciales
_update_status = "️ Introduce un ID y pulsa *Buscar*."
update_dic = None
if update_fetch_button.value and table_selector.value:
try:
fetched_obj = repo.get_by_id(update_id_input.value)
if fetched_obj:
fetched_dict = MapperCls.to_dict(fetched_obj)
update_dic = {
col: (
mo.ui.checkbox(value=bool(fetched_dict[col]), label=col)
if isinstance(fetched_dict[col], bool) else
mo.ui.number(value=fetched_dict[col], label=col)
if isinstance(fetched_dict[col], (int, float)) else
mo.ui.date(value=fetched_dict[col], label=col)
if hasattr(fetched_dict[col], "isoformat") else
mo.ui.text(value=str(fetched_dict[col]) if fetched_dict[col] is not None else "", label=col)
)
for col in fetched_dict.keys()
if not col.startswith("sys_") and col != "id"
}
_update_status = f"✅ Registro encontrado con ID: {update_id_input.value}"
else:
_update_status = f"⚠️ No se encontró registro con ID: {update_id_input.value}"
except Exception as exc:
session.rollback()
_update_status = f"❌ Error al buscar: {exc!s}"
mo.vstack([
mo.md(f"**Estado:** {_update_status}"),
mo.vstack(list(update_dic.values())) if update_dic else mo.md("")
])
return (update_dic,)
@app.cell(hide_code=True)
def _(confirm_update_button, mo, repo, session, update_dic, update_id_input):
# Estado por defecto
final_status = "️ Esperando confirmación de actualización."
if confirm_update_button.value and update_dic is not None:
try:
updated_data = {}
for _col, _ui in update_dic.items():
val = _ui.value
# Normalización: "" → None
if val == "":
updated_data[_col] = None
# Normalización: fechas → ISO string
elif hasattr(val, "isoformat"):
updated_data[_col] = val.isoformat()
else:
updated_data[_col] = val
ok = repo.update(
update_id_input.value,
updated_data,
updated_by="admin",
notes="Actualización desde UI"
)
if ok:
final_status = f"✅ Registro con ID {update_id_input.value} actualizado correctamente."
else:
final_status = f"⚠️ No se pudo actualizar el registro con ID {update_id_input.value}."
except Exception as exc:
session.rollback()
final_status = f"❌ Error al actualizar: {exc!s}"
mo.md(f"**Estado final:** {final_status}")
return (final_status,)
@app.cell(hide_code=True)
def _(mo):
mo.md(r"""## Eliminar datos""")
return
@app.cell
def _(mo):
delete_id_input = mo.ui.number(label="ID a eliminar", value=1)
delete_button = mo.ui.run_button(label="Hard delete", kind="danger")
mo.hstack([delete_id_input, delete_button])
return delete_button, delete_id_input
@app.cell
def _(delete_button, delete_id_input, mo, repo, session, table_selector):
delete_status = "️ Introduce un ID y pulsa *Eliminar*."
if delete_button.value and table_selector.value:
try:
_ok = repo.delete_by_id(delete_id_input.value)
if _ok:
delete_status = f"✅ Registro con ID {delete_id_input.value} eliminado permanentemente."
else:
delete_status = f"⚠️ No se encontró registro con ID {delete_id_input.value}."
except Exception as exc:
session.rollback()
delete_status = f"❌ Error al eliminar: {exc!s}"
mo.md(f"**Estado:** {delete_status}")
return (delete_status,)
@app.cell
def _(mo):
soft_delete_id_input = mo.ui.number(label="ID a eliminar (soft)", value=1)
soft_delete_button = mo.ui.run_button(label="Soft delete", kind="warn")
mo.hstack([soft_delete_id_input, soft_delete_button])
return soft_delete_button, soft_delete_id_input
@app.cell
def _(
mo,
repo,
session,
soft_delete_button,
soft_delete_id_input,
table_selector,
):
soft_delete_status = "️ Introduce un ID y pulsa *Soft delete*."
if soft_delete_button.value and table_selector.value:
try:
_ok = repo.soft_delete(
soft_delete_id_input.value,
deleted_by="admin",
notes="Eliminado desde UI"
)
if _ok:
soft_delete_status = f"✅ Registro con ID {soft_delete_id_input.value} marcado como eliminado (soft delete)."
else:
soft_delete_status = f"⚠️ No se encontró registro con ID {soft_delete_id_input.value} o ya estaba eliminado."
except Exception as exc:
session.rollback()
soft_delete_status = f"❌ Error al aplicar soft delete: {exc!s}"
mo.md(f"**Estado:** {soft_delete_status}")
return (soft_delete_status,)
@app.cell
def _(mo):
soft_restore_id_input = mo.ui.number(label="ID a restaurar (soft)", value=1)
soft_restore_button = mo.ui.run_button(label="Restaurar registro", kind="success")
mo.hstack([soft_restore_id_input, soft_restore_button])
return soft_restore_button, soft_restore_id_input
@app.cell
def _(
mo,
repo,
session,
soft_restore_button,
soft_restore_id_input,
table_selector,
):
soft_restore_status = "️ Introduce un ID y pulsa *Restaurar*."
if soft_restore_button.value and table_selector.value:
try:
_ok = repo.soft_restore(
soft_restore_id_input.value,
restored_by="admin",
notes="Restaurado desde UI"
)
if _ok:
soft_restore_status = f"✅ Registro con ID {soft_restore_id_input.value} restaurado correctamente."
else:
soft_restore_status = f"⚠️ No se encontró registro con ID {soft_restore_id_input.value} o no estaba eliminado."
except Exception as exc:
session.rollback()
soft_restore_status = f"❌ Error al restaurar: {exc!s}"
mo.md(f"**Estado:** {soft_restore_status}")
return (soft_restore_status,)
@app.cell
def _():
# # Arregla transacciones rotas
# try:
# _dominio_obj = MapperCls.from_dict(raw_data)
# _inserted_id = repo.add(_dominio_obj, created_by="admin")
# _status_message = f"✅ Insertado con ID {_inserted_id}"
# except Exception as exc:
# session.rollback() # 👈 limpia la transacción rota
# _status_message = f"❌ Error al insertar: {exc!s}"
return
@app.cell(hide_code=True)
def _(table_selector):
import importlib
from backend.db.session import SessionLocal
def load_classes(entidad: str):
"""
Importa dinámicamente Repo, Mapper y Dom según convención:
- Módulo: domains.{entidad} (minúsculas)
- Clases: {EntidadCapitalizada}Repo, {EntidadCapitalizada}Mapper, {EntidadCapitalizada}Dom
"""
entidad = entidad.lower() # siempre minúscula para el módulo
module_name = f"domains.{entidad}"
module = importlib.import_module(module_name)
# Nombre base: capitalizar primera letra
base_name = entidad.lower()
repo_class = getattr(module, f"{base_name}Repo")
mapper_class = getattr(module, f"{base_name}Mapper")
dom_class = getattr(module, f"{base_name}Dom")
return repo_class, mapper_class, dom_class
# 👉 Uso interactivo en Marimo
session = SessionLocal()
if table_selector.value: # valor que escribiste en el textarea
RepoCls, MapperCls, DomCls = load_classes(table_selector.value)
repo = RepoCls(session)
return MapperCls, repo, session
if __name__ == "__main__":
app.run()
@@ -3,5 +3,4 @@ CREATE EXTENSION IF NOT EXISTS hstore;
CREATE EXTENSION IF NOT EXISTS citext;
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE EXTENSION IF NOT EXISTS postgis;
CREATE EXTENSION IF NOT EXISTS vector;
+75
View File
@@ -0,0 +1,75 @@
# Mapper.py
from abc import ABC, abstractmethod
from typing import TypeVar, Generic, Type
import json
TDominio = TypeVar("TDominio")
TModelo = TypeVar("TModelo")
class Mapper_base(ABC, Generic[TDominio, TModelo]):
# ----------------------------
# Conversiones individuales
# ----------------------------
@staticmethod
@abstractmethod
def to_model(obj: TDominio) -> TModelo:
"""Convierte objeto de dominio a modelo ORM"""
pass
@staticmethod
@abstractmethod
def from_model(model: TModelo) -> TDominio:
"""Convierte modelo ORM a objeto de dominio"""
pass
@staticmethod
@abstractmethod
def to_dict(obj: TDominio) -> dict:
"""Convierte objeto de dominio a diccionario plano"""
pass
@staticmethod
@abstractmethod
def from_dict(d: dict) -> TDominio:
"""Convierte diccionario plano a objeto de dominio"""
pass
@classmethod
def to_json(cls, obj: TDominio) -> str:
"""Convierte objeto de dominio a JSON string"""
return json.dumps(cls.to_dict(obj), default=str)
@classmethod
def from_json(cls, json_str: str) -> TDominio:
"""Convierte JSON string a objeto de dominio"""
return cls.from_dict(json.loads(json_str))
# ----------------------------
# Conversiones en lote (bulk)
# ----------------------------
@classmethod
def to_model_list(cls, objs: list[TDominio]) -> list[TModelo]:
return [cls.to_model(o) for o in objs]
@classmethod
def from_model_list(cls, models: list[TModelo]) -> list[TDominio]:
return [cls.from_model(m) for m in models]
@classmethod
def to_dict_list(cls, objs: list[TDominio]) -> list[dict]:
return [cls.to_dict(o) for o in objs]
@classmethod
def from_dict_list(cls, dicts: list[dict]) -> list[TDominio]:
return [cls.from_dict(d) for d in dicts]
@classmethod
def to_json_list(cls, objs: list[TDominio]) -> str:
return json.dumps(cls.to_dict_list(objs), default=str)
@classmethod
def from_json_list(cls, json_str: str) -> list[TDominio]:
return cls.from_dict_list(json.loads(json_str))
+58
View File
@@ -0,0 +1,58 @@
# Model.py
from sqlalchemy import Column, DateTime, String, Integer, Text, BigInteger, func
from sqlalchemy.ext.declarative import declared_attr, as_declarative
from datetime import datetime
from backend.db.base import Base # tu Base declarativa
class Model_base(Base):
__abstract__ = True
# ID autoincremental por defecto en todos los modelos
id = Column(BigInteger, primary_key=True, autoincrement=True)
@declared_attr
def sys_created_at(cls):
# timestamptz + default en BD
return Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
@declared_attr
def sys_created_by(cls):
return Column(String, nullable=True)
@declared_attr
def sys_updated_at(cls):
# onupdate lo pone la BD al actualizar
return Column(DateTime(timezone=True), onupdate=func.now(), nullable=True)
@declared_attr
def sys_updated_by(cls):
return Column(String, nullable=True)
@declared_attr
def sys_version(cls):
return Column(Integer, default=1, nullable=False)
@declared_attr
def sys_notes(cls):
return Column(Text, nullable=True)
@declared_attr
def sys_deleted_at(cls):
return Column(DateTime(timezone=True), nullable=True)
def __repr__(self):
id_val = getattr(self, "id", None)
return f"<{self.__class__.__name__} id={id_val}>"
def __str__(self):
cls = self.__class__.__name__
id_val = getattr(self, "id", None)
return f"{cls}(id={id_val})"
def __json__(self) -> dict:
out = {}
if not hasattr(self, "__table__"): return out
for c in self.__table__.columns:
v = getattr(self, c.name, None)
out[c.name] = v.isoformat() if isinstance(v, datetime) else v
return out
+149
View File
@@ -0,0 +1,149 @@
# Repo.py
from abc import ABC
from typing import Type, TypeVar, Generic, Optional
from sqlalchemy.orm import Session
from sqlalchemy import func
from datetime import datetime
from datetime import datetime, timezone
from .Mapper import Mapper_base # Asegúrate de importar tu ABC base
TModelo = TypeVar("TModelo")
TDominio = TypeVar("TDominio")
class Repo_base(ABC, Generic[TModelo, TDominio]):
def __init__(self, session: Session, modelo: type[TModelo], mapper: type[Mapper_base[TDominio, TModelo]]):
self.session = session
self.Modelo = modelo
self.Mapper = mapper
# ----------------------
# ADD
# ----------------------
def add(self, dominio: TDominio, created_by: Optional[str] = None, notes: Optional[str] = None) -> str:
data = self.Mapper.to_dict(dominio)
data.update({
"sys_created_by": created_by,
"sys_notes": notes,
"sys_version": 1
})
model = self.Modelo(**data)
self.session.add(model)
self.session.commit()
return model.id
def add_many(self, dominios: list[TDominio], created_by: Optional[str] = None, notes: Optional[str] = None) -> list[str]:
ids = []
for dominio in dominios:
data = self.Mapper.to_dict(dominio)
data.update({
"sys_created_by": created_by,
"sys_notes": notes,
"sys_version": 1
})
model = self.Modelo(**data)
self.session.add(model)
ids.append(model.id)
self.session.commit()
return ids
# ----------------------
# GET
# ----------------------
def get_by_id(self, id_: str) -> Optional[TDominio]:
model = self.session.query(self.Modelo).filter_by(id=id_, sys_deleted_at=None).first()
return self.Mapper.from_model(model) if model else None
def get_all(self) -> list[TDominio]:
models = self.session.query(self.Modelo).filter_by(sys_deleted_at=None).all()
return self.Mapper.from_model_list(models)
def get_paginated(self, offset: int = 0, limit: int = 10) -> list[TDominio]:
models = self.session.query(self.Modelo).filter_by(sys_deleted_at=None).offset(offset).limit(limit).all()
return self.Mapper.from_model_list(models)
def get_deleted(self) -> list[TDominio]:
models = self.session.query(self.Modelo).filter(self.Modelo.sys_deleted_at.isnot(None)).all()
return self.Mapper.from_model_list(models)
# ----------------------
# UPDATE
# ----------------------
def update(self, id_: str, new_data: dict, updated_by: Optional[str] = None, notes: Optional[str] = None) -> bool:
model = self.session.query(self.Modelo).filter_by(id=id_, sys_deleted_at=None).first()
if not model:
return False
for key, value in new_data.items():
if hasattr(model, key):
setattr(model, key, value)
model.sys_updated_by = updated_by
model.sys_notes = notes
model.sys_version = (model.sys_version or 1) + 1
self.session.commit()
return True
def bulk_update(self, updates: list[tuple[str, dict]], updated_by: Optional[str] = None, notes: Optional[str] = None) -> int:
count = 0
for id_, data in updates:
if self.update(id_, data, updated_by=updated_by, notes=notes):
count += 1
return count
# ----------------------
# DELETE
# ----------------------
def delete_by_id(self, id_: str) -> bool:
model = self.session.query(self.Modelo).filter_by(id=id_).first()
if model:
self.session.delete(model)
self.session.commit()
return True
return False
def delete_all(self) -> int:
deleted = self.session.query(self.Modelo).delete()
self.session.commit()
return deleted
# ----------------------
# SOFT DELETE
# ----------------------
def soft_delete(self, id_: str, deleted_by: Optional[str] = None, notes: Optional[str] = None) -> bool:
model = self.session.query(self.Modelo).filter_by(id=id_, sys_deleted_at=None).first()
if model:
model.sys_deleted_at = datetime.now(timezone.utc) # TZ-aware
model.sys_updated_by = deleted_by
model.sys_notes = notes
model.sys_version = (model.sys_version or 1) + 1
self.session.commit()
return True
return False
def soft_restore(self, id_: str, restored_by: Optional[str] = None, notes: Optional[str] = None) -> bool:
model = self.session.query(self.Modelo).filter_by(id=id_).first()
if model and model.sys_deleted_at is not None:
model.sys_deleted_at = None
model.sys_updated_by = restored_by
model.sys_notes = notes
model.sys_version = (model.sys_version or 1) + 1
self.session.commit()
return True
return False
# ----------------------
# OTROS
# ----------------------
def exists(self, id_: str) -> bool:
return self.session.query(self.Modelo).filter_by(id=id_, sys_deleted_at=None).first() is not None
def count(self) -> int:
return self.session.query(self.Modelo).filter_by(sys_deleted_at=None).count()
+6
View File
@@ -0,0 +1,6 @@
# domains/arquitecture_layer/__init__.py
from .Repo import *
from .Mapper import *
from .Model import *
__all__ = ["Repo", "Mapper", "Model"]
+26
View File
@@ -0,0 +1,26 @@
# embedder_nomic.py
from transformers import AutoTokenizer, AutoModel
import torch
class NomicEmbedder:
_instance = None
def __init__(self, model_path: str = ".model/nomic-embed-text-v1.5"):
# Load model only once
self.tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True)
self.model = AutoModel.from_pretrained(model_path, trust_remote_code=True)
@classmethod
def get_instance(cls) -> "NomicEmbedder":
if cls._instance is None:
cls._instance = NomicEmbedder()
return cls._instance
def embed(self, text: str) -> list[float]:
"""Generate embedding from text"""
inputs = self.tokenizer(
[text], return_tensors="pt", padding=True, truncation=True, max_length=8192
)
with torch.no_grad():
embedding = self.model(**inputs).last_hidden_state.mean(dim=1).squeeze()
return embedding.tolist()
+137
View File
@@ -0,0 +1,137 @@
from __future__ import annotations
from typing import Optional
from sqlalchemy import Column, String, Text, JSON
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import Session
from pgvector.sqlalchemy import Vector # ✅ importación correcta para pgvector
from domains.arquitecture_layer.Model import Model_base
from domains.arquitecture_layer.Mapper import Mapper_base
from domains.arquitecture_layer.Repo import Repo_base
from .embedder import NomicEmbedder
Base = declarative_base()
class notaDom:
def __init__(
self,
titulo: str,
contenido: str,
embedder: list[float], # ✅ sigue siendo list[float] en Python
tags: Optional[dict] = None,
relaciones: Optional[dict] = None,
propiedades: Optional[dict] = None, # ✅ nueva propiedad
):
self.titulo = titulo
self.contenido = contenido
self.embedder = embedder
self.tags = tags
self.relaciones = relaciones
self.propiedades = propiedades
# ------------------------
class notaModel(Model_base):
__tablename__ = "nota"
__table_args__ = ({"schema": "public"})
titulo = Column(String(255), nullable=False, comment="Título de la nota")
contenido = Column(Text, nullable=True, comment="Contenido de la nota")
embedder = Column(Vector(768), nullable=True, comment="Vectores de embedder de tamaño 768") # ✅ ahora pgvector
tags = Column(JSON, nullable=True, default=None, comment="Tags en formato JSON")
relaciones = Column(JSON, nullable=True, default=None, comment="Relaciones en formato JSON")
propiedades = Column(JSON, nullable=True, default=None, comment="Propiedades adicionales en formato JSON")
# ------------------
class notaMapper(Mapper_base[notaDom, notaModel]):
emb = NomicEmbedder.get_instance()
@staticmethod
def to_model(obj: notaDom) -> notaModel:
# Auto-generate embedding if not provided
if obj.embedder is None:
obj.embedder = notaMapper.emb.embed(obj.contenido)
return notaModel(
titulo=obj.titulo,
contenido=obj.contenido,
embedder=obj.embedder,
tags=obj.tags,
relaciones=obj.relaciones,
propiedades=obj.propiedades,
)
@staticmethod
def from_model(model: notaModel) -> notaDom:
return notaDom(
titulo=model.titulo,
contenido=model.contenido,
embedder=model.embedder,
tags=model.tags,
relaciones=model.relaciones,
propiedades=model.propiedades,
)
@staticmethod
def from_dict(d: dict) -> notaDom:
# Auto-generate embedding if missing
emb = d.get("embedder")
if emb is None and "contenido" in d:
emb = notaMapper.emb.embed(d["contenido"])
return notaDom(
titulo=d["titulo"],
contenido=d["contenido"],
embedder=emb,
tags=d.get("tags"),
relaciones=d.get("relaciones"),
propiedades=d.get("propiedades"),
)
# -----------------
class notaRepo(Repo_base[notaModel, notaDom]):
def __init__(self, session: Session):
super().__init__(session=session, modelo=notaModel, mapper=notaMapper)
def update_contenido(self, nota: notaModel, nuevo_contenido: str) -> notaDom:
emb = notaMapper.emb.embed(nuevo_contenido)
nota.contenido = nuevo_contenido
nota.embedder = emb
self.session.commit()
self.session.refresh(nota)
return self.Mapper.from_model(nota)
def get_by_titulo(self, titulo: str) -> Optional[notaDom]:
model = (
self.session.query(self.Modelo)
.filter_by(titulo=titulo, sys_deleted_at=None)
.first()
)
return self.Mapper.from_model(model) if model else None
def buscar_por_contenido(self, contenido: str) -> list[notaDom]:
models = (
self.session.query(self.Modelo)
.filter(
self.Modelo.contenido.ilike(f"%{contenido}%"),
self.Modelo.sys_deleted_at.is_(None),
)
.all()
)
return self.Mapper.from_model_list(models)
def get_paginated_by_tags(self, tags: dict, offset: int = 0, limit: int = 10) -> list[notaDom]:
models = (
self.session.query(self.Modelo)
.filter(
self.Modelo.tags.contains(tags),
self.Modelo.sys_deleted_at.is_(None),
)
.offset(offset)
.limit(limit)
.all()
)
return self.Mapper.from_model_list(models)
+10
View File
@@ -0,0 +1,10 @@
#!/usr/bin/env bash
# Activar el entorno virtual (si usas venv/uv/poetry, ajusta aquí)
source .venv/bin/activate
# Ejecutar Uvicorn
uvicorn backend.main:app \
--reload \
--host 0.0.0.0 \
--port 8000
+27
View File
@@ -0,0 +1,27 @@
name: npm test
on:
pull_request:
branches:
- '**'
concurrency:
group: ${{ github.workflow }}-${{ github.event.number || github.sha }}
cancel-in-progress: true
jobs:
test_pull_request:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
cache: 'yarn'
cache-dependency-path: '**/yarn.lock'
- name: Install dependencies
run: yarn
- name: Run build
run: npm run build
- name: Run tests
run: npm test
+132
View File
@@ -0,0 +1,132 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
.DS_Store
+1
View File
@@ -0,0 +1 @@
v24.3.0
+35
View File
@@ -0,0 +1,35 @@
/** @type {import("@ianvs/prettier-plugin-sort-imports").PrettierConfig} */
const config = {
printWidth: 100,
singleQuote: true,
trailingComma: 'es5',
plugins: ['@ianvs/prettier-plugin-sort-imports'],
importOrder: [
'.*styles.css$',
'',
'dayjs',
'^react$',
'^next$',
'^next/.*$',
'<BUILTIN_MODULES>',
'<THIRD_PARTY_MODULES>',
'^@mantine/(.*)$',
'^@mantinex/(.*)$',
'^@mantine-tests/(.*)$',
'^@docs/(.*)$',
'^@/.*$',
'^../(?!.*.css$).*$',
'^./(?!.*.css$).*$',
'\\.css$',
],
overrides: [
{
files: '*.mdx',
options: {
printWidth: 70,
},
},
],
};
export default config;
+17
View File
@@ -0,0 +1,17 @@
import type { StorybookConfig } from '@storybook/react-vite';
const config: StorybookConfig = {
core: {
disableWhatsNewNotifications: true,
disableTelemetry: true,
enableCrashReports: false,
},
stories: ['../src/**/*.mdx', '../src/**/*.story.@(js|jsx|ts|tsx)'],
addons: ['@storybook/addon-themes'],
framework: {
name: '@storybook/react-vite',
options: {},
},
};
export default config;
+40
View File
@@ -0,0 +1,40 @@
import '@mantine/core/styles.css';
import { ColorSchemeScript, MantineProvider } from '@mantine/core';
import { theme } from '../src/theme';
export const parameters = {
layout: 'fullscreen',
options: {
showPanel: false,
storySort: (a: any, b: any) => a.title.localeCompare(b.title, undefined, { numeric: true }),
},
backgrounds: { disable: true },
};
export const globalTypes = {
theme: {
name: 'Theme',
description: 'Mantine color scheme',
defaultValue: 'light',
toolbar: {
icon: 'mirror',
items: [
{ value: 'light', title: 'Light' },
{ value: 'dark', title: 'Dark' },
],
},
},
};
export const decorators = [
(renderStory: any, context: any) => {
const scheme = (context.globals.theme || 'light') as 'light' | 'dark';
return (
<MantineProvider theme={theme} forceColorScheme={scheme}>
<ColorSchemeScript />
{renderStory()}
</MantineProvider>
);
},
];
+1
View File
@@ -0,0 +1 @@
dist
+28
View File
@@ -0,0 +1,28 @@
{
"extends": ["stylelint-config-standard-scss"],
"rules": {
"custom-property-pattern": null,
"selector-class-pattern": null,
"scss/no-duplicate-mixins": null,
"declaration-empty-line-before": null,
"declaration-block-no-redundant-longhand-properties": null,
"alpha-value-notation": null,
"custom-property-empty-line-before": null,
"property-no-vendor-prefix": null,
"color-function-notation": null,
"length-zero-no-unit": null,
"selector-not-notation": null,
"no-descending-specificity": null,
"comment-empty-line-before": null,
"scss/at-mixin-pattern": null,
"scss/at-rule-no-unknown": null,
"value-keyword-case": null,
"media-feature-range-notation": null,
"selector-pseudo-class-no-unknown": [
true,
{
"ignorePseudoClasses": ["global"]
}
]
}
}
+942
View File
File diff suppressed because one or more lines are too long
+3
View File
@@ -0,0 +1,3 @@
nodeLinker: node-modules
yarnPath: .yarn/releases/yarn-4.9.4.cjs
+34
View File
@@ -0,0 +1,34 @@
# Mantine Vite template
## Features
This template comes with the following features:
- [PostCSS](https://postcss.org/) with [mantine-postcss-preset](https://mantine.dev/styles/postcss-preset)
- [TypeScript](https://www.typescriptlang.org/)
- [Storybook](https://storybook.js.org/)
- [Vitest](https://vitest.dev/) setup with [React Testing Library](https://testing-library.com/docs/react-testing-library/intro)
- ESLint setup with [eslint-config-mantine](https://github.com/mantinedev/eslint-config-mantine)
## npm scripts
## Build and dev scripts
- `dev` start development server
- `build` build production version of the app
- `preview` locally preview production build
### Testing scripts
- `typecheck` checks TypeScript types
- `lint` runs ESLint
- `prettier:check` checks files with Prettier
- `vitest` runs vitest tests
- `vitest:watch` starts vitest watch
- `test` runs `vitest`, `prettier:check`, `lint` and `typecheck` scripts
### Other scripts
- `storybook` starts storybook dev server
- `storybook:build` build production storybook bundle to `storybook-static`
- `prettier:write` formats all files with Prettier
+22
View File
@@ -0,0 +1,22 @@
import mantine from 'eslint-config-mantine';
import { defineConfig } from 'eslint/config';
import tseslint from 'typescript-eslint';
// @ts-check
export default defineConfig(
tseslint.configs.recommended,
...mantine,
{ ignores: ['**/*.{mjs,cjs,js,d.ts,d.mts}'] },
{
files: ['**/*.story.tsx'],
rules: { 'no-console': 'off' },
},
{
languageOptions: {
parserOptions: {
tsconfigRootDir: process.cwd(),
project: ['./tsconfig.json'],
},
},
}
);
+16
View File
@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/src/favicon.svg" />
<meta
name="viewport"
content="minimum-scale=1, initial-scale=1, width=device-width, user-scalable=no"
/>
<title>Vite + Mantine App</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
+9546
View File
File diff suppressed because it is too large Load Diff
+67
View File
@@ -0,0 +1,67 @@
{
"name": "mantine-vite-template",
"private": true,
"type": "module",
"version": "0.0.0",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"typecheck": "tsc --noEmit",
"lint": "npm run eslint && npm run stylelint",
"eslint": "eslint . --cache",
"stylelint": "stylelint '**/*.css' --cache",
"prettier": "prettier --check \"**/*.{ts,tsx}\"",
"prettier:write": "prettier --write \"**/*.{ts,tsx}\"",
"vitest": "vitest run",
"vitest:watch": "vitest",
"test": "npm run typecheck && npm run prettier && npm run lint && npm run vitest && npm run build",
"storybook": "storybook dev -p 6006",
"storybook:build": "storybook build"
},
"dependencies": {
"@mantine/core": "8.3.1",
"@mantine/hooks": "8.3.1",
"@react-three/fiber": "^9.3.0",
"phosphor-react": "^1.4.1",
"react": "^19.1.1",
"react-dom": "^19.1.1",
"react-router-dom": "^7.8.2",
"three": "^0.180.0"
},
"devDependencies": {
"@eslint/js": "^9.35.0",
"@ianvs/prettier-plugin-sort-imports": "^4.7.0",
"@storybook/addon-themes": "^9.1.5",
"@storybook/react": "^9.1.5",
"@storybook/react-vite": "^9.1.5",
"@testing-library/dom": "^10.4.1",
"@testing-library/jest-dom": "^6.8.0",
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^14.6.1",
"@types/node": "^24.3.1",
"@types/react": "^19.1.12",
"@types/react-dom": "^19.1.9",
"@vitejs/plugin-react": "^5.0.2",
"eslint": "^9.35.0",
"eslint-config-mantine": "^4.0.3",
"eslint-plugin-jsx-a11y": "^6.10.2",
"eslint-plugin-react": "^7.37.5",
"identity-obj-proxy": "^3.0.0",
"jsdom": "^26.1.0",
"postcss": "^8.5.6",
"postcss-preset-mantine": "1.18.0",
"postcss-simple-vars": "^7.0.1",
"prettier": "^3.6.2",
"prop-types": "^15.8.1",
"storybook": "^9.1.5",
"stylelint": "^16.24.0",
"stylelint-config-standard-scss": "^16.0.0",
"typescript": "^5.9.2",
"typescript-eslint": "^8.43.0",
"vite": "^7.1.5",
"vite-tsconfig-paths": "^5.1.4",
"vitest": "^3.2.4"
},
"packageManager": "yarn@4.9.4"
}
+14
View File
@@ -0,0 +1,14 @@
module.exports = {
plugins: {
'postcss-preset-mantine': {},
'postcss-simple-vars': {
variables: {
'mantine-breakpoint-xs': '36em',
'mantine-breakpoint-sm': '48em',
'mantine-breakpoint-md': '62em',
'mantine-breakpoint-lg': '75em',
'mantine-breakpoint-xl': '88em',
},
},
},
};
+13
View File
@@ -0,0 +1,13 @@
import '@mantine/core/styles.css';
import { MantineProvider } from '@mantine/core';
import { Router } from './Router';
import { theme } from './theme';
export default function App() {
return (
<MantineProvider theme={theme}>
<Router />
</MantineProvider>
);
}
+22
View File
@@ -0,0 +1,22 @@
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import { HomePage } from './pages/Home.page';
import { Error_404 } from './components/404/404';
const router = createBrowserRouter([
{
path: '/',
element: <HomePage />,
},
{
path: '*',
element: <Error_404 /> },
]);
export function Router() {
return <RouterProvider router={router} />;
}
+59
View File
@@ -0,0 +1,59 @@
import { Box, Title, Text, Button, Group, Stack, Image, Center } from '@mantine/core';
import { useMantineTheme } from '@mantine/core';
import { ArrowLeft } from 'phosphor-react'; // ← Importa el icono directamente
import { Link } from 'react-router-dom';
import { MantineCardWithShader } from './HoloShader_404';
import { AppShellWithMenu } from '../Appshell/Appshell';
export function Error_404() {
const theme = useMantineTheme();
return (
<AppShellWithMenu>
<Box
style={{
flex: 1,
display: 'flex',
justifyContent: 'center',
alignItems: 'flex-start',
padding: '2rem',
paddingTop: '0.5rem',
}}
>
<Box
style={{
flex: 1,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
padding: '2rem',
}}
>
<Stack align="center" maw={500} mx="auto">
<MantineCardWithShader />
<Title order={1}>Página no encontrada</Title>
<Text size="lg">
Parece que la página que estás buscando no existe o fue removida. Pero no te preocupes,
puedes volver al inicio fácilmente.
</Text>
<Group mt="md">
<Button
component={Link}
to="/"
size="md"
variant="gradient"
gradient={{
from: theme.colors.brand[7],
to: theme.colors.secondary[4],
}}
leftSection={<ArrowLeft size={18} />} // ← Usa el icono Phosphor aquí
>
Volver al inicio
</Button>
</Group>
</Stack>
</Box>
</Box>
</AppShellWithMenu>
);
}
@@ -0,0 +1,142 @@
import { Card, Title, Box, useMantineTheme } from '@mantine/core';
import { Canvas, extend, useFrame, useThree } from '@react-three/fiber';
import { useRef, useMemo } from 'react';
import * as THREE from 'three';
// 🎨 Utilidad para convertir hex a RGB [01]
function hexToRGBArray(hex: string): [number, number, number] {
const bigint = parseInt(hex.replace('#', ''), 16);
return [
((bigint >> 16) & 255) / 255,
((bigint >> 8) & 255) / 255,
(bigint & 255) / 255,
];
}
// ✨ Shader personalizado estilo holográfico, con color dinámico
class HoloShaderMaterial extends THREE.ShaderMaterial {
constructor(color: [number, number, number]) {
super({
uniforms: {
u_time: { value: 0 },
u_resolution: { value: new THREE.Vector2() },
u_color: { value: new THREE.Vector3(...color) },
},
vertexShader: `
void main() {
gl_Position = vec4(position, 1.0);
}
`,
fragmentShader: `
precision mediump float;
uniform float u_time;
uniform vec2 u_resolution;
uniform vec3 u_color;
void main() {
vec2 uv = gl_FragCoord.xy / u_resolution;
vec2 pos = uv * 10.0;
pos.x += u_time * 0.3;
pos.y += sin(u_time * 0.2) * 2.0;
float color = sin(pos.x + sin(pos.y + sin(pos.x))) * 0.5 + 0.5;
vec3 c = vec3(
u_color.r + 0.2 * sin(u_time + pos.x),
u_color.g + 0.2 * cos(u_time + pos.y),
u_color.b + 0.2 * sin(pos.x + pos.y + u_time)
);
gl_FragColor = vec4(c * color, 1.0);
}
`,
});
}
}
extend({ HoloShaderMaterial });
// 🎥 Plano con el shader
function HoloPlane({ color }: { color: [number, number, number] }) {
const mat = useRef<any>(null);
const { size } = useThree();
useFrame(({ clock }) => {
if (mat.current) {
mat.current.uniforms.u_time.value = clock.getElapsedTime();
mat.current.uniforms.u_resolution.value.set(size.width, size.height);
}
});
const material = useMemo(() => new HoloShaderMaterial(color), [color]);
return (
<mesh>
<planeGeometry args={[2, 2]} />
<primitive object={material} ref={mat} attach="material" />
</mesh>
);
}
// 🎨 Fondo que ocupa todo el contenedor
function HolographicBackground({ color }: { color: [number, number, number] }) {
return (
<Box
style={{
position: 'absolute',
inset: 0,
zIndex: 0,
pointerEvents: 'none',
}}
>
<Canvas orthographic camera={{ zoom: 1, position: [0, 0, 1] }}>
<HoloPlane color={color} />
</Canvas>
</Box>
);
}
// 🧩 Componente final con fondo shader y texto 404
export function MantineCardWithShader() {
const theme = useMantineTheme();
const hex = theme.colors[theme.primaryColor][6];
const rgb = hexToRGBArray(hex);
return (
<Card
withBorder
radius="lg"
shadow="xl"
style={{
position: 'relative',
overflow: 'hidden',
minHeight: 300,
minWidth: 400,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<HolographicBackground color={rgb} />
<Box style={{ position: 'relative', zIndex: 1, textAlign: 'center' }}>
<Title
order={1}
style={{
fontSize: '15rem',
fontWeight: 900,
backgroundImage: 'linear-gradient(to bottom, rgba(255,255,255,1), rgba(255,255,255,0))',
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
textAlign: 'center',
lineHeight: 1,
userSelect: 'none', // <-- evita selección
textDecoration: 'none', // <-- evita subrayado
}}
>
404
</Title>
</Box>
</Card>
);
}
@@ -0,0 +1,103 @@
.navbar {
height: 100vh; /* ← Ocupa todo el alto de la ventana */
display: flex;
flex-direction: column;
background-color: light-dark(var(--mantine-color-white), var(--mantine-color-dark-6));
width: 300px;
border-right: 1px solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4));
position: sticky; /* ← Opcional, si quieres que se quede "pegado" */
top: 0; /* ← Ancla arriba */
}
.title {
font-family:
Greycliff CF,
var(--mantine-font-family);
margin-bottom: var(--mantine-spacing-sm);
background-color: var(--mantine-color-body);
padding: var(--mantine-spacing-xs);
padding-top: 15px;
height: 50px;
border-bottom: 1px solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-7));
}
.wrapper {
display: flex;
flex: 1;
}
/* Esta es la barra izquierda pequeña donde los iconos */
.aside {
flex: 0 0 52px;
background-color: var(--mantine-color-body);
display: flex;
flex-direction: column;
align-items: center;
border-right: 1px solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-7));
}
.main {
flex: 1;
background-color: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-6));
}
.topSection {
padding-top: 12px; /* o la cantidad que desees */
}
/* Estos son los iconos */
.mainLink {
width: 40px;
height: 40px;
margin-bottom: 4px;
border-radius: var(--mantine-radius-md);
display: flex;
align-items: center;
justify-content: center;
color: light-dark(var(--mantine-color-gray-7), var(--mantine-color-dark-0));
&:hover {
background-color: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-5));
}
&[data-active] {
&,
&:hover {
background-color: var(--mantine-color-brand-7);
color: var(--mantine-color-brand-2);
}
}
}
.link {
display: block;
text-decoration: none;
border-top-right-radius: var(--mantine-radius-md);
border-bottom-right-radius: var(--mantine-radius-md);
color: light-dark(var(--mantine-color-gray-7), var(--mantine-color-dark-0));
padding: 0 var(--mantine-spacing-md);
font-size: var(--mantine-font-size-sm);
margin-right: var(--mantine-spacing-md);
font-weight: 420;
height: 30px;
line-height: 30px;
&:hover {
background-color: light-dark(var(--mantine-color-gray-1), var(--mantine-color-dark-5));
color: light-dark(var(--mantine-color-dark), var(--mantine-color-light));
}
&[data-active] {
&,
&:hover {
background-color: var(--mantine-color-brand-7);
color: var(--mantine-color-brand-2);
}
}
}
@@ -0,0 +1,222 @@
import {
AppShell,
Burger,
Group,
Tooltip,
UnstyledButton,
Title,
useMantineTheme,
} from '@mantine/core';
import { useDisclosure, useMediaQuery } from '@mantine/hooks';
import { useEffect, useMemo, useRef, useState } from 'react';
import { Link, useLocation, useNavigate } from 'react-router-dom';
// import { default as LogoIcon } from '../../../assets/icons/favicon';
// import { mainLinksdata } from '../Links_Appshell/navigationsLinks';
// import { submenuLinks } from '../Links_Appshell/submenuLinks';
import classes from './Appshell.module.css';
type AppShellWithMenuProps = {
children?: React.ReactNode;
};
// Persistencia en localStorage
const STORAGE_KEY = 'lastSubmenuRoutes';
function getLastSubmenuRoute(section: string): string | null {
try {
const raw = localStorage.getItem(STORAGE_KEY);
const parsed = raw ? JSON.parse(raw) : {};
return parsed[section] ?? null;
} catch {
return null;
}
}
function setLastSubmenuRoute(section: string, route: string) {
try {
const raw = localStorage.getItem(STORAGE_KEY);
const parsed = raw ? JSON.parse(raw) : {};
parsed[section] = route;
localStorage.setItem(STORAGE_KEY, JSON.stringify(parsed));
} catch {
// fallback silencioso
}
}
export function AppShellWithMenu({ children }: AppShellWithMenuProps) {
const theme = useMantineTheme();
const location = useLocation();
const navigate = useNavigate();
const isMobile = useMediaQuery('(max-width: 768px)');
const [mobileOpened, { toggle: toggleMobile, close: closeMobile }] = useDisclosure(false);
const [desktopOpened, { toggle: toggleDesktop, open: openDesktop }] = useDisclosure(true);
const isCollapsed = useMemo(
() => (isMobile ? !mobileOpened : !desktopOpened),
[isMobile, mobileOpened, desktopOpened]
);
// Estado para el main link activo
const [activeMain, setActiveMain] = useState<string>('Home');
// Ref para saber si el usuario ha hecho clic manualmente en el main link
const userClickedMainRef = useRef(false);
useEffect(() => {
const currentPath = location.pathname?.toLowerCase().replace(/\/$/, '') ?? '';
let matchedMain: string | null = null;
let maxMatchLength = 0;
Object.entries(submenuLinks).forEach(([main, items]) => {
(items as SubmenuLinkItem[]).forEach((item: SubmenuLinkItem) => {
const itemPath = item.to.toLowerCase().replace(/\/$/, '');
if (
currentPath === itemPath ||
currentPath.startsWith(itemPath + '/')
) {
if (itemPath.length > maxMatchLength) {
matchedMain = main;
maxMatchLength = itemPath.length;
}
}
});
});
if (matchedMain) {
setActiveMain(matchedMain);
}
}, [location.pathname]);
interface SubmenuLinkItem {
label: string;
to: string;
}
type SubmenuLinks = {
[key: string]: SubmenuLinkItem[];
};
const activeLink: string =
(submenuLinks as SubmenuLinks)[activeMain as keyof SubmenuLinks]?.find(
(item: SubmenuLinkItem) => item.to === location.pathname
)?.label ?? '';
const mainLinks = mainLinksdata.map((link) => (
<Tooltip
label={link.label}
position="right"
withArrow
transitionProps={{ duration: 0 }}
key={link.label}
>
<UnstyledButton
onClick={() => {
userClickedMainRef.current = true;
setActiveMain(link.label);
const remembered = getLastSubmenuRoute(link.label);
const fallback = submenuLinks[link.label as keyof typeof submenuLinks]?.[0]?.to;
if (isCollapsed && (remembered || fallback)) {
navigate(remembered ?? fallback);
}
}}
className={classes.mainLink}
data-active={link.label === activeMain || undefined}
>
<link.icon size={24} weight="duotone" />
</UnstyledButton>
</Tooltip>
));
interface SubmenuLinkItem {
label: string;
to: string;
}
interface LinksProps {
activeMain: string;
activeLink: string;
isCollapsed: boolean;
isMobile: boolean;
closeMobile: () => void;
}
const links: React.ReactNode = (
(submenuLinks[activeMain as keyof typeof submenuLinks] || []) as SubmenuLinkItem[]
).map((item: SubmenuLinkItem) => (
<Link
className={classes.link}
data-active={activeLink === item.label || undefined}
to={item.to}
key={item.label}
style={{ display: isCollapsed ? 'none' : 'block' }}
onClick={() => {
setLastSubmenuRoute(activeMain, item.to);
if (isMobile) closeMobile();
}}
>
{item.label}
</Link>
));
useEffect(() => {
if (!isMobile) openDesktop();
}, [isMobile, openDesktop]);
return (
<AppShell
header={{ height: 60 }}
navbar={{
width: isCollapsed ? 60 : 300,
breakpoint: 'sm',
collapsed: { mobile: !mobileOpened, desktop: !desktopOpened },
}}
padding={0}
styles={{
main: {
height: '100dvh', // o '100vh', pero mejor con 100dvh para evitar bugs móviles
display: 'flex',
flexDirection: 'column',
},
}}
>
{/* Header */}
<AppShell.Header>
<Group h="100%" px="sm">
<Burger opened={mobileOpened} onClick={toggleMobile} hiddenFrom="sm" size="sm" />
<Burger opened={desktopOpened} onClick={toggleDesktop} visibleFrom="sm" size="sm" />
<LogoIcon
style={{ width: 30, height: 30 }}
circleFill={theme.colors.brand[9]}
pathFill={theme.colors.secondary[2]}
/>
</Group>
</AppShell.Header>
{/* Navbar */}
<AppShell.Navbar>
<div className={classes.wrapper}>
<div className={classes.aside}>
<div className={classes.topSection}>{mainLinks}</div>
</div>
<div className={classes.main}>
{!isCollapsed && (
<Title order={4} className={classes.title}>
{activeMain}
</Title>
)}
{links}
</div>
</div>
</AppShell.Navbar>
{/* Main Content */}
<AppShell.Main>{children}</AppShell.Main>
</AppShell>
);
}
@@ -0,0 +1,13 @@
import { Button, Group, useMantineColorScheme } from '@mantine/core';
export function ColorSchemeToggle() {
const { setColorScheme } = useMantineColorScheme();
return (
<Group justify="center" mt="xl">
<Button onClick={() => setColorScheme('light')}>Light</Button>
<Button onClick={() => setColorScheme('dark')}>Dark</Button>
<Button onClick={() => setColorScheme('auto')}>Auto</Button>
</Group>
);
}
+10
View File
@@ -0,0 +1,10 @@
import { AppShellWithMenu } from './Appshell/Appshell';
export function Plantilla() {
return (
<AppShellWithMenu>
</AppShellWithMenu>
);
}
@@ -0,0 +1,65 @@
import { useState } from 'react';
import { TextInput, PasswordInput, Button, Paper, Title, Container, Group, Alert } from '@mantine/core';
import { User, Lock } from 'phosphor-react'; // ← Importa los iconos Phosphor
export function LoginPage() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const [loading, setLoading] = useState(false);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
setError('');
// Aquí deberías llamar a tu endpoint de login (ajusta la URL y payload)
try {
const res = await fetch('/api/v1/usuarios/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password })
});
if (!res.ok) throw new Error('Credenciales incorrectas');
// Aquí puedes guardar el usuario/token en el estado global o localStorage
window.location.href = '/';
} catch (err: any) {
setError(err.message);
} finally {
setLoading(false);
}
};
return (
<Container size={420} my={40}>
<Title align="center" mb={20}>Iniciar sesión</Title>
<Paper withBorder shadow="md" p={30} mt={30} radius="md">
<form onSubmit={handleSubmit}>
<TextInput
label="Email"
placeholder="tucorreo@ejemplo.com"
icon={<User size={18} />} // ← Usa el icono Phosphor aquí
value={email}
onChange={e => setEmail(e.target.value)}
required
mb={10}
/>
<PasswordInput
label="Contraseña"
placeholder="Tu contraseña"
icon={<Lock size={18} />} // ← Usa el icono Phosphor aquí
value={password}
onChange={e => setPassword(e.target.value)}
required
mb={20}
/>
{error && <Alert color="red" mb={10}>{error}</Alert>}
<Group mt="md">
<Button type="submit" loading={loading} fullWidth>
Entrar
</Button>
</Group>
</form>
</Paper>
</Container>
);
}
@@ -0,0 +1,10 @@
.title {
color: light-dark(var(--mantine-color-black), var(--mantine-color-white));
font-size: rem(100px);
font-weight: 900;
letter-spacing: rem(-2px);
@media (max-width: $mantine-breakpoint-md) {
font-size: rem(50px);
}
}
@@ -0,0 +1,7 @@
import { Welcome } from './Welcome';
export default {
title: 'Welcome',
};
export const Usage = () => <Welcome />;
@@ -0,0 +1,12 @@
import { render, screen } from '@test-utils';
import { Welcome } from './Welcome';
describe('Welcome component', () => {
it('has correct Vite guide link', () => {
render(<Welcome />);
expect(screen.getByText('this guide')).toHaveAttribute(
'href',
'https://mantine.dev/guides/vite/'
);
});
});
@@ -0,0 +1,23 @@
import { Anchor, Text, Title } from '@mantine/core';
import classes from './Welcome.module.css';
export function Welcome() {
return (
<>
<Title className={classes.title} ta="center" mt={100}>
Welcome to{' '}
<Text inherit variant="gradient" component="span" gradient={{ from: 'pink', to: 'yellow' }}>
Mantine
</Text>
</Title>
<Text c="dimmed" ta="center" size="lg" maw={580} mx="auto" mt="xl">
This starter Vite project includes a minimal setup, if you want to learn more on Mantine +
Vite integration follow{' '}
<Anchor href="https://mantine.dev/guides/vite/" size="lg">
this guide
</Anchor>
. To get started edit pages/Home.page.tsx file.
</Text>
</>
);
}
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 163 163"><path fill="#339AF0" d="M162.162 81.5c0-45.011-36.301-81.5-81.08-81.5C36.301 0 0 36.489 0 81.5 0 126.51 36.301 163 81.081 163s81.081-36.49 81.081-81.5z"/><path fill="#fff" d="M65.983 43.049a6.234 6.234 0 00-.336 6.884 6.14 6.14 0 001.618 1.786c9.444 7.036 14.866 17.794 14.866 29.52 0 11.726-5.422 22.484-14.866 29.52a6.145 6.145 0 00-1.616 1.786 6.21 6.21 0 00-.694 4.693 6.21 6.21 0 001.028 2.186 6.151 6.151 0 006.457 2.319 6.154 6.154 0 002.177-1.035 50.083 50.083 0 007.947-7.39h17.493c3.406 0 6.174-2.772 6.174-6.194s-2.762-6.194-6.174-6.194h-9.655a49.165 49.165 0 004.071-19.69 49.167 49.167 0 00-4.07-19.692h9.66c3.406 0 6.173-2.771 6.173-6.194 0-3.422-2.762-6.193-6.173-6.193H82.574a50.112 50.112 0 00-7.952-7.397 6.15 6.15 0 00-4.578-1.153 6.189 6.189 0 00-4.055 2.438h-.006z"/><path fill="#fff" fill-rule="evenodd" d="M56.236 79.391a9.342 9.342 0 01.632-3.608 9.262 9.262 0 011.967-3.077 9.143 9.143 0 012.994-2.063 9.06 9.06 0 017.103 0 9.145 9.145 0 012.995 2.063 9.262 9.262 0 011.967 3.077 9.339 9.339 0 01-2.125 10.003 9.094 9.094 0 01-6.388 2.63 9.094 9.094 0 01-6.39-2.63 9.3 9.3 0 01-2.755-6.395z" clip-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

+4
View File
@@ -0,0 +1,4 @@
import ReactDOM from 'react-dom/client';
import App from './App';
ReactDOM.createRoot(document.getElementById('root')!).render(<App />);
+11
View File
@@ -0,0 +1,11 @@
import { ColorSchemeToggle } from '../components/ColorSchemeToggle/ColorSchemeToggle';
import { Welcome } from '../components/Welcome/Welcome';
export function HomePage() {
return (
<>
<Welcome />
<ColorSchemeToggle />
</>
);
}
+5
View File
@@ -0,0 +1,5 @@
import { createTheme } from '@mantine/core';
export const theme = createTheme({
/** Put your mantine theme override here */
});
+1
View File
@@ -0,0 +1 @@
/// <reference types="vite/client" />
+5
View File
@@ -0,0 +1,5 @@
import userEvent from '@testing-library/user-event';
export * from '@testing-library/react';
export { render } from './render';
export { userEvent };
+13
View File
@@ -0,0 +1,13 @@
import { render as testingLibraryRender } from '@testing-library/react';
import { MantineProvider } from '@mantine/core';
import { theme } from '../src/theme';
export function render(ui: React.ReactNode) {
return testingLibraryRender(ui, {
wrapper: ({ children }: { children: React.ReactNode }) => (
<MantineProvider theme={theme} env="test">
{children}
</MantineProvider>
),
});
}
+25
View File
@@ -0,0 +1,25 @@
{
"compilerOptions": {
"types": ["node", "@testing-library/jest-dom", "vitest/globals"],
"target": "ESNext",
"useDefineForClassFields": true,
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"allowJs": false,
"skipLibCheck": true,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "ESNext",
"moduleResolution": "Node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"paths": {
"@/*": ["./src/*"],
"@test-utils": ["./test-utils"]
}
},
"include": ["src", "test-utils", ".storybook/main.ts", ".storybook/preview.tsx"]
}
+12
View File
@@ -0,0 +1,12 @@
import react from '@vitejs/plugin-react';
import { defineConfig } from 'vite';
import tsconfigPaths from 'vite-tsconfig-paths';
export default defineConfig({
plugins: [react(), tsconfigPaths()],
test: {
globals: true,
environment: 'jsdom',
setupFiles: './vitest.setup.mjs',
},
});
+28
View File
@@ -0,0 +1,28 @@
import '@testing-library/jest-dom/vitest';
import { vi } from 'vitest';
const { getComputedStyle } = window;
window.getComputedStyle = (elt) => getComputedStyle(elt);
window.HTMLElement.prototype.scrollIntoView = () => {};
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: vi.fn().mockImplementation((query) => ({
matches: false,
media: query,
onchange: null,
addListener: vi.fn(),
removeListener: vi.fn(),
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
dispatchEvent: vi.fn(),
})),
});
class ResizeObserver {
observe() {}
unobserve() {}
disconnect() {}
}
window.ResizeObserver = ResizeObserver;
+4437
View File
File diff suppressed because it is too large Load Diff
+627
View File
@@ -0,0 +1,627 @@
import marimo
__generated_with = "0.15.5"
app = marimo.App(width="columns")
@app.cell(column=0)
def _():
import marimo as mo
return (mo,)
@app.cell
def _():
PUERTO_POSTGRES = 55455
SERVICIO = "postgres_ext"
DB_PASSWORD = "mipassword"
DB_USER = "postgres"
DB_NAME = "basededatos"
DB_HOST = "localhost"
return DB_HOST, DB_NAME, DB_PASSWORD, DB_USER, PUERTO_POSTGRES
@app.cell
def _(DB_HOST, DB_NAME, DB_PASSWORD, DB_USER, PUERTO_POSTGRES):
# scaffold_backend.py
import os
from pathlib import Path
BASE_DIR = Path.cwd() / "backend"
DB_DIR = BASE_DIR / "db"
DB_DIR.mkdir(parents=True, exist_ok=True)
# __init__.py
(DB_DIR / "__init__.py").write_text("", encoding="utf-8")
# base.py
base_py = """\
# backend/db/base.py
from sqlalchemy.orm import declarative_base
# Declarative Base común para todos los modelos ORM
Base = declarative_base()
"""
(DB_DIR / "base.py").write_text(base_py, encoding="utf-8")
# session.py
session_py = f"""\
# backend/db/session.py
import os
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from urllib.parse import quote_plus
from dotenv import load_dotenv
# Cargar variables de entorno (.env) desde backend/.env
load_dotenv(dotenv_path=os.path.join(os.path.dirname(__file__), "..", ".env"))
DB_USER = os.getenv("DB_USER")
DB_PASSWORD = quote_plus(os.getenv("DB_PASSWORD"))
DB_HOST = os.getenv("DB_HOST")
DB_PORT = os.getenv("DB_PORT", "{PUERTO_POSTGRES}")
DB_NAME = os.getenv("DB_NAME")
DATABASE_URL = (
f"postgresql+psycopg2://{{DB_USER}}:{{DB_PASSWORD}}@{{DB_HOST}}:{{DB_PORT}}/{{DB_NAME}}"
)
# Crear engine
engine = create_engine(
DATABASE_URL,
pool_size=5,
max_overflow=10,
future=True,
echo=False, # pon True para ver las queries en consola
)
# Fábrica de sesiones
SessionLocal = sessionmaker(
bind=engine,
autoflush=False,
expire_on_commit=False,
future=True,
)
"""
(DB_DIR / "session.py").write_text(session_py, encoding="utf-8")
# .env handling
env_file = BASE_DIR / ".env"
default_env = {
"DB_USER": DB_USER,
"DB_PASSWORD": DB_PASSWORD,
"DB_HOST": DB_HOST,
"DB_PORT": PUERTO_POSTGRES,
"DB_NAME": DB_NAME,
}
if not env_file.exists():
# Crear archivo nuevo con todas las claves
env_content = "\n".join([f"{k}={v}" for k, v in default_env.items()]) + "\n"
env_file.write_text(env_content, encoding="utf-8")
print(f"✅ Creado {env_file}")
else:
# Leer y comprobar claves
existing = env_file.read_text(encoding="utf-8").splitlines()
existing_keys = {line.split("=")[0] for line in existing if "=" in line}
missing = {k: v for k, v in default_env.items() if k not in existing_keys}
if missing:
with env_file.open("a", encoding="utf-8") as f:
for k, v in missing.items():
f.write(f"{k}={v}\n")
print(f"📝 Añadidas claves faltantes: {', '.join(missing.keys())}")
else:
print(f"{env_file} ya tiene todas las claves necesarias.")
print(f"✅ Creado {DB_DIR/'base.py'}")
print(f"✅ Creado {DB_DIR/'session.py'}")
return Path, os
@app.cell
def _(Path, os):
# import os
RUTA_CARPETA_SNIPPETS = os.environ["SNIPPETS_ROUTE"]
DEST_BASE = Path("./domains/arquitecture_layer")
DEST_BASE.mkdir(parents=True, exist_ok=True)
archivos = {
"utils/ArquitectureLayer/Repo.py": "Repo.py",
"utils/ArquitectureLayer/Mapper.py": "Mapper.py",
"utils/ArquitectureLayer/Model.py": "Model.py",
}
# Copiar los archivos
for origen_rel, nombre_dest in archivos.items():
origen = Path(RUTA_CARPETA_SNIPPETS) / origen_rel
destino = DEST_BASE / nombre_dest
destino.write_text(origen.read_text(encoding="utf-8"), encoding="utf-8")
print(f"{origen}{destino}")
# Crear __init__.py para exponer los módulos
init_file = DEST_BASE / "__init__.py"
init_code = """\
# domains/arquitecture_layer/__init__.py
from .Repo import *
from .Mapper import *
from .Model import *
__all__ = ["Repo", "Mapper", "Model"]
"""
init_file.write_text(init_code, encoding="utf-8")
print(f"✅ Creado {init_file}")
return (RUTA_CARPETA_SNIPPETS,)
@app.cell(column=1)
def _(os):
#Obtenemos las variables
# import os
def obtener_texto_archivo(ruta_archivo):
"""Lee un archivo o todos los archivos de un directorio."""
if os.path.isdir(ruta_archivo):
textos = {}
for archivo in os.listdir(ruta_archivo):
ruta = os.path.join(ruta_archivo, archivo)
if os.path.isfile(ruta):
with open(ruta, 'r', encoding='utf-8') as f:
textos[archivo] = f.read()
return textos
elif os.path.isfile(ruta_archivo):
with open(ruta_archivo, 'r', encoding='utf-8') as f:
return f.read()
else:
raise FileNotFoundError(f"No se encontró archivo o directorio: {ruta_archivo}")
repo_code = obtener_texto_archivo("domains/arquitecture_layer/Repo.py")
mapper_code = obtener_texto_archivo("domains/arquitecture_layer/Mapper.py")
model_code = obtener_texto_archivo("domains/arquitecture_layer/Model.py")
repo_ruta_code = "from domains.arquitecture_layer.Model import Model_base"
mapper_ruta_code = "domains.arquitecture_layer.Mapper import Mapper_base"
model_ruta_code = "domains.arquitecture_layer.Repo import Repo_base"
return (
mapper_code,
mapper_ruta_code,
model_code,
model_ruta_code,
repo_code,
repo_ruta_code,
)
@app.cell
def _(mo):
input_entidad = mo.ui.text_area("", label="Entidad a crear", rows=1, full_width=True)
input_descripcion = mo.ui.text_area("", label="Descripcion de la Entidad", rows=3, full_width=True)
mo.vstack([input_entidad, input_descripcion])
return input_descripcion, input_entidad
@app.cell
def _(mo):
esquema = mo.ui.text(
value="public",
label="Esquema:"
)
esquema
return (esquema,)
@app.cell
def _(
RUTA_CARPETA_SNIPPETS,
esquema,
input_descripcion,
input_entidad,
mo,
os,
):
from jinja2 import Template
# from pathlib import Path
# RUTA_CARPETA_SNIPPETS = os.environ["SNIPPETS_ROUTE"]
with open(os.path.join(RUTA_CARPETA_SNIPPETS, "markdown/plantilla_generacion_campos_detalles.md"), "r", encoding="utf-8") as _f:
plantilla_detalles = Template(_f.read())
entidad = input_entidad.value.lower()
descripcion = input_descripcion.value.lower()
_contexto = {
"ENTIDAD" : entidad, # Nombre de la entidad
"DESCRIPCION" : descripcion,
"SCHEMA" : esquema.value
}
# Pasar el diccionario con **
resultado_plantilla = plantilla_detalles.render(**_contexto)
mo.output.append(mo.md("### Prompt para la base del modelo:"))
mo.output.append(mo.md(resultado_plantilla))
return Template, entidad, resultado_plantilla
@app.cell
def _(mo):
run_button = mo.ui.run_button(label="Generar JSON", kind="neutral")
run_button
return (run_button,)
@app.cell
def _(mo, os, resultado_plantilla, run_button):
from openai import OpenAI
import json
def call_openai_to_json(template_text: str) -> dict:
"""Call OpenAI and return parsed JSON; raises on errors."""
# NOTE: Read API key from environment for reproducibility
client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])
resp = client.chat.completions.create(
model="gpt-4o", # or "gpt-5" if available
response_format={"type": "json_object"},
messages=[
{"role": "system", "content": "Eres un asistente estricto que SIEMPRE devuelve JSON válido y nada más."},
{"role": "user", "content": resultado_plantilla}
],
temperature=0.2
)
json_text = resp.choices[0].message.content
return json.loads(json_text)
status_message = "Pulsa el botón para generar."
json_text = None # Default sentinel so dependents can run
if run_button.value:
try:
json_text = call_openai_to_json(resultado_plantilla)
status_message = "JSON generado correctamente."
except Exception as exc:
# Safe error surface without stopping the graph
status_message = f"Error al generar: {exc!s}"
# Return a UI element; if no resultado, mostramos solo el mensaje
mo.vstack([
mo.md(f"**Estado:** {status_message}"),
mo.json(json_text) if json_text is not None else mo.md("")
])
return OpenAI, json, json_text
@app.cell
def _(json, json_text, mo):
from glom import glom, Coalesce
# --- 1) Normalizar entrada: str JSON -> dict
def to_obj(x):
if isinstance(x, str):
return json.loads(x) # <- parsea el texto JSON
return x
data = to_obj(json_text) # json_text puede ser str o dict
# Extraer listas
campos_list = glom(data, Coalesce("CAMPOS", default=[]))
relaciones_list = glom(data, Coalesce("RELACIONES", default=[]))
indices_list = glom(data, Coalesce("INDEX_LIST", default=[]))
unicos_list = glom(data, Coalesce("UNIQUE_LIST", default=[]))
# Por si vienen como string en lugar de lista:
def ensure_list(v):
if v is None:
return []
if isinstance(v, list):
return v
return [str(v)]
# Convertir a texto multilínea
campos_list = ensure_list(campos_list)
relaciones_list = ensure_list(relaciones_list)
indices_list = ensure_list(indices_list)
unicos_list = ensure_list(unicos_list)
campos = "\n".join(map(str, campos_list))
relaciones = "\n".join(map(str, relaciones_list))
indices = "\n".join(map(str, indices_list))
unicos = "\n".join(map(str, unicos_list))
campos_final = mo.ui.text_area(campos, label="Campos", rows=15, full_width=True)
relaciones_final = mo.ui.text_area(relaciones, label="Relaciones", rows=3, full_width=True)
indices_final = mo.ui.text_area(indices, label="Indices", rows=3, full_width=True)
unicos_final = mo.ui.text_area(unicos, label="Unicos", rows=3, full_width=True)
layout = mo.hstack(
[
campos_final,
mo.vstack([relaciones_final, indices_final, unicos_final])
],
widths=[2, 1], # t1 ocupa el doble de ancho que el stack vertical
gap=1 # margen entre los elementos en rem (1 rem ≈ 16 px)
)
layout
return campos_final, indices_final, relaciones_final, unicos_final
@app.cell
def _(
RUTA_CARPETA_SNIPPETS,
Template,
campos_final,
entidad,
esquema,
indices_final,
mapper_code,
mapper_ruta_code,
mo,
model_code,
model_ruta_code,
os,
relaciones_final,
repo_code,
repo_ruta_code,
unicos_final,
):
with open(os.path.join(RUTA_CARPETA_SNIPPETS, "markdown/plantilla_generacion_mmr.md"), "r", encoding="utf-8") as _f:
plantilla = Template(_f.read())
with open(os.path.join(RUTA_CARPETA_SNIPPETS, "markdown/ejemplos_base_mmr.md"), "r", encoding="utf-8") as _f:
ejemplo = _f.read()
# Diccionario con variables
_contexto = {
"ENTIDAD" : entidad, # Nombre de la entidad
"RUTA_REPO_BASE": repo_ruta_code,
"RUTA_MAPPER_BASE": mapper_ruta_code,
"RUTA_MODEL_BASE": model_ruta_code,
"REPO_BASE": repo_code,
"MAPPER_BASE": mapper_code,
"MODEL_BASE": model_code,
"EJEMPLO": ejemplo,
"SCHEMA": esquema.value,
"TABLA": entidad,
"CAMPOS": campos_final.value,
"RELACIONES" : relaciones_final.value,
"INDEX_LIST": indices_final.value,
"UNIQUE_LIST": unicos_final.value,
}
# Pasar el diccionario con **
resultado = plantilla.render(**_contexto)
mo.output.append(mo.md("### Prompt para el modelo:"))
mo.output.append(mo.md(resultado))
return (resultado,)
@app.cell
def _(mo):
run_button_code = mo.ui.run_button(label="Generar código Python", kind="neutral")
run_button_code
return (run_button_code,)
@app.cell
def _(OpenAI, mo, os, resultado, run_button_code):
# from openai import OpenAI
def call_openai_to_code(user_prompt: str) -> str:
"""Call OpenAI and return Python code as plain text."""
client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])
resp = client.chat.completions.create(
model="gpt-4o", # o "gpt-5" si lo tienes
messages=[
{
"role": "system",
"content": "Eres un asistente estricto que SIEMPRE devuelve codigo python de acuerdo a sus reglas"
},
{"role": "user", "content": user_prompt}
],
temperature=0.2
)
return resp.choices[0].message.content
_status_message = "Pulsa el botón para generar."
respuesta_codigo = None # valor por defecto
if run_button_code.value:
try:
respuesta_codigo = call_openai_to_code(resultado)
_status_message = "Código generado correctamente."
except Exception as exc:
_status_message = f"Error al generar: {exc!s}"
mo.vstack([
mo.md(f"**Estado:** {_status_message}"),
mo.md(f"```python\n{respuesta_codigo}\n```") if respuesta_codigo else mo.md("")
])
return (respuesta_codigo,)
@app.cell
def _(entidad, mo, os, respuesta_codigo):
# Botón para guardar
boton_guardar = mo.ui.button(
label="Guardar archivo",
on_click=lambda _: guardar()
)
def guardar():
codigo_limpio = (
respuesta_codigo.strip()
.removeprefix("```python")
.removesuffix("```")
.strip()
)
# Carpeta relativa (ej: ./domains dentro de tu proyecto)
carpeta = "domains"
os.makedirs(carpeta, exist_ok=True)
ruta = os.path.join(carpeta, f"{entidad}.py")
with open(ruta, "w", encoding="utf-8") as f:
f.write(codigo_limpio)
print(f"✅ Archivo guardado en {ruta}")
boton_guardar
return
@app.cell
def _(mo, run_button_db):
from backend.db.session import engine
from backend.db.base import Base
# # -> AQUI IMPORTA TUS MODELOS para que se registren en Base.metadata
# from domains.Clientes import ClientesModel
# from domains.Vehiculos import VehiculosModel
# from domains.ejemplo import ejemploModel
from domains.nota import notaModel
def create_all_tables() -> dict:
"""Create all tables bound to Base.metadata and return a summary."""
# Ensure models are imported so their metadata is registered (done in Cell 1)
Base.metadata.create_all(bind=engine)
created_tables = list(Base.metadata.tables.keys())
return {
"message": "✔ Tablas creadas",
"tables": created_tables,
"count": len(created_tables),
}
_status_message = "Importa tus modelos y pulsa el botón para crear las tablas."
result_payload = None # Default so dependents don't break
if run_button_db.value:
try:
result_payload = create_all_tables()
_status_message = "Operación completada correctamente."
except Exception as exc:
_status_message = f"Error al crear tablas: {exc!s}"
mo.vstack([
mo.md(f"**Estado:** {_status_message}"),
mo.json(result_payload) if result_payload is not None else mo.md("")
])
return (engine,)
@app.cell
def _(mo):
run_button_db = mo.ui.run_button(label="Crear tablas en la base de datos", kind="neutral")
mo.vstack([mo.md("Finalmente registrar las tablas en la bbdd"),
run_button_db])
return (run_button_db,)
@app.cell
def _(mo):
# Cell 2
run_button_drop = mo.ui.run_button(label="Eliminar todas las tablas vacías", kind = "danger" )
run_button_drop
return (run_button_drop,)
@app.cell
def _(engine, mo, run_button_drop):
from sqlalchemy import inspect, func, select
from sqlalchemy import MetaData
from sqlalchemy import MetaData, select, exists
def drop_all_empty_tables_improved():
engine.dispose()
metadata = MetaData()
metadata.reflect(bind=engine, resolve_fks=False)
dropped, skipped = [], []
with engine.connect() as connection:
trans = connection.begin()
try:
for table in metadata.sorted_tables:
col = list(table.c)[0] # primera columna para usar en exists()
has_data = connection.execute(
select(exists().where(col != None))
).scalar()
if not has_data:
table.drop(engine, checkfirst=True)
dropped.append(table.name)
else:
skipped.append(table.name)
trans.commit()
except:
trans.rollback()
raise
return {"dropped": dropped, "skipped": skipped}
_status_message = "Pulsa el botón para eliminar tablas vacías."
_result_payload = None
if run_button_drop.value:
try:
_result_payload = drop_all_empty_tables_improved()
_status_message = "Operación completada correctamente."
except Exception as exc:
_status_message = f"Error al eliminar tablas: {exc!s}"
mo.vstack([
mo.md(f"**Estado:** {_status_message}"),
mo.json(_result_payload) if _result_payload else mo.md("")
])
return
@app.cell
def _():
# # Obtenemos las variables de nuestro template
# from jinja2 import Environment, meta
# # Crear un entorno de Jinja
# env = Environment()
# with open(os.path.join(RUTA_CARPETA_SNIPPETS, "markdown/plantilla_mmr.md"), "r", encoding="utf-8") as _f:
# source = _f.read()
# # Parsear la plantilla sin renderizar
# parsed_content = env.parse(source)
# # Obtener las variables no declaradas
# variables = meta.find_undeclared_variables(parsed_content)
# print("Variables encontradas:", variables)
return
if __name__ == "__main__":
app.run()
+2
View File
@@ -5,7 +5,9 @@ description = "Add your description here"
readme = "README.md"
requires-python = ">=3.10"
dependencies = [
"fastapi>=0.116.2",
"marimo>=0.15.3",
"pgvector>=0.4.1",
]
[tool.marimo.runtime]
+30
View File
@@ -0,0 +1,30 @@
# create_tables.py
from backend.db.session import engine
from backend.db.base import Base
# 👉 Importa aquí tus modelos para que se registren en Base.metadata
# from domains.Clientes import ClientesModel
# from domains.Vehiculos import VehiculosModel
# from domains.ejemplo import ejemploModel
from domains.nota import notaModel
def create_all_tables() -> dict:
"""Create all tables bound to Base.metadata and return a summary."""
Base.metadata.create_all(bind=engine)
created_tables = list(Base.metadata.tables.keys())
return {
"message": "✔ Tablas creadas",
"tables": created_tables,
"count": len(created_tables),
}
if __name__ == "__main__":
try:
result_payload = create_all_tables()
print("Estado: Operación completada correctamente.")
print(result_payload)
except Exception as exc:
print(f"❌ Error al crear tablas: {exc!s}")
@@ -11,217 +11,6 @@ def _():
@app.cell
def _():
import os
import textwrap
import yaml
import subprocess
from pathlib import Path
# ==== Parámetros ====
PUERTO_POSTGRES = 55455
SERVICIO = "postgres_ext"
PASSWORD = "mipassword"
USER = "postgres"
DBNAME = "basededatos"
# Lista de extensiones que quieres habilitar
EXTENSIONES = [
# builtin
"hstore", "citext", "uuid-ossp", "pg_trgm",
# requieren paquetes
"postgis", "pgvector",
# "timescaledb" # <- si quieres usar base de timescaledb, activa la bandera abajo
]
# Usa imagen base de timescaledb cuando la extensión 'timescaledb' esté en la lista
timescaledb_base_image = False # pon True si quieres usar la imagen base de TimescaleDB
RUTA_PROYECTO = Path(".").resolve()
return (
DBNAME,
EXTENSIONES,
PASSWORD,
PUERTO_POSTGRES,
Path,
RUTA_PROYECTO,
SERVICIO,
USER,
subprocess,
textwrap,
timescaledb_base_image,
yaml,
)
@app.function
def pkgs_para_extensiones(exts, pg_major=15, use_timescale_base=False):
"""
Devuelve (pkgs_apt, builtins) para las extensiones solicitadas.
builtins = extensiones que no requieren apt
pkgs_apt = paquetes apt necesarios para otras extensiones
"""
builtins = []
pkgs_apt = []
for e in exts:
e_low = e.lower()
if e_low in {"hstore", "citext", "uuid-ossp", "pg_trgm"}:
builtins.append(e_low)
elif e_low == "postgis":
pkgs_apt += [
f"postgresql-{pg_major}-postgis-3",
f"postgresql-{pg_major}-postgis-3-scripts",
"gdal-bin",
"proj-bin",
]
elif e_low == "pgvector":
pkgs_apt += [f"postgresql-{pg_major}-pgvector"]
elif e_low == "timescaledb":
if not use_timescale_base:
# Para instalar timescaledb necesitarías repos adicionales
pass
else:
raise ValueError(f"Extensión no soportada en este helper: {e}")
pkgs_apt = sorted(set(pkgs_apt))
return pkgs_apt, builtins
@app.cell
def _(DBNAME, Path, USER, textwrap):
def generar_dockerfile(ruta: Path, exts, use_timescale_base=False, pg_major=15):
ruta.mkdir(parents=True, exist_ok=True)
pkgs_apt, builtins = pkgs_para_extensiones(exts, pg_major, use_timescale_base)
base_image = (
f"timescale/timescaledb:2.16-pg{pg_major}" if use_timescale_base else f"postgres:{pg_major}"
)
apt_block = ""
if pkgs_apt:
apt_lines = [
"RUN apt-get update && \\",
" DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \\",
]
# 👇 Backslash en **todas** las líneas de paquetes
for pkg in pkgs_apt:
apt_lines.append(f" {pkg} \\")
# y ya colgamos el rm del último paquete
apt_lines.append(" && rm -rf /var/lib/apt/lists/*")
apt_block = "\n".join(apt_lines)
dockerfile = f"""
FROM {base_image}
# Variables de entorno útiles
ENV POSTGRES_USER={USER} \\
POSTGRES_DB={DBNAME}
{apt_block}
# Copiamos scripts de inicialización
COPY docker-entrypoint-initdb.d/ /docker-entrypoint-initdb.d/
"""
(ruta / "Dockerfile").write_text(textwrap.dedent(dockerfile).strip() + "\n", encoding="utf-8")
return (generar_dockerfile,)
@app.cell
def _(Path):
def generar_init_sql(ruta: Path, exts):
init_dir = ruta / "docker-entrypoint-initdb.d"
init_dir.mkdir(parents=True, exist_ok=True)
lines = ["-- Habilitar extensiones solicitadas"]
for e in exts:
e_low = e.lower()
ext_name = {
"pgvector": "vector",
"postgis": "postgis",
"hstore": "hstore",
"citext": "citext",
"uuid-ossp": "\"uuid-ossp\"",
"pg_trgm": "pg_trgm",
"timescaledb": "timescaledb",
}.get(e_low, e_low)
lines.append(f"CREATE EXTENSION IF NOT EXISTS {ext_name};")
sql = "\n".join(lines) + "\n"
(init_dir / "10-extensions.sql").write_text(sql, encoding="utf-8")
return (generar_init_sql,)
@app.cell
def _(DBNAME, Path, USER, yaml):
def crear_docker_compose(ruta: Path, servicio: str, puerto_host: int, password: str):
compose = {
"version": "3.8",
"services": {
servicio: {
"build": {"context": "."},
"restart": "always",
"ports": [f"{puerto_host}:5432"],
"environment": {
"POSTGRES_PASSWORD": password,
"POSTGRES_USER": USER,
"POSTGRES_DB": DBNAME,
},
"healthcheck": {
"test": ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"],
"interval": "10s",
"timeout": "5s",
"retries": 5,
},
"volumes": [
"postgres_data:/var/lib/postgresql/data"
],
}
},
"volumes": {"postgres_data": {}}
}
(ruta / "docker-compose.yml").write_text(yaml.dump(compose, sort_keys=False), encoding="utf-8")
return (crear_docker_compose,)
@app.cell
def _(Path, subprocess):
def construir_y_levantar(ruta: Path):
def _run(cmd):
subprocess.run(cmd, cwd=ruta, check=True)
try:
_run(["docker", "compose", "build"])
_run(["docker", "compose", "up", "-d"])
except Exception:
_run(["docker-compose", "build"])
_run(["docker-compose", "up", "-d"])
return (construir_y_levantar,)
@app.cell
def _(
EXTENSIONES,
PASSWORD,
PUERTO_POSTGRES,
RUTA_PROYECTO,
SERVICIO,
construir_y_levantar,
crear_docker_compose,
generar_dockerfile,
generar_init_sql,
timescaledb_base_image,
):
if __name__ == "__main__":
RUTA_PROYECTO.mkdir(parents=True, exist_ok=True)
generar_dockerfile(RUTA_PROYECTO, EXTENSIONES, timescaledb_base_image)
generar_init_sql(RUTA_PROYECTO, EXTENSIONES)
crear_docker_compose(RUTA_PROYECTO, SERVICIO, PUERTO_POSTGRES, PASSWORD)
construir_y_levantar(RUTA_PROYECTO)
return
@app.cell(column=1)
def _():
from huggingface_hub import snapshot_download
@@ -243,6 +32,11 @@ def _():
tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True)
model = AutoModel.from_pretrained(model_path, trust_remote_code=True)
return model, tokenizer, torch
@app.cell
def _(model, tokenizer, torch):
texts = [
"La inteligencia artificial está transformando el mundo.",
"Los embeddings convierten texto en vectores numéricos."
@@ -256,7 +50,34 @@ def _():
for text, _vector in zip(texts, embeddings):
print(f"Texto: {text}\nDimensión: {_vector.shape[0]}\nPrimeros valores: {_vector[:5].tolist()}\n")
return model, tokenizer, torch
return
@app.cell
def _():
import os
import textwrap
import yaml
import subprocess
from pathlib import Path
# ==== Parámetros ====
PUERTO_POSTGRES = 55455
SERVICIO = "postgres_ext"
PASSWORD = "mipassword"
USER = "postgres"
DBNAME = "basededatos"
return (
DBNAME,
PASSWORD,
PUERTO_POSTGRES,
Path,
SERVICIO,
USER,
subprocess,
textwrap,
yaml,
)
@app.cell
@@ -350,7 +171,7 @@ def _(Engine, PUERTO_POSTGRES, create_engine, quote_plus):
future=True
)
return engine
return (conectar_postgres,)
return
@app.cell
@@ -363,7 +184,22 @@ def _(Engine, pd):
- params: dict opcional con parámetros de la consulta
"""
return pd.read_sql(query, con=conn, params=params)
return (consultar_df,)
return
@app.cell
def _(DATABASE_URL, create_engine, sessionmaker):
# Create engine
engine = create_engine(DATABASE_URL, echo=True)
# Create session
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
session = SessionLocal()
print("✅ Conexión establecida con la BBDD")
return
@app.cell
@@ -377,24 +213,6 @@ def _(conn2, mo):
return
@app.cell
def _(conectar_postgres, consultar_df):
conn2 = conectar_postgres()
query = """
SELECT *
FROM information_schema.tables
-- WHERE table_type = 'BASE TABLE'
-- AND table_schema NOT IN ('pg_catalog', 'information_schema')
ORDER BY table_schema, table_name;
"""
consultar_df(conn2, query)
return (conn2,)
@app.cell
def _():
from sqlalchemy import create_engine
@@ -409,17 +227,205 @@ def _():
return Engine, create_engine, pd, quote_plus
@app.cell
def _(mo):
_df = mo.sql(
f"""
SELECT * FROM
@app.cell(disabled=True)
def _(Path):
# import os
# import textwrap
# import yaml
# import subprocess
# from pathlib import Path
# # ==== Parámetros ====
# PUERTO_POSTGRES = 55455
# SERVICIO = "postgres_ext"
# PASSWORD = "mipassword"
# USER = "postgres"
# DBNAME = "basededatos"
# Lista de extensiones que quieres habilitar
EXTENSIONES = [
# builtin
"hstore", "citext", "uuid-ossp", "pg_trgm",
# requieren paquetes
"postgis", "pgvector",
# "timescaledb" # <- si quieres usar base de timescaledb, activa la bandera abajo
]
# Usa imagen base de timescaledb cuando la extensión 'timescaledb' esté en la lista
timescaledb_base_image = False # pon True si quieres usar la imagen base de TimescaleDB
RUTA_PROYECTO = Path(".").resolve()
return EXTENSIONES, RUTA_PROYECTO, timescaledb_base_image
@app.function(disabled=True)
def pkgs_para_extensiones(exts, pg_major=15, use_timescale_base=False):
"""
Devuelve (pkgs_apt, builtins) para las extensiones solicitadas.
builtins = extensiones que no requieren apt
pkgs_apt = paquetes apt necesarios para otras extensiones
"""
builtins = []
pkgs_apt = []
for e in exts:
e_low = e.lower()
if e_low in {"hstore", "citext", "uuid-ossp", "pg_trgm"}:
builtins.append(e_low)
elif e_low == "postgis":
pkgs_apt += [
f"postgresql-{pg_major}-postgis-3",
f"postgresql-{pg_major}-postgis-3-scripts",
"gdal-bin",
"proj-bin",
]
elif e_low == "pgvector":
pkgs_apt += [f"postgresql-{pg_major}-pgvector"]
elif e_low == "timescaledb":
if not use_timescale_base:
# Para instalar timescaledb necesitarías repos adicionales
pass
else:
raise ValueError(f"Extensión no soportada en este helper: {e}")
pkgs_apt = sorted(set(pkgs_apt))
return pkgs_apt, builtins
@app.cell(disabled=True)
def _(DBNAME, Path, USER, textwrap):
def generar_dockerfile(ruta: Path, exts, use_timescale_base=False, pg_major=15):
ruta.mkdir(parents=True, exist_ok=True)
pkgs_apt, builtins = pkgs_para_extensiones(exts, pg_major, use_timescale_base)
base_image = (
f"timescale/timescaledb:2.16-pg{pg_major}" if use_timescale_base else f"postgres:{pg_major}"
)
apt_block = ""
if pkgs_apt:
apt_lines = [
"RUN apt-get update && \\",
" DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \\",
]
# 👇 Backslash en **todas** las líneas de paquetes
for pkg in pkgs_apt:
apt_lines.append(f" {pkg} \\")
# y ya colgamos el rm del último paquete
apt_lines.append(" && rm -rf /var/lib/apt/lists/*")
apt_block = "\n".join(apt_lines)
dockerfile = f"""
FROM {base_image}
# Variables de entorno útiles
ENV POSTGRES_USER={USER} \\
POSTGRES_DB={DBNAME}
{apt_block}
# Copiamos scripts de inicialización
COPY docker-entrypoint-initdb.d/ /docker-entrypoint-initdb.d/
"""
)
(ruta / "Dockerfile").write_text(textwrap.dedent(dockerfile).strip() + "\n", encoding="utf-8")
return (generar_dockerfile,)
@app.cell(disabled=True)
def _(Path):
def generar_init_sql(ruta: Path, exts):
init_dir = ruta / "docker-entrypoint-initdb.d"
init_dir.mkdir(parents=True, exist_ok=True)
lines = ["-- Habilitar extensiones solicitadas"]
for e in exts:
e_low = e.lower()
ext_name = {
"pgvector": "vector",
"postgis": "postgis",
"hstore": "hstore",
"citext": "citext",
"uuid-ossp": "\"uuid-ossp\"",
"pg_trgm": "pg_trgm",
"timescaledb": "timescaledb",
}.get(e_low, e_low)
lines.append(f"CREATE EXTENSION IF NOT EXISTS {ext_name};")
sql = "\n".join(lines) + "\n"
(init_dir / "10-extensions.sql").write_text(sql, encoding="utf-8")
return (generar_init_sql,)
@app.cell(disabled=True)
def _(DBNAME, Path, USER, yaml):
def crear_docker_compose(ruta: Path, servicio: str, puerto_host: int, password: str):
compose = {
"version": "3.8",
"services": {
servicio: {
"build": {"context": "."},
"restart": "always",
"ports": [f"{puerto_host}:5432"],
"environment": {
"POSTGRES_PASSWORD": password,
"POSTGRES_USER": USER,
"POSTGRES_DB": DBNAME,
},
"healthcheck": {
"test": ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"],
"interval": "10s",
"timeout": "5s",
"retries": 5,
},
"volumes": [
"postgres_data:/var/lib/postgresql/data"
],
}
},
"volumes": {"postgres_data": {}}
}
(ruta / "docker-compose.yml").write_text(yaml.dump(compose, sort_keys=False), encoding="utf-8")
return (crear_docker_compose,)
@app.cell(disabled=True)
def _(Path, subprocess):
def construir_y_levantar(ruta: Path):
def _run(cmd):
subprocess.run(cmd, cwd=ruta, check=True)
try:
_run(["docker", "compose", "build"])
_run(["docker", "compose", "up", "-d"])
except Exception:
_run(["docker-compose", "build"])
_run(["docker-compose", "up", "-d"])
return (construir_y_levantar,)
@app.cell
def _(
EXTENSIONES,
PASSWORD,
PUERTO_POSTGRES,
RUTA_PROYECTO,
SERVICIO,
construir_y_levantar,
crear_docker_compose,
generar_dockerfile,
generar_init_sql,
timescaledb_base_image,
):
if __name__ == "__main__":
RUTA_PROYECTO.mkdir(parents=True, exist_ok=True)
generar_dockerfile(RUTA_PROYECTO, EXTENSIONES, timescaledb_base_image)
generar_init_sql(RUTA_PROYECTO, EXTENSIONES)
crear_docker_compose(RUTA_PROYECTO, SERVICIO, PUERTO_POSTGRES, PASSWORD)
construir_y_levantar(RUTA_PROYECTO)
return
@app.cell(column=2)
@app.cell(column=1)
def _(DBNAME, PASSWORD, PUERTO_POSTGRES, USER):
import psycopg2
@@ -461,7 +467,7 @@ def _():
return
@app.cell(column=3)
@app.cell(column=2)
def _(mo):
# Cell 2
# Caja de texto para la consulta
Generated
+309 -4
View File
@@ -6,6 +6,15 @@ resolution-markers = [
"python_full_version < '3.11'",
]
[[package]]
name = "annotated-types"
version = "0.7.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" },
]
[[package]]
name = "anyio"
version = "4.10.0"
@@ -44,11 +53,11 @@ wheels = [
[[package]]
name = "docutils"
version = "0.22"
version = "0.22.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/e9/86/5b41c32ecedcfdb4c77b28b6cb14234f252075f8cdb254531727a35547dd/docutils-0.22.tar.gz", hash = "sha256:ba9d57750e92331ebe7c08a1bbf7a7f8143b86c476acd51528b042216a6aad0f", size = 2277984, upload-time = "2025-07-29T15:20:31.06Z" }
sdist = { url = "https://files.pythonhosted.org/packages/e4/47/d869000fb74438584858acc628a364b277fc012695f0dfd513cb10f99768/docutils-0.22.1.tar.gz", hash = "sha256:d2fb50923a313532b6d41a77776d24cb459a594be9b7e4afa1fbcb5bda1893e6", size = 2291655, upload-time = "2025-09-17T17:58:45.409Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/44/57/8db39bc5f98f042e0153b1de9fb88e1a409a33cda4dd7f723c2ed71e01f6/docutils-0.22-py3-none-any.whl", hash = "sha256:4ed966a0e96a0477d852f7af31bdcb3adc049fbb35ccba358c2ea8a03287615e", size = 630709, upload-time = "2025-07-29T15:20:28.335Z" },
{ url = "https://files.pythonhosted.org/packages/8c/dc/1948b90c5d9dbfa4d1fd3991013a042ba3ac62ebd3afdcb3fac08366e755/docutils-0.22.1-py3-none-any.whl", hash = "sha256:806e896f256a17466426544038f30cb860a99f5d4af640e36c284bfcb1824512", size = 638455, upload-time = "2025-09-17T17:58:42.498Z" },
]
[[package]]
@@ -63,6 +72,20 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674, upload-time = "2025-05-10T17:42:49.33Z" },
]
[[package]]
name = "fastapi"
version = "0.116.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pydantic" },
{ name = "starlette" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/01/64/1296f46d6b9e3b23fb22e5d01af3f104ef411425531376212f1eefa2794d/fastapi-0.116.2.tar.gz", hash = "sha256:231a6af2fe21cfa2c32730170ad8514985fc250bec16c9b242d3b94c835ef529", size = 298595, upload-time = "2025-09-16T18:29:23.058Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/32/e4/c543271a8018874b7f682bf6156863c416e1334b8ed3e51a69495c5d4360/fastapi-0.116.2-py3-none-any.whl", hash = "sha256:c3a7a8fb830b05f7e087d920e0d786ca1fc9892eb4e9a84b227be4c1bc7569db", size = 95670, upload-time = "2025-09-16T18:29:21.329Z" },
]
[[package]]
name = "h11"
version = "0.16.0"
@@ -279,11 +302,17 @@ name = "myrag"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "fastapi" },
{ name = "marimo" },
{ name = "pgvector" },
]
[package.metadata]
requires-dist = [{ name = "marimo", specifier = ">=0.15.3" }]
requires-dist = [
{ name = "fastapi", specifier = ">=0.116.2" },
{ name = "marimo", specifier = ">=0.15.3" },
{ name = "pgvector", specifier = ">=0.4.1" },
]
[[package]]
name = "narwhals"
@@ -294,6 +323,155 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/f8/5a/22741c5c0e5f6e8050242bfc2052ba68bc94b1735ed5bca35404d136d6ec/narwhals-2.5.0-py3-none-any.whl", hash = "sha256:7e213f9ca7db3f8bf6f7eff35eaee6a1cf80902997e1b78d49b7755775d8f423", size = 407296, upload-time = "2025-09-12T10:04:22.524Z" },
]
[[package]]
name = "numpy"
version = "2.2.6"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version < '3.11'",
]
sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440, upload-time = "2025-05-17T22:38:04.611Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/9a/3e/ed6db5be21ce87955c0cbd3009f2803f59fa08df21b5df06862e2d8e2bdd/numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb", size = 21165245, upload-time = "2025-05-17T21:27:58.555Z" },
{ url = "https://files.pythonhosted.org/packages/22/c2/4b9221495b2a132cc9d2eb862e21d42a009f5a60e45fc44b00118c174bff/numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90", size = 14360048, upload-time = "2025-05-17T21:28:21.406Z" },
{ url = "https://files.pythonhosted.org/packages/fd/77/dc2fcfc66943c6410e2bf598062f5959372735ffda175b39906d54f02349/numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163", size = 5340542, upload-time = "2025-05-17T21:28:30.931Z" },
{ url = "https://files.pythonhosted.org/packages/7a/4f/1cb5fdc353a5f5cc7feb692db9b8ec2c3d6405453f982435efc52561df58/numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf", size = 6878301, upload-time = "2025-05-17T21:28:41.613Z" },
{ url = "https://files.pythonhosted.org/packages/eb/17/96a3acd228cec142fcb8723bd3cc39c2a474f7dcf0a5d16731980bcafa95/numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83", size = 14297320, upload-time = "2025-05-17T21:29:02.78Z" },
{ url = "https://files.pythonhosted.org/packages/b4/63/3de6a34ad7ad6646ac7d2f55ebc6ad439dbbf9c4370017c50cf403fb19b5/numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915", size = 16801050, upload-time = "2025-05-17T21:29:27.675Z" },
{ url = "https://files.pythonhosted.org/packages/07/b6/89d837eddef52b3d0cec5c6ba0456c1bf1b9ef6a6672fc2b7873c3ec4e2e/numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680", size = 15807034, upload-time = "2025-05-17T21:29:51.102Z" },
{ url = "https://files.pythonhosted.org/packages/01/c8/dc6ae86e3c61cfec1f178e5c9f7858584049b6093f843bca541f94120920/numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289", size = 18614185, upload-time = "2025-05-17T21:30:18.703Z" },
{ url = "https://files.pythonhosted.org/packages/5b/c5/0064b1b7e7c89137b471ccec1fd2282fceaae0ab3a9550f2568782d80357/numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d", size = 6527149, upload-time = "2025-05-17T21:30:29.788Z" },
{ url = "https://files.pythonhosted.org/packages/a3/dd/4b822569d6b96c39d1215dbae0582fd99954dcbcf0c1a13c61783feaca3f/numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3", size = 12904620, upload-time = "2025-05-17T21:30:48.994Z" },
{ url = "https://files.pythonhosted.org/packages/da/a8/4f83e2aa666a9fbf56d6118faaaf5f1974d456b1823fda0a176eff722839/numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", size = 21176963, upload-time = "2025-05-17T21:31:19.36Z" },
{ url = "https://files.pythonhosted.org/packages/b3/2b/64e1affc7972decb74c9e29e5649fac940514910960ba25cd9af4488b66c/numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", size = 14406743, upload-time = "2025-05-17T21:31:41.087Z" },
{ url = "https://files.pythonhosted.org/packages/4a/9f/0121e375000b5e50ffdd8b25bf78d8e1a5aa4cca3f185d41265198c7b834/numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", size = 5352616, upload-time = "2025-05-17T21:31:50.072Z" },
{ url = "https://files.pythonhosted.org/packages/31/0d/b48c405c91693635fbe2dcd7bc84a33a602add5f63286e024d3b6741411c/numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", size = 6889579, upload-time = "2025-05-17T21:32:01.712Z" },
{ url = "https://files.pythonhosted.org/packages/52/b8/7f0554d49b565d0171eab6e99001846882000883998e7b7d9f0d98b1f934/numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", size = 14312005, upload-time = "2025-05-17T21:32:23.332Z" },
{ url = "https://files.pythonhosted.org/packages/b3/dd/2238b898e51bd6d389b7389ffb20d7f4c10066d80351187ec8e303a5a475/numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", size = 16821570, upload-time = "2025-05-17T21:32:47.991Z" },
{ url = "https://files.pythonhosted.org/packages/83/6c/44d0325722cf644f191042bf47eedad61c1e6df2432ed65cbe28509d404e/numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", size = 15818548, upload-time = "2025-05-17T21:33:11.728Z" },
{ url = "https://files.pythonhosted.org/packages/ae/9d/81e8216030ce66be25279098789b665d49ff19eef08bfa8cb96d4957f422/numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", size = 18620521, upload-time = "2025-05-17T21:33:39.139Z" },
{ url = "https://files.pythonhosted.org/packages/6a/fd/e19617b9530b031db51b0926eed5345ce8ddc669bb3bc0044b23e275ebe8/numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", size = 6525866, upload-time = "2025-05-17T21:33:50.273Z" },
{ url = "https://files.pythonhosted.org/packages/31/0a/f354fb7176b81747d870f7991dc763e157a934c717b67b58456bc63da3df/numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", size = 12907455, upload-time = "2025-05-17T21:34:09.135Z" },
{ url = "https://files.pythonhosted.org/packages/82/5d/c00588b6cf18e1da539b45d3598d3557084990dcc4331960c15ee776ee41/numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", size = 20875348, upload-time = "2025-05-17T21:34:39.648Z" },
{ url = "https://files.pythonhosted.org/packages/66/ee/560deadcdde6c2f90200450d5938f63a34b37e27ebff162810f716f6a230/numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", size = 14119362, upload-time = "2025-05-17T21:35:01.241Z" },
{ url = "https://files.pythonhosted.org/packages/3c/65/4baa99f1c53b30adf0acd9a5519078871ddde8d2339dc5a7fde80d9d87da/numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", size = 5084103, upload-time = "2025-05-17T21:35:10.622Z" },
{ url = "https://files.pythonhosted.org/packages/cc/89/e5a34c071a0570cc40c9a54eb472d113eea6d002e9ae12bb3a8407fb912e/numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", size = 6625382, upload-time = "2025-05-17T21:35:21.414Z" },
{ url = "https://files.pythonhosted.org/packages/f8/35/8c80729f1ff76b3921d5c9487c7ac3de9b2a103b1cd05e905b3090513510/numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", size = 14018462, upload-time = "2025-05-17T21:35:42.174Z" },
{ url = "https://files.pythonhosted.org/packages/8c/3d/1e1db36cfd41f895d266b103df00ca5b3cbe965184df824dec5c08c6b803/numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", size = 16527618, upload-time = "2025-05-17T21:36:06.711Z" },
{ url = "https://files.pythonhosted.org/packages/61/c6/03ed30992602c85aa3cd95b9070a514f8b3c33e31124694438d88809ae36/numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", size = 15505511, upload-time = "2025-05-17T21:36:29.965Z" },
{ url = "https://files.pythonhosted.org/packages/b7/25/5761d832a81df431e260719ec45de696414266613c9ee268394dd5ad8236/numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", size = 18313783, upload-time = "2025-05-17T21:36:56.883Z" },
{ url = "https://files.pythonhosted.org/packages/57/0a/72d5a3527c5ebffcd47bde9162c39fae1f90138c961e5296491ce778e682/numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", size = 6246506, upload-time = "2025-05-17T21:37:07.368Z" },
{ url = "https://files.pythonhosted.org/packages/36/fa/8c9210162ca1b88529ab76b41ba02d433fd54fecaf6feb70ef9f124683f1/numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", size = 12614190, upload-time = "2025-05-17T21:37:26.213Z" },
{ url = "https://files.pythonhosted.org/packages/f9/5c/6657823f4f594f72b5471f1db1ab12e26e890bb2e41897522d134d2a3e81/numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84", size = 20867828, upload-time = "2025-05-17T21:37:56.699Z" },
{ url = "https://files.pythonhosted.org/packages/dc/9e/14520dc3dadf3c803473bd07e9b2bd1b69bc583cb2497b47000fed2fa92f/numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b", size = 14143006, upload-time = "2025-05-17T21:38:18.291Z" },
{ url = "https://files.pythonhosted.org/packages/4f/06/7e96c57d90bebdce9918412087fc22ca9851cceaf5567a45c1f404480e9e/numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d", size = 5076765, upload-time = "2025-05-17T21:38:27.319Z" },
{ url = "https://files.pythonhosted.org/packages/73/ed/63d920c23b4289fdac96ddbdd6132e9427790977d5457cd132f18e76eae0/numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566", size = 6617736, upload-time = "2025-05-17T21:38:38.141Z" },
{ url = "https://files.pythonhosted.org/packages/85/c5/e19c8f99d83fd377ec8c7e0cf627a8049746da54afc24ef0a0cb73d5dfb5/numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f", size = 14010719, upload-time = "2025-05-17T21:38:58.433Z" },
{ url = "https://files.pythonhosted.org/packages/19/49/4df9123aafa7b539317bf6d342cb6d227e49f7a35b99c287a6109b13dd93/numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f", size = 16526072, upload-time = "2025-05-17T21:39:22.638Z" },
{ url = "https://files.pythonhosted.org/packages/b2/6c/04b5f47f4f32f7c2b0e7260442a8cbcf8168b0e1a41ff1495da42f42a14f/numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868", size = 15503213, upload-time = "2025-05-17T21:39:45.865Z" },
{ url = "https://files.pythonhosted.org/packages/17/0a/5cd92e352c1307640d5b6fec1b2ffb06cd0dabe7d7b8227f97933d378422/numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d", size = 18316632, upload-time = "2025-05-17T21:40:13.331Z" },
{ url = "https://files.pythonhosted.org/packages/f0/3b/5cba2b1d88760ef86596ad0f3d484b1cbff7c115ae2429678465057c5155/numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd", size = 6244532, upload-time = "2025-05-17T21:43:46.099Z" },
{ url = "https://files.pythonhosted.org/packages/cb/3b/d58c12eafcb298d4e6d0d40216866ab15f59e55d148a5658bb3132311fcf/numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c", size = 12610885, upload-time = "2025-05-17T21:44:05.145Z" },
{ url = "https://files.pythonhosted.org/packages/6b/9e/4bf918b818e516322db999ac25d00c75788ddfd2d2ade4fa66f1f38097e1/numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6", size = 20963467, upload-time = "2025-05-17T21:40:44Z" },
{ url = "https://files.pythonhosted.org/packages/61/66/d2de6b291507517ff2e438e13ff7b1e2cdbdb7cb40b3ed475377aece69f9/numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda", size = 14225144, upload-time = "2025-05-17T21:41:05.695Z" },
{ url = "https://files.pythonhosted.org/packages/e4/25/480387655407ead912e28ba3a820bc69af9adf13bcbe40b299d454ec011f/numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40", size = 5200217, upload-time = "2025-05-17T21:41:15.903Z" },
{ url = "https://files.pythonhosted.org/packages/aa/4a/6e313b5108f53dcbf3aca0c0f3e9c92f4c10ce57a0a721851f9785872895/numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8", size = 6712014, upload-time = "2025-05-17T21:41:27.321Z" },
{ url = "https://files.pythonhosted.org/packages/b7/30/172c2d5c4be71fdf476e9de553443cf8e25feddbe185e0bd88b096915bcc/numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f", size = 14077935, upload-time = "2025-05-17T21:41:49.738Z" },
{ url = "https://files.pythonhosted.org/packages/12/fb/9e743f8d4e4d3c710902cf87af3512082ae3d43b945d5d16563f26ec251d/numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa", size = 16600122, upload-time = "2025-05-17T21:42:14.046Z" },
{ url = "https://files.pythonhosted.org/packages/12/75/ee20da0e58d3a66f204f38916757e01e33a9737d0b22373b3eb5a27358f9/numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571", size = 15586143, upload-time = "2025-05-17T21:42:37.464Z" },
{ url = "https://files.pythonhosted.org/packages/76/95/bef5b37f29fc5e739947e9ce5179ad402875633308504a52d188302319c8/numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1", size = 18385260, upload-time = "2025-05-17T21:43:05.189Z" },
{ url = "https://files.pythonhosted.org/packages/09/04/f2f83279d287407cf36a7a8053a5abe7be3622a4363337338f2585e4afda/numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff", size = 6377225, upload-time = "2025-05-17T21:43:16.254Z" },
{ url = "https://files.pythonhosted.org/packages/67/0e/35082d13c09c02c011cf21570543d202ad929d961c02a147493cb0c2bdf5/numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06", size = 12771374, upload-time = "2025-05-17T21:43:35.479Z" },
{ url = "https://files.pythonhosted.org/packages/9e/3b/d94a75f4dbf1ef5d321523ecac21ef23a3cd2ac8b78ae2aac40873590229/numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d", size = 21040391, upload-time = "2025-05-17T21:44:35.948Z" },
{ url = "https://files.pythonhosted.org/packages/17/f4/09b2fa1b58f0fb4f7c7963a1649c64c4d315752240377ed74d9cd878f7b5/numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db", size = 6786754, upload-time = "2025-05-17T21:44:47.446Z" },
{ url = "https://files.pythonhosted.org/packages/af/30/feba75f143bdc868a1cc3f44ccfa6c4b9ec522b36458e738cd00f67b573f/numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543", size = 16643476, upload-time = "2025-05-17T21:45:11.871Z" },
{ url = "https://files.pythonhosted.org/packages/37/48/ac2a9584402fb6c0cd5b5d1a91dcf176b15760130dd386bbafdbfe3640bf/numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00", size = 12812666, upload-time = "2025-05-17T21:45:31.426Z" },
]
[[package]]
name = "numpy"
version = "2.3.3"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.11'",
]
sdist = { url = "https://files.pythonhosted.org/packages/d0/19/95b3d357407220ed24c139018d2518fab0a61a948e68286a25f1a4d049ff/numpy-2.3.3.tar.gz", hash = "sha256:ddc7c39727ba62b80dfdbedf400d1c10ddfa8eefbd7ec8dcb118be8b56d31029", size = 20576648, upload-time = "2025-09-09T16:54:12.543Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/7a/45/e80d203ef6b267aa29b22714fb558930b27960a0c5ce3c19c999232bb3eb/numpy-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0ffc4f5caba7dfcbe944ed674b7eef683c7e94874046454bb79ed7ee0236f59d", size = 21259253, upload-time = "2025-09-09T15:56:02.094Z" },
{ url = "https://files.pythonhosted.org/packages/52/18/cf2c648fccf339e59302e00e5f2bc87725a3ce1992f30f3f78c9044d7c43/numpy-2.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e7e946c7170858a0295f79a60214424caac2ffdb0063d4d79cb681f9aa0aa569", size = 14450980, upload-time = "2025-09-09T15:56:05.926Z" },
{ url = "https://files.pythonhosted.org/packages/93/fb/9af1082bec870188c42a1c239839915b74a5099c392389ff04215dcee812/numpy-2.3.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:cd4260f64bc794c3390a63bf0728220dd1a68170c169088a1e0dfa2fde1be12f", size = 5379709, upload-time = "2025-09-09T15:56:07.95Z" },
{ url = "https://files.pythonhosted.org/packages/75/0f/bfd7abca52bcbf9a4a65abc83fe18ef01ccdeb37bfb28bbd6ad613447c79/numpy-2.3.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:f0ddb4b96a87b6728df9362135e764eac3cfa674499943ebc44ce96c478ab125", size = 6913923, upload-time = "2025-09-09T15:56:09.443Z" },
{ url = "https://files.pythonhosted.org/packages/79/55/d69adad255e87ab7afda1caf93ca997859092afeb697703e2f010f7c2e55/numpy-2.3.3-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:afd07d377f478344ec6ca2b8d4ca08ae8bd44706763d1efb56397de606393f48", size = 14589591, upload-time = "2025-09-09T15:56:11.234Z" },
{ url = "https://files.pythonhosted.org/packages/10/a2/010b0e27ddeacab7839957d7a8f00e91206e0c2c47abbb5f35a2630e5387/numpy-2.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bc92a5dedcc53857249ca51ef29f5e5f2f8c513e22cfb90faeb20343b8c6f7a6", size = 16938714, upload-time = "2025-09-09T15:56:14.637Z" },
{ url = "https://files.pythonhosted.org/packages/1c/6b/12ce8ede632c7126eb2762b9e15e18e204b81725b81f35176eac14dc5b82/numpy-2.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7af05ed4dc19f308e1d9fc759f36f21921eb7bbfc82843eeec6b2a2863a0aefa", size = 16370592, upload-time = "2025-09-09T15:56:17.285Z" },
{ url = "https://files.pythonhosted.org/packages/b4/35/aba8568b2593067bb6a8fe4c52babb23b4c3b9c80e1b49dff03a09925e4a/numpy-2.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:433bf137e338677cebdd5beac0199ac84712ad9d630b74eceeb759eaa45ddf30", size = 18884474, upload-time = "2025-09-09T15:56:20.943Z" },
{ url = "https://files.pythonhosted.org/packages/45/fa/7f43ba10c77575e8be7b0138d107e4f44ca4a1ef322cd16980ea3e8b8222/numpy-2.3.3-cp311-cp311-win32.whl", hash = "sha256:eb63d443d7b4ffd1e873f8155260d7f58e7e4b095961b01c91062935c2491e57", size = 6599794, upload-time = "2025-09-09T15:56:23.258Z" },
{ url = "https://files.pythonhosted.org/packages/0a/a2/a4f78cb2241fe5664a22a10332f2be886dcdea8784c9f6a01c272da9b426/numpy-2.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:ec9d249840f6a565f58d8f913bccac2444235025bbb13e9a4681783572ee3caa", size = 13088104, upload-time = "2025-09-09T15:56:25.476Z" },
{ url = "https://files.pythonhosted.org/packages/79/64/e424e975adbd38282ebcd4891661965b78783de893b381cbc4832fb9beb2/numpy-2.3.3-cp311-cp311-win_arm64.whl", hash = "sha256:74c2a948d02f88c11a3c075d9733f1ae67d97c6bdb97f2bb542f980458b257e7", size = 10460772, upload-time = "2025-09-09T15:56:27.679Z" },
{ url = "https://files.pythonhosted.org/packages/51/5d/bb7fc075b762c96329147799e1bcc9176ab07ca6375ea976c475482ad5b3/numpy-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cfdd09f9c84a1a934cde1eec2267f0a43a7cd44b2cca4ff95b7c0d14d144b0bf", size = 20957014, upload-time = "2025-09-09T15:56:29.966Z" },
{ url = "https://files.pythonhosted.org/packages/6b/0e/c6211bb92af26517acd52125a237a92afe9c3124c6a68d3b9f81b62a0568/numpy-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cb32e3cf0f762aee47ad1ddc6672988f7f27045b0783c887190545baba73aa25", size = 14185220, upload-time = "2025-09-09T15:56:32.175Z" },
{ url = "https://files.pythonhosted.org/packages/22/f2/07bb754eb2ede9073f4054f7c0286b0d9d2e23982e090a80d478b26d35ca/numpy-2.3.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:396b254daeb0a57b1fe0ecb5e3cff6fa79a380fa97c8f7781a6d08cd429418fe", size = 5113918, upload-time = "2025-09-09T15:56:34.175Z" },
{ url = "https://files.pythonhosted.org/packages/81/0a/afa51697e9fb74642f231ea36aca80fa17c8fb89f7a82abd5174023c3960/numpy-2.3.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:067e3d7159a5d8f8a0b46ee11148fc35ca9b21f61e3c49fbd0a027450e65a33b", size = 6647922, upload-time = "2025-09-09T15:56:36.149Z" },
{ url = "https://files.pythonhosted.org/packages/5d/f5/122d9cdb3f51c520d150fef6e87df9279e33d19a9611a87c0d2cf78a89f4/numpy-2.3.3-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c02d0629d25d426585fb2e45a66154081b9fa677bc92a881ff1d216bc9919a8", size = 14281991, upload-time = "2025-09-09T15:56:40.548Z" },
{ url = "https://files.pythonhosted.org/packages/51/64/7de3c91e821a2debf77c92962ea3fe6ac2bc45d0778c1cbe15d4fce2fd94/numpy-2.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9192da52b9745f7f0766531dcfa978b7763916f158bb63bdb8a1eca0068ab20", size = 16641643, upload-time = "2025-09-09T15:56:43.343Z" },
{ url = "https://files.pythonhosted.org/packages/30/e4/961a5fa681502cd0d68907818b69f67542695b74e3ceaa513918103b7e80/numpy-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cd7de500a5b66319db419dc3c345244404a164beae0d0937283b907d8152e6ea", size = 16056787, upload-time = "2025-09-09T15:56:46.141Z" },
{ url = "https://files.pythonhosted.org/packages/99/26/92c912b966e47fbbdf2ad556cb17e3a3088e2e1292b9833be1dfa5361a1a/numpy-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:93d4962d8f82af58f0b2eb85daaf1b3ca23fe0a85d0be8f1f2b7bb46034e56d7", size = 18579598, upload-time = "2025-09-09T15:56:49.844Z" },
{ url = "https://files.pythonhosted.org/packages/17/b6/fc8f82cb3520768718834f310c37d96380d9dc61bfdaf05fe5c0b7653e01/numpy-2.3.3-cp312-cp312-win32.whl", hash = "sha256:5534ed6b92f9b7dca6c0a19d6df12d41c68b991cef051d108f6dbff3babc4ebf", size = 6320800, upload-time = "2025-09-09T15:56:52.499Z" },
{ url = "https://files.pythonhosted.org/packages/32/ee/de999f2625b80d043d6d2d628c07d0d5555a677a3cf78fdf868d409b8766/numpy-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:497d7cad08e7092dba36e3d296fe4c97708c93daf26643a1ae4b03f6294d30eb", size = 12786615, upload-time = "2025-09-09T15:56:54.422Z" },
{ url = "https://files.pythonhosted.org/packages/49/6e/b479032f8a43559c383acb20816644f5f91c88f633d9271ee84f3b3a996c/numpy-2.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:ca0309a18d4dfea6fc6262a66d06c26cfe4640c3926ceec90e57791a82b6eee5", size = 10195936, upload-time = "2025-09-09T15:56:56.541Z" },
{ url = "https://files.pythonhosted.org/packages/7d/b9/984c2b1ee61a8b803bf63582b4ac4242cf76e2dbd663efeafcb620cc0ccb/numpy-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f5415fb78995644253370985342cd03572ef8620b934da27d77377a2285955bf", size = 20949588, upload-time = "2025-09-09T15:56:59.087Z" },
{ url = "https://files.pythonhosted.org/packages/a6/e4/07970e3bed0b1384d22af1e9912527ecbeb47d3b26e9b6a3bced068b3bea/numpy-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d00de139a3324e26ed5b95870ce63be7ec7352171bc69a4cf1f157a48e3eb6b7", size = 14177802, upload-time = "2025-09-09T15:57:01.73Z" },
{ url = "https://files.pythonhosted.org/packages/35/c7/477a83887f9de61f1203bad89cf208b7c19cc9fef0cebef65d5a1a0619f2/numpy-2.3.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:9dc13c6a5829610cc07422bc74d3ac083bd8323f14e2827d992f9e52e22cd6a6", size = 5106537, upload-time = "2025-09-09T15:57:03.765Z" },
{ url = "https://files.pythonhosted.org/packages/52/47/93b953bd5866a6f6986344d045a207d3f1cfbad99db29f534ea9cee5108c/numpy-2.3.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:d79715d95f1894771eb4e60fb23f065663b2298f7d22945d66877aadf33d00c7", size = 6640743, upload-time = "2025-09-09T15:57:07.921Z" },
{ url = "https://files.pythonhosted.org/packages/23/83/377f84aaeb800b64c0ef4de58b08769e782edcefa4fea712910b6f0afd3c/numpy-2.3.3-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:952cfd0748514ea7c3afc729a0fc639e61655ce4c55ab9acfab14bda4f402b4c", size = 14278881, upload-time = "2025-09-09T15:57:11.349Z" },
{ url = "https://files.pythonhosted.org/packages/9a/a5/bf3db6e66c4b160d6ea10b534c381a1955dfab34cb1017ea93aa33c70ed3/numpy-2.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5b83648633d46f77039c29078751f80da65aa64d5622a3cd62aaef9d835b6c93", size = 16636301, upload-time = "2025-09-09T15:57:14.245Z" },
{ url = "https://files.pythonhosted.org/packages/a2/59/1287924242eb4fa3f9b3a2c30400f2e17eb2707020d1c5e3086fe7330717/numpy-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b001bae8cea1c7dfdb2ae2b017ed0a6f2102d7a70059df1e338e307a4c78a8ae", size = 16053645, upload-time = "2025-09-09T15:57:16.534Z" },
{ url = "https://files.pythonhosted.org/packages/e6/93/b3d47ed882027c35e94ac2320c37e452a549f582a5e801f2d34b56973c97/numpy-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8e9aced64054739037d42fb84c54dd38b81ee238816c948c8f3ed134665dcd86", size = 18578179, upload-time = "2025-09-09T15:57:18.883Z" },
{ url = "https://files.pythonhosted.org/packages/20/d9/487a2bccbf7cc9d4bfc5f0f197761a5ef27ba870f1e3bbb9afc4bbe3fcc2/numpy-2.3.3-cp313-cp313-win32.whl", hash = "sha256:9591e1221db3f37751e6442850429b3aabf7026d3b05542d102944ca7f00c8a8", size = 6312250, upload-time = "2025-09-09T15:57:21.296Z" },
{ url = "https://files.pythonhosted.org/packages/1b/b5/263ebbbbcede85028f30047eab3d58028d7ebe389d6493fc95ae66c636ab/numpy-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f0dadeb302887f07431910f67a14d57209ed91130be0adea2f9793f1a4f817cf", size = 12783269, upload-time = "2025-09-09T15:57:23.034Z" },
{ url = "https://files.pythonhosted.org/packages/fa/75/67b8ca554bbeaaeb3fac2e8bce46967a5a06544c9108ec0cf5cece559b6c/numpy-2.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:3c7cf302ac6e0b76a64c4aecf1a09e51abd9b01fc7feee80f6c43e3ab1b1dbc5", size = 10195314, upload-time = "2025-09-09T15:57:25.045Z" },
{ url = "https://files.pythonhosted.org/packages/11/d0/0d1ddec56b162042ddfafeeb293bac672de9b0cfd688383590090963720a/numpy-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:eda59e44957d272846bb407aad19f89dc6f58fecf3504bd144f4c5cf81a7eacc", size = 21048025, upload-time = "2025-09-09T15:57:27.257Z" },
{ url = "https://files.pythonhosted.org/packages/36/9e/1996ca6b6d00415b6acbdd3c42f7f03ea256e2c3f158f80bd7436a8a19f3/numpy-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:823d04112bc85ef5c4fda73ba24e6096c8f869931405a80aa8b0e604510a26bc", size = 14301053, upload-time = "2025-09-09T15:57:30.077Z" },
{ url = "https://files.pythonhosted.org/packages/05/24/43da09aa764c68694b76e84b3d3f0c44cb7c18cdc1ba80e48b0ac1d2cd39/numpy-2.3.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:40051003e03db4041aa325da2a0971ba41cf65714e65d296397cc0e32de6018b", size = 5229444, upload-time = "2025-09-09T15:57:32.733Z" },
{ url = "https://files.pythonhosted.org/packages/bc/14/50ffb0f22f7218ef8af28dd089f79f68289a7a05a208db9a2c5dcbe123c1/numpy-2.3.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:6ee9086235dd6ab7ae75aba5662f582a81ced49f0f1c6de4260a78d8f2d91a19", size = 6738039, upload-time = "2025-09-09T15:57:34.328Z" },
{ url = "https://files.pythonhosted.org/packages/55/52/af46ac0795e09657d45a7f4db961917314377edecf66db0e39fa7ab5c3d3/numpy-2.3.3-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94fcaa68757c3e2e668ddadeaa86ab05499a70725811e582b6a9858dd472fb30", size = 14352314, upload-time = "2025-09-09T15:57:36.255Z" },
{ url = "https://files.pythonhosted.org/packages/a7/b1/dc226b4c90eb9f07a3fff95c2f0db3268e2e54e5cce97c4ac91518aee71b/numpy-2.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da1a74b90e7483d6ce5244053399a614b1d6b7bc30a60d2f570e5071f8959d3e", size = 16701722, upload-time = "2025-09-09T15:57:38.622Z" },
{ url = "https://files.pythonhosted.org/packages/9d/9d/9d8d358f2eb5eced14dba99f110d83b5cd9a4460895230f3b396ad19a323/numpy-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2990adf06d1ecee3b3dcbb4977dfab6e9f09807598d647f04d385d29e7a3c3d3", size = 16132755, upload-time = "2025-09-09T15:57:41.16Z" },
{ url = "https://files.pythonhosted.org/packages/b6/27/b3922660c45513f9377b3fb42240bec63f203c71416093476ec9aa0719dc/numpy-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ed635ff692483b8e3f0fcaa8e7eb8a75ee71aa6d975388224f70821421800cea", size = 18651560, upload-time = "2025-09-09T15:57:43.459Z" },
{ url = "https://files.pythonhosted.org/packages/5b/8e/3ab61a730bdbbc201bb245a71102aa609f0008b9ed15255500a99cd7f780/numpy-2.3.3-cp313-cp313t-win32.whl", hash = "sha256:a333b4ed33d8dc2b373cc955ca57babc00cd6f9009991d9edc5ddbc1bac36bcd", size = 6442776, upload-time = "2025-09-09T15:57:45.793Z" },
{ url = "https://files.pythonhosted.org/packages/1c/3a/e22b766b11f6030dc2decdeff5c2fb1610768055603f9f3be88b6d192fb2/numpy-2.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:4384a169c4d8f97195980815d6fcad04933a7e1ab3b530921c3fef7a1c63426d", size = 12927281, upload-time = "2025-09-09T15:57:47.492Z" },
{ url = "https://files.pythonhosted.org/packages/7b/42/c2e2bc48c5e9b2a83423f99733950fbefd86f165b468a3d85d52b30bf782/numpy-2.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:75370986cc0bc66f4ce5110ad35aae6d182cc4ce6433c40ad151f53690130bf1", size = 10265275, upload-time = "2025-09-09T15:57:49.647Z" },
{ url = "https://files.pythonhosted.org/packages/6b/01/342ad585ad82419b99bcf7cebe99e61da6bedb89e213c5fd71acc467faee/numpy-2.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cd052f1fa6a78dee696b58a914b7229ecfa41f0a6d96dc663c1220a55e137593", size = 20951527, upload-time = "2025-09-09T15:57:52.006Z" },
{ url = "https://files.pythonhosted.org/packages/ef/d8/204e0d73fc1b7a9ee80ab1fe1983dd33a4d64a4e30a05364b0208e9a241a/numpy-2.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:414a97499480067d305fcac9716c29cf4d0d76db6ebf0bf3cbce666677f12652", size = 14186159, upload-time = "2025-09-09T15:57:54.407Z" },
{ url = "https://files.pythonhosted.org/packages/22/af/f11c916d08f3a18fb8ba81ab72b5b74a6e42ead4c2846d270eb19845bf74/numpy-2.3.3-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:50a5fe69f135f88a2be9b6ca0481a68a136f6febe1916e4920e12f1a34e708a7", size = 5114624, upload-time = "2025-09-09T15:57:56.5Z" },
{ url = "https://files.pythonhosted.org/packages/fb/11/0ed919c8381ac9d2ffacd63fd1f0c34d27e99cab650f0eb6f110e6ae4858/numpy-2.3.3-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:b912f2ed2b67a129e6a601e9d93d4fa37bef67e54cac442a2f588a54afe5c67a", size = 6642627, upload-time = "2025-09-09T15:57:58.206Z" },
{ url = "https://files.pythonhosted.org/packages/ee/83/deb5f77cb0f7ba6cb52b91ed388b47f8f3c2e9930d4665c600408d9b90b9/numpy-2.3.3-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9e318ee0596d76d4cb3d78535dc005fa60e5ea348cd131a51e99d0bdbe0b54fe", size = 14296926, upload-time = "2025-09-09T15:58:00.035Z" },
{ url = "https://files.pythonhosted.org/packages/77/cc/70e59dcb84f2b005d4f306310ff0a892518cc0c8000a33d0e6faf7ca8d80/numpy-2.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ce020080e4a52426202bdb6f7691c65bb55e49f261f31a8f506c9f6bc7450421", size = 16638958, upload-time = "2025-09-09T15:58:02.738Z" },
{ url = "https://files.pythonhosted.org/packages/b6/5a/b2ab6c18b4257e099587d5b7f903317bd7115333ad8d4ec4874278eafa61/numpy-2.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e6687dc183aa55dae4a705b35f9c0f8cb178bcaa2f029b241ac5356221d5c021", size = 16071920, upload-time = "2025-09-09T15:58:05.029Z" },
{ url = "https://files.pythonhosted.org/packages/b8/f1/8b3fdc44324a259298520dd82147ff648979bed085feeacc1250ef1656c0/numpy-2.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d8f3b1080782469fdc1718c4ed1d22549b5fb12af0d57d35e992158a772a37cf", size = 18577076, upload-time = "2025-09-09T15:58:07.745Z" },
{ url = "https://files.pythonhosted.org/packages/f0/a1/b87a284fb15a42e9274e7fcea0dad259d12ddbf07c1595b26883151ca3b4/numpy-2.3.3-cp314-cp314-win32.whl", hash = "sha256:cb248499b0bc3be66ebd6578b83e5acacf1d6cb2a77f2248ce0e40fbec5a76d0", size = 6366952, upload-time = "2025-09-09T15:58:10.096Z" },
{ url = "https://files.pythonhosted.org/packages/70/5f/1816f4d08f3b8f66576d8433a66f8fa35a5acfb3bbd0bf6c31183b003f3d/numpy-2.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:691808c2b26b0f002a032c73255d0bd89751425f379f7bcd22d140db593a96e8", size = 12919322, upload-time = "2025-09-09T15:58:12.138Z" },
{ url = "https://files.pythonhosted.org/packages/8c/de/072420342e46a8ea41c324a555fa90fcc11637583fb8df722936aed1736d/numpy-2.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:9ad12e976ca7b10f1774b03615a2a4bab8addce37ecc77394d8e986927dc0dfe", size = 10478630, upload-time = "2025-09-09T15:58:14.64Z" },
{ url = "https://files.pythonhosted.org/packages/d5/df/ee2f1c0a9de7347f14da5dd3cd3c3b034d1b8607ccb6883d7dd5c035d631/numpy-2.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9cc48e09feb11e1db00b320e9d30a4151f7369afb96bd0e48d942d09da3a0d00", size = 21047987, upload-time = "2025-09-09T15:58:16.889Z" },
{ url = "https://files.pythonhosted.org/packages/d6/92/9453bdc5a4e9e69cf4358463f25e8260e2ffc126d52e10038b9077815989/numpy-2.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:901bf6123879b7f251d3631967fd574690734236075082078e0571977c6a8e6a", size = 14301076, upload-time = "2025-09-09T15:58:20.343Z" },
{ url = "https://files.pythonhosted.org/packages/13/77/1447b9eb500f028bb44253105bd67534af60499588a5149a94f18f2ca917/numpy-2.3.3-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:7f025652034199c301049296b59fa7d52c7e625017cae4c75d8662e377bf487d", size = 5229491, upload-time = "2025-09-09T15:58:22.481Z" },
{ url = "https://files.pythonhosted.org/packages/3d/f9/d72221b6ca205f9736cb4b2ce3b002f6e45cd67cd6a6d1c8af11a2f0b649/numpy-2.3.3-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:533ca5f6d325c80b6007d4d7fb1984c303553534191024ec6a524a4c92a5935a", size = 6737913, upload-time = "2025-09-09T15:58:24.569Z" },
{ url = "https://files.pythonhosted.org/packages/3c/5f/d12834711962ad9c46af72f79bb31e73e416ee49d17f4c797f72c96b6ca5/numpy-2.3.3-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0edd58682a399824633b66885d699d7de982800053acf20be1eaa46d92009c54", size = 14352811, upload-time = "2025-09-09T15:58:26.416Z" },
{ url = "https://files.pythonhosted.org/packages/a1/0d/fdbec6629d97fd1bebed56cd742884e4eead593611bbe1abc3eb40d304b2/numpy-2.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:367ad5d8fbec5d9296d18478804a530f1191e24ab4d75ab408346ae88045d25e", size = 16702689, upload-time = "2025-09-09T15:58:28.831Z" },
{ url = "https://files.pythonhosted.org/packages/9b/09/0a35196dc5575adde1eb97ddfbc3e1687a814f905377621d18ca9bc2b7dd/numpy-2.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8f6ac61a217437946a1fa48d24c47c91a0c4f725237871117dea264982128097", size = 16133855, upload-time = "2025-09-09T15:58:31.349Z" },
{ url = "https://files.pythonhosted.org/packages/7a/ca/c9de3ea397d576f1b6753eaa906d4cdef1bf97589a6d9825a349b4729cc2/numpy-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:179a42101b845a816d464b6fe9a845dfaf308fdfc7925387195570789bb2c970", size = 18652520, upload-time = "2025-09-09T15:58:33.762Z" },
{ url = "https://files.pythonhosted.org/packages/fd/c2/e5ed830e08cd0196351db55db82f65bc0ab05da6ef2b72a836dcf1936d2f/numpy-2.3.3-cp314-cp314t-win32.whl", hash = "sha256:1250c5d3d2562ec4174bce2e3a1523041595f9b651065e4a4473f5f48a6bc8a5", size = 6515371, upload-time = "2025-09-09T15:58:36.04Z" },
{ url = "https://files.pythonhosted.org/packages/47/c7/b0f6b5b67f6788a0725f744496badbb604d226bf233ba716683ebb47b570/numpy-2.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:b37a0b2e5935409daebe82c1e42274d30d9dd355852529eab91dab8dcca7419f", size = 13112576, upload-time = "2025-09-09T15:58:37.927Z" },
{ url = "https://files.pythonhosted.org/packages/06/b9/33bba5ff6fb679aa0b1f8a07e853f002a6b04b9394db3069a1270a7784ca/numpy-2.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:78c9f6560dc7e6b3990e32df7ea1a50bbd0e2a111e05209963f5ddcab7073b0b", size = 10545953, upload-time = "2025-09-09T15:58:40.576Z" },
{ url = "https://files.pythonhosted.org/packages/b8/f2/7e0a37cfced2644c9563c529f29fa28acbd0960dde32ece683aafa6f4949/numpy-2.3.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1e02c7159791cd481e1e6d5ddd766b62a4d5acf8df4d4d1afe35ee9c5c33a41e", size = 21131019, upload-time = "2025-09-09T15:58:42.838Z" },
{ url = "https://files.pythonhosted.org/packages/1a/7e/3291f505297ed63831135a6cc0f474da0c868a1f31b0dd9a9f03a7a0d2ed/numpy-2.3.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:dca2d0fc80b3893ae72197b39f69d55a3cd8b17ea1b50aa4c62de82419936150", size = 14376288, upload-time = "2025-09-09T15:58:45.425Z" },
{ url = "https://files.pythonhosted.org/packages/bf/4b/ae02e985bdeee73d7b5abdefeb98aef1207e96d4c0621ee0cf228ddfac3c/numpy-2.3.3-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:99683cbe0658f8271b333a1b1b4bb3173750ad59c0c61f5bbdc5b318918fffe3", size = 5305425, upload-time = "2025-09-09T15:58:48.6Z" },
{ url = "https://files.pythonhosted.org/packages/8b/eb/9df215d6d7250db32007941500dc51c48190be25f2401d5b2b564e467247/numpy-2.3.3-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:d9d537a39cc9de668e5cd0e25affb17aec17b577c6b3ae8a3d866b479fbe88d0", size = 6819053, upload-time = "2025-09-09T15:58:50.401Z" },
{ url = "https://files.pythonhosted.org/packages/57/62/208293d7d6b2a8998a4a1f23ac758648c3c32182d4ce4346062018362e29/numpy-2.3.3-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8596ba2f8af5f93b01d97563832686d20206d303024777f6dfc2e7c7c3f1850e", size = 14420354, upload-time = "2025-09-09T15:58:52.704Z" },
{ url = "https://files.pythonhosted.org/packages/ed/0c/8e86e0ff7072e14a71b4c6af63175e40d1e7e933ce9b9e9f765a95b4e0c3/numpy-2.3.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1ec5615b05369925bd1125f27df33f3b6c8bc10d788d5999ecd8769a1fa04db", size = 16760413, upload-time = "2025-09-09T15:58:55.027Z" },
{ url = "https://files.pythonhosted.org/packages/af/11/0cc63f9f321ccf63886ac203336777140011fb669e739da36d8db3c53b98/numpy-2.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:2e267c7da5bf7309670523896df97f93f6e469fb931161f483cd6882b3b1a5dc", size = 12971844, upload-time = "2025-09-09T15:58:57.359Z" },
]
[[package]]
name = "packaging"
version = "25.0"
@@ -312,6 +490,19 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/16/32/f8e3c85d1d5250232a5d3477a2a28cc291968ff175caeadaf3cc19ce0e4a/parso-0.8.5-py2.py3-none-any.whl", hash = "sha256:646204b5ee239c396d040b90f9e272e9a8017c630092bf59980beb62fd033887", size = 106668, upload-time = "2025-08-23T15:15:25.663Z" },
]
[[package]]
name = "pgvector"
version = "0.4.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
{ name = "numpy", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/44/43/9a0fb552ab4fd980680c2037962e331820f67585df740bedc4a2b50faf20/pgvector-0.4.1.tar.gz", hash = "sha256:83d3a1c044ff0c2f1e95d13dfb625beb0b65506cfec0941bfe81fd0ad44f4003", size = 30646, upload-time = "2025-04-26T18:56:37.151Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/bf/21/b5735d5982892c878ff3d01bb06e018c43fc204428361ee9fc25a1b2125c/pgvector-0.4.1-py3-none-any.whl", hash = "sha256:34bb4e99e1b13d08a2fe82dda9f860f15ddcd0166fbb25bffe15821cbfeb7362", size = 27086, upload-time = "2025-04-26T18:56:35.956Z" },
]
[[package]]
name = "psutil"
version = "7.0.0"
@@ -327,6 +518,108 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/50/1b/6921afe68c74868b4c9fa424dad3be35b095e16687989ebbb50ce4fceb7c/psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553", size = 244885, upload-time = "2025-02-13T21:54:37.486Z" },
]
[[package]]
name = "pydantic"
version = "2.11.9"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "annotated-types" },
{ name = "pydantic-core" },
{ name = "typing-extensions" },
{ name = "typing-inspection" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ff/5d/09a551ba512d7ca404d785072700d3f6727a02f6f3c24ecfd081c7cf0aa8/pydantic-2.11.9.tar.gz", hash = "sha256:6b8ffda597a14812a7975c90b82a8a2e777d9257aba3453f973acd3c032a18e2", size = 788495, upload-time = "2025-09-13T11:26:39.325Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/3e/d3/108f2006987c58e76691d5ae5d200dd3e0f532cb4e5fa3560751c3a1feba/pydantic-2.11.9-py3-none-any.whl", hash = "sha256:c42dd626f5cfc1c6950ce6205ea58c93efa406da65f479dcb4029d5934857da2", size = 444855, upload-time = "2025-09-13T11:26:36.909Z" },
]
[[package]]
name = "pydantic-core"
version = "2.33.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195, upload-time = "2025-04-23T18:33:52.104Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e5/92/b31726561b5dae176c2d2c2dc43a9c5bfba5d32f96f8b4c0a600dd492447/pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8", size = 2028817, upload-time = "2025-04-23T18:30:43.919Z" },
{ url = "https://files.pythonhosted.org/packages/a3/44/3f0b95fafdaca04a483c4e685fe437c6891001bf3ce8b2fded82b9ea3aa1/pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d", size = 1861357, upload-time = "2025-04-23T18:30:46.372Z" },
{ url = "https://files.pythonhosted.org/packages/30/97/e8f13b55766234caae05372826e8e4b3b96e7b248be3157f53237682e43c/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0069c9acc3f3981b9ff4cdfaf088e98d83440a4c7ea1bc07460af3d4dc22e72d", size = 1898011, upload-time = "2025-04-23T18:30:47.591Z" },
{ url = "https://files.pythonhosted.org/packages/9b/a3/99c48cf7bafc991cc3ee66fd544c0aae8dc907b752f1dad2d79b1b5a471f/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d53b22f2032c42eaaf025f7c40c2e3b94568ae077a606f006d206a463bc69572", size = 1982730, upload-time = "2025-04-23T18:30:49.328Z" },
{ url = "https://files.pythonhosted.org/packages/de/8e/a5b882ec4307010a840fb8b58bd9bf65d1840c92eae7534c7441709bf54b/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0405262705a123b7ce9f0b92f123334d67b70fd1f20a9372b907ce1080c7ba02", size = 2136178, upload-time = "2025-04-23T18:30:50.907Z" },
{ url = "https://files.pythonhosted.org/packages/e4/bb/71e35fc3ed05af6834e890edb75968e2802fe98778971ab5cba20a162315/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b25d91e288e2c4e0662b8038a28c6a07eaac3e196cfc4ff69de4ea3db992a1b", size = 2736462, upload-time = "2025-04-23T18:30:52.083Z" },
{ url = "https://files.pythonhosted.org/packages/31/0d/c8f7593e6bc7066289bbc366f2235701dcbebcd1ff0ef8e64f6f239fb47d/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bdfe4b3789761f3bcb4b1ddf33355a71079858958e3a552f16d5af19768fef2", size = 2005652, upload-time = "2025-04-23T18:30:53.389Z" },
{ url = "https://files.pythonhosted.org/packages/d2/7a/996d8bd75f3eda405e3dd219ff5ff0a283cd8e34add39d8ef9157e722867/pydantic_core-2.33.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:efec8db3266b76ef9607c2c4c419bdb06bf335ae433b80816089ea7585816f6a", size = 2113306, upload-time = "2025-04-23T18:30:54.661Z" },
{ url = "https://files.pythonhosted.org/packages/ff/84/daf2a6fb2db40ffda6578a7e8c5a6e9c8affb251a05c233ae37098118788/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:031c57d67ca86902726e0fae2214ce6770bbe2f710dc33063187a68744a5ecac", size = 2073720, upload-time = "2025-04-23T18:30:56.11Z" },
{ url = "https://files.pythonhosted.org/packages/77/fb/2258da019f4825128445ae79456a5499c032b55849dbd5bed78c95ccf163/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:f8de619080e944347f5f20de29a975c2d815d9ddd8be9b9b7268e2e3ef68605a", size = 2244915, upload-time = "2025-04-23T18:30:57.501Z" },
{ url = "https://files.pythonhosted.org/packages/d8/7a/925ff73756031289468326e355b6fa8316960d0d65f8b5d6b3a3e7866de7/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:73662edf539e72a9440129f231ed3757faab89630d291b784ca99237fb94db2b", size = 2241884, upload-time = "2025-04-23T18:30:58.867Z" },
{ url = "https://files.pythonhosted.org/packages/0b/b0/249ee6d2646f1cdadcb813805fe76265745c4010cf20a8eba7b0e639d9b2/pydantic_core-2.33.2-cp310-cp310-win32.whl", hash = "sha256:0a39979dcbb70998b0e505fb1556a1d550a0781463ce84ebf915ba293ccb7e22", size = 1910496, upload-time = "2025-04-23T18:31:00.078Z" },
{ url = "https://files.pythonhosted.org/packages/66/ff/172ba8f12a42d4b552917aa65d1f2328990d3ccfc01d5b7c943ec084299f/pydantic_core-2.33.2-cp310-cp310-win_amd64.whl", hash = "sha256:b0379a2b24882fef529ec3b4987cb5d003b9cda32256024e6fe1586ac45fc640", size = 1955019, upload-time = "2025-04-23T18:31:01.335Z" },
{ url = "https://files.pythonhosted.org/packages/3f/8d/71db63483d518cbbf290261a1fc2839d17ff89fce7089e08cad07ccfce67/pydantic_core-2.33.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7", size = 2028584, upload-time = "2025-04-23T18:31:03.106Z" },
{ url = "https://files.pythonhosted.org/packages/24/2f/3cfa7244ae292dd850989f328722d2aef313f74ffc471184dc509e1e4e5a/pydantic_core-2.33.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246", size = 1855071, upload-time = "2025-04-23T18:31:04.621Z" },
{ url = "https://files.pythonhosted.org/packages/b3/d3/4ae42d33f5e3f50dd467761304be2fa0a9417fbf09735bc2cce003480f2a/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f", size = 1897823, upload-time = "2025-04-23T18:31:06.377Z" },
{ url = "https://files.pythonhosted.org/packages/f4/f3/aa5976e8352b7695ff808599794b1fba2a9ae2ee954a3426855935799488/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc", size = 1983792, upload-time = "2025-04-23T18:31:07.93Z" },
{ url = "https://files.pythonhosted.org/packages/d5/7a/cda9b5a23c552037717f2b2a5257e9b2bfe45e687386df9591eff7b46d28/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de", size = 2136338, upload-time = "2025-04-23T18:31:09.283Z" },
{ url = "https://files.pythonhosted.org/packages/2b/9f/b8f9ec8dd1417eb9da784e91e1667d58a2a4a7b7b34cf4af765ef663a7e5/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a", size = 2730998, upload-time = "2025-04-23T18:31:11.7Z" },
{ url = "https://files.pythonhosted.org/packages/47/bc/cd720e078576bdb8255d5032c5d63ee5c0bf4b7173dd955185a1d658c456/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef", size = 2003200, upload-time = "2025-04-23T18:31:13.536Z" },
{ url = "https://files.pythonhosted.org/packages/ca/22/3602b895ee2cd29d11a2b349372446ae9727c32e78a94b3d588a40fdf187/pydantic_core-2.33.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e", size = 2113890, upload-time = "2025-04-23T18:31:15.011Z" },
{ url = "https://files.pythonhosted.org/packages/ff/e6/e3c5908c03cf00d629eb38393a98fccc38ee0ce8ecce32f69fc7d7b558a7/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d", size = 2073359, upload-time = "2025-04-23T18:31:16.393Z" },
{ url = "https://files.pythonhosted.org/packages/12/e7/6a36a07c59ebefc8777d1ffdaf5ae71b06b21952582e4b07eba88a421c79/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30", size = 2245883, upload-time = "2025-04-23T18:31:17.892Z" },
{ url = "https://files.pythonhosted.org/packages/16/3f/59b3187aaa6cc0c1e6616e8045b284de2b6a87b027cce2ffcea073adf1d2/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf", size = 2241074, upload-time = "2025-04-23T18:31:19.205Z" },
{ url = "https://files.pythonhosted.org/packages/e0/ed/55532bb88f674d5d8f67ab121a2a13c385df382de2a1677f30ad385f7438/pydantic_core-2.33.2-cp311-cp311-win32.whl", hash = "sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51", size = 1910538, upload-time = "2025-04-23T18:31:20.541Z" },
{ url = "https://files.pythonhosted.org/packages/fe/1b/25b7cccd4519c0b23c2dd636ad39d381abf113085ce4f7bec2b0dc755eb1/pydantic_core-2.33.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab", size = 1952909, upload-time = "2025-04-23T18:31:22.371Z" },
{ url = "https://files.pythonhosted.org/packages/49/a9/d809358e49126438055884c4366a1f6227f0f84f635a9014e2deb9b9de54/pydantic_core-2.33.2-cp311-cp311-win_arm64.whl", hash = "sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65", size = 1897786, upload-time = "2025-04-23T18:31:24.161Z" },
{ url = "https://files.pythonhosted.org/packages/18/8a/2b41c97f554ec8c71f2a8a5f85cb56a8b0956addfe8b0efb5b3d77e8bdc3/pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc", size = 2009000, upload-time = "2025-04-23T18:31:25.863Z" },
{ url = "https://files.pythonhosted.org/packages/a1/02/6224312aacb3c8ecbaa959897af57181fb6cf3a3d7917fd44d0f2917e6f2/pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7", size = 1847996, upload-time = "2025-04-23T18:31:27.341Z" },
{ url = "https://files.pythonhosted.org/packages/d6/46/6dcdf084a523dbe0a0be59d054734b86a981726f221f4562aed313dbcb49/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025", size = 1880957, upload-time = "2025-04-23T18:31:28.956Z" },
{ url = "https://files.pythonhosted.org/packages/ec/6b/1ec2c03837ac00886ba8160ce041ce4e325b41d06a034adbef11339ae422/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011", size = 1964199, upload-time = "2025-04-23T18:31:31.025Z" },
{ url = "https://files.pythonhosted.org/packages/2d/1d/6bf34d6adb9debd9136bd197ca72642203ce9aaaa85cfcbfcf20f9696e83/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f", size = 2120296, upload-time = "2025-04-23T18:31:32.514Z" },
{ url = "https://files.pythonhosted.org/packages/e0/94/2bd0aaf5a591e974b32a9f7123f16637776c304471a0ab33cf263cf5591a/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88", size = 2676109, upload-time = "2025-04-23T18:31:33.958Z" },
{ url = "https://files.pythonhosted.org/packages/f9/41/4b043778cf9c4285d59742281a769eac371b9e47e35f98ad321349cc5d61/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1", size = 2002028, upload-time = "2025-04-23T18:31:39.095Z" },
{ url = "https://files.pythonhosted.org/packages/cb/d5/7bb781bf2748ce3d03af04d5c969fa1308880e1dca35a9bd94e1a96a922e/pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b", size = 2100044, upload-time = "2025-04-23T18:31:41.034Z" },
{ url = "https://files.pythonhosted.org/packages/fe/36/def5e53e1eb0ad896785702a5bbfd25eed546cdcf4087ad285021a90ed53/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1", size = 2058881, upload-time = "2025-04-23T18:31:42.757Z" },
{ url = "https://files.pythonhosted.org/packages/01/6c/57f8d70b2ee57fc3dc8b9610315949837fa8c11d86927b9bb044f8705419/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6", size = 2227034, upload-time = "2025-04-23T18:31:44.304Z" },
{ url = "https://files.pythonhosted.org/packages/27/b9/9c17f0396a82b3d5cbea4c24d742083422639e7bb1d5bf600e12cb176a13/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea", size = 2234187, upload-time = "2025-04-23T18:31:45.891Z" },
{ url = "https://files.pythonhosted.org/packages/b0/6a/adf5734ffd52bf86d865093ad70b2ce543415e0e356f6cacabbc0d9ad910/pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290", size = 1892628, upload-time = "2025-04-23T18:31:47.819Z" },
{ url = "https://files.pythonhosted.org/packages/43/e4/5479fecb3606c1368d496a825d8411e126133c41224c1e7238be58b87d7e/pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2", size = 1955866, upload-time = "2025-04-23T18:31:49.635Z" },
{ url = "https://files.pythonhosted.org/packages/0d/24/8b11e8b3e2be9dd82df4b11408a67c61bb4dc4f8e11b5b0fc888b38118b5/pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab", size = 1888894, upload-time = "2025-04-23T18:31:51.609Z" },
{ url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688, upload-time = "2025-04-23T18:31:53.175Z" },
{ url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808, upload-time = "2025-04-23T18:31:54.79Z" },
{ url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580, upload-time = "2025-04-23T18:31:57.393Z" },
{ url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859, upload-time = "2025-04-23T18:31:59.065Z" },
{ url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810, upload-time = "2025-04-23T18:32:00.78Z" },
{ url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498, upload-time = "2025-04-23T18:32:02.418Z" },
{ url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611, upload-time = "2025-04-23T18:32:04.152Z" },
{ url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924, upload-time = "2025-04-23T18:32:06.129Z" },
{ url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196, upload-time = "2025-04-23T18:32:08.178Z" },
{ url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389, upload-time = "2025-04-23T18:32:10.242Z" },
{ url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223, upload-time = "2025-04-23T18:32:12.382Z" },
{ url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473, upload-time = "2025-04-23T18:32:14.034Z" },
{ url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269, upload-time = "2025-04-23T18:32:15.783Z" },
{ url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921, upload-time = "2025-04-23T18:32:18.473Z" },
{ url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162, upload-time = "2025-04-23T18:32:20.188Z" },
{ url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560, upload-time = "2025-04-23T18:32:22.354Z" },
{ url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777, upload-time = "2025-04-23T18:32:25.088Z" },
{ url = "https://files.pythonhosted.org/packages/30/68/373d55e58b7e83ce371691f6eaa7175e3a24b956c44628eb25d7da007917/pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5c4aa4e82353f65e548c476b37e64189783aa5384903bfea4f41580f255fddfa", size = 2023982, upload-time = "2025-04-23T18:32:53.14Z" },
{ url = "https://files.pythonhosted.org/packages/a4/16/145f54ac08c96a63d8ed6442f9dec17b2773d19920b627b18d4f10a061ea/pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d946c8bf0d5c24bf4fe333af284c59a19358aa3ec18cb3dc4370080da1e8ad29", size = 1858412, upload-time = "2025-04-23T18:32:55.52Z" },
{ url = "https://files.pythonhosted.org/packages/41/b1/c6dc6c3e2de4516c0bb2c46f6a373b91b5660312342a0cf5826e38ad82fa/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87b31b6846e361ef83fedb187bb5b4372d0da3f7e28d85415efa92d6125d6e6d", size = 1892749, upload-time = "2025-04-23T18:32:57.546Z" },
{ url = "https://files.pythonhosted.org/packages/12/73/8cd57e20afba760b21b742106f9dbdfa6697f1570b189c7457a1af4cd8a0/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa9d91b338f2df0508606f7009fde642391425189bba6d8c653afd80fd6bb64e", size = 2067527, upload-time = "2025-04-23T18:32:59.771Z" },
{ url = "https://files.pythonhosted.org/packages/e3/d5/0bb5d988cc019b3cba4a78f2d4b3854427fc47ee8ec8e9eaabf787da239c/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2058a32994f1fde4ca0480ab9d1e75a0e8c87c22b53a3ae66554f9af78f2fe8c", size = 2108225, upload-time = "2025-04-23T18:33:04.51Z" },
{ url = "https://files.pythonhosted.org/packages/f1/c5/00c02d1571913d496aabf146106ad8239dc132485ee22efe08085084ff7c/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:0e03262ab796d986f978f79c943fc5f620381be7287148b8010b4097f79a39ec", size = 2069490, upload-time = "2025-04-23T18:33:06.391Z" },
{ url = "https://files.pythonhosted.org/packages/22/a8/dccc38768274d3ed3a59b5d06f59ccb845778687652daa71df0cab4040d7/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1a8695a8d00c73e50bff9dfda4d540b7dee29ff9b8053e38380426a85ef10052", size = 2237525, upload-time = "2025-04-23T18:33:08.44Z" },
{ url = "https://files.pythonhosted.org/packages/d4/e7/4f98c0b125dda7cf7ccd14ba936218397b44f50a56dd8c16a3091df116c3/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:fa754d1850735a0b0e03bcffd9d4b4343eb417e47196e4485d9cca326073a42c", size = 2238446, upload-time = "2025-04-23T18:33:10.313Z" },
{ url = "https://files.pythonhosted.org/packages/ce/91/2ec36480fdb0b783cd9ef6795753c1dea13882f2e68e73bce76ae8c21e6a/pydantic_core-2.33.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a11c8d26a50bfab49002947d3d237abe4d9e4b5bdc8846a63537b6488e197808", size = 2066678, upload-time = "2025-04-23T18:33:12.224Z" },
{ url = "https://files.pythonhosted.org/packages/7b/27/d4ae6487d73948d6f20dddcd94be4ea43e74349b56eba82e9bdee2d7494c/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8", size = 2025200, upload-time = "2025-04-23T18:33:14.199Z" },
{ url = "https://files.pythonhosted.org/packages/f1/b8/b3cb95375f05d33801024079b9392a5ab45267a63400bf1866e7ce0f0de4/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593", size = 1859123, upload-time = "2025-04-23T18:33:16.555Z" },
{ url = "https://files.pythonhosted.org/packages/05/bc/0d0b5adeda59a261cd30a1235a445bf55c7e46ae44aea28f7bd6ed46e091/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612", size = 1892852, upload-time = "2025-04-23T18:33:18.513Z" },
{ url = "https://files.pythonhosted.org/packages/3e/11/d37bdebbda2e449cb3f519f6ce950927b56d62f0b84fd9cb9e372a26a3d5/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7", size = 2067484, upload-time = "2025-04-23T18:33:20.475Z" },
{ url = "https://files.pythonhosted.org/packages/8c/55/1f95f0a05ce72ecb02a8a8a1c3be0579bbc29b1d5ab68f1378b7bebc5057/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e", size = 2108896, upload-time = "2025-04-23T18:33:22.501Z" },
{ url = "https://files.pythonhosted.org/packages/53/89/2b2de6c81fa131f423246a9109d7b2a375e83968ad0800d6e57d0574629b/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8", size = 2069475, upload-time = "2025-04-23T18:33:24.528Z" },
{ url = "https://files.pythonhosted.org/packages/b8/e9/1f7efbe20d0b2b10f6718944b5d8ece9152390904f29a78e68d4e7961159/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf", size = 2239013, upload-time = "2025-04-23T18:33:26.621Z" },
{ url = "https://files.pythonhosted.org/packages/3c/b2/5309c905a93811524a49b4e031e9851a6b00ff0fb668794472ea7746b448/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb", size = 2238715, upload-time = "2025-04-23T18:33:28.656Z" },
{ url = "https://files.pythonhosted.org/packages/32/56/8a7ca5d2cd2cda1d245d34b1c9a942920a718082ae8e54e5f3e5a58b7add/pydantic_core-2.33.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1", size = 2066757, upload-time = "2025-04-23T18:33:30.645Z" },
]
[[package]]
name = "pygments"
version = "2.19.2"
@@ -433,6 +726,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
]
[[package]]
name = "typing-inspection"
version = "0.4.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726, upload-time = "2025-05-21T18:55:23.885Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552, upload-time = "2025-05-21T18:55:22.152Z" },
]
[[package]]
name = "uvicorn"
version = "0.35.0"