first commit
This commit is contained in:
BIN
app/pipelines/__pycache__/base.cpython-311.pyc
Normal file
BIN
app/pipelines/__pycache__/base.cpython-311.pyc
Normal file
Binary file not shown.
BIN
app/pipelines/__pycache__/crud.cpython-311.pyc
Normal file
BIN
app/pipelines/__pycache__/crud.cpython-311.pyc
Normal file
Binary file not shown.
79
app/pipelines/base.py
Normal file
79
app/pipelines/base.py
Normal file
@@ -0,0 +1,79 @@
|
||||
from typing import Any, Dict, Optional, Callable, List, TypeVar, Generic
|
||||
from sqlalchemy.orm import Session
|
||||
from app.core.auth import CurrentUser
|
||||
|
||||
from app.serviceInput.base import ServiceInput
|
||||
from app.serviceOutput.base import ServiceOutput
|
||||
|
||||
InputT = TypeVar("InputT")
|
||||
OutputT = TypeVar("OutputT")
|
||||
|
||||
class Pipeline(Generic[InputT, OutputT]):
|
||||
"""
|
||||
Pipeline base que unifica entrada, procesamiento y salida.
|
||||
"""
|
||||
|
||||
def __init__(self, db: Session, user: CurrentUser, operation: str):
|
||||
self.db = db
|
||||
self.user = user
|
||||
self.operation = operation
|
||||
self._steps: List[Callable] = []
|
||||
self._input: Optional[ServiceInput] = None
|
||||
self._output: Optional[ServiceOutput] = None
|
||||
self._input_data: Any = None
|
||||
self._schema = None
|
||||
self._permission: Optional[str] = None
|
||||
self._affidavit: Optional[Any] = None
|
||||
|
||||
def set_input_data(self, data: Any) -> 'Pipeline':
|
||||
"""Define los datos de entrada"""
|
||||
self._input_data = data
|
||||
return self
|
||||
|
||||
def with_schema(self, schema: Any) -> 'Pipeline':
|
||||
"""Define el schema de validación"""
|
||||
self._schema = schema
|
||||
return self
|
||||
|
||||
def with_permission(self, permission: Optional[str]) -> 'Pipeline':
|
||||
"""Define el permiso requerido"""
|
||||
self._permission = permission
|
||||
return self
|
||||
|
||||
def with_affidavit(self, affidavit: Any) -> 'Pipeline':
|
||||
"""Define el certificador legal"""
|
||||
self._affidavit = affidavit
|
||||
return self
|
||||
|
||||
def add_step(self, name: str, func: Callable) -> 'Pipeline':
|
||||
"""Agrega un paso al pipeline"""
|
||||
self._steps.append({"name": name, "func": func})
|
||||
return self
|
||||
|
||||
async def execute(self, context: Dict = None) -> Dict:
|
||||
"""
|
||||
Ejecuta el pipeline completo:
|
||||
1. Validación de entrada
|
||||
2. Verificación de permisos
|
||||
3. Ejecución de pasos
|
||||
4. Retorna resultado
|
||||
"""
|
||||
if context is None:
|
||||
context = {}
|
||||
|
||||
context["db"] = self.db
|
||||
context["user"] = self.user
|
||||
context["operation"] = self.operation
|
||||
context["input_data"] = self._input_data
|
||||
context["schema"] = self._schema
|
||||
context["permission"] = self._permission
|
||||
context["affidavit"] = self._affidavit
|
||||
|
||||
result = context
|
||||
|
||||
for step in self._steps:
|
||||
result = await step["func"](result)
|
||||
if result.get("error"):
|
||||
return {"success": False, "error": result["error"]}
|
||||
|
||||
return {"success": True, "data": result.get("output")}
|
||||
289
app/pipelines/crud.py
Normal file
289
app/pipelines/crud.py
Normal file
@@ -0,0 +1,289 @@
|
||||
from typing import Type, Dict, Any, List
|
||||
from sqlalchemy.orm import Session
|
||||
from app.pipelines.base import Pipeline
|
||||
# #
|
||||
from app.serviceInput.base import ServiceInput
|
||||
from app.core.auth import CurrentUser
|
||||
|
||||
|
||||
class CreatePipe(Pipeline):
|
||||
"""Pipe prefab to create enititys"""
|
||||
|
||||
def __init__(self, db: Session, user: CurrentUser, model: Type, repository):
|
||||
super().__init__(db, user, f"CREATE_{model.__tablename__.upper()}")
|
||||
self.model = model
|
||||
self.repository = repository
|
||||
self._build()
|
||||
|
||||
def _build(self):
|
||||
"""Build steps to Pipe"""
|
||||
self.add_step("Validate", self._validate)
|
||||
self.add_step("check_permissions", self._check_permissions)
|
||||
self.add_step("execute", self._execute)
|
||||
self.add_step("format_output", self._format)
|
||||
|
||||
async def _validate(self, ctx):
|
||||
data = ctx.get("input_data", {})
|
||||
if ctx.get("schema"):
|
||||
validated = ctx["schema"](**data)
|
||||
ctx["validated_data"] = validated.dict()
|
||||
else:
|
||||
ctx["validated_data"] = data
|
||||
return ctx
|
||||
|
||||
|
||||
async def _check_permissions(self, ctx):
|
||||
if ctx.get("permission"):
|
||||
if not ctx.get("user"):
|
||||
ctx["error"] = "Usuario no autenticado"
|
||||
return ctx
|
||||
|
||||
|
||||
async def _execute(self, ctx):
|
||||
if ctx.get("error"):
|
||||
return ctx
|
||||
|
||||
try:
|
||||
created_by = ctx["user"].id if ctx.get("user") else None
|
||||
entity = self.repository.create(ctx["validated_data"], created_by)
|
||||
ctx["entity"] = entity
|
||||
except Exception as e:
|
||||
ctx["error"] = str(e)
|
||||
return ctx
|
||||
|
||||
|
||||
async def _format(self, ctx):
|
||||
if ctx.get("error"):
|
||||
ctx["output"] = None
|
||||
return ctx
|
||||
|
||||
if ctx.get("entity"):
|
||||
ctx["output"] = {c.name: getattr(ctx["entity"], c.name)
|
||||
for c in self.model.__table__.columns}
|
||||
print(f"FORMAT: output creado con {len(ctx['output'])} campos")
|
||||
else:
|
||||
print("FORMAT: No hay entity para formatear")
|
||||
ctx["output"] = None
|
||||
return ctx
|
||||
|
||||
|
||||
class ReadPipe(Pipeline):
|
||||
""" Read prefab """
|
||||
|
||||
def __init__(self, db: Session, user: CurrentUser, model: Type, repository):
|
||||
super().__init__(db, user, f"READ_{model.__tablename__.upper()}")
|
||||
self.model = model
|
||||
self.repository = repository
|
||||
self._build()
|
||||
|
||||
def _build(self):
|
||||
self.add_step("validate_id", self._validate_id)
|
||||
self.add_step("check_permissions", self._check_permissions)
|
||||
self.add_step("execurte", self._execute)
|
||||
self.add_step("format_output", self._format)
|
||||
|
||||
async def _validate_id(self, ctx):
|
||||
input_data = ctx["input"].raw_data
|
||||
entity_id = input_data.get("id")
|
||||
if not entity_id:
|
||||
raise ValueError("se requiere ID")
|
||||
ctx["entity_id"] = entity_id
|
||||
return ctx
|
||||
|
||||
async def _check_permissions(self, ctx):
|
||||
return ctx
|
||||
|
||||
async def _execute(self, ctx):
|
||||
entity = self.repository.get_by_id(ctx["entity_id"])
|
||||
if not entity:
|
||||
raise ValueError(f"{self.model.__name__} no encontrado")
|
||||
ctx["entity"] = entity
|
||||
ctx["output"] = entity
|
||||
return ctx
|
||||
async def _format(self, ctx):
|
||||
ctx["output"] = {c.name: getattr(ctx["entity"], c.name)
|
||||
for c in self.model.__table__.columns}
|
||||
return ctx
|
||||
|
||||
|
||||
class UpdatePipe(Pipeline):
|
||||
|
||||
"""Pipe prefab to Update"""
|
||||
|
||||
def __init__(self, db:Session, user:CurrentUser, model: Type, repository):
|
||||
super().__init__(db, user, f"UPDATE_{model.__tablename__.upper()}")
|
||||
self.model = model
|
||||
self.repository = repository
|
||||
self._build()
|
||||
|
||||
def _build(self):
|
||||
self.add_step("validate", self._validate)
|
||||
self.add_step("validate_id", self._validate_id)
|
||||
self.add_step("check_permissions", self._check_permissions)
|
||||
self.add_step("execute", self._execute)
|
||||
self.add_step("certify", self._certify)
|
||||
self.add_step("format_output", self._format)
|
||||
|
||||
async def _validate(self, ctx):
|
||||
data = ctx["input"].get_validated()
|
||||
ctx["validated_data"] = data.dict() if data else {}
|
||||
return ctx
|
||||
|
||||
async def _validate_id(self, ctx):
|
||||
entity_id = ctx["validated_data"].get("id")
|
||||
if not entity_id:
|
||||
raise ValueError("Se rqeuiere ID")
|
||||
ctx["entity_id"] = entity_id
|
||||
return ctx
|
||||
|
||||
async def _check_permissions(self, ctx):
|
||||
return ctx
|
||||
async def _execute(self, ctx):
|
||||
entity = self.repository.update(ctx["entity_id"], ctx["validated_data"])
|
||||
if not entity:
|
||||
raise ValueError(f"{self.model.__name__} no encontrado")
|
||||
ctx["entity"] = entity
|
||||
ctx["output"] = entity
|
||||
return ctx
|
||||
|
||||
async def _certify(self, ctx):
|
||||
return ctx
|
||||
|
||||
|
||||
async def _format(self, ctx):
|
||||
ctx["output"] = {
|
||||
"id": ctx["entity"].id,
|
||||
"message": f"{self.model.__name__} actualizado exitosamente"
|
||||
}
|
||||
return ctx
|
||||
|
||||
class SoftDelete(Pipeline):
|
||||
|
||||
"""Pipe prefab to SoftDelete"""
|
||||
|
||||
def __init__(self, db:Session, user:CurrentUser, model: Type, repository):
|
||||
super().__init__(db, user, f"DELETE_{model.__tablename__.upper()}")
|
||||
self.model = model
|
||||
self.repository = repository
|
||||
self._build()
|
||||
|
||||
def _build(self):
|
||||
self.add_step("validate", self._validate)
|
||||
self.add_step("validate_id", self._validate_id)
|
||||
self.add_step("check_permissions", self._check_permissions)
|
||||
self.add_step("check_already_deleted", self._check_already_deleted)
|
||||
self.add_step("execute", self._execute)
|
||||
self.add_step("certify", self._certify)
|
||||
self.add_step("format_output", self._format)
|
||||
|
||||
async def _validate_id(self, ctx):
|
||||
input_data = ctx["input"].raw_data
|
||||
entity_id = input_data.get("id")
|
||||
if not entity_id:
|
||||
raise ValueError("se requiere ID")
|
||||
ctx["entity_id"] = entity_id
|
||||
return ctx
|
||||
|
||||
async def _check_permissions(self, ctx):
|
||||
return ctx
|
||||
|
||||
async def _check_already_deleted(self, ctx):
|
||||
"""Verify entity has no deleted"""
|
||||
entity = self.repository.get_by_id(ctx["entity_id"])
|
||||
if not entity:
|
||||
raise ValueError(f"{self.model.__name__} no encontrado")
|
||||
|
||||
if not entity.is_active or entity.deleted_at is not None:
|
||||
raise ValueError(f"{self.model.__name__} ya esta eliminado")
|
||||
|
||||
ctx["entity"] = entity
|
||||
return ctx
|
||||
|
||||
async def _execute(self, ctx):
|
||||
""" SoftDelete implemented"""
|
||||
|
||||
entity = ctx["entity"]
|
||||
from datetime import datetime
|
||||
|
||||
entity.is_active = False
|
||||
entity.deleted_at = datetime.utcnow()
|
||||
entity.deleted_by = ctx["user"].id
|
||||
|
||||
self.db.commit()
|
||||
self.db.refresh(entity)
|
||||
|
||||
ctx["deleted"] = True
|
||||
ctx["output"] = {"id": entity.id, "deleted": True}
|
||||
return ctx
|
||||
|
||||
|
||||
async def _certify(self, ctx):
|
||||
return ctx
|
||||
|
||||
|
||||
async def _format(self, ctx):
|
||||
|
||||
ctx["output"]["message"] = f"{self.model.__name__} eliminado corectamente"
|
||||
ctx["output"]["deleted_at"] = ctx["entity"].deleted_at.isoformat()
|
||||
ctx["output"]["deleted_by"] = ctx["entity"].delted_by
|
||||
return ctx
|
||||
|
||||
class ListPipe(Pipeline):
|
||||
"""Pipeline prefab to list entitys"""
|
||||
|
||||
def __init__(self, db: Session, user: CurrentUser, model: Type, repository):
|
||||
super().__init__(db, user, f"LIST_{model.__tablename__.upper()}")
|
||||
self.model = model
|
||||
self.repository = repository
|
||||
self._skip = 0
|
||||
self._limit = 100
|
||||
self._filters = {}
|
||||
self._include_inactive = False
|
||||
self._build()
|
||||
|
||||
def with_pagination(self, skip: int, limit: int) -> 'ListPipe':
|
||||
self._skip = skip
|
||||
self._limit = limit
|
||||
return self
|
||||
|
||||
def with_filters(self, filters: Dict) -> 'ListPipe':
|
||||
self._filters = filters
|
||||
return self
|
||||
|
||||
def include_inactive(self, value: bool = True) -> 'ListPipe':
|
||||
self._include_inactive = value
|
||||
return self
|
||||
|
||||
def _build(self):
|
||||
self.add_step("parse_params", self._parse_params)
|
||||
self.add_step("check_permissions", self._check_permissions)
|
||||
self.add_step("execute", self._execute)
|
||||
self.add_step("format_output", self._format)
|
||||
|
||||
async def _parse_params(self, ctx):
|
||||
ctx["skip"] = self._skip
|
||||
ctx["limit"] = self._limit
|
||||
ctx["filters"] = self._filters
|
||||
ctx["include_inactive"] = self._include_inactive
|
||||
return ctx
|
||||
|
||||
async def _check_permissions(self, ctx):
|
||||
return ctx
|
||||
|
||||
async def _execute(self, ctx):
|
||||
entities = self.repository.get_all(
|
||||
skip=ctx["skip"],
|
||||
limit=ctx["limit"],
|
||||
filters=ctx["filters"]
|
||||
)
|
||||
ctx["entities"] = entities
|
||||
ctx["output"] = entities
|
||||
return ctx
|
||||
|
||||
async def _format(self, ctx):
|
||||
ctx["output"] = [
|
||||
{c.name: getattr(e, c.name) for c in self.model.__table__.columns}
|
||||
for e in ctx["entities"]
|
||||
]
|
||||
return ctx
|
||||
|
||||
Reference in New Issue
Block a user