Skip to content

Commit 0937df5

Browse files
committed
switch from characteristic to attrs
1 parent d0b949f commit 0937df5

8 files changed

Lines changed: 74 additions & 72 deletions

File tree

effect/_base.py

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,31 +5,24 @@
55

66
from functools import partial
77

8-
from characteristic import attributes
8+
import attr
99

1010
import six
1111

1212
from ._continuation import trampoline
1313

1414

15-
@attributes([
16-
'intent', 'callbacks',
17-
], apply_with_init=False, apply_immutable=True)
15+
@attr.s
1816
class Effect(object):
1917
"""
2018
Take an object that describes a desired effect (called an "Intent"), and
2119
allow binding callbacks to be called with the result of the effect.
2220
2321
Effects can be performed with :func:`perform`.
2422
"""
25-
def __init__(self, intent, callbacks=None):
26-
"""
27-
:param intent: An object that describes an effect to be performed.
28-
"""
29-
self.intent = intent
30-
if callbacks is None:
31-
callbacks = []
32-
self.callbacks = callbacks
23+
24+
intent = attr.ib()
25+
callbacks = attr.ib(default=attr.Factory(list))
3326

3427
def on(self, success=None, error=None):
3528
"""

effect/_dispatcher.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,33 @@
22
Dispatcher!
33
"""
44

5+
import attr
6+
57
from six.moves import filter
6-
from characteristic import attributes
78

89

9-
@attributes(['mapping'], apply_with_init=False)
10+
@attr.s
1011
class TypeDispatcher(object):
1112
"""
1213
An Effect dispatcher which looks up the performer to use by type.
1314
"""
14-
def __init__(self, mapping):
15-
"""
16-
:param collections.Mapping mapping: mapping of intent type to performer
17-
"""
18-
self.mapping = mapping
15+
16+
mapping = attr.ib()
1917

2018
def __call__(self, intent):
2119
return self.mapping.get(type(intent))
2220

2321

