Add OpenAI and PostgreSQL credential management

- Implemented OpenAICredencial class for managing OpenAI API keys.
- Created OpenAICredencialModel and OpenAICredencialMapper for SQLAlchemy integration.
- Developed OpenAICredencialRepo for CRUD operations on OpenAI credentials.
- Established OpenAICliente class for interacting with OpenAI API.
- Introduced PostgresCredencial class for managing PostgreSQL connection details.
- Created PostgresCredencialModel and PostgresCredencialMapper for SQLAlchemy integration.
- Developed PostgresCredencialRepo for CRUD operations on PostgreSQL credentials.
- Added base connection class and PostgreSQL connection implementation.
- Included environment variable loading for sensitive data management.
This commit is contained in:
2025-05-05 23:54:17 +02:00
parent 7b6f525809
commit 613cd90662
56 changed files with 16237 additions and 131 deletions
View File
+21
View File
@@ -0,0 +1,21 @@
class OpenAICredencial:
def __init__(self, titulo: str, api_key: str, organizacion: str = None):
"""
:param titulo: Nombre descriptivo para esta credencial.
:param api_key: Clave secreta de la API de OpenAI.
:param organizacion: (Opcional) ID de la organización asociada a la cuenta de OpenAI.
"""
self.titulo = titulo
self.api_key = api_key
self.organizacion = organizacion
def get_headers(self) -> dict:
"""
Retorna los encabezados necesarios para autenticar una petición HTTP a OpenAI.
"""
headers = {
"Authorization": f"Bearer {self.api_key}"
}
if self.organizacion:
headers["OpenAI-Organization"] = self.organizacion
return headers
+92
View File
@@ -0,0 +1,92 @@
import os
import base64
from dotenv import load_dotenv
from sqlalchemy import Column, Integer, String
from src.ConexionSql.Base_conexion import ConexionBase
from src.base import Base
from src.ApiKeys.openai_apikey import OpenAICredencial
from security.Encriptar import Encriptar_fernet
from entrypoint import ENV_PATH
# ----------------------
# Cargar clave maestra
# ----------------------
load_dotenv(ENV_PATH)
pssword = os.getenv('MASTER_PASSWORD')
if pssword is None:
raise ValueError("MASTER_PASSWORD no está definida en el archivo .env")
# ----------------------
# MODELO (SQLAlchemy)
# ----------------------
class OpenAICredencialModel(Base):
__tablename__ = 'openai_credenciales'
id = Column(Integer, primary_key=True)
titulo = Column(String, nullable=False)
api_key = Column(String, nullable=False) # Encriptada como base64 string
organizacion = Column(String, nullable=True)
# ----------------------
# MAPPER
# ----------------------
class OpenAICredencialMapper:
@staticmethod
def to_dict(obj: OpenAICredencial) -> dict:
return {
"titulo": obj.titulo,
"api_key": base64.b64encode(
Encriptar_fernet.encriptar(obj.api_key, pssword)
).decode('utf-8'),
"organizacion": obj.organizacion
}
@staticmethod
def from_dict(data: dict) -> OpenAICredencial:
return OpenAICredencial(
titulo=data["titulo"],
api_key=Encriptar_fernet.desencriptar(
base64.b64decode(data["api_key"]), pssword
),
organizacion=data.get("organizacion")
)
@staticmethod
def from_model(model: OpenAICredencialModel) -> OpenAICredencial:
return OpenAICredencial(
titulo=model.titulo,
api_key=Encriptar_fernet.desencriptar(
base64.b64decode(model.api_key), pssword
),
organizacion=model.organizacion
)
# ----------------------
# REPO
# ----------------------
class OpenAICredencialRepo:
def __init__(self, conexion: ConexionBase):
self.session = conexion.get_session()
def add(self, credencial: OpenAICredencial) -> int:
data = OpenAICredencialMapper.to_dict(credencial)
model = OpenAICredencialModel(**data)
self.session.add(model)
self.session.commit()
return model.id
def get_all(self) -> list[OpenAICredencial]:
models = self.session.query(OpenAICredencialModel).all()
return [OpenAICredencialMapper.from_model(m) for m in models]
def get_by_titulo(self, titulo: str) -> OpenAICredencial | None:
model = self.session.query(OpenAICredencialModel).filter_by(titulo=titulo).first()
return OpenAICredencialMapper.from_model(model) if model else None
def get_by_id(self, id_: int) -> OpenAICredencial | None:
model = self.session.get(OpenAICredencialModel, id_)
return OpenAICredencialMapper.from_model(model) if model else None
+59
View File
@@ -0,0 +1,59 @@
from openai import OpenAI
from src.ApiKeys.openai_apikey import OpenAICredencial
class OpenAICliente:
def __init__(self, credencial: OpenAICredencial):
"""
Inicializa el cliente de OpenAI con una instancia de OpenAICredencial.
"""
self.credencial = credencial
self.client = OpenAI(api_key=self.credencial.api_key)
if self.credencial.organizacion:
self.client.organization = self.credencial.organizacion
# --- Chat Completions ---
def chat_completion(self, model: str, messages: list, **kwargs):
return self.client.chat.completions.create(model=model, messages=messages, **kwargs)
# --- Text Completions (legacy) ---
def completion(self, model: str, prompt: str, **kwargs):
return self.client.completions.create(model=model, prompt=prompt, **kwargs)
# --- Text Edits ---
def edit(self, model: str, input: str, instruction: str, **kwargs):
return self.client.edits.create(model=model, input=input, instruction=instruction, **kwargs)
# --- Embeddings ---
def embedding(self, model: str, input: str | list[str], **kwargs):
return self.client.embeddings.create(model=model, input=input, **kwargs)
# --- Moderation ---
def moderation(self, input: str | list[str], model="text-moderation-latest", **kwargs):
return self.client.moderations.create(input=input, model=model, **kwargs)
# --- Image Generation ---
def image_generate(self, prompt: str, **kwargs):
return self.client.images.generate(prompt=prompt, **kwargs)
# --- Audio Transcription ---
def audio_transcribe(self, model: str, file_path: str, **kwargs):
with open(file_path, "rb") as f:
return self.client.audio.transcriptions.create(model=model, file=f, **kwargs)
# --- Audio Translation ---
def audio_translate(self, model: str, file_path: str, **kwargs):
with open(file_path, "rb") as f:
return self.client.audio.translations.create(model=model, file=f, **kwargs)
# --- File Upload ---
def file_upload(self, file_path: str, purpose="fine-tune", **kwargs):
with open(file_path, "rb") as f:
return self.client.files.create(file=f, purpose=purpose, **kwargs)
# --- File List ---
def file_list(self, **kwargs):
return self.client.files.list(**kwargs)
# --- File Delete ---
def file_delete(self, file_id: str, **kwargs):
return self.client.files.delete(file_id=file_id, **kwargs)
View File
+7
View File
@@ -0,0 +1,7 @@
from abc import ABC, abstractmethod
from sqlalchemy.orm import Session
class ConexionBase(ABC):
@abstractmethod
def get_session(self) -> Session:
pass
+45
View File
@@ -0,0 +1,45 @@
from datetime import datetime, timezone
from sqlalchemy import create_engine, text
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.orm import sessionmaker
from src.ConexionSql.Base_conexion import ConexionBase
from src.Credenciales.postgres_credencial import PostgresCredencial
class PostgresConexion(ConexionBase):
def __init__(self, *args, **kwargs):
self.estado = "pendiente"
self.timestamp = datetime.now(timezone.utc)
if args and isinstance(args[0], PostgresCredencial):
credencial = args[0]
self.host = credencial.host
self.port = credencial.port
self.dbname = credencial.dbname
self.user = credencial.user
self.password = credencial.password # se guarda la contraseña
uri = credencial.get_uri()
else:
self.user = kwargs.get("user")
self.password = kwargs.get("password") # se guarda la contraseña
self.host = kwargs.get("host")
self.port = kwargs.get("port", 5432)
self.dbname = kwargs.get("db") or kwargs.get("dbname")
uri = f"postgresql://{self.user}:{self.password}@{self.host}:{self.port}/{self.dbname}"
self.engine = create_engine(uri)
self.SessionLocal = sessionmaker(bind=self.engine)
def get_session(self):
return self.SessionLocal()
def probar_conexion(self) -> bool:
try:
with self.engine.connect() as connection:
connection.execute(text("SELECT 1"))
self.estado = "exito"
return True
except SQLAlchemyError:
self.estado = "fallo"
return False
View File
View File
+14
View File
@@ -0,0 +1,14 @@
class PostgresCredencial:
def __init__(self, titulo: str, host: str, port: int, dbname: str, user: str, password: str):
self.titulo = titulo
self.host = host
self.port = port
self.dbname = dbname
self.user = user
self.password = password
def get_uri(self) -> str:
"""
Retorna una URI de conexión para PostgreSQL.
"""
return f"postgresql://{self.user}:{self.password}@{self.host}:{self.port}/{self.dbname}"
+106
View File
@@ -0,0 +1,106 @@
import os
from dotenv import load_dotenv
from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import relationship
from src.ConexionSql.Base_conexion import ConexionBase
from src.base import Base
from src.Credenciales.postgres_credencial import PostgresCredencial
from security.Encriptar import Encriptar_fernet
# ----------------------
# Cargar clave maestra
# ----------------------
from entrypoint import ENV_PATH
load_dotenv(ENV_PATH)
pssword = os.getenv('MASTER_PASSWORD')
if pssword is None:
raise ValueError("MASTER_PASSWORD no está definida en el archivo .env")
# ----------------------
# MODELO (SQLAlchemy)
# ----------------------
class PostgresCredencialModel(Base):
__tablename__ = 'postgres_credenciales'
id = Column(Integer, primary_key=True)
titulo = Column(String, nullable=False)
host = Column(String, nullable=False)
port = Column(Integer, nullable=False)
dbname = Column(String, nullable=False)
user = Column(String, nullable=False)
password = Column(String, nullable=False) # Encriptada como base64 string
# ----------------------
# MAPPER
# ----------------------
import base64
class PostgresCredencialMapper:
@staticmethod
def to_dict(obj: PostgresCredencial) -> dict:
return {
"titulo": obj.titulo,
"host": obj.host,
"port": obj.port,
"dbname": obj.dbname,
"user": obj.user,
"password": base64.b64encode(
Encriptar_fernet.encriptar(obj.password, pssword)
).decode('utf-8')
}
@staticmethod
def from_dict(data: dict) -> PostgresCredencial:
return PostgresCredencial(
titulo=data["titulo"],
host=data["host"],
port=data["port"],
dbname=data["dbname"],
user=data["user"],
password=Encriptar_fernet.desencriptar(
base64.b64decode(data["password"]), pssword
)
)
@staticmethod
def from_model(model: PostgresCredencialModel) -> PostgresCredencial:
return PostgresCredencial(
titulo=model.titulo,
host=model.host,
port=model.port,
dbname=model.dbname,
user=model.user,
password=Encriptar_fernet.desencriptar(
base64.b64decode(model.password), pssword
)
)
# ----------------------
# REPO
# ----------------------
class PostgresCredencialRepo:
def __init__(self, conexion: ConexionBase):
self.session = conexion.get_session()
def add(self, credencial: PostgresCredencial) -> int:
data = PostgresCredencialMapper.to_dict(credencial)
model = PostgresCredencialModel(**data)
self.session.add(model)
self.session.commit()
return model.id
def get_all(self) -> list[PostgresCredencial]:
models = self.session.query(PostgresCredencialModel).all()
return [PostgresCredencialMapper.from_model(m) for m in models]
def get_by_titulo(self, titulo: str) -> PostgresCredencial | None:
model = self.session.query(PostgresCredencialModel).filter_by(titulo=titulo).first()
return PostgresCredencialMapper.from_model(model) if model else None
def get_by_id(self, id_: int) -> PostgresCredencial | None:
model = self.session.get(PostgresCredencialModel, id_)
return PostgresCredencialMapper.from_model(model) if model else None
View File
+3
View File
@@ -0,0 +1,3 @@
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()