import re import fdb import datetime import decimal from typing import Optional def _safe(v): if v is None: return None if isinstance(v, datetime.datetime): return v.strftime("%Y-%m-%dT%H:%M:%S") if isinstance(v, datetime.date): return v.isoformat() if isinstance(v, datetime.time): return v.strftime("%H:%M:%S") if isinstance(v, decimal.Decimal): return float(v) if isinstance(v, (bytes, bytearray, memoryview)): return f"" if isinstance(v, (int, float, bool)): return v # strings: strip any spaces around colons (Firebird sometimes returns "12: 47: 37") s = str(v) return re.sub(r'\s*:\s*', ':', s) if ':' in s else s class FirebirdService: def __init__(self): self.conn = None def connect(self, host: str, port: int, database: str, user: str, password: str): try: self.conn = fdb.connect( host=host, port=port, database=database, user=user, password=password, charset="UTF8", ) return True, "Conexión exitosa" except Exception as e: return False, str(e) def disconnect(self): if self.conn: self.conn.close() self.conn = None def is_connected(self) -> bool: return self.conn is not None def test_connection(self) -> tuple: if not self.conn: return False, "No hay conexión activa" try: cur = self.conn.cursor() cur.execute("SELECT 1 FROM RDB$DATABASE") cur.fetchone() return True, "Conexión OK" except Exception as e: return False, str(e) def execute_query(self, query: str, params: Optional[dict] = None) -> tuple: if not self.conn: return False, "No hay conexión activa", [] try: cur = self.conn.cursor() if params: # fdb no soporta :name — convertir a positional ? param_names = re.findall(r':([a-zA-Z_][a-zA-Z0-9_]*)', query) if param_names: positional_sql = re.sub(r':[a-zA-Z_][a-zA-Z0-9_]*', '?', query) positional_vals = [params[n] for n in param_names] cur.execute(positional_sql, positional_vals) else: cur.execute(query) else: cur.execute(query) columns = [desc[0] for desc in cur.description] if cur.description else [] rows = cur.fetchall() return True, "", [{c: _safe(v) for c, v in zip(columns, row)} for row in rows] except Exception as e: return False, str(e), [] def _port(val, default=3050) -> int: try: return int(val) except (TypeError, ValueError): return default def get_firebird_from_config(configs: dict) -> tuple: fb = FirebirdService() ok, msg = fb.connect( configs.get("firebird_host", "localhost"), _port(configs.get("firebird_port"), 3050), configs.get("firebird_database", ""), configs.get("firebird_user", "SYSDBA"), configs.get("firebird_password", "masterkey"), ) return fb, ok, msg