266 lines
9.4 KiB
Python
266 lines
9.4 KiB
Python
from azure.storage.blob import BlobServiceClient, BlobClient, ContainerClient
|
|
from azure.core.exceptions import ResourceNotFoundError, ResourceExistsError
|
|
from typing import List, Optional, BinaryIO
|
|
import logging
|
|
from datetime import datetime, timezone
|
|
import os
|
|
from ..core.config import settings
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class AzureBlobService:
|
|
"""
|
|
Servicio para interactuar con Azure Blob Storage
|
|
"""
|
|
|
|
def __init__(self):
|
|
"""Inicializar el cliente de Azure Blob Storage"""
|
|
try:
|
|
self.blob_service_client = BlobServiceClient.from_connection_string(
|
|
settings.AZURE_STORAGE_CONNECTION_STRING
|
|
)
|
|
self.container_name = settings.AZURE_CONTAINER_NAME
|
|
logger.info(f"Cliente de Azure Blob Storage inicializado para container: {self.container_name}")
|
|
except Exception as e:
|
|
logger.error(f"Error inicializando Azure Blob Service: {e}")
|
|
raise e
|
|
|
|
async def create_container_if_not_exists(self) -> bool:
|
|
"""
|
|
Crear el container si no existe
|
|
Returns: True si se creó, False si ya existía
|
|
"""
|
|
try:
|
|
container_client = self.blob_service_client.get_container_client(self.container_name)
|
|
container_client.create_container()
|
|
logger.info(f"Container '{self.container_name}' creado exitosamente")
|
|
return True
|
|
except ResourceExistsError:
|
|
logger.info(f"Container '{self.container_name}' ya existe")
|
|
return False
|
|
except Exception as e:
|
|
logger.error(f"Error creando container: {e}")
|
|
raise e
|
|
|
|
async def upload_file(self, file_data: BinaryIO, blob_name: str, tema: str = "") -> dict:
|
|
"""
|
|
Subir un archivo a Azure Blob Storage
|
|
|
|
Args:
|
|
file_data: Datos del archivo
|
|
blob_name: Nombre del archivo en el blob
|
|
tema: Tema/carpeta donde guardar el archivo
|
|
|
|
Returns:
|
|
dict: Información del archivo subido
|
|
"""
|
|
try:
|
|
# Construir la ruta completa con tema si se proporciona
|
|
full_blob_name = f"{tema}/{blob_name}" if tema else blob_name
|
|
|
|
# Obtener cliente del blob
|
|
blob_client = self.blob_service_client.get_blob_client(
|
|
container=self.container_name,
|
|
blob=full_blob_name
|
|
)
|
|
|
|
# Subir el archivo
|
|
blob_client.upload_blob(file_data, overwrite=True)
|
|
|
|
# Obtener propiedades del blob
|
|
blob_properties = blob_client.get_blob_properties()
|
|
|
|
logger.info(f"Archivo '{full_blob_name}' subido exitosamente")
|
|
|
|
return {
|
|
"name": blob_name,
|
|
"full_path": full_blob_name,
|
|
"tema": tema,
|
|
"size": blob_properties.size,
|
|
"last_modified": blob_properties.last_modified,
|
|
"url": blob_client.url
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error subiendo archivo '{blob_name}': {e}")
|
|
raise e
|
|
|
|
async def download_file(self, blob_name: str, tema: str = "") -> bytes:
|
|
"""
|
|
Descargar un archivo de Azure Blob Storage
|
|
|
|
Args:
|
|
blob_name: Nombre del archivo
|
|
tema: Tema/carpeta donde está el archivo
|
|
|
|
Returns:
|
|
bytes: Contenido del archivo
|
|
"""
|
|
try:
|
|
# Construir la ruta completa
|
|
full_blob_name = f"{tema}/{blob_name}" if tema else blob_name
|
|
|
|
# Obtener cliente del blob
|
|
blob_client = self.blob_service_client.get_blob_client(
|
|
container=self.container_name,
|
|
blob=full_blob_name
|
|
)
|
|
|
|
# Descargar el archivo
|
|
blob_data = blob_client.download_blob()
|
|
content = blob_data.readall()
|
|
|
|
logger.info(f"Archivo '{full_blob_name}' descargado exitosamente")
|
|
return content
|
|
|
|
except ResourceNotFoundError:
|
|
logger.error(f"Archivo '{full_blob_name}' no encontrado")
|
|
raise FileNotFoundError(f"El archivo '{blob_name}' no existe")
|
|
except Exception as e:
|
|
logger.error(f"Error descargando archivo '{blob_name}': {e}")
|
|
raise e
|
|
|
|
async def delete_file(self, blob_name: str, tema: str = "") -> bool:
|
|
"""
|
|
Eliminar un archivo de Azure Blob Storage
|
|
|
|
Args:
|
|
blob_name: Nombre del archivo
|
|
tema: Tema/carpeta donde está el archivo
|
|
|
|
Returns:
|
|
bool: True si se eliminó exitosamente
|
|
"""
|
|
try:
|
|
# Construir la ruta completa
|
|
full_blob_name = f"{tema}/{blob_name}" if tema else blob_name
|
|
|
|
# Obtener cliente del blob
|
|
blob_client = self.blob_service_client.get_blob_client(
|
|
container=self.container_name,
|
|
blob=full_blob_name
|
|
)
|
|
|
|
# Eliminar el archivo
|
|
blob_client.delete_blob()
|
|
|
|
logger.info(f"Archivo '{full_blob_name}' eliminado exitosamente")
|
|
return True
|
|
|
|
except ResourceNotFoundError:
|
|
logger.error(f"Archivo '{full_blob_name}' no encontrado para eliminar")
|
|
raise FileNotFoundError(f"El archivo '{blob_name}' no existe")
|
|
except Exception as e:
|
|
logger.error(f"Error eliminando archivo '{blob_name}': {e}")
|
|
raise e
|
|
|
|
async def list_files(self, tema: str = "") -> List[dict]:
|
|
"""
|
|
Listar archivos en el container o en un tema específico
|
|
|
|
Args:
|
|
tema: Tema/carpeta específica (opcional)
|
|
|
|
Returns:
|
|
List[dict]: Lista de archivos con sus propiedades
|
|
"""
|
|
try:
|
|
container_client = self.blob_service_client.get_container_client(self.container_name)
|
|
|
|
# Filtrar por tema si se proporciona
|
|
name_starts_with = f"{tema}/" if tema else None
|
|
|
|
blobs = container_client.list_blobs(name_starts_with=name_starts_with)
|
|
|
|
files = []
|
|
for blob in blobs:
|
|
# Extraer información del blob
|
|
blob_info = {
|
|
"name": os.path.basename(blob.name),
|
|
"full_path": blob.name,
|
|
"tema": os.path.dirname(blob.name) if "/" in blob.name else "",
|
|
"size": blob.size,
|
|
"last_modified": blob.last_modified,
|
|
"content_type": blob.content_settings.content_type if blob.content_settings else None
|
|
}
|
|
files.append(blob_info)
|
|
|
|
logger.info(f"Listados {len(files)} archivos" + (f" en tema '{tema}'" if tema else ""))
|
|
return files
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error listando archivos: {e}")
|
|
raise e
|
|
|
|
async def get_file_info(self, blob_name: str, tema: str = "") -> dict:
|
|
"""
|
|
Obtener información de un archivo específico
|
|
|
|
Args:
|
|
blob_name: Nombre del archivo
|
|
tema: Tema/carpeta donde está el archivo
|
|
|
|
Returns:
|
|
dict: Información del archivo
|
|
"""
|
|
try:
|
|
# Construir la ruta completa
|
|
full_blob_name = f"{tema}/{blob_name}" if tema else blob_name
|
|
|
|
# Obtener cliente del blob
|
|
blob_client = self.blob_service_client.get_blob_client(
|
|
container=self.container_name,
|
|
blob=full_blob_name
|
|
)
|
|
|
|
# Obtener propiedades
|
|
properties = blob_client.get_blob_properties()
|
|
|
|
return {
|
|
"name": blob_name,
|
|
"full_path": full_blob_name,
|
|
"tema": tema,
|
|
"size": properties.size,
|
|
"last_modified": properties.last_modified,
|
|
"content_type": properties.content_settings.content_type,
|
|
"url": blob_client.url
|
|
}
|
|
|
|
except ResourceNotFoundError:
|
|
logger.error(f"Archivo '{full_blob_name}' no encontrado")
|
|
raise FileNotFoundError(f"El archivo '{blob_name}' no existe")
|
|
except Exception as e:
|
|
logger.error(f"Error obteniendo info del archivo '{blob_name}': {e}")
|
|
raise e
|
|
|
|
async def get_download_url(self, blob_name: str, tema: str = "") -> str:
|
|
"""
|
|
Obtener URL de descarga directa para un archivo
|
|
|
|
Args:
|
|
blob_name: Nombre del archivo
|
|
tema: Tema/carpeta donde está el archivo
|
|
|
|
Returns:
|
|
str: URL de descarga
|
|
"""
|
|
try:
|
|
# Construir la ruta completa
|
|
full_blob_name = f"{tema}/{blob_name}" if tema else blob_name
|
|
|
|
# Obtener cliente del blob
|
|
blob_client = self.blob_service_client.get_blob_client(
|
|
container=self.container_name,
|
|
blob=full_blob_name
|
|
)
|
|
|
|
return blob_client.url
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error obteniendo URL de descarga para '{blob_name}': {e}")
|
|
raise e
|
|
|
|
|
|
# Instancia global del servicio
|
|
azure_service = AzureBlobService() |