Last active
September 17, 2015 17:17
-
-
Save justanr/5be68b74339d380d4d2e to your computer and use it in GitHub Desktop.
Todo: Support loading from arbitrary data store (yaml, xml, Mongo, etc)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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')) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class HandlerNotFound(Exeception): | |
pass | |
class ValidationError(Exception): | |
pass |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
def get_type(obj): | |
if isinstance(obj, type): | |
return obj | |
else: | |
return type(obj) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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