fixes with SQL Alchemy to avoid precaching
This commit is contained in:
@@ -2,6 +2,7 @@ from application.ports.user_repository import UserRepository
|
||||
from domain.users import User
|
||||
from infrastructure.adapters.persistence.models import UserModel
|
||||
from infrastructure.adapters.persistence.db import SessionLocal
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List, Optional
|
||||
import logging
|
||||
|
||||
@@ -9,12 +10,25 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class UserRepositorySQL(UserRepository):
|
||||
"""Implementación del repositorio de Usuarios usando SQLAlchemy (MySQL)"""
|
||||
|
||||
def __init__(self, db_session=None):
|
||||
self.db = db_session or SessionLocal()
|
||||
|
||||
|
||||
def __init__(self, db_session: Session = None):
|
||||
# FIXED: la sesión se guarda solo si se inyecta explícitamente.
|
||||
# Cuando db_session es None, cada método abre y cierra su propia
|
||||
# sesión, eliminando la caché de primer nivel entre requests.
|
||||
self._injected_session = db_session
|
||||
|
||||
def _get_session(self):
|
||||
"""
|
||||
Devuelve la sesión inyectada si existe, o crea una nueva.
|
||||
El caller es responsable de cerrarla cuando no fue inyectada.
|
||||
"""
|
||||
if self._injected_session is not None:
|
||||
return self._injected_session, False # (sesión, es_propia)
|
||||
return SessionLocal(), True
|
||||
|
||||
def save(self, user: User) -> User:
|
||||
"""Guarda un nuevo usuario con manejo de transacciones"""
|
||||
db, is_own = self._get_session()
|
||||
try:
|
||||
db_user = UserModel(
|
||||
nombre=user.nombre,
|
||||
@@ -29,126 +43,164 @@ class UserRepositorySQL(UserRepository):
|
||||
biografia=user.biografia,
|
||||
is_admin=user.is_admin
|
||||
)
|
||||
self.db.add(db_user)
|
||||
self.db.commit()
|
||||
self.db.refresh(db_user)
|
||||
db.add(db_user)
|
||||
db.commit()
|
||||
db.refresh(db_user)
|
||||
logger.info(f"Usuario guardado exitosamente: {db_user.user_id}")
|
||||
return self._to_domain(db_user)
|
||||
except Exception as e:
|
||||
self.db.rollback()
|
||||
db.rollback()
|
||||
logger.error(f"Error al guardar usuario: {e}", exc_info=True)
|
||||
raise
|
||||
|
||||
finally:
|
||||
if is_own:
|
||||
db.close()
|
||||
|
||||
def find_by_id(self, user_id: int) -> Optional[User]:
|
||||
"""Obtiene un usuario por ID"""
|
||||
db, is_own = self._get_session()
|
||||
try:
|
||||
db_user = self.db.query(UserModel).filter(UserModel.user_id == user_id).first()
|
||||
db_user = db.query(UserModel).filter(UserModel.user_id == user_id).first()
|
||||
if db_user:
|
||||
return self._to_domain(db_user)
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.error(f"Error al buscar usuario por ID {user_id}: {e}", exc_info=True)
|
||||
raise
|
||||
|
||||
finally:
|
||||
if is_own:
|
||||
db.close()
|
||||
|
||||
def find_by_email(self, email: str) -> Optional[User]:
|
||||
"""Obtiene un usuario por email"""
|
||||
db, is_own = self._get_session()
|
||||
try:
|
||||
db_user = self.db.query(UserModel).filter(UserModel.email == email).first()
|
||||
db_user = db.query(UserModel).filter(UserModel.email == email).first()
|
||||
if db_user:
|
||||
return self._to_domain(db_user)
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.error(f"Error al buscar usuario por email {email}: {e}", exc_info=True)
|
||||
raise
|
||||
|
||||
finally:
|
||||
if is_own:
|
||||
db.close()
|
||||
|
||||
def find_by_email_with_password(self, email: str) -> Optional[UserModel]:
|
||||
"""Obtiene un usuario por email incluyendo el hash de contraseña (para autenticación)"""
|
||||
db, is_own = self._get_session()
|
||||
try:
|
||||
db_user = self.db.query(UserModel).filter(UserModel.email == email).first()
|
||||
db_user = db.query(UserModel).filter(UserModel.email == email).first()
|
||||
# FIXED: si la sesión es propia, expunge para poder usar el objeto
|
||||
# fuera de la sesión sin que SQLAlchemy intente lazy-load nada.
|
||||
if db_user and is_own:
|
||||
db.expunge(db_user)
|
||||
return db_user
|
||||
except Exception as e:
|
||||
logger.error(f"Error al buscar usuario por email {email}: {e}", exc_info=True)
|
||||
raise
|
||||
|
||||
finally:
|
||||
if is_own:
|
||||
db.close()
|
||||
|
||||
def find_all(self) -> List[User]:
|
||||
"""Obtiene todos los usuarios"""
|
||||
db, is_own = self._get_session()
|
||||
try:
|
||||
db_users = self.db.query(UserModel).all()
|
||||
db_users = db.query(UserModel).all()
|
||||
return [self._to_domain(user) for user in db_users]
|
||||
except Exception as e:
|
||||
logger.error(f"Error al obtener todos los usuarios: {e}", exc_info=True)
|
||||
raise
|
||||
|
||||
finally:
|
||||
if is_own:
|
||||
db.close()
|
||||
|
||||
def update(self, user: User) -> User:
|
||||
"""Actualiza un usuario con manejo de transacciones"""
|
||||
db, is_own = self._get_session()
|
||||
try:
|
||||
db_user = self.db.query(UserModel).filter(UserModel.user_id == user.user_id).first()
|
||||
db_user = db.query(UserModel).filter(UserModel.user_id == user.user_id).first()
|
||||
if not db_user:
|
||||
logger.warning(f"Usuario no encontrado para actualizar: {user.user_id}")
|
||||
return user
|
||||
|
||||
|
||||
db_user.nombre = user.nombre
|
||||
db_user.apellido = user.apellido
|
||||
db_user.calificacion = user.calificacion
|
||||
db_user.numero_reportes = user.numero_reportes
|
||||
db_user.url_foto_perfil = user.url_foto_perfil
|
||||
db_user.biografia = user.biografia
|
||||
self.db.commit()
|
||||
self.db.refresh(db_user)
|
||||
db.commit()
|
||||
db.refresh(db_user)
|
||||
logger.info(f"Usuario actualizado exitosamente: {user.user_id}")
|
||||
return self._to_domain(db_user)
|
||||
except Exception as e:
|
||||
self.db.rollback()
|
||||
db.rollback()
|
||||
logger.error(f"Error al actualizar usuario {user.user_id}: {e}", exc_info=True)
|
||||
raise
|
||||
|
||||
finally:
|
||||
if is_own:
|
||||
db.close()
|
||||
|
||||
def delete(self, user_id: int) -> bool:
|
||||
"""Elimina un usuario con manejo de transacciones"""
|
||||
db, is_own = self._get_session()
|
||||
try:
|
||||
db_user = self.db.query(UserModel).filter(UserModel.user_id == user_id).first()
|
||||
db_user = db.query(UserModel).filter(UserModel.user_id == user_id).first()
|
||||
if db_user:
|
||||
self.db.delete(db_user)
|
||||
self.db.commit()
|
||||
db.delete(db_user)
|
||||
db.commit()
|
||||
logger.info(f"Usuario eliminado exitosamente: {user_id}")
|
||||
return True
|
||||
logger.warning(f"Usuario no encontrado para eliminar: {user_id}")
|
||||
return False
|
||||
except Exception as e:
|
||||
self.db.rollback()
|
||||
db.rollback()
|
||||
logger.error(f"Error al eliminar usuario {user_id}: {e}", exc_info=True)
|
||||
raise
|
||||
|
||||
finally:
|
||||
if is_own:
|
||||
db.close()
|
||||
|
||||
def increment_reports(self, user_id: int) -> None:
|
||||
"""Incrementa el contador de reportes con manejo de transacciones"""
|
||||
db, is_own = self._get_session()
|
||||
try:
|
||||
db_user = self.db.query(UserModel).filter(UserModel.user_id == user_id).first()
|
||||
db_user = db.query(UserModel).filter(UserModel.user_id == user_id).first()
|
||||
if db_user:
|
||||
db_user.numero_reportes += 1
|
||||
self.db.commit()
|
||||
db.commit()
|
||||
logger.info(f"Contador de reportes incrementado para usuario: {user_id}")
|
||||
else:
|
||||
logger.warning(f"Usuario no encontrado para incrementar reportes: {user_id}")
|
||||
except Exception as e:
|
||||
self.db.rollback()
|
||||
db.rollback()
|
||||
logger.error(f"Error al incrementar reportes del usuario {user_id}: {e}", exc_info=True)
|
||||
raise
|
||||
|
||||
finally:
|
||||
if is_own:
|
||||
db.close()
|
||||
|
||||
def update_rating(self, user_id: int, new_rating: float) -> None:
|
||||
"""Actualiza la calificación de un usuario con manejo de transacciones"""
|
||||
db, is_own = self._get_session()
|
||||
try:
|
||||
db_user = self.db.query(UserModel).filter(UserModel.user_id == user_id).first()
|
||||
db_user = db.query(UserModel).filter(UserModel.user_id == user_id).first()
|
||||
if db_user:
|
||||
# Asegurar que la calificación esté en el rango 0-100
|
||||
db_user.calificacion = max(0, min(100, new_rating))
|
||||
self.db.commit()
|
||||
db.commit()
|
||||
logger.info(f"Calificación actualizada para usuario {user_id}: {db_user.calificacion}")
|
||||
else:
|
||||
logger.warning(f"Usuario no encontrado para actualizar calificación: {user_id}")
|
||||
except Exception as e:
|
||||
self.db.rollback()
|
||||
db.rollback()
|
||||
logger.error(f"Error al actualizar calificación del usuario {user_id}: {e}", exc_info=True)
|
||||
raise
|
||||
|
||||
finally:
|
||||
if is_own:
|
||||
db.close()
|
||||
|
||||
def _to_domain(self, db_user: UserModel) -> User:
|
||||
"""Convierte un modelo SQLAlchemy a un objeto de dominio"""
|
||||
return User(
|
||||
@@ -163,4 +215,4 @@ class UserRepositorySQL(UserRepository):
|
||||
url_foto_perfil=db_user.url_foto_perfil,
|
||||
biografia=db_user.biografia,
|
||||
is_admin=db_user.is_admin
|
||||
)
|
||||
)
|
||||
@@ -1,4 +1,4 @@
|
||||
from fastapi import APIRouter, HTTPException, status, Depends
|
||||
from fastapi import APIRouter, HTTPException, status
|
||||
from infrastructure.api.users.schemas import (
|
||||
UserCreateRequest, UserUpdateRequest, UserResponse,
|
||||
UserLoginRequest, UserLoginResponse
|
||||
@@ -14,7 +14,6 @@ import logging
|
||||
from datetime import datetime
|
||||
|
||||
router = APIRouter()
|
||||
user_repo = UserRepositorySQL()
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -44,6 +43,7 @@ async def login_user(credentials: UserLoginRequest):
|
||||
- email: Email confirmado
|
||||
"""
|
||||
try:
|
||||
user_repo = UserRepositorySQL() # sesión nueva por request
|
||||
get_use_case = GetUserByEmail(user_repo)
|
||||
user = get_use_case.execute(credentials.email)
|
||||
if not user:
|
||||
@@ -74,6 +74,7 @@ async def create_user(user_data: UserCreateRequest):
|
||||
- biografia: Biografía del usuario (opcional)
|
||||
"""
|
||||
try:
|
||||
user_repo = UserRepositorySQL() # sesión nueva por request
|
||||
create_use_case = CreateUser(user_repo)
|
||||
result = create_use_case.execute(
|
||||
nombre=user_data.nombre, apellido=user_data.apellido, email=user_data.email,
|
||||
@@ -97,6 +98,7 @@ async def create_user(user_data: UserCreateRequest):
|
||||
async def get_user(user_id: int):
|
||||
"""Obtiene un usuario por ID"""
|
||||
try:
|
||||
user_repo = UserRepositorySQL() # sesión nueva por request
|
||||
get_use_case = GetUserById(user_repo)
|
||||
user = get_use_case.execute(user_id)
|
||||
if not user:
|
||||
@@ -112,6 +114,7 @@ async def get_user(user_id: int):
|
||||
async def get_user_by_email(email: str):
|
||||
"""Obtiene un usuario por email"""
|
||||
try:
|
||||
user_repo = UserRepositorySQL() # sesión nueva por request
|
||||
get_use_case = GetUserByEmail(user_repo)
|
||||
user = get_use_case.execute(email)
|
||||
if not user:
|
||||
@@ -127,6 +130,7 @@ async def get_user_by_email(email: str):
|
||||
async def list_users():
|
||||
"""Obtiene todos los usuarios - retorna lista vacía si no hay registros"""
|
||||
try:
|
||||
user_repo = UserRepositorySQL() # sesión nueva por request
|
||||
list_use_case = ListAllUsers(user_repo)
|
||||
return list_use_case.execute()
|
||||
except Exception as e:
|
||||
@@ -137,6 +141,7 @@ async def list_users():
|
||||
async def update_user(user_id: int, user_data: UserUpdateRequest):
|
||||
"""Actualiza un usuario - envía a cola de procesamiento con validaciones previas"""
|
||||
try:
|
||||
user_repo = UserRepositorySQL() # sesión nueva por request
|
||||
update_use_case = UpdateUser(user_repo)
|
||||
result = update_use_case.execute(user_id=user_id, nombre=user_data.nombre, apellido=user_data.apellido, url_foto_perfil=user_data.url_foto_perfil, biografia=user_data.biografia)
|
||||
if result["status"] == "error":
|
||||
@@ -156,6 +161,7 @@ async def update_user(user_id: int, user_data: UserUpdateRequest):
|
||||
async def delete_user(user_id: int):
|
||||
"""Elimina un usuario - envía a cola de procesamiento con validaciones previas"""
|
||||
try:
|
||||
user_repo = UserRepositorySQL() # sesión nueva por request
|
||||
delete_use_case = DeleteUser(user_repo)
|
||||
result = delete_use_case.execute(user_id)
|
||||
if result["status"] == "error":
|
||||
|
||||
Reference in New Issue
Block a user