Roudrigus commited on
Commit
cfd9653
·
verified ·
1 Parent(s): da2ec04

Update banco.py

Browse files
Files changed (1) hide show
  1. banco.py +213 -121
banco.py CHANGED
@@ -1,121 +1,213 @@
1
- # -*- coding: utf-8 -*-
2
- from sqlalchemy import create_engine
3
- from sqlalchemy.orm import sessionmaker, declarative_base
4
- import os
5
- from dotenv import load_dotenv
6
- import importlib
7
-
8
- # 🔒 Caminho absoluto do projeto
9
- BASE_DIR = os.path.dirname(os.path.abspath(__file__))
10
-
11
- # Carrega variáveis de ambiente (.env) antes de ler DATABASE_URL
12
- load_dotenv()
13
-
14
- # ============================================================
15
- # 🔀 SUPORTE A DOIS BANCOS (Produção/Teste) COM FALLBACK
16
- # ============================================================
17
- # Tentamos usar o roteador (db_router.py). Se não existir ainda,
18
- # caímos no comportamento original usando apenas DATABASE_URL.
19
- try:
20
- from db_router import (
21
- get_engine as _router_get_engine,
22
- get_session_factory as _router_get_session_factory,
23
- SessionLocal as _router_SessionLocal,
24
- )
25
- _HAS_ROUTER = True
26
- except Exception:
27
- _HAS_ROUTER = False
28
-
29
- # 🔧 Fallback: mesma lógica do seu módulo original — um único DATABASE_URL
30
- DATABASE_URL = os.getenv(
31
- "DATABASE_URL",
32
- f"sqlite:///{os.path.join(BASE_DIR, 'load.db')}"
33
- )
34
-
35
- engine_args = {
36
- "echo": False,
37
- "pool_pre_ping": True,
38
- }
39
-
40
- # Parâmetros específicos para SQLite (apenas se o fallback estiver ativo)
41
- if DATABASE_URL.startswith("sqlite"):
42
- engine_args["connect_args"] = {"check_same_thread": False}
43
-
44
- # ============================================================
45
- # Engine / SessionLocal (com ou sem roteador)
46
- # ============================================================
47
- if _HAS_ROUTER:
48
- # ✅ Usa engine e SessionLocal do banco ATIVO (Produção/Teste), conforme escolha no login
49
- def get_engine():
50
- return _router_get_engine()
51
-
52
- def _session_factory():
53
- return _router_get_session_factory()
54
-
55
- # A SessionLocal do roteador já entrega sessões no banco ativo
56
- SessionLocal = _router_SessionLocal
57
-
58
- else:
59
- # ✅ Fallback: comportamento original com DATABASE_URL único
60
- _engine = create_engine(DATABASE_URL, **engine_args)
61
-
62
- def get_engine():
63
- return _engine
64
-
65
- _SessionFactory = sessionmaker(
66
- autocommit=False,
67
- autoflush=False,
68
- bind=_engine,
69
- )
70
-
71
- def _session_factory():
72
- return _SessionFactory
73
-
74
- # Compatível com seu uso atual: SessionLocal() -> sessão
75
- SessionLocal = _SessionFactory
76
-
77
- # ⚠️ Compatibilidade: expõe 'engine' resolvendo via get_engine()
78
- # Observação importante:
79
- # - Se trocar o banco após a importação deste módulo (via login),
80
- # prefira sempre chamar get_engine() ou criar sessões com SessionLocal(),
81
- # pois 'engine' abaixo é resolvido apenas uma vez (na importação).
82
- engine = get_engine()
83
-
84
- # ORM Base
85
- Base = declarative_base()
86
-
87
- # ============================================================
88
- # 🛠️ Utilitários (opcionais)
89
- # ============================================================
90
- def init_schema():
91
- """
92
- Cria/atualiza as tabelas no banco ATIVO.
93
- Com roteador: aplica no banco escolhido (Produção/Teste).
94
- Sem roteador: aplica no DATABASE_URL padrão.
95
- Use em DEV/TESTE; em produção, prefira migrações (ex.: Alembic).
96
- """
97
- # Importa 'models' de forma tardia e segura (sem wildcard) para registrar todos os mapeamentos
98
- # antes de criar as tabelas. Isso evita import circular no topo.
99
- try:
100
- importlib.import_module("models")
101
- except ModuleNotFoundError:
102
- # Se seus modelos estiverem em outro pacote/caminho, ajuste aqui:
103
- # importlib.import_module("app.models") # exemplo
104
- raise
105
-
106
- Base.metadata.create_all(bind=get_engine())
107
-
108
- def db_info() -> dict:
109
- """
110
- Retorna informações básicas do banco ativo (para debug/UX).
111
- """
112
- eng = get_engine()
113
- try:
114
- url = str(eng.url)
115
- except Exception:
116
- url = DATABASE_URL
117
- return {
118
- "url": url,
119
- "using_router": _HAS_ROUTER,
120
- }
121
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ banco.py
4
+ 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
11
+ import shutil
12
+ from sqlalchemy import create_engine
13
+ from sqlalchemy.orm import sessionmaker, declarative_base
14
+ from dotenv import load_dotenv
15
+
16
+ # Caminho base do projeto
17
+ BASE_DIR = os.path.dirname(os.path.abspath(__file__))
18
+
19
+ # Carrega variáveis (.env) — no Spaces você usa Settings → Secrets
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
+ """
27
+ Garante que exista 'load.db' no diretório do app.
28
+ Se encontrar 'Load.db' (ou outra variação de caixa), copia para 'load.db'.
29
+ Retorna o caminho absoluto de 'load.db'.
30
+ """
31
+ lower = os.path.join(BASE_DIR, "load.db")
32
+ if os.path.exists(lower):
33
+ return lower
34
+
35
+ # Candidatos com caixa diferente
36
+ for cand in ("Load.db", "LOAD.DB", "Load.DB"):
37
+ up = os.path.join(BASE_DIR, cand)
38
+ if os.path.exists(up):
39
+ try:
40
+ shutil.copy(up, lower)
41
+ except Exception:
42
+ # Se falhar a cópia, segue adiante; o sqlite criará um vazio no primeiro uso
43
+ pass
44
+ break
45
+
46
+ return lower
47
+
48
+
49
+ # =========================================================
50
+ # 2) Suporte a roteador (db_router.py) — opcional
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,
57
+ get_session_factory as _router_get_session_factory,
58
+ SessionLocal as _router_SessionLocal,
59
+ )
60
+ _HAS_ROUTER = True
61
+ except Exception:
62
+ _HAS_ROUTER = False
63
+
64
+ # =========================================================
65
+ # 3) Fallback quando NÃO há roteador: construir a URI
66
+ # =========================================================
67
+ def _build_fallback_uri() -> str:
68
+ """
69
+ Monta a URI do banco quando não existe db_router.
70
+ Ordem de preferência:
71
+ 1. DATABASE_URL (completo)
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
+ # 3.1 DATABASE_URL completo
76
+ url = os.getenv("DATABASE_URL")
77
+ if url:
78
+ return url
79
+
80
+ # 3.2 Campos separados
81
+ driver = (os.getenv("DB_DRIVER") or "").strip().lower() # "postgresql", "mysql"
82
+ host = os.getenv("DB_HOST")
83
+ port = os.getenv("DB_PORT")
84
+ user = os.getenv("DB_USER")
85
+ pwd = os.getenv("DB_PASS")
86
+ name = os.getenv("DB_NAME")
87
+
88
+ if driver and host and user and pwd and name:
89
+ # Defaults de porta
90
+ if not port:
91
+ port = "5432" if driver.startswith("post") else "3306"
92
+
93
+ if driver.startswith("post"): # PostgreSQL
94
+ return f"postgresql+psycopg2://{user}:{pwd}@{host}:{port}/{name}"
95
+ elif driver.startswith("mysql"): # MySQL/MariaDB
96
+ return f"mysql+pymysql://{user}:{pwd}@{host}:{port}/{name}"
97
+
98
+ # 3.3 SQLite local (fallback)
99
+ sqlite_path = _ensure_sqlite_case_alias()
100
+ return f"sqlite:///{sqlite_path}"
101
+
102
+
103
+ # =========================================================
104
+ # 4) Engine / SessionLocal
105
+ # =========================================================
106
+ # Observação importante:
107
+ # - Se houver db_router, usamos as fábricas do roteador (engine/sessões por ambiente).
108
+ # - Caso contrário, criamos uma engine única a partir de DATABASE_URL/DB_* ou SQLite.
109
+
110
+ if _HAS_ROUTER:
111
+ # =========== Com roteador ===========
112
+ def get_engine():
113
+ """
114
+ Retorna a engine do banco ATUAL (prod/test/treinamento),
115
+ conforme implementado pelo seu db_router.get_engine().
116
+ """
117
+ return _router_get_engine()
118
+
119
+ def _session_factory():
120
+ """
121
+ Fábrica de Session para o banco ATUAL (vinda do roteador).
122
+ """
123
+ return _router_get_session_factory()
124
+
125
+ # SessionLocal fornecida pelo roteador (respeita o banco atual)
126
+ SessionLocal = _router_SessionLocal
127
+
128
+ else:
129
+ # =========== Fallback sem roteador ===========
130
+ DATABASE_URL = _build_fallback_uri()
131
+
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"):
139
+ # Parâmetros específicos para SQLite
140
+ engine_args["connect_args"] = {"check_same_thread": False}
141
+
142
+ _engine = create_engine(DATABASE_URL, **engine_args)
143
+
144
+ def get_engine():
145
+ """
146
+ Engine fixa do fallback. Se quiser trocar de banco em runtime sem roteador,
147
+ você precisará recriar a engine manualmente (ou adotar db_router).
148
+ """
149
+ return _engine
150
+
151
+ _SessionFactory = sessionmaker(
152
+ autocommit=False,
153
+ autoflush=False,
154
+ bind=_engine,
155
+ )
156
+
157
+ def _session_factory():
158
+ return _SessionFactory
159
+
160
+ # Para compatibilidade com código que usa SessionLocal()
161
+ SessionLocal = _SessionFactory
162
+
163
+
164
+ # =========================================================
165
+ # 5) Expor 'engine' e Base ORM
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()
169
+ # e criar a sessão com SessionLocal() para assegurar o banco ATUAL.
170
+ engine = get_engine()
171
+ Base = declarative_base()
172
+
173
+
174
+ # =========================================================
175
+ # 6) Utilitários (opcionais)
176
+ # =========================================================
177
+ def init_schema():
178
+ """
179
+ Cria/atualiza as tabelas no banco ATUAL.
180
+ • Com roteador: aplica no banco escolhido (Produção/Teste/...).
181
+ • Sem roteador: aplica no DATABASE_URL padrão.
182
+
183
+ Use em DEV/TESTE; em produção, prefira migrações (ex.: Alembic).
184
+ """
185
+ # Importa 'models' de forma tardia/segura para registrar mapeamentos
186
+ import importlib
187
+ try:
188
+ importlib.import_module("models")
189
+ except ModuleNotFoundError:
190
+ # Ajuste se seus modelos estiverem em outro pacote
191
+ # importlib.import_module("app.models")
192
+ raise
193
+
194
+ Base.metadata.create_all(bind=get_engine())
195
+
196
+
197
+ def db_info() -> dict:
198
+ """
199
+ Retorna informações básicas do banco ativo (para debug/UX).
200
+ """
201
+ eng = get_engine()
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:
209
+ url = "(não disponível)"
210
+ return {
211
+ "url": url,
212
+ "using_router": _HAS_ROUTER,
213
+ }