Skip to content

Commit

Permalink
Merge pull request #287 from gyermolenko/separate_version_specific_sc…
Browse files Browse the repository at this point in the history
…ripts

Separate version specific scripts
  • Loading branch information
faif authored Mar 8, 2019
2 parents 4292a34 + 02b653a commit 423fe9d
Show file tree
Hide file tree
Showing 10 changed files with 251 additions and 86 deletions.
5 changes: 3 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@ install:
- pip install -r requirements-dev.txt

script:
- pytest --doctest-modules patterns/
- if [ "${TRAVIS_PYTHON_VERSION:0:1}" = 2 ]; then export PYEXCLUDE=3; else export PYEXCLUDE=2; fi
- flake8 --exclude="*__py${PYEXCLUDE}.py" patterns/
- pytest --doctest-modules --ignore-glob="*__py${PYEXCLUDE}.py" patterns/
- pytest -s -vv --cov=. --log-level=INFO tests/
# Actually run all the scripts, contributing to coverage
- PYTHONPATH=. ./run_all.sh
- flake8 patterns/

after_success:
- codecov
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ __Behavioral Patterns__:

| Pattern | Description |
|:-------:| ----------- |
| [chain_of_responsibility](patterns/behavioral/chain_of_responsibility.py) | apply a chain of successive handlers to try and process the data |
| [chain_of_responsibility](patterns/behavioral/chain_of_responsibility__py3.py) | apply a chain of successive handlers to try and process the data |
| [catalog](patterns/behavioral/catalog.py) | general methods will call different specialized methods based on construction parameter |
| [chaining_method](patterns/behavioral/chaining_method.py) | continue callback next object method |
| [command](patterns/behavioral/command.py) | bundle a command and arguments to call later |
Expand All @@ -46,7 +46,7 @@ __Behavioral Patterns__:
| [memento](patterns/behavioral/memento.py) | generate an opaque token that can be used to go back to a previous state |
| [observer](patterns/behavioral/observer.py) | provide a callback for notification of events/changes to data |
| [publish_subscribe](patterns/behavioral/publish_subscribe.py) | a source syndicates events/data to 0+ registered listeners |
| [registry](patterns/behavioral/registry.py) | keep track of all subclasses of a given class |
| [registry](patterns/behavioral/registry__py3.py) | keep track of all subclasses of a given class |
| [specification](patterns/behavioral/specification.py) | business rules can be recombined by chaining the business rules together using boolean logic |
| [state](patterns/behavioral/state.py) | logic is organized into a discrete number of potential states and the next state that can be transitioned to |
| [strategy](patterns/behavioral/strategy.py) | selectable operations over the same data |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,29 +92,28 @@ def check_range(request):


def main():
h0 = ConcreteHandler0()
h1 = ConcreteHandler1()
h2 = ConcreteHandler2(FallbackHandler())
h0.successor = h1
h1.successor = h2

requests = [2, 5, 14, 22, 18, 3, 35, 27, 20]
for request in requests:
h0.handle(request)
"""
>>> h0 = ConcreteHandler0()
>>> h1 = ConcreteHandler1()
>>> h2 = ConcreteHandler2(FallbackHandler())
>>> h0.successor = h1
>>> h1.successor = h2
>>> requests = [2, 5, 14, 22, 18, 3, 35, 27, 20]
>>> for request in requests:
... h0.handle(request)
request 2 handled in handler 0
request 5 handled in handler 0
request 14 handled in handler 1
request 22 handled in handler 2
request 18 handled in handler 1
request 3 handled in handler 0
end of chain, no handler for 35
request 27 handled in handler 2
request 20 handled in handler 2
"""


if __name__ == "__main__":
main()


OUTPUT = """
request 2 handled in handler 0
request 5 handled in handler 0
request 14 handled in handler 1
request 22 handled in handler 2
request 18 handled in handler 1
request 3 handled in handler 0
end of chain, no handler for 35
request 27 handled in handler 2
request 20 handled in handler 2
"""
import doctest
doctest.testmod(optionflags=doctest.ELLIPSIS)
118 changes: 118 additions & 0 deletions patterns/behavioral/chain_of_responsibility__py3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
*What is this pattern about?
The Chain of responsibility is an object oriented version of the
`if ... elif ... elif ... else ...` idiom, with the
benefit that the condition–action blocks can be dynamically rearranged
and reconfigured at runtime.
This pattern aims to decouple the senders of a request from its
receivers by allowing request to move through chained
receivers until it is handled.
Request receiver in simple form keeps a reference to a single successor.
As a variation some receivers may be capable of sending requests out
in several directions, forming a `tree of responsibility`.
*TL;DR80
Allow a request to pass down a chain of receivers until it is handled.
"""

import abc


class Handler(metaclass=abc.ABCMeta):

def __init__(self, successor=None):
self.successor = successor

def handle(self, request):
"""
Handle request and stop.
If can't - call next handler in chain.
As an alternative you might even in case of success
call the next handler.
"""
res = self.check_range(request)
if not res and self.successor:
self.successor.handle(request)

@abc.abstractmethod
def check_range(self, request):
"""Compare passed value to predefined interval"""


class ConcreteHandler0(Handler):
"""Each handler can be different.
Be simple and static...
"""

@staticmethod
def check_range(request):
if 0 <= request < 10:
print("request {} handled in handler 0".format(request))
return True


class ConcreteHandler1(Handler):
"""... With it's own internal state"""