24-
@attributes(['dispatchers'], apply_with_init=False)
22+
@attr.s
2523
class ComposedDispatcher(object):
2624
"""
2725
A dispatcher which composes other dispatchers.
2826
2927
The dispatchers given will be searched in order until a performer is found.
3028
"""
29+
30+
dispatchers = attr.ib()
31+
3132
def __init__(self, dispatchers):
3233
"""
3334
:param collections.Iterable dispatchers: Dispatchers to search.

effect/_intents.py

Lines changed: 17 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,15 @@
1616

1717

1818
from __future__ import print_function, absolute_import
19-
from characteristic import attributes
19+
20+
import attr
2021

2122
from ._base import Effect
2223
from ._sync import sync_performer
2324
from ._dispatcher import TypeDispatcher
2425

2526

26-
@attributes(['effects'], apply_with_init=False, apply_immutable=True)
27+
@attr.s
2728
class ParallelEffects(object):
2829
"""
2930
An effect intent that asks for a number of effects to be run in parallel,
@@ -41,11 +42,8 @@ class ParallelEffects(object):
4142
Performers of this intent must fail with a :obj:`FirstError` exception when
4243
any child effect fails, representing the first error.
4344
"""
44-
def __init__(self, effects):
45-
"""
46-
:param effects: Effects which should be performed in parallel.
47-
"""
48-
self.effects = effects
45+
46+
effects = attr.ib()
4947

5048

5149
def parallel(effects):
@@ -83,40 +81,35 @@ def parallel_all_errors(effects):
8381
return Effect(ParallelEffects(list(effects)))
8482

8583

86-
@attributes(['exc_info', 'index'])
84+
@attr.s
8785
class FirstError(Exception):
8886
"""
8987
One of the effects in a :obj:`ParallelEffects` resulted in an error. This
9088
represents the first such error that occurred.
9189
"""
90+
exc_info = attr.ib()
91+
index = attr.ib()
92+
9293
def __str__(self):
9394
return '(index=%s) %s: %s' % (
9495
self.index, self.exc_info[0].__name__, self.exc_info[1])
9596

9697

97-
@attributes(['delay'], apply_with_init=False, apply_immutable=True)
98+
@attr.s
9899
class Delay(object):
99100
"""
100101
An intent which represents a delay in time.
101102
102103
When performed, the specified delay will pass and then the effect will
103104
result in None.
104105
"""
105-
def __init__(self, delay):
106-
"""
107-
:param float delay: The number of seconds to delay.
108-
"""
109-
self.delay = delay
106+
delay = attr.ib()
110107

111108

112-
@attributes(['result'], apply_with_init=False, apply_immutable=True)
109+
@attr.s
113110
class Constant(object):
114111
"""An intent that returns a pre-specified result when performed."""
115-
def __init__(self, result):
116-
"""
117-
:param result: The object which the Effect should result in.
118-
"""
119-
self.result = result
112+
result = attr.ib()
120113

121114

122115
@sync_performer
@@ -125,11 +118,10 @@ def perform_constant(dispatcher, intent):
125118
return intent.result
126119

127120

128-
@attributes(['exception'], apply_with_init=False, apply_immutable=True)
121+
@attr.s
129122
class Error(object):
130123
"""An intent that raises a pre-specified exception when performed."""
131-
def __init__(self, exception):
132-
self.exception = exception
124+
exception = attr.ib()
133125

134126

135127
@sync_performer
@@ -138,7 +130,7 @@ def perform_error(dispatcher, intent):
138130
raise intent.exception
139131

140132

141-
@attributes(['func'], apply_with_init=False, apply_immutable=True)
133+
@attr.s
142134
class Func(object):
143135
"""
144136
An intent that returns the result of the specified function.
@@ -158,11 +150,7 @@ class Func(object):
158150
this is useful for integrating wih "legacy" side-effecting code in a quick
159151
way.
160152
"""
161-
def __init__(self, func):
162-
"""
163-
:param func: The function to call when this intent is performed.
164-
"""
165-
self.func = func
153+
func = attr.ib()
166154

167155

168156
@sync_performer

effect/_test_utils.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,16 @@
33
import sys
44
import traceback
55

6-
from characteristic import attributes
6+
import attr
77

88
from testtools.matchers import Equals
99

1010

11-
@attributes(['expected_tb', 'got_tb'])
11+
@attr.s
1212
class ReraisedTracebackMismatch(object):
13+
expected_tb = attr.ib()
14+
got_tb = attr.ib()
15+
1316
def describe(self):
1417
return ("The reference traceback:\n"
1518
+ ''.join(self.expected_tb)
@@ -18,11 +21,10 @@ def describe(self):
1821
+ "\nbut it doesn't.")
1922

2023

21-
@attributes(['expected'], apply_with_init=False)
24+
@attr.s
2225
class MatchesReraisedExcInfo(object):
2326

24-
def __init__(self, expected):
25-
self.expected = expected
27+
expected = attr.ib()
2628

2729
def match(self, actual):
2830
valcheck = Equals(self.expected[1]).match(actual[1])

effect/ref.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from characteristic import attributes
1+
import attr
22

33
from ._base import Effect
44
from ._dispatcher import TypeDispatcher
@@ -42,12 +42,14 @@ def __repr__(self):
4242
return "<Reference({})>".format(self._value)
4343

4444

45-
@attributes(['ref'])
45+
@attr.s
4646
class ReadReference(object):
4747
"""Intent that gets a Reference's current value."""
4848

49+
ref = attr.ib()
4950

50-
@attributes(['ref', 'transformer'])
51+
52+
@attr.s
5153
class ModifyReference(object):
5254
"""
5355
Intent that modifies a Reference value in-place with a transformer func.
@@ -56,6 +58,9 @@ class ModifyReference(object):
5658
modifying the same reference at the same time.
5759
"""
5860

61+
ref = attr.ib()
62+
transformer = attr.ib()
63+
5964

6065
@sync_performer
6166
def perform_read_reference(dispatcher, intent):

effect/test_parallel_performers.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from functools import partial
22

3-
from characteristic import attributes
3+
import attr
44

55
import six
66

@@ -12,9 +12,9 @@
1212
from ._test_utils import MatchesReraisedExcInfo, get_exc_info
1313

1414

15-
@attributes(['message'])
15+
@attr.s
1616
class EquitableException(Exception):
17-
pass
17+
message = attr.ib()
1818

1919

