frontend añadido y backend de creacion de notas
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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'>'/home/lucas/DataProyects/myrag/.model/nomic-embed-text-v1.5'</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\">"/home/lucas/DataProyects/myrag/.venv/lib/python3.10/site-packages/marimo/_runtime/executor.py"</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\">"/tmp/marimo_87281/__marimo__cell_iLit_.py"</span>, line <span class=\"m\">5</span>, in <span class=\"n\"><module></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 'conn2' is not defined. Did you mean: 'conn'?</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='""' data-label='"<span class=\"markdown prose dark:prose-invert\"><span class=\"paragraph\">Texto de b\u00fasqueda</span></span>"' data-placeholder='""' data-kind='"text"' 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='"[]"' data-total-rows='0' data-total-columns='3' data-max-columns='50' data-banner-text='""' data-pagination='true' data-page-size='10' data-field-types='[["ID",["string","object"]],["T\u00edtulo",["string","object"]],["Distancia",["string","object"]]]' 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": []
|
||||
}
|
||||
]
|
||||
|
||||
@@ -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='["nota"]' data-label='"<span class=\"markdown prose dark:prose-invert\"><span class=\"paragraph\">Selecciona una tabla</span></span>"' data-options='["nota","prueba_simple"]' 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='"[]"' data-total-rows='0' data-total-columns='14' data-max-columns='50' data-banner-text='""' data-pagination='false' data-page-size='10' data-field-types='[["titulo",["string","object"]],["contenido",["string","object"]],["embedder",["string","object"]],["tags",["string","object"]],["relaciones",["string","object"]],["propiedades",["string","object"]],["id",["string","object"]],["sys_created_at",["string","object"]],["sys_created_by",["string","object"]],["sys_updated_at",["string","object"]],["sys_updated_by",["string","object"]],["sys_version",["string","object"]],["sys_notes",["string","object"]],["sys_deleted_at",["string","object"]]]' data-selection='"multi"' 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='"<span class=\"markdown prose dark:prose-invert\"><span class=\"paragraph\">ID a recuperar</span></span>"' 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='"<span class=\"markdown prose dark:prose-invert\"><span class=\"paragraph\">Recuperar objeto</span></span>"' data-kind='"neutral"' 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='""' data-label='"<span class=\"markdown prose dark:prose-invert\"><span class=\"paragraph\">titulo</span></span>"' data-placeholder='""' data-kind='"text"' 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='""' data-label='"<span class=\"markdown prose dark:prose-invert\"><span class=\"paragraph\">contenido</span></span>"' data-placeholder='""' data-kind='"text"' 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='""' data-label='"<span class=\"markdown prose dark:prose-invert\"><span class=\"paragraph\">embedder</span></span>"' data-placeholder='""' data-kind='"text"' 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='""' data-label='"<span class=\"markdown prose dark:prose-invert\"><span class=\"paragraph\">tags</span></span>"' data-placeholder='""' data-kind='"text"' 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='""' data-label='"<span class=\"markdown prose dark:prose-invert\"><span class=\"paragraph\">relaciones</span></span>"' data-placeholder='""' data-kind='"text"' 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='""' data-label='"<span class=\"markdown prose dark:prose-invert\"><span class=\"paragraph\">propiedades</span></span>"' data-placeholder='""' data-kind='"text"' 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='"<span class=\"markdown prose dark:prose-invert\"><span class=\"paragraph\">Agregar fila a nota</span></span>"' data-kind='"neutral"' 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='"<span class=\"markdown prose dark:prose-invert\"><span class=\"paragraph\">Insertar objeto en la bbdd</span></span>"' data-kind='"neutral"' 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='"<span class=\"markdown prose dark:prose-invert\"><span class=\"paragraph\">ID a actualizar</span></span>"' 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='"<span class=\"markdown prose dark:prose-invert\"><span class=\"paragraph\">Buscar registro</span></span>"' data-kind='"neutral"' 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='"<span class=\"markdown prose dark:prose-invert\"><span class=\"paragraph\">Confirmar actualizaci\u00f3n</span></span>"' data-kind='"success"' 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='"<span class=\"markdown prose dark:prose-invert\"><span class=\"paragraph\">ID a eliminar</span></span>"' 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='"<span class=\"markdown prose dark:prose-invert\"><span class=\"paragraph\">Hard delete</span></span>"' data-kind='"danger"' 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='"<span class=\"markdown prose dark:prose-invert\"><span class=\"paragraph\">ID a eliminar (soft)</span></span>"' 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='"<span class=\"markdown prose dark:prose-invert\"><span class=\"paragraph\">Soft delete</span></span>"' data-kind='"warn"' 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='"<span class=\"markdown prose dark:prose-invert\"><span class=\"paragraph\">ID a restaurar (soft)</span></span>"' 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='"<span class=\"markdown prose dark:prose-invert\"><span class=\"paragraph\">Restaurar registro</span></span>"' data-kind='"success"' 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": []
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
@@ -0,0 +1,8 @@
|
||||
from backend.db.session import SessionLocal
|
||||
|
||||
def get_db():
|
||||
db = SessionLocal()
|
||||
try:
|
||||
yield db
|
||||
finally:
|
||||
db.close()
|
||||
@@ -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,
|
||||
)
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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]
|
||||
@@ -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;
|
||||
|
||||
@@ -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))
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -0,0 +1,6 @@
|
||||
# domains/arquitecture_layer/__init__.py
|
||||
from .Repo import *
|
||||
from .Mapper import *
|
||||
from .Model import *
|
||||
|
||||
__all__ = ["Repo", "Mapper", "Model"]
|
||||
@@ -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
@@ -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)
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
v24.3.0
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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>
|
||||
);
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1 @@
|
||||
dist
|
||||
@@ -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
File diff suppressed because one or more lines are too long
@@ -0,0 +1,3 @@
|
||||
nodeLinker: node-modules
|
||||
|
||||
yarnPath: .yarn/releases/yarn-4.9.4.cjs
|
||||
@@ -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
|
||||
@@ -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'],
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
@@ -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>
|
||||
Generated
+9546
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||
}
|
||||
@@ -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',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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} />;
|
||||
}
|
||||
@@ -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 [0–1]
|
||||
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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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 |
@@ -0,0 +1,4 @@
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import App from './App';
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')!).render(<App />);
|
||||
@@ -0,0 +1,11 @@
|
||||
import { ColorSchemeToggle } from '../components/ColorSchemeToggle/ColorSchemeToggle';
|
||||
import { Welcome } from '../components/Welcome/Welcome';
|
||||
|
||||
export function HomePage() {
|
||||
return (
|
||||
<>
|
||||
<Welcome />
|
||||
<ColorSchemeToggle />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { createTheme } from '@mantine/core';
|
||||
|
||||
export const theme = createTheme({
|
||||
/** Put your mantine theme override here */
|
||||
});
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
@@ -0,0 +1,5 @@
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
export * from '@testing-library/react';
|
||||
export { render } from './render';
|
||||
export { userEvent };
|
||||
@@ -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>
|
||||
),
|
||||
});
|
||||
}
|
||||
@@ -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"]
|
||||
}
|
||||
@@ -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',
|
||||
},
|
||||
});
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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()
|
||||
@@ -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]
|
||||
|
||||
@@ -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}")
|
||||
+246
-240
@@ -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
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user