start, end = 10, 20

def check_range(self, request):
if self.start <= request < self.end:
print("request {} handled in handler 1".format(request))
return True


class ConcreteHandler2(Handler):
"""... With helper methods."""

def check_range(self, request):
start, end = self.get_interval_from_db()
if start <= request < end:
print("request {} handled in handler 2".format(request))
return True

@staticmethod
def get_interval_from_db():
return (20, 30)


class FallbackHandler(Handler):
@staticmethod
def check_range(request):
print("end of chain, no handler for {}".format(request))
return False


def main():
"""
>>> h0 = ConcreteHandler0()
>>> h1 = ConcreteHandler1()
>>> h2 = ConcreteHandler2(FallbackHandler())
>>> h0.successor = h1
>>> h1.successor = h2
>>> requests = [2, 5, 14, 22, 18, 3, 35, 27, 20]
>>> for request in requests:
... h0.handle(request)
request 2 handled in handler 0
request 5 handled in handler 0
request 14 handled in handler 1
request 22 handled in handler 2
request 18 handled in handler 1
request 3 handled in handler 0
end of chain, no handler for 35
request 27 handled in handler 2
request 20 handled in handler 2
"""


if __name__ == "__main__":
import doctest
doctest.testmod(optionflags=doctest.ELLIPSIS)
51 changes: 0 additions & 51 deletions patterns/behavioral/registry.py

This file was deleted.

50 changes: 50 additions & 0 deletions patterns/behavioral/registry__py2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-


class RegistryHolder(type):

REGISTRY = {}

def __new__(cls, name, bases, attrs):
new_cls = type.__new__(cls, name, bases, attrs)
"""
Here the name of the class is used as key but it could be any class
parameter.
"""
cls.REGISTRY[new_cls.__name__] = new_cls
return new_cls

@classmethod
def get_registry(cls):
return dict(cls.REGISTRY)


class BaseRegisteredClass(object):
"""
Any class that will inherits from BaseRegisteredClass will be included
inside the dict RegistryHolder.REGISTRY, the key being the name of the
class and the associated value, the class itself.
"""
__metaclass__ = RegistryHolder


def main():
"""
Before subclassing
>>> sorted(RegistryHolder.REGISTRY)
['BaseRegisteredClass']
>>> class ClassRegistree(BaseRegisteredClass):
... def __init__(self, *args, **kwargs):
... pass
After subclassing
>>> sorted(RegistryHolder.REGISTRY)
['BaseRegisteredClass', 'ClassRegistree']
"""


if __name__ == "__main__":
import doctest
doctest.testmod(optionflags=doctest.ELLIPSIS)
49 changes: 49 additions & 0 deletions patterns/behavioral/registry__py3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-


class RegistryHolder(type):

REGISTRY = {}

def __new__(cls, name, bases, attrs):
new_cls = type.__new__(cls, name, bases, attrs)
"""
Here the name of the class is used as key but it could be any class
parameter.
"""
cls.REGISTRY[new_cls.__name__] = new_cls
return new_cls

@classmethod
def get_registry(cls):
return dict(cls.REGISTRY)


class BaseRegisteredClass(metaclass=RegistryHolder):
"""
Any class that will inherits from BaseRegisteredClass will be included
inside the dict RegistryHolder.REGISTRY, the key being the name of the
class and the associated value, the class itself.
"""


def main():
"""
Before subclassing
>>> sorted(RegistryHolder.REGISTRY)
['BaseRegisteredClass']
>>> class ClassRegistree(BaseRegisteredClass):
... def __init__(self, *args, **kwargs):
... pass
After subclassing
>>> sorted(RegistryHolder.REGISTRY)
['BaseRegisteredClass', 'ClassRegistree']
"""


if __name__ == "__main__":
import doctest
doctest.testmod(optionflags=doctest.ELLIPSIS)
12 changes: 7 additions & 5 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
-e .
pytest~=4.1
pytest-cov~=2.6
flake8~=3.6
codecov~=2.0
mock~=2.0

pytest~=4.3.0
pytest-cov~=2.6.0
flake8~=3.7.0
codecov~=2.0.0

mock~=2.0.0; python_version < "3.*"
Empty file removed tests/__init__.py
Empty file.
3 changes: 0 additions & 3 deletions tests/test_outputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@

from patterns.behavioral.catalog import main as catalog_main
from patterns.behavioral.catalog import OUTPUT as catalog_output
from patterns.behavioral.chain_of_responsibility import main as chain_main
from patterns.behavioral.chain_of_responsibility import OUTPUT as chain_output
from patterns.behavioral.chaining_method import main as chaining_method_main
from patterns.behavioral.chaining_method import OUTPUT as chaining_method_output
from patterns.behavioral.command import main as command_main
Expand All @@ -38,7 +36,6 @@
reason="requires python3.4 or higher")
@pytest.mark.parametrize("main,output", [
(catalog_main, catalog_output),
(chain_main, chain_output),
(chaining_method_main, chaining_method_output),
(command_main, command_output),
(iterator_main, iterator_output),
Expand Down

0 comments on commit 423fe9d

Please sign in to comment.