2020
class ParallelPerformerTestsMixin(object):

effect/testing.py

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from functools import partial
1313
import sys
1414

15-
from characteristic import attributes
15+
import attr
1616

1717
from ._base import Effect, guard, _Box, NoPerformerFoundError
1818
from ._sync import NotSynchronousError, sync_performer
@@ -21,42 +21,43 @@
2121
import six
2222

2323

24-
@attributes(['intent'], apply_with_init=False)
24+
@attr.s
2525
class Stub(object):
2626
"""
27+
DEPRECATED in favor of using :obj:`SequenceDispatcher`.
28+
29+
2730
An intent which wraps another intent, to flag that the intent should
2831
be automatically resolved by :func:`resolve_stub`.
2932
3033
:class:`Stub` is intentionally not performable by any default
3134
mechanism.
3235
"""
33-
34-
def __init__(self, intent):
35-
"""
36-
:param intent: The intent to perform with :func:`resolve_stub`.
37-
"""
38-
self.intent = intent
36+
intent = attr.ib()
3937

4038

4139
def ESConstant(x):
42-
"""Return Effect(Stub(Constant(x)))"""
40+
"""DEPRECATED. Return Effect(Stub(Constant(x)))"""
4341
return Effect(Stub(Constant(x)))
4442

4543

4644
def ESError(x):
47-
"""Return Effect(Stub(Error(x)))"""
45+
"""DEPRECATED. Return Effect(Stub(Error(x)))"""
4846
return Effect(Stub(Error(x)))
4947

5048

5149
def ESFunc(x):
52-
"""Return Effect(Stub(Func(x)))"""
50+
"""DEPRECATED. Return Effect(Stub(Func(x)))"""
5351
return Effect(Stub(Func(x)))
5452

5553

5654
def resolve_effect(effect, result, is_error=False):
5755
"""
5856
Supply a result for an effect, allowing its callbacks to run.
5957
58+
Note that is a pretty low-level testing utility; it's much better to use a
59+
higher-level tool like :obj:`SequenceDispatcher` in your tests.
60+
6061
The return value of the last callback is returned, unless any callback
6162
returns another Effect, in which case an Effect representing that
6263
operation plus the remaining callbacks will be returned.
@@ -116,6 +117,8 @@ def fail_effect(effect, exception):
116117

117118
def resolve_stub(dispatcher, effect):
118119
"""
120+
DEPRECATED in favor of obj:`SequenceDispatcher`.
121+
119122
Automatically perform an effect, if its intent is a :obj:`Stub`.
120123
121124
Note that resolve_stubs is preferred to this function, since it handles
@@ -147,6 +150,8 @@ def resolve_stub(dispatcher, effect):
147150

148151
def resolve_stubs(dispatcher, effect):
149152
"""
153+
DEPRECATED in favor of obj:`SequenceDispatcher`.
154+
150155
Successively performs effects with resolve_stub until a non-Effect value,
151156
or an Effect with a non-stub intent is returned, and return that value.
152157
@@ -181,6 +186,10 @@ class EQDispatcher(object):
181186
This dispatcher looks up intents by equality and performs them by returning
182187
an associated constant value.
183188
189+
This is sometimes useful, but :obj:`SequenceDispatcher` should be
190+
preferred, since it constrains the order of effects, which is usually
191+
important.
192+
184193
Users provide a mapping of intents to results, where the intents are
185194
matched against the intents being performed with a simple equality check
186195
(not a type check!).
@@ -219,6 +228,10 @@ class EQFDispatcher(object):
219228
This dispatcher looks up intents by equality and performs them by invoking
220229
an associated function.
221230
231+
This is sometimes useful, but :obj:`SequenceDispatcher` should be
232+
preferred, since it constrains the order of effects, which is usually
233+
important.
234+
222235
Users provide a mapping of intents to functions, where the intents are
223236
matched against the intents being performed with a simple equality check
224237
(not a type check!). The functions in the mapping will be passed only the

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,5 @@
1616
'Programming Language :: Python :: 3',
1717
],
1818
packages=['effect'],
19-
install_requires=['six', 'characteristic>=14.0.0'],
19+
install_requires=['six', 'attrs'],
2020
)

0 commit comments

Comments
 (0)