Source code for ahoyhoy.circuit.circuit
"""
StateMachine pattern
http://python-3-patterns-idioms-test.readthedocs.io/en/latest/StateMachine.html
"""
from ..classifiers import raise_for_status
class CircuitClassifier(object):
def __init__(self):
self._state = None
self._host = None
self._pre = None
self._post = None
self._retry = None
self._exc = None
self._client = None
def classify(self, func, *args, **kwargs):
return self._state.classify(self, func, *args, **kwargs)
def dispatch(self, name):
return self._state.dispatch(self, name)
[docs]class Circuit(CircuitClassifier):
"""
Simple base class to handle the transition between
closed and open circuit states.
"""
def __init__(self):
super(Circuit, self).__init__()
self._state = ClosedState
def change_state(self, state):
self._state = state
def open(self):
return self._state.open(self)
def close(self):
return self._state.close(self)
def is_open(self):
return self._state == OpenState
def is_closed(self):
return self._state == ClosedState
@property
def available(self):
return self.is_closed()
[docs]class StateClassifier(object):
@staticmethod
[docs] def classify(circuit, func, *args, **kwargs):
"""
Look at the result, and determine what to do, either with the response
or any exceptions raised along the way.
"""
try:
if circuit._retry is None:
result = func(*args, **kwargs)
else:
result = circuit._retry(func, fargs=args, fkwargs=kwargs)
except Exception as e:
# Eventually, we'll want to catch separate exceptions
# and handle the reactions differently
circuit.open()
if circuit._exc is None:
raise e
return circuit._exc(e)
else:
return raise_for_status(result)
@staticmethod
[docs] def dispatch(circuit, name):
"""
Return requested function so it can be then
called in `classify` method and proccessed respectively.
"""
return getattr(circuit._session, name)
class State(StateClassifier):
@staticmethod
def open(circuit):
circuit.change_state(OpenState)
@staticmethod
def close(circuit):
circuit.change_state(ClosedState)
[docs]class ClosedState(State):
"""
Circuit in a closed state. Closing will throw RuntimeError.
"""
@staticmethod
def close(circuit):
raise RuntimeError("State is already closed.")
[docs]class OpenState(State):
"""
Circut in an open state. Opening as well as all other methods will throw RuntimeError.
"""
@staticmethod
def open(circuit):
raise RuntimeError("State is already open.")
@staticmethod
def dispatch(circuit, name):
raise RuntimeError("Circuit state is open, no connections possible.")
@staticmethod
def classify(circuit, func, *args, **kwargs):
raise RuntimeError("Circuit state is open, dispatch should have \
already caught this, but no connections possible.")