proyectos ejemplos python exception-handling with-statement contextmanager

python - ejemplos - django



Encapsulando los reintentos en el bloque `with` (4)

Estoy buscando encapsular la lógica para transacciones de base de datos en un bloque with ; envolver el código en una transacción y manejar varias excepciones (problemas de bloqueo). Esto es bastante simple, sin embargo, también me gustaría que el bloque encapsule el reintento del bloque de código después de ciertas excepciones. No puedo ver una manera de empaquetar esto perfectamente en el administrador de contexto.

¿Es posible repetir el código dentro de una sentencia with ?

Me gustaría usarlo tan simple como esto, lo cual es realmente genial.

def do_work(): ... # This is ideal! with transaction(retries=3): # Atomic DB statements ... ...

Actualmente estoy manejando esto con un decorador, pero prefiero ofrecer el administrador de contexto (o, de hecho, ambos), así que puedo optar por envolver unas pocas líneas de código en el bloque with lugar de una función en línea envuelta en un Decorador, que es lo que hago en este momento:

def do_work(): ... # This is not ideal! @transaction(retries=3) def _perform_in_transaction(): # Atomic DB statements ... _perform_in_transaction() ...


¿Es posible repetir el código dentro de una sentencia with ?

No.

Como se señaló anteriormente en ese hilo de la lista de correo, puede reducir un poco la duplicación haciendo que el decorador llame a la función pasada:

def do_work(): ... # This is not ideal! @transaction(retries=3) def _perform_in_transaction(): # Atomic DB statements ... # called implicitly ...


Aunque estoy de acuerdo en que no se puede hacer con un administrador de contexto ... ¡se puede hacer con dos administradores de contexto!

El resultado es un poco incómodo, y no estoy seguro si apruebo mi propio código todavía, pero este es el aspecto que tiene el cliente:

with RetryManager(retries=3) as rm: while rm: with rm.protect: print("Attempt #%d of %d" % (rm.attempt_count, rm.max_retries)) # Atomic DB statements

Hay un bucle explícito while, y no uno, sino dos, with declaraciones, lo que deja demasiadas oportunidades de cometer errores para mi gusto.

Aquí está el código:

class RetryManager(object): """ Context manager that counts attempts to run statements without exceptions being raised. - returns True when there should be more attempts """ class _RetryProtector(object): """ Context manager that only raises exceptions if its parent RetryManager has given up.""" def __init__(self, retry_manager): self._retry_manager = retry_manager def __enter__(self): self._retry_manager._note_try() return self def __exit__(self, exc_type, exc_val, traceback): if exc_type is None: self._retry_manager._note_success() else: # This would be a good place to implement sleep between # retries. pass # Suppress exception if the retry manager is still alive. return self._retry_manager.is_still_trying() def __init__(self, retries=1): self.max_retries = retries self.attempt_count = 0 # Note: 1-based. self._success = False self.protect = RetryManager._RetryProtector(self) def __enter__(self): return self def __exit__(self, exc_type, exc_val, traceback): pass def _note_try(self): self.attempt_count += 1 def _note_success(self): self._success = True def is_still_trying(self): return not self._success and self.attempt_count < self.max_retries def __bool__(self): return self.is_still_trying()

Bonificación: Sé que no desea separar su trabajo en funciones separadas envueltas con decoradores ... pero si está satisfecho con eso, el paquete de redo de Mozilla ofrece a los decoradores que lo hagan, por lo que no tiene que hacerlo. rodar su propio Incluso hay un administrador de contexto que actúa como un decorador temporal para su función, pero aún depende de su código recuperable para ser factorizado en una sola función.


Como los decoradores son solo funciones en sí, podrías hacer lo siguiente:

with transaction(_perform_in_transaction, retries=3) as _perf: _perf()

Para los detalles, deberá implementar la transaction() como un método de fábrica que devuelve un objeto con __callable__() configurado para llamar al método original y repetirlo para retries varias veces en el fallo; __enter__() y __exit__() se definirían como normales para los administradores de contexto de transacciones de base de datos.

Como alternativa, puede configurar la transaction() manera que ejecute el método pasado hasta varios retries , lo que probablemente requiera la misma cantidad de trabajo que la implementación del administrador de contexto, pero significaría que el uso real se reduciría a solo la transaction(_perform_in_transaction, retries=3) (que es, de hecho, equivalente al ejemplo de decorador delnan provisto).


La forma en que se me ocurre hacer esto es simplemente implementar un administrador de contexto de transacciones de base de datos estándar, pero permitirle tomar un argumento de retries en el constructor. Luego simplemente lo resumiría en tus implementaciones de métodos. Algo como esto:

class transaction(object): def __init__(self, retries=0): self.retries = retries def __enter__(self): return self def __exit__(self, exc_type, exc_val, traceback): pass # Implementation... def execute(self, query): err = None for _ in range(self.retries): try: return self._cursor.execute(query) except Exception as e: err = e # probably ought to save all errors, but hey raise err with transaction(retries=3) as cursor: cursor.execute(''BLAH'')