Skip to content

Instantly share code, notes, and snippets.

@justanr
Last active September 17, 2015 17:17
Show Gist options
  • Save justanr/5be68b74339d380d4d2e to your computer and use it in GitHub Desktop.
Save justanr/5be68b74339d380d4d2e to your computer and use it in GitHub Desktop.
Todo: Support loading from arbitrary data store (yaml, xml, Mongo, etc)
from .command_bus import DefaultCommandBus, ValidatingCommandBus, LoggingCommandBus, SupportsSelfExecution
from .command_name_formatters import (ChangeToOn_CommandName, ChangeToLowerCaseCommandName,
RemoveCommandFromName, MultipleCommandNameFormatter)
from .command_validators import LogsFailedValidationsValidator, GenerousMappingCommandValidator
from .commands import HelloPersonCommand, HelloPersonHandler
from .dependencies import DependencyStore, BasicDependencySatsifier
from .displayer import CallableDisplayer
from .formatter import StringFormatter
from .inflector import (CallableOrNextInflector, DefaultInflector, InstantiatingInflector
CommandNameInflector, MultipleCommandNameInflector, FirstOneWinsInflector)
from .locator import MappingLocator
import logging
from .validators import SenderAndReceiverAreDifferent
CommandLogger = logging.getLogger()
location_mapper = {HelloPersonCommand: HelloPersonHandler}
dependency_graph = {
HelloPersonHandler: DependencyStore(
positional=(),
keywords={'formatter': StringFormatter("{0} says: Hello {1}!"),
'displayer': CallableDisplayer(displayer=print)
})}
bus = ValidatingCommandBus(
LoggingCommandBus(
SupportsSelfExecutionBus(
DefaultCommandBus(
MappingLocator(location_mapper),
InstantiatingInflector(
CallableOrNextInflector(
FirstOneWinsInflectors(
DefaultInflector('execute'),
DefaultInflector('react'),
DefaultInflector('handle'),
DefaultInflector('do'),
MultipleCommandNameInflector(
CommandNameInflector(
MultipleCommandNameFormater(
RemoveCommandFromName(),
ChangeToLowerCaseCommandName(),
ChangeToOn_CommandName()
)
),
CommandNameInflector(ChangeToOn_CommandName()),
CommandNameInflector(ChangeToLowerCaseCommandName())
)
)
),
satsifier=BasicDependencySatsifier(dependency_graph)
)
)
),
logger=CommandLogger
),
validator=LogsFailedValidationsValidators(
GenerousMappingCommandValidator({
HelloPersonCommand: SenderAndReceiverAreDifferent()
}),
logger=CommandLogger
)
)
bus.execute(HelloPersonCommand('fred', 'fred'))
from abc import ABC, abstractmethod
class CommandBus(ABC):
@abstractmethod
def execute(self, command):
pass
class DefaultCommandBus(CommandBus):
def __init__(self, locator, inflector):
self.locator = locator
self.inflector = inflector
def execute(self, command):
handler = self.locator.locate_handler(command)
strategy = self.inflector.inflect(handler)
strategy(command)
class SupportsSelfExecutionBus(CommandBus):
def __init__(self, next):
self.next = next
def execute(self, command):
if hasattr(command, 'execute'):
command.execute()
else:
self.next.execute(command)
class LoggingCommandBus(CommandBus):
def __init__(self, next, logger):
self.next = next
self.logger = logger
def execute(self, command):
self.logger.info(str(command))
self.next.execute(command)
class ValidatingCommandBus(CommandBus):
def __init__(self, next, validator):
self.validator = validator
self.next = next
def execute(self, command):
self.validator.validate(command)
self.next.execute(command)
class ChangeToOn_CommandName(object):
def format(self, command_name):
return "on_{0}".format(command_name)
class ChangeToLowerCaseCommandName(object):
def format(self, command_name):
return command_name.lower()
class RemoveCommandFromName(object):
def format(self, command_name):
return command_name.replace('Command', '')
class MultipleCommandNameFormater(object):
def __init__(self, *formatters):
self.formatters = formatters
def format(self, command_name):
for formatter in self.formatters:
command_name = formatter.format(command_name)
return command_name
from abc import ABC, abstractmethod
from .excs import ValidationError
class CommandValidator(ABC):
@abstractmethod
def validate(self, command):
pass
class LogsFailedValidationsValidators(CommandValidator):
def __init__(self, next, logger):
self.logger = logger
self.next = next
def validate(self, command):
try:
self.next.validate(command)
except Exception as e:
self.logger.exception("Validation failed for {0}".format(command))
raise
class StrictMappingCommandValidator(CommandValidator):
def __init__(self, mapping):
self.mapping = mapping
def validate(self, command):
validator = self.mapping.get(_get_type(command))
if not validator:
raise ValidationError("No validators found for {0}".format(command))
validator.validate(command)
class GenerousMappingCommandValidator(CommandValidator):
def __init__(self, mapping):
self.mapping = mapping
def validate(self, command):
validator = self.mapping.get(_get_type(command), CallableCommandValidator(lambda c: True))
validator.validate(command)
class CallableCommandValidator(CommandValidator):
def __init__(self, callable):
self.callable = callable
def validate(self, command):
self.callable(command)
class StrictMultipleCommandValidators(CommandValidator):
def __init__(self, *validators):
self.validators = validators
def validate(self, command):
for validator in self.validators:
validator.validate(command)
class GenerousMultipleCommandValidator(CommandValidator):
def __init__(self, *validators):
self.validators = validators
def validate(self, command):
for validator in self.validators:
try:
validator.validate(command)
except ValidationError:
pass
else:
break
else:
raise ValidationError("Command {0} is invalid".format(command))
from collections import namedtuple
HelloWorldPerson = namedtuple('HelloWorldPerson', ['sender', 'receiver'])
class HelloPersonHandler(object):
def __init__(self, displayer, formatter):
self.displayer = displayer
self.formatter = formatter
def on_helloperson(self, command):
payload = self.formatter(command.sender, command.receiver)
self.displayer(payload)
from abc import ABC, abstractmethod
class SubjectNotFound(Exception):
pass
DependencyStore = namedtuple('DependencyStore', ['positional', 'keywords'])
class DependencySatsifier(ABC):
@abstractmethod
def satsify(self, cls):
pass
@abstractmethod
def register(self, cls, dependencies):
pass
class BasicDependencySatsifier(DependencySatsifier):
def __init__(self, dependency_graph=None):
if dependency_graph is None:
dependency_graph = {}
self.dependency_graph = dependency_graph
def register(self, cls, dependencies):
self.dependency_graph[cls] = dependencies
def satsify(self, cls):
dependencies = self.dependency_graph.get(cls, None)
if not dependencies:
raise SubjectNotFound(cls)
return cls(*dependencies.positional, **dependencies.keywords)
from abc import ABC, abstractmethod
class Displayer(ABC):
@abstractmethod
def display(self, payload):
pass
class CallableDisplayer(Displayer):
def __init__(self, displayer):
self.displayer = displayer
def display(self, payload):
return self.displayer(payload)
class HandlerNotFound(Exeception):
pass
class ValidationError(Exception):
pass
from abc import ABC, abstractmethod
class Formatter(ABC):
@abstractmethod
def format(self, *args, **kwargs):
pass
class StringFormatter(Formatter):
def __init__(self, template):
self.template = template
def format(self, *args, **kwargs):
return self.template.format(*args, **kwargs)
from abc import ABC, abstractmethod
from collections import namedtuple
from operator import attrgetter
from .excs import HandlerNotFound
class HandlerInflector(ABC):
@abstractmethod
def inflect(self, handler):
pass
class DefaultInflector(HandlerInflector):
def __init__(self, default='execute'):
self.find_default = attrgetter(default)
self.default_name = default
def inflect(self, handler):
try:
return self.find_default(handler)
except AttributeError:
raise HandlerNotFound("Handle method {0} not found on {1}".format(self.default_name, handler))
class InstantiatingInflector(HandlerInflector):
def __init__(self, next, satsifier):
self.next = next
self.satsifier = satsifier
def inflect(self, handler):
return self.next.inflect(self.satsifier.satsify(handler))
class CallableOrNextInflector(HandlerInflector):
def __init__(self, next):
self.next = next
def inflect(self, handler):
if callable(handler):
return handler
else:
return self.next.inflect(handler)
class FirstOneWinsInflector(HandlerInflector):
def __init__(self, *inflectors):
self.inflectors = inflectors
def inflect(self, handler):
for inflector in self.inflectors:
try:
action = inflector.inflect(handler)
except HandlerNotFound:
pass
else:
return action
else:
raise HandlerNotFound("Handle method not found for {0}".format(handler))
class CommandNameInflector(HandlerInflector):
def __init__(self, command_name_formatter):
self.command_name_formatter = command_name_formatter
def inflect(self, handler):
return self._await_command(handler)
def _await_command(self, handler):
def _command_awaiter(command):
command_name = _get_type(command).__name__
method_name = self.command_name_formatter.format(command_name)
method = getattr(handler, method_name, None)
if method is None:
raise HandlerNotFound("{0} not found for {1}".format(method_name, handler))
return method(command)
return _command_awaiter
class MultipleCommandNameInflector(HandlerInflector):
def __init__(self, *command_name_inflectors):
self.command_name_inflectors = command_name_inflectors
def inflect(self, handler):
return self._await_command(handler)
def _await_command(self, handler):
awaiters = [inflector.inflect(handler) for inflector in self.command_name_inflectors]
def _command_awaiter(command):
for awaiter in awaiters:
try:
awaiter(command)
except HandlerNotFound:
pass
else: break
else:
raise HandlerNotFound("Handler method not found for {0}".format(handler))
return _command_awaiter
from abc import ABC, abstractmethod
from .excs import HandlerNotFound
from .utils import get_type
class HandlerLocator(ABC):
@abstractmethod
def locate_handler(self, command):
pass
class MappingLocator(HandlerLocator):
def __init__(self, load_from=None):
self._mapping = {}
if load_from is not None:
self._mapping.update(load_from)
def locate_handler(self, command):
command_type = get_type(command)
handler = self._mapping.get(command_type, None)
if handler is None:
raise HandlerNotFound("Handler not found for {0!r}".format(command_type))
return handler
def register(self, command, handler):
self._mapping[get_type(command)] = handler
def get_type(obj):
if isinstance(obj, type):
return obj
else:
return type(obj)
from .command_validators import CommandValidator
class SenderAndReceiverAreDifferent(CommandValidator):
def validate(self, command):
if command.receiver == command.sender:
raise ValidationError("Sender and Receiver must be different")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment