feat: Initialize SQLAlchemy backend and database structure
- Added SQLAlchemy initialization script for backend setup. - Created SQLAlchemy prompts for generating database models with LLMs. - Introduced Docker Compose configuration for PostgreSQL service. - Added SQL script to enable necessary PostgreSQL extensions.
This commit is contained in:
+17
@@ -0,0 +1,17 @@
|
|||||||
|
FROM postgres:15
|
||||||
|
|
||||||
|
# Variables de entorno útiles
|
||||||
|
ENV POSTGRES_USER=postgres \
|
||||||
|
POSTGRES_DB=basededatos
|
||||||
|
|
||||||
|
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
|
||||||
|
COPY docker-entrypoint-initdb.d/ /docker-entrypoint-initdb.d/
|
||||||
@@ -0,0 +1,318 @@
|
|||||||
|
import marimo
|
||||||
|
|
||||||
|
__generated_with = "0.14.17"
|
||||||
|
app = marimo.App()
|
||||||
|
|
||||||
|
|
||||||
|
@app.cell
|
||||||
|
def _():
|
||||||
|
import marimo as mo
|
||||||
|
return (mo,)
|
||||||
|
|
||||||
|
|
||||||
|
@app.cell
|
||||||
|
def _():
|
||||||
|
import os
|
||||||
|
import textwrap
|
||||||
|
import yaml
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# ==== Parámetros ====
|
||||||
|
PUERTO_POSTGRES = 55432
|
||||||
|
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
|
||||||
|
def _(PUERTO_POSTGRES):
|
||||||
|
from sqlalchemy import create_engine
|
||||||
|
from sqlalchemy.engine import Engine
|
||||||
|
from urllib.parse import quote_plus
|
||||||
|
|
||||||
|
def conectar_postgres(
|
||||||
|
host: str = "localhost",
|
||||||
|
port: int = PUERTO_POSTGRES,
|
||||||
|
dbname: str = "basededatos",
|
||||||
|
user: str = "postgres",
|
||||||
|
password: str = "mipassword"
|
||||||
|
) -> Engine:
|
||||||
|
"""
|
||||||
|
Devuelve un objeto SQLAlchemy Engine conectado a PostgreSQL.
|
||||||
|
Compatible con pandas.to_sql y read_sql.
|
||||||
|
"""
|
||||||
|
pwd = quote_plus(password) # Escapar caracteres especiales del password
|
||||||
|
url = f"postgresql+psycopg2://{user}:{pwd}@{host}:{port}/{dbname}"
|
||||||
|
|
||||||
|
engine = create_engine(
|
||||||
|
url,
|
||||||
|
pool_size=5,
|
||||||
|
max_overflow=10,
|
||||||
|
future=True
|
||||||
|
)
|
||||||
|
return engine
|
||||||
|
|
||||||
|
return Engine, conectar_postgres
|
||||||
|
|
||||||
|
|
||||||
|
@app.cell
|
||||||
|
def _(Engine):
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
def consultar_df(conn: Engine, query: str, params: dict | None = None) -> pd.DataFrame:
|
||||||
|
"""
|
||||||
|
Ejecuta una consulta SQL usando SQLAlchemy y devuelve los resultados como un DataFrame.
|
||||||
|
- conn: Engine devuelto por conectar_postgres()
|
||||||
|
- query: str con la consulta SQL
|
||||||
|
- params: dict opcional con parámetros de la consulta
|
||||||
|
"""
|
||||||
|
return pd.read_sql(query, con=conn, params=params)
|
||||||
|
|
||||||
|
return (consultar_df,)
|
||||||
|
|
||||||
|
|
||||||
|
@app.cell
|
||||||
|
def _(conectar_postgres, consultar_df):
|
||||||
|
conn = 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(conn, query)
|
||||||
|
return (conn,)
|
||||||
|
|
||||||
|
|
||||||
|
@app.cell
|
||||||
|
def _(conn, mo):
|
||||||
|
_df = mo.sql(
|
||||||
|
f"""
|
||||||
|
SELECT * FROM information_schema.tables
|
||||||
|
""",
|
||||||
|
engine=conn
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
@app.cell
|
||||||
|
def _(mo):
|
||||||
|
mo.md(r"""# Generacion de esquemas con sqlalchemy""")
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
@app.cell
|
||||||
|
def _():
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app.run()
|
||||||
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"version": "1",
|
||||||
|
"metadata": {
|
||||||
|
"marimo_version": "0.14.17"
|
||||||
|
},
|
||||||
|
"cells": [
|
||||||
|
{
|
||||||
|
"id": "Hbol",
|
||||||
|
"code_hash": "1d0db38904205bec4d6f6f6a1f6cec3e",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"type": "data",
|
||||||
|
"data": {
|
||||||
|
"text/plain": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"console": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "LPNa",
|
||||||
|
"code_hash": null,
|
||||||
|
"outputs": [],
|
||||||
|
"console": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,85 @@
|
|||||||
|
{
|
||||||
|
"version": "1",
|
||||||
|
"metadata": {
|
||||||
|
"marimo_version": "0.14.17"
|
||||||
|
},
|
||||||
|
"cells": [
|
||||||
|
{
|
||||||
|
"id": "Hbol",
|
||||||
|
"code_hash": "1d0db38904205bec4d6f6f6a1f6cec3e",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"type": "data",
|
||||||
|
"data": {
|
||||||
|
"text/plain": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"console": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "MJUe",
|
||||||
|
"code_hash": "0df0bfd7ff72ff9c8e46d08b9cafe41f",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"type": "data",
|
||||||
|
"data": {
|
||||||
|
"text/html": "<span class=\"markdown prose dark:prose-invert\"><h1 id=\"sqlalchemy-1-inicializacion-del-backend\">SQLAlchemy 1: Inicializaci\u00f3n del backend</h1></span>"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"console": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "vblA",
|
||||||
|
"code_hash": "431dc7f7109c8c3f501ddfb63cd22792",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"type": "data",
|
||||||
|
"data": {
|
||||||
|
"text/html": "<span class=\"markdown prose dark:prose-invert\"><span class=\"paragraph\">Aqui vamos a generar la Base y la session de el backend usando SQLAlchemy, que usar\u00e1n todos los objetos que se registren en la bbdd ademas de las capas de arquitecture_layer que hereden nuestras clases</span></span>"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"console": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "bkHC",
|
||||||
|
"code_hash": "1b2dbc3e0e1a520310bb37cab8c2b812",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"type": "data",
|
||||||
|
"data": {
|
||||||
|
"text/plain": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"console": [
|
||||||
|
{
|
||||||
|
"type": "stream",
|
||||||
|
"name": "stdout",
|
||||||
|
"text": "\u2139\ufe0f /home/lucas/DataProyects/pdf_extraccion/backend/.env ya tiene todas las claves necesarias.\n\u2705 Creado /home/lucas/DataProyects/pdf_extraccion/backend/db/base.py\n\u2705 Creado /home/lucas/DataProyects/pdf_extraccion/backend/db/session.py\n"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "lEQa",
|
||||||
|
"code_hash": "8a9301f35990da46a5875fb8c2d57351",
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"type": "data",
|
||||||
|
"data": {
|
||||||
|
"text/plain": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"console": [
|
||||||
|
{
|
||||||
|
"type": "stream",
|
||||||
|
"name": "stdout",
|
||||||
|
"text": "\u2705 /home/lucas/DataProyects/Snippets_marimo_noteebooks/utils/ArquitectureLayer/Repo.py \u2192 domains/arquitecture_layer/Repo.py\n\u2705 /home/lucas/DataProyects/Snippets_marimo_noteebooks/utils/ArquitectureLayer/Mapper.py \u2192 domains/arquitecture_layer/Mapper.py\n\u2705 /home/lucas/DataProyects/Snippets_marimo_noteebooks/utils/ArquitectureLayer/Model.py \u2192 domains/arquitecture_layer/Model.py\n\u2705 Creado domains/arquitecture_layer/__init__.py\n"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,23 @@
|
|||||||
|
version: '3.8'
|
||||||
|
services:
|
||||||
|
postgres_ext:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- 55432:5432
|
||||||
|
environment:
|
||||||
|
POSTGRES_PASSWORD: mipassword
|
||||||
|
POSTGRES_USER: postgres
|
||||||
|
POSTGRES_DB: basededatos
|
||||||
|
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: {}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
-- Habilitar extensiones solicitadas
|
||||||
|
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;
|
||||||
Reference in New Issue
Block a user