Update banco.py
Browse files
banco.py
CHANGED
|
@@ -5,6 +5,7 @@ Compatível com:
|
|
| 5 |
- Roteamento por ambiente (db_router.py): produção/teste/treinamento
|
| 6 |
- Fallback: um único DATABASE_URL vindo de env/Secrets
|
| 7 |
- Postgres / MySQL / SQLite (c/ alias de case para load.db no Linux)
|
|
|
|
| 8 |
"""
|
| 9 |
|
| 10 |
import os
|
|
@@ -20,7 +21,7 @@ BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
| 20 |
load_dotenv()
|
| 21 |
|
| 22 |
# =========================================================
|
| 23 |
-
# 1) Correção de case para SQLite (Load.db → load.db)
|
| 24 |
# =========================================================
|
| 25 |
def _ensure_sqlite_case_alias() -> str:
|
| 26 |
"""
|
|
@@ -47,10 +48,42 @@ def _ensure_sqlite_case_alias() -> str:
|
|
| 47 |
|
| 48 |
|
| 49 |
# =========================================================
|
| 50 |
-
# 2)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
# =========================================================
|
| 52 |
# Se existir um roteador, delegamos a criação da engine e da SessionLocal
|
| 53 |
-
# conforme o "banco atual" selecionado (ex.: prod/test).
|
| 54 |
try:
|
| 55 |
from db_router import (
|
| 56 |
get_engine as _router_get_engine,
|
|
@@ -61,8 +94,9 @@ try:
|
|
| 61 |
except Exception:
|
| 62 |
_HAS_ROUTER = False
|
| 63 |
|
|
|
|
| 64 |
# =========================================================
|
| 65 |
-
#
|
| 66 |
# =========================================================
|
| 67 |
def _build_fallback_uri() -> str:
|
| 68 |
"""
|
|
@@ -72,12 +106,13 @@ def _build_fallback_uri() -> str:
|
|
| 72 |
2. Variáveis separadas: DB_DRIVER, DB_HOST, DB_PORT, DB_USER, DB_PASS, DB_NAME
|
| 73 |
3. SQLite local em 'load.db'
|
| 74 |
"""
|
| 75 |
-
#
|
| 76 |
url = os.getenv("DATABASE_URL")
|
| 77 |
if url:
|
| 78 |
-
|
|
|
|
| 79 |
|
| 80 |
-
#
|
| 81 |
driver = (os.getenv("DB_DRIVER") or "").strip().lower() # "postgresql", "mysql"
|
| 82 |
host = os.getenv("DB_HOST")
|
| 83 |
port = os.getenv("DB_PORT")
|
|
@@ -95,13 +130,13 @@ def _build_fallback_uri() -> str:
|
|
| 95 |
elif driver.startswith("mysql"): # MySQL/MariaDB
|
| 96 |
return f"mysql+pymysql://{user}:{pwd}@{host}:{port}/{name}"
|
| 97 |
|
| 98 |
-
#
|
| 99 |
sqlite_path = _ensure_sqlite_case_alias()
|
| 100 |
-
return f"sqlite:///{sqlite_path}"
|
| 101 |
|
| 102 |
|
| 103 |
# =========================================================
|
| 104 |
-
#
|
| 105 |
# =========================================================
|
| 106 |
# Observação importante:
|
| 107 |
# - Se houver db_router, usamos as fábricas do roteador (engine/sessões por ambiente).
|
|
@@ -132,7 +167,6 @@ else:
|
|
| 132 |
engine_args = {
|
| 133 |
"echo": False, # defina True para depuração de SQL
|
| 134 |
"pool_pre_ping": True, # valida conexões antes de usar
|
| 135 |
-
# "future": True, # opcional (SQLAlchemy 2.x APIs)
|
| 136 |
}
|
| 137 |
|
| 138 |
if DATABASE_URL.startswith("sqlite"):
|
|
@@ -162,7 +196,7 @@ else:
|
|
| 162 |
|
| 163 |
|
| 164 |
# =========================================================
|
| 165 |
-
#
|
| 166 |
# =========================================================
|
| 167 |
# Atenção: 'engine' é resolvido no momento da importação.
|
| 168 |
# Se você troca de banco após importar 'banco', prefira usar get_engine()
|
|
@@ -172,7 +206,7 @@ Base = declarative_base()
|
|
| 172 |
|
| 173 |
|
| 174 |
# =========================================================
|
| 175 |
-
#
|
| 176 |
# =========================================================
|
| 177 |
def init_schema():
|
| 178 |
"""
|
|
@@ -202,7 +236,6 @@ def db_info() -> dict:
|
|
| 202 |
try:
|
| 203 |
url = str(eng.url)
|
| 204 |
except Exception:
|
| 205 |
-
# Quando sem roteador, se falhar, tenta DATABASE_URL/DB_* do fallback
|
| 206 |
try:
|
| 207 |
url = DATABASE_URL # type: ignore[name-defined]
|
| 208 |
except Exception:
|
|
|
|
| 5 |
- Roteamento por ambiente (db_router.py): produção/teste/treinamento
|
| 6 |
- Fallback: um único DATABASE_URL vindo de env/Secrets
|
| 7 |
- Postgres / MySQL / SQLite (c/ alias de case para load.db no Linux)
|
| 8 |
+
- Garantia de criação do diretório pai do SQLite (evita 'unable to open database file')
|
| 9 |
"""
|
| 10 |
|
| 11 |
import os
|
|
|
|
| 21 |
load_dotenv()
|
| 22 |
|
| 23 |
# =========================================================
|
| 24 |
+
# 1) Correção de case para SQLite (Load.db → load.db) — opcional
|
| 25 |
# =========================================================
|
| 26 |
def _ensure_sqlite_case_alias() -> str:
|
| 27 |
"""
|
|
|
|
| 48 |
|
| 49 |
|
| 50 |
# =========================================================
|
| 51 |
+
# 2) Helper: garantir diretório pai do arquivo SQLite
|
| 52 |
+
# =========================================================
|
| 53 |
+
def _ensure_parent_dir_sqlite(sqlite_url: str) -> str:
|
| 54 |
+
"""
|
| 55 |
+
Garante que o diretório pai do arquivo SQLite exista.
|
| 56 |
+
Caso não consiga criar (permissão), cai para ~/.ioirun/<arquivo>.db
|
| 57 |
+
Retorna a URL (que pode ser ajustada para fallback).
|
| 58 |
+
"""
|
| 59 |
+
if not sqlite_url or not sqlite_url.startswith("sqlite"):
|
| 60 |
+
return sqlite_url
|
| 61 |
+
|
| 62 |
+
# Extrai caminho do arquivo a partir da URL
|
| 63 |
+
# Formatos: sqlite:///relativo.db | sqlite:////abs/path/to.db
|
| 64 |
+
path = sqlite_url.replace("sqlite:///", "", 1)
|
| 65 |
+
# se vier com // (pouco comum), normaliza
|
| 66 |
+
if path.startswith("//"):
|
| 67 |
+
path = path[1:]
|
| 68 |
+
file_path = os.path.abspath(path)
|
| 69 |
+
parent = os.path.dirname(file_path)
|
| 70 |
+
|
| 71 |
+
try:
|
| 72 |
+
os.makedirs(parent, exist_ok=True)
|
| 73 |
+
return sqlite_url
|
| 74 |
+
except Exception:
|
| 75 |
+
# fallback para HOME (gravável no Spaces)
|
| 76 |
+
home_dir = os.path.join(os.path.expanduser("~"), ".ioirun")
|
| 77 |
+
os.makedirs(home_dir, exist_ok=True)
|
| 78 |
+
alt = os.path.join(home_dir, os.path.basename(file_path))
|
| 79 |
+
return f"sqlite:///{alt}"
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
# =========================================================
|
| 83 |
+
# 3) Suporte a roteador (db_router.py) — opcional
|
| 84 |
# =========================================================
|
| 85 |
# Se existir um roteador, delegamos a criação da engine e da SessionLocal
|
| 86 |
+
# conforme o "banco atual" selecionado (ex.: prod/test/treinamento).
|
| 87 |
try:
|
| 88 |
from db_router import (
|
| 89 |
get_engine as _router_get_engine,
|
|
|
|
| 94 |
except Exception:
|
| 95 |
_HAS_ROUTER = False
|
| 96 |
|
| 97 |
+
|
| 98 |
# =========================================================
|
| 99 |
+
# 4) Fallback quando NÃO há roteador: construir a URI
|
| 100 |
# =========================================================
|
| 101 |
def _build_fallback_uri() -> str:
|
| 102 |
"""
|
|
|
|
| 106 |
2. Variáveis separadas: DB_DRIVER, DB_HOST, DB_PORT, DB_USER, DB_PASS, DB_NAME
|
| 107 |
3. SQLite local em 'load.db'
|
| 108 |
"""
|
| 109 |
+
# 4.1 DATABASE_URL completo
|
| 110 |
url = os.getenv("DATABASE_URL")
|
| 111 |
if url:
|
| 112 |
+
# Garante diretório pai no caso de sqlite
|
| 113 |
+
return _ensure_parent_dir_sqlite(url)
|
| 114 |
|
| 115 |
+
# 4.2 Campos separados
|
| 116 |
driver = (os.getenv("DB_DRIVER") or "").strip().lower() # "postgresql", "mysql"
|
| 117 |
host = os.getenv("DB_HOST")
|
| 118 |
port = os.getenv("DB_PORT")
|
|
|
|
| 130 |
elif driver.startswith("mysql"): # MySQL/MariaDB
|
| 131 |
return f"mysql+pymysql://{user}:{pwd}@{host}:{port}/{name}"
|
| 132 |
|
| 133 |
+
# 4.3 SQLite local (fallback)
|
| 134 |
sqlite_path = _ensure_sqlite_case_alias()
|
| 135 |
+
return _ensure_parent_dir_sqlite(f"sqlite:///{sqlite_path}")
|
| 136 |
|
| 137 |
|
| 138 |
# =========================================================
|
| 139 |
+
# 5) Engine / SessionLocal
|
| 140 |
# =========================================================
|
| 141 |
# Observação importante:
|
| 142 |
# - Se houver db_router, usamos as fábricas do roteador (engine/sessões por ambiente).
|
|
|
|
| 167 |
engine_args = {
|
| 168 |
"echo": False, # defina True para depuração de SQL
|
| 169 |
"pool_pre_ping": True, # valida conexões antes de usar
|
|
|
|
| 170 |
}
|
| 171 |
|
| 172 |
if DATABASE_URL.startswith("sqlite"):
|
|
|
|
| 196 |
|
| 197 |
|
| 198 |
# =========================================================
|
| 199 |
+
# 6) Expor 'engine' e Base ORM
|
| 200 |
# =========================================================
|
| 201 |
# Atenção: 'engine' é resolvido no momento da importação.
|
| 202 |
# Se você troca de banco após importar 'banco', prefira usar get_engine()
|
|
|
|
| 206 |
|
| 207 |
|
| 208 |
# =========================================================
|
| 209 |
+
# 7) Utilitários (opcionais)
|
| 210 |
# =========================================================
|
| 211 |
def init_schema():
|
| 212 |
"""
|
|
|
|
| 236 |
try:
|
| 237 |
url = str(eng.url)
|
| 238 |
except Exception:
|
|
|
|
| 239 |
try:
|
| 240 |
url = DATABASE_URL # type: ignore[name-defined]
|
| 241 |
except Exception:
|