Skip to content

Commit 423fe9d

Browse files
authored
Merge pull request faif#287 from gyermolenko/separate_version_specific_scripts
Separate version specific scripts
2 parents 4292a34 + 02b653a commit 423fe9d

File tree

10 files changed

+251
-86
lines changed

10 files changed

+251
-86
lines changed

.travis.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,12 @@ install:
1616
- pip install -r requirements-dev.txt
1717

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

2526
after_success:
2627
- codecov

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ __Behavioral Patterns__:
3737

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

patterns/behavioral/chain_of_responsibility.py renamed to patterns/behavioral/chain_of_responsibility__py2.py

Lines changed: 22 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -92,29 +92,28 @@ def check_range(request):
9292

9393

9494
def main():
95-
h0 = ConcreteHandler0()
96-
h1 = ConcreteHandler1()
97-
h2 = ConcreteHandler2(FallbackHandler())
98-
h0.successor = h1
99-
h1.successor = h2
100-
101-
requests = [2, 5, 14, 22, 18, 3, 35, 27, 20]
102-
for request in requests:
103-
h0.handle(request)
95+
"""
96+
>>> h0 = ConcreteHandler0()
97+
>>> h1 = ConcreteHandler1()
98+
>>> h2 = ConcreteHandler2(FallbackHandler())
99+
>>> h0.successor = h1
100+
>>> h1.successor = h2
101+
102+
>>> requests = [2, 5, 14, 22, 18, 3, 35, 27, 20]
103+
>>> for request in requests:
104+
... h0.handle(request)
105+
request 2 handled in handler 0
106+
request 5 handled in handler 0
107+
request 14 handled in handler 1
108+
request 22 handled in handler 2
109+
request 18 handled in handler 1
110+
request 3 handled in handler 0
111+
end of chain, no handler for 35
112+
request 27 handled in handler 2
113+
request 20 handled in handler 2
114+
"""
104115

105116

106117
if __name__ == "__main__":
107-
main()
108-
109-
110-
OUTPUT = """
111-
request 2 handled in handler 0
112-
request 5 handled in handler 0
113-
request 14 handled in handler 1
114-
request 22 handled in handler 2
115-
request 18 handled in handler 1
116-
request 3 handled in handler 0
117-
end of chain, no handler for 35
118-
request 27 handled in handler 2
119-
request 20 handled in handler 2
120-
"""
118+
import doctest
119+
doctest.testmod(optionflags=doctest.ELLIPSIS)
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
4+
"""
5+
*What is this pattern about?
6+
7+
The Chain of responsibility is an object oriented version of the
8+
`if ... elif ... elif ... else ...` idiom, with the
9+
benefit that the condition–action blocks can be dynamically rearranged
10+
and reconfigured at runtime.
11+
12+
This pattern aims to decouple the senders of a request from its
13+
receivers by allowing request to move through chained
14+
receivers until it is handled.
15+
16+
Request receiver in simple form keeps a reference to a single successor.
17+
As a variation some receivers may be capable of sending requests out
18+
in several directions, forming a `tree of responsibility`.
19+
20+
*TL;DR80
21+
Allow a request to pass down a chain of receivers until it is handled.
22+
"""
23+
24+
import abc
25+
26+
27+
class Handler(metaclass=abc.ABCMeta):
28+
29+
def __init__(self, successor=None):
30+
self.successor = successor
31+
32+
def handle(self, request):
33+
"""
34+
Handle request and stop.
35+
If can't - call next handler in chain.
36+
37+
As an alternative you might even in case of success
38+
call the next handler.
39+
"""
40+
res = self.check_range(request)
41+
if not res and self.successor:
42+
self.successor.handle(request)
43+
44+
@abc.abstractmethod
45+
def check_range(self, request):
46+
"""Compare passed value to predefined interval"""
47+
48+
49+
class ConcreteHandler0(Handler):
50+
"""Each handler can be different.
51+
Be simple and static...
52+
"""
53+
54+
@staticmethod
55+
def check_range(request):
56+
if 0 <= request < 10:
57+
print("request {} handled in handler 0".format(request))
58+
return True
59+
60+
61+
class ConcreteHandler1(Handler):
62+
"""... With it's own internal state"""
63+
64+
start, end = 10, 20
65+
66+
def check_range(self, request):
67+
if self.start <= request < self.end:
68+
print("request {} handled in handler 1".format(request))
69+
return True
70+
71+
72+
class ConcreteHandler2(Handler):
73+
"""... With helper methods."""
74+
75+
def check_range(self, request):
76+
start, end = self.get_interval_from_db()
77+
if start <= request < end:
78+
print("request {} handled in handler 2".format(request))
79+
return True
80+
81+
@staticmethod
82+
def get_interval_from_db():
83+
return (20, 30)
84+
85+
86+
class FallbackHandler(Handler):
87+
@staticmethod
88+
def check_range(request):
89+
print("end of chain, no handler for {}".format(request))
90+
return False
91+
92+
93+
def main():
94+
"""
95+
>>> h0 = ConcreteHandler0()
96+
>>> h1 = ConcreteHandler1()
97+
>>> h2 = ConcreteHandler2(FallbackHandler())
98+
>>> h0.successor = h1
99+
>>> h1.successor = h2
100+
101+
>>> requests = [2, 5, 14, 22, 18, 3, 35, 27, 20]
102+
>>> for request in requests:
103+
... h0.handle(request)
104+
request 2 handled in handler 0
105+
request 5 handled in handler 0
106+
request 14 handled in handler 1
107+
request 22 handled in handler 2
108+
request 18 handled in handler 1
109+
request 3 handled in handler 0
110+
end of chain, no handler for 35
111+
request 27 handled in handler 2
112+
request 20 handled in handler 2
113+
"""
114+
115+
116+
if __name__ == "__main__":
117+
import doctest
118+
doctest.testmod(optionflags=doctest.ELLIPSIS)

patterns/behavioral/registry.py

Lines changed: 0 additions & 51 deletions
This file was deleted.

patterns/behavioral/registry__py2.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
4+
5+
class RegistryHolder(type):
6+
7+
REGISTRY = {}
8+
9+
def __new__(cls, name, bases, attrs):
10+
new_cls = type.__new__(cls, name, bases, attrs)
11+
"""
12+
Here the name of the class is used as key but it could be any class
13+
parameter.
14+
"""
15+
cls.REGISTRY[new_cls.__name__] = new_cls
16+
return new_cls
17+
18+
@classmethod
19+
def get_registry(cls):
20+
return dict(cls.REGISTRY)
21+
22+
23+
class BaseRegisteredClass(object):
24+
"""
25+
Any class that will inherits from BaseRegisteredClass will be included
26+
inside the dict RegistryHolder.REGISTRY, the key being the name of the
27+
class and the associated value, the class itself.
28+
"""
29+
__metaclass__ = RegistryHolder
30+
31+
32+
def main():
33+
"""
34+
Before subclassing
35+
>>> sorted(RegistryHolder.REGISTRY)
36+
['BaseRegisteredClass']
37+
38+
>>> class ClassRegistree(BaseRegisteredClass):
39+
... def __init__(self, *args, **kwargs):
40+
... pass
41+
42+
After subclassing
43+
>>> sorted(RegistryHolder.REGISTRY)
44+
['BaseRegisteredClass', 'ClassRegistree']
45+
"""
46+
47+
48+
if __name__ == "__main__":
49+
import doctest
50+
doctest.testmod(optionflags=doctest.ELLIPSIS)

patterns/behavioral/registry__py3.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
4+
5+
class RegistryHolder(type):
6+
7+
REGISTRY = {}
8+
9+
def __new__(cls, name, bases, attrs):
10+
new_cls = type.__new__(cls, name, bases, attrs)
11+
"""
12+
Here the name of the class is used as key but it could be any class
13+
parameter.
14+
"""
15+
cls.REGISTRY[new_cls.__name__] = new_cls
16+
return new_cls
17+
18+
@classmethod
19+
def get_registry(cls):
20+
return dict(cls.REGISTRY)
21+
22+
23+
class BaseRegisteredClass(metaclass=RegistryHolder):
24+
"""
25+
Any class that will inherits from BaseRegisteredClass will be included
26+
inside the dict RegistryHolder.REGISTRY, the key being the name of the
27+
class and the associated value, the class itself.
28+
"""
29+
30+
31+
def main():
32+
"""
33+
Before subclassing
34+
>>> sorted(RegistryHolder.REGISTRY)
35+
['BaseRegisteredClass']
36+
37+
>>> class ClassRegistree(BaseRegisteredClass):
38+
... def __init__(self, *args, **kwargs):
39+
... pass
40+
41+
After subclassing
42+
>>> sorted(RegistryHolder.REGISTRY)
43+
['BaseRegisteredClass', 'ClassRegistree']
44+
"""
45+
46+
47+
if __name__ == "__main__":
48+
import doctest
49+
doctest.testmod(optionflags=doctest.ELLIPSIS)

requirements-dev.txt

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
-e .
2-
pytest~=4.1
3-
pytest-cov~=2.6
4-
flake8~=3.6
5-
codecov~=2.0
6-
mock~=2.0
2+
3+
pytest~=4.3.0
4+
pytest-cov~=2.6.0
5+
flake8~=3.7.0
6+
codecov~=2.0.0
7+
8+
mock~=2.0.0; python_version < "3.*"

tests/__init__.py

Whitespace-only changes.

tests/test_outputs.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@
1010

1111
from patterns.behavioral.catalog import main as catalog_main
1212
from patterns.behavioral.catalog import OUTPUT as catalog_output
13-
from patterns.behavioral.chain_of_responsibility import main as chain_main
14-
from patterns.behavioral.chain_of_responsibility import OUTPUT as chain_output
1513
from patterns.behavioral.chaining_method import main as chaining_method_main
1614
from patterns.behavioral.chaining_method import OUTPUT as chaining_method_output
1715
from patterns.behavioral.command import main as command_main
@@ -38,7 +36,6 @@
3836
reason="requires python3.4 or higher")
3937
@pytest.mark.parametrize("main,output", [
4038
(catalog_main, catalog_output),
41-
(chain_main, chain_output),
4239
(chaining_method_main, chaining_method_output),
4340
(command_main, command_output),
4441
(iterator_main, iterator_output),

0 commit comments

Comments
 (0)