diff options
author | Raphaël Barrois <raphael.barrois@polyconseil.fr> | 2011-09-06 17:18:00 +0200 |
---|---|---|
committer | Raphaël Barrois <raphael.barrois@polyconseil.fr> | 2011-09-06 17:18:00 +0200 |
commit | 41c68349cf3925818a6c05477790a2f67d431d39 (patch) | |
tree | b3fe0111b500ec97b28f5c5badb9a4709ca9cc7e | |
parent | d17972e3e21c14ecca91e4238fa0726bd1a68a23 (diff) | |
download | factory-boy-41c68349cf3925818a6c05477790a2f67d431d39.tar factory-boy-41c68349cf3925818a6c05477790a2f67d431d39.tar.gz |
Rewrite all the 'OrderedDeclaration' part.
Signed-off-by: Raphaël Barrois <raphael.barrois@polyconseil.fr>
-rw-r--r-- | factory/base.py | 2 | ||||
-rw-r--r-- | factory/containers.py | 181 | ||||
-rw-r--r-- | factory/declarations.py | 41 | ||||
-rw-r--r-- | factory/test_base.py | 9 | ||||
-rw-r--r-- | factory/test_containers.py | 191 | ||||
-rw-r--r-- | factory/test_declarations.py | 18 |
6 files changed, 151 insertions, 291 deletions
diff --git a/factory/base.py b/factory/base.py index e5729de..890a7c9 100644 --- a/factory/base.py +++ b/factory/base.py @@ -21,7 +21,7 @@ import re import sys -from containers import AttributeBuilder, DeclarationDict, ObjectParamsWrapper, StubObject +from containers import AttributeBuilder, DeclarationDict, StubObject from declarations import OrderedDeclaration # Strategies diff --git a/factory/containers.py b/factory/containers.py index 8012444..62c8847 100644 --- a/factory/containers.py +++ b/factory/containers.py @@ -23,7 +23,12 @@ from declarations import OrderedDeclaration, SubFactory ATTR_SPLITTER = '__' -class ObjectParamsWrapper(object): + +class CyclicDefinitionError(Exception): + """Raised when cyclic definition were found.""" + + +class LazyStub(object): """A generic container that allows for getting but not setting of attributes. Attributes are set at initialization time.""" @@ -31,107 +36,45 @@ class ObjectParamsWrapper(object): initialized = False def __init__(self, attrs): - self.attrs = attrs + self.__attrs = attrs + self.__values = {} + self.__pending = [] self.initialized = True - def __setattr__(self, name, value): - if not self.initialized: - return super(ObjectParamsWrapper, self).__setattr__(name, value) - else: - raise AttributeError('Setting of object attributes is not allowed') + def __fill__(self): + res = {} + for attr in self.__attrs: + res[attr] = getattr(self, attr) + return res def __getattr__(self, name): - try: - return self.attrs[name] - except KeyError: - raise AttributeError("The param '{0}' does not exist. Perhaps your declarations are out of order?".format(name)) - - -class OrderedDict(object): - def __init__(self, **kwargs): - self._order = {} - self._values = {} - for k, v in kwargs.iteritems(): - self[k] = v - - def __contains__(self, key): - return key in self._values - - def __getitem__(self, key): - return self._values[key] - - def __setitem__(self, key, val): - if key in self: - del self[key] - self._values[key] = val - self._order.setdefault(val.order, set()).add(key) - - def __delitem__(self, key): - self.pop(key) - - def pop(self, key): - val = self._values.pop(key) - self._order[val.order].remove(key) - return val - - def items(self): - return list(self.iteritems()) - - def iteritems(self): - order = sorted(self._order.keys()) - for i in order: - for key in self._order[i]: - yield (key, self._values[key]) - - def __iter__(self): - order = sorted(self._order.keys()) - for i in order: - for k in self._order[i]: - yield k - - -class DeclarationDict(object): - """Holds a dict of declarations, keeping OrderedDeclaration at the end.""" - def __init__(self, extra=None): - if not extra: - extra = {} - self._ordered = OrderedDict() - self._unordered = {} - self.update(extra) + if name in self.__pending: + raise CyclicDefinitionError( + "Cyclic lazy attribute definition for %s. Current cycle is %r." % + (name, self.__pending)) + elif name in self.__values: + return self.__values[name] + elif name in self.__attrs: + val = self.__attrs[name] + if isinstance(val, LazyValue): + self.__pending.append(name) + val = val.evaluate(self) + assert name == self.__pending.pop() + self.__values[name] = val + return val + else: + raise AttributeError( + "The parameter %s is unknown. Evaluated attributes are %r, definitions are %r." % (name, self.__values, self.__attrs)) - def __setitem__(self, key, value): - if key in self: - del self[key] - if isinstance(value, OrderedDeclaration): - self._ordered[key] = value - else: - self._unordered[key] = value - - def __getitem__(self, key): - """Try in _unordered first, then in _ordered.""" - try: - return self._unordered[key] - except KeyError: - return self._ordered[key] - - def __delitem__(self, key): - if key in self._unordered: - del self._unordered[key] + def __setattr__(self, name, value): + if not self.initialized: + return super(LazyStub, self).__setattr__(name, value) else: - del self._ordered[key] - - def pop(self, key, *args): - assert len(args) <= 1 - try: - return self._unordered.pop(key) - except KeyError: - return self._ordered.pop(key, *args) + raise AttributeError('Setting of object attributes is not allowed') - def update(self, d): - for k in d: - self[k] = d[k] +class DeclarationDict(dict): def update_with_public(self, d): """Updates the DeclarationDict from a class definition dict. @@ -155,23 +98,29 @@ class DeclarationDict(object): new.update(extra) return new - def __contains__(self, key): - return key in self._unordered or key in self._ordered - def items(self): - return list(self.iteritems()) +class LazyValue(object): + def evaluate(self, obj): + raise NotImplementedError("This is an abstract method.") + + +class SubFactoryWrapper(LazyValue): + def __init__(self, subfactory, subfields, create): + self.subfactory = subfactory + self.subfields = subfields + self.create = create + + def evaluate(self, obj): + return self.subfactory.evaluate(self.create, self.subfields) + - def iteritems(self): - for pair in self._unordered.iteritems(): - yield pair - for pair in self._ordered.iteritems(): - yield pair +class OrderedDeclarationWrapper(LazyValue): + def __init__(self, declaration, sequence): + self.declaration = declaration + self.sequence = sequence - def __iter__(self): - for k in self._unordered: - yield k - for k in self._ordered: - yield k + def evaluate(self, obj): + return self.declaration.evaluate(self.sequence, obj) class AttributeBuilder(object): @@ -196,16 +145,16 @@ class AttributeBuilder(object): def build(self, create): self.factory.sequence = self.factory._generate_next_sequence() - attributes = {} - for key, val in self._attrs.iteritems(): - if isinstance(val, SubFactory): - val = val.evaluate(self.factory, create, self._subfields.get(key, {})) - elif isinstance(val, OrderedDeclaration): - wrapper = ObjectParamsWrapper(attributes) - val = val.evaluate(self.factory, wrapper) - attributes[key] = val + wrapped_attrs = {} + for k, v in self._attrs.iteritems(): + if isinstance(v, SubFactory): + v = SubFactoryWrapper(v, self._subfields.get(k, {}), create) + elif isinstance(v, OrderedDeclaration): + v = OrderedDeclarationWrapper(v, self.factory.sequence) + wrapped_attrs[k] = v - return attributes + stub = LazyStub(wrapped_attrs) + return stub.__fill__() class StubObject(object): diff --git a/factory/declarations.py b/factory/declarations.py index a1e9102..f1112c5 100644 --- a/factory/declarations.py +++ b/factory/declarations.py @@ -18,37 +18,12 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -import threading - -global_counter_lock = threading.Lock() - -class GlobalCounter(object): - """A simple global counter. - - It is used to order the various OrderedDeclaration together. - """ - - _value = 0 - - @classmethod - def step(cls): - with global_counter_lock: - current = cls._value - cls._value += 1 - return current - - class OrderedDeclaration(object): """A factory declaration. Ordered declarations keep track of the order in which they're defined so that later declarations can refer to attributes created by earlier declarations when the declarations are evaluated.""" - _next_order = 0 - - def __init__(self): - self.order = GlobalCounter.step() - - def evaluate(self, factory, obj): + def evaluate(self, sequence, obj): """Evaluate this declaration. Args: @@ -64,7 +39,7 @@ class LazyAttribute(OrderedDeclaration): super(LazyAttribute, self).__init__() self.function = function - def evaluate(self, factory, obj): + def evaluate(self, sequence, obj): return self.function(obj) @@ -73,7 +48,7 @@ class SelfAttribute(OrderedDeclaration): super(SelfAttribute, self).__init__() self.attribute_name = attribute_name - def evaluate(self, factory, obj): + def evaluate(self, sequence, obj): return getattr(obj, self.attribute_name) @@ -83,13 +58,13 @@ class Sequence(OrderedDeclaration): self.function = function self.type = type - def evaluate(self, factory, obj): - return self.function(self.type(factory.sequence)) + def evaluate(self, sequence, obj): + return self.function(self.type(sequence)) class LazyAttributeSequence(Sequence): - def evaluate(self, factory, obj): - return self.function(obj, self.type(factory.sequence)) + def evaluate(self, sequence, obj): + return self.function(obj, self.type(sequence)) class SubFactory(OrderedDeclaration): @@ -105,7 +80,7 @@ class SubFactory(OrderedDeclaration): self.defaults = kwargs self.factory = factory - def evaluate(self, factory, create, extra): + def evaluate(self, create, extra): """Evaluate the current definition and fill its attributes. Uses attributes definition in the following order: diff --git a/factory/test_base.py b/factory/test_base.py index 753f116..783e041 100644 --- a/factory/test_base.py +++ b/factory/test_base.py @@ -104,11 +104,7 @@ class FactoryTestCase(unittest.TestCase): class TestObjectFactory(Factory): one = declarations.LazyAttribute(lambda a: a.does_not_exist ) - try: - TestObjectFactory() - self.fail() - except AttributeError as e: - self.assertTrue('does not exist' in str(e)) + self.assertRaises(AttributeError, TestObjectFactory) def testLazyAttributeSequence(self): class TestObjectFactory(Factory): @@ -368,6 +364,7 @@ class FactoryDefaultStrategyTestCase(unittest.TestCase): FACTORY_FOR = TestObject wrapped = declarations.SubFactory(TestObjectFactory) + friend = declarations.LazyAttribute(lambda o: o.wrapped.two.four + 1) class OuterWrappingTestObjectFactory(Factory): FACTORY_FOR = TestObject @@ -375,9 +372,9 @@ class FactoryDefaultStrategyTestCase(unittest.TestCase): wrap = declarations.SubFactory(WrappingTestObjectFactory, wrapped__two=declarations.SubFactory(TestObjectFactory, four=4)) - outer = OuterWrappingTestObjectFactory.build() self.assertEqual(outer.wrap.wrapped.two.four, 4) + self.assertEqual(outer.wrap.friend, 5) def testStubStrategy(self): Factory.default_strategy = STUB_STRATEGY diff --git a/factory/test_containers.py b/factory/test_containers.py index 627bc86..9cc0378 100644 --- a/factory/test_containers.py +++ b/factory/test_containers.py @@ -20,109 +20,51 @@ import unittest -from containers import AttributeBuilder, DeclarationDict, OrderedDict, ObjectParamsWrapper import base +import containers import declarations -class ObjectParamsWrapperTestCase(unittest.TestCase): - def test_read(self): - vals = {'one': 1, 'two': 2} - wrapper = ObjectParamsWrapper(vals) +class LazyStubTestCase(unittest.TestCase): + def test_basic(self): + stub = containers.LazyStub({'one': 1, 'two': 2}) - self.assertEqual(1, wrapper.one) - self.assertEqual(2, wrapper.two) - self.assertRaises(AttributeError, getattr, wrapper, 'three') - self.assertRaises(AttributeError, setattr, wrapper, 'one', 1) + self.assertEqual({'one': 1, 'two': 2}, stub.__fill__()) - def test_standard_attributes(self): - wrapper = ObjectParamsWrapper({}) - self.assertEqual(ObjectParamsWrapper, wrapper.__class__) + def test_setting_values(self): + stub = containers.LazyStub({'one': 1, 'two': 2}) + self.assertRaises(AttributeError, setattr, stub, 'one', 1) -class OrderedDeclarationMock(object): - def __init__(self, order): - self.order = order + def test_reading_value(self): + stub = containers.LazyStub({'one': 1, 'two': 2}) + self.assertEqual(1, stub.one) + self.assertRaises(AttributeError, getattr, stub, 'three') -class OrderedDictTestCase(unittest.TestCase): - def test_basics(self): - one = OrderedDeclarationMock(1) - two = OrderedDeclarationMock(2) - three = OrderedDeclarationMock(3) - d = OrderedDict(one=one, two=two, three=three) - self.assertEqual(one, d['one']) - self.assertEqual(two, d['two']) - self.assertEqual(three, d['three']) - - self.assertTrue('one' in d) - self.assertTrue('two' in d) - self.assertTrue('three' in d) - - self.assertEqual(one, d.pop('one')) - self.assertFalse('one' in d) - - d['one'] = one - self.assertTrue('one' in d) - self.assertEqual(one, d['one']) - - self.assertEqual(set(['one', 'two', 'three']), - set(d)) + def test_cyclic_definition(self): + class LazyAttr(containers.LazyValue): + def __init__(self, attrname): + self.attrname = attrname - def test_order(self): - one = OrderedDeclarationMock(1) - two = OrderedDeclarationMock(2) - ten = OrderedDeclarationMock(10) - d = OrderedDict(one=one, two=two, ten=ten) + def evaluate(self, obj): + return 1 + getattr(obj, self.attrname) - self.assertEqual(['one', 'two', 'ten'], list(d)) - self.assertEqual([('one', one), ('two', two), ('ten', ten)], - d.items()) - self.assertEqual([('one', one), ('two', two), ('ten', ten)], - list(d.iteritems())) + stub = containers.LazyStub({'one': LazyAttr('two'), 'two': LazyAttr('one')}) - def test_insert(self): - one = OrderedDeclarationMock(1) - two = OrderedDeclarationMock(2) - four = OrderedDeclarationMock(4) - ten = OrderedDeclarationMock(10) - d = OrderedDict(one=one, two=two, ten=ten) - - self.assertEqual(['one', 'two', 'ten'], list(d)) - d['four'] = four - self.assertEqual(['one', 'two', 'four', 'ten'], list(d)) - - def test_replace(self): - one = OrderedDeclarationMock(1) - two = OrderedDeclarationMock(2) - three = OrderedDeclarationMock(3) - ten = OrderedDeclarationMock(10) - d = OrderedDict(one=one, two=two, ten=ten) - - self.assertEqual(['one', 'two', 'ten'], list(d)) - d['one'] = three + self.assertRaises(containers.CyclicDefinitionError, getattr, stub, 'one') - self.assertEqual(three, d['one']) - self.assertEqual(['two', 'one', 'ten'], list(d)) - def test_order_conflict(self): - one = OrderedDeclarationMock(1) - two = OrderedDeclarationMock(1) - - d = OrderedDict(one=one, two=two) - self.assertEqual(set(['one', 'two']), set(d)) - - -class OrderedDeclarationFullMock(declarations.OrderedDeclaration): +class OrderedDeclarationMock(declarations.OrderedDeclaration): pass class DeclarationDictTestCase(unittest.TestCase): def test_basics(self): - one = OrderedDeclarationFullMock() + one = OrderedDeclarationMock() two = 2 - three = OrderedDeclarationFullMock() + three = OrderedDeclarationMock() - d = DeclarationDict(dict(one=one, two=two, three=three)) + d = containers.DeclarationDict(dict(one=one, two=two, three=three)) self.assertTrue('one' in d) self.assertTrue('two' in d) @@ -142,44 +84,57 @@ class DeclarationDictTestCase(unittest.TestCase): self.assertEqual(set(['one', 'two', 'three']), set(d)) - def test_order(self): - one = OrderedDeclarationFullMock() + def test_insert(self): + one = OrderedDeclarationMock() two = 2 - three = OrderedDeclarationFullMock() + three = OrderedDeclarationMock() + four = OrderedDeclarationMock() - d = DeclarationDict(dict(one=one, two=two, three=three)) + d = containers.DeclarationDict(dict(one=one, two=two, four=four)) - self.assertEqual(['two', 'one', 'three'], list(d)) - self.assertEqual([('two', two), ('one', one), ('three', three)], - list(d.items())) - self.assertEqual([('two', two), ('one', one), ('three', three)], - list(d.iteritems())) + self.assertEqual(set(['two', 'one', 'four']), set(d)) - def test_insert(self): - one = OrderedDeclarationFullMock() + d['three'] = three + self.assertEqual(set(['two', 'one', 'three', 'four']), set(d)) + + def test_replace(self): + one = OrderedDeclarationMock() two = 2 - three = OrderedDeclarationFullMock() - four = OrderedDeclarationFullMock() + three = OrderedDeclarationMock() + four = OrderedDeclarationMock() - d = DeclarationDict(dict(one=one, two=two, four=four)) + d = containers.DeclarationDict(dict(one=one, two=two, three=three)) - self.assertEqual(['two', 'one', 'four'], list(d)) + self.assertEqual(set(['two', 'one', 'three']), set(d)) - d['three'] = three - self.assertEqual(['two', 'one', 'three', 'four'], list(d)) + d['three'] = four + self.assertEqual(set(['two', 'one', 'three']), set(d)) + self.assertEqual(set([two, one, four]), set(d.values())) - def test_replace(self): - one = OrderedDeclarationFullMock() + def test_copy(self): + one = OrderedDeclarationMock() two = 2 - three = OrderedDeclarationFullMock() - four = OrderedDeclarationFullMock() + three = OrderedDeclarationMock() + four = OrderedDeclarationMock() + + d = containers.DeclarationDict(dict(one=one, two=two, three=three)) + d2 = d.copy({'five': 5}) - d = DeclarationDict(dict(one=one, two=two, three=three)) + self.assertEqual(5, d2['five']) + self.assertFalse('five' in d) - self.assertEqual(['two', 'one', 'three'], list(d)) + d.pop('one') + self.assertEqual(one, d2['one']) + + d2['two'] = four + self.assertEqual(four, d2['two']) + self.assertEqual(two, d['two']) - d['one'] = four - self.assertEqual(['two', 'three', 'one'], list(d)) + def test_update_with_public(self): + d = containers.DeclarationDict() + d.update_with_public({'one': 1, '_two': 2, 'three': 3}) + self.assertEqual(set(['one', 'three']), set(d)) + self.assertEqual(set([1, 3]), set(d.values())) class AttributeBuilderTestCase(unittest.TestCase): @@ -195,7 +150,7 @@ class AttributeBuilderTestCase(unittest.TestCase): def _generate_next_sequence(cls): return 1 - ab = AttributeBuilder(FakeFactory) + ab = containers.AttributeBuilder(FakeFactory) self.assertEqual({}, ab.build(create=False)) @@ -211,7 +166,7 @@ class AttributeBuilderTestCase(unittest.TestCase): def _generate_next_sequence(cls): return 1 - ab = AttributeBuilder(FakeFactory) + ab = containers.AttributeBuilder(FakeFactory) self.assertEqual({'one': 1}, ab.build(create=False)) def test_extended(self): @@ -226,7 +181,7 @@ class AttributeBuilderTestCase(unittest.TestCase): def _generate_next_sequence(cls): return 1 - ab = AttributeBuilder(FakeFactory, {'two': 2}) + ab = containers.AttributeBuilder(FakeFactory, {'two': 2}) self.assertEqual({'one': 1, 'two': 2}, ab.build(create=False)) def test_overridden(self): @@ -241,7 +196,7 @@ class AttributeBuilderTestCase(unittest.TestCase): def _generate_next_sequence(cls): return 1 - ab = AttributeBuilder(FakeFactory, {'one': 2}) + ab = containers.AttributeBuilder(FakeFactory, {'one': 2}) self.assertEqual({'one': 2}, ab.build(create=False)) def test_factory_defined_sequence(self): @@ -258,7 +213,7 @@ class AttributeBuilderTestCase(unittest.TestCase): def _generate_next_sequence(cls): return 1 - ab = AttributeBuilder(FakeFactory) + ab = containers.AttributeBuilder(FakeFactory) self.assertEqual({'one': 'xx1'}, ab.build(create=False)) def test_additionnal_sequence(self): @@ -275,7 +230,7 @@ class AttributeBuilderTestCase(unittest.TestCase): def _generate_next_sequence(cls): return 1 - ab = AttributeBuilder(FakeFactory, extra={'two': seq}) + ab = containers.AttributeBuilder(FakeFactory, extra={'two': seq}) self.assertEqual({'one': 1, 'two': 'xx1'}, ab.build(create=False)) def test_replaced_sequence(self): @@ -293,7 +248,7 @@ class AttributeBuilderTestCase(unittest.TestCase): def _generate_next_sequence(cls): return 1 - ab = AttributeBuilder(FakeFactory, extra={'one': seq2}) + ab = containers.AttributeBuilder(FakeFactory, extra={'one': seq2}) self.assertEqual({'one': 'yy1'}, ab.build(create=False)) def test_lazy_attribute(self): @@ -302,7 +257,7 @@ class AttributeBuilderTestCase(unittest.TestCase): class FakeFactory(object): @classmethod def declarations(cls, extra): - d = DeclarationDict({'one': 1, 'two': la}) + d = containers.DeclarationDict({'one': 1, 'two': la}) d.update(extra) return d @@ -310,13 +265,13 @@ class AttributeBuilderTestCase(unittest.TestCase): def _generate_next_sequence(cls): return 1 - ab = AttributeBuilder(FakeFactory) + ab = containers.AttributeBuilder(FakeFactory) self.assertEqual({'one': 1, 'two': 2}, ab.build(create=False)) - ab = AttributeBuilder(FakeFactory, {'one': 4}) + ab = containers.AttributeBuilder(FakeFactory, {'one': 4}) self.assertEqual({'one': 4, 'two': 8}, ab.build(create=False)) - ab = AttributeBuilder(FakeFactory, {'one': 4, 'three': la}) + ab = containers.AttributeBuilder(FakeFactory, {'one': 4, 'three': la}) self.assertEqual({'one': 4, 'two': 8, 'three': 8}, ab.build(create=False)) def test_sub_factory(self): diff --git a/factory/test_declarations.py b/factory/test_declarations.py index 27d5f9e..f692ccf 100644 --- a/factory/test_declarations.py +++ b/factory/test_declarations.py @@ -20,28 +20,12 @@ import unittest -from declarations import GlobalCounter, OrderedDeclaration, Sequence - -class GlobalCounterTestCase(unittest.TestCase): - def test_incr(self): - init = GlobalCounter.step() - mid = GlobalCounter.step() - last = GlobalCounter.step() - self.assertEqual(2, last - init) - self.assertEqual(1, mid - init) - +from declarations import OrderedDeclaration, Sequence class OrderedDeclarationTestCase(unittest.TestCase): def test_errors(self): decl = OrderedDeclaration() self.assertRaises(NotImplementedError, decl.evaluate, None, {}) - def test_order(self): - decl1 = OrderedDeclaration() - decl2 = OrderedDeclaration() - decl3 = Sequence(lambda n: 3 * n) - self.assertTrue(decl1.order < decl2.order) - self.assertTrue(decl2.order < decl3.order) - if __name__ == '__main__': unittest.main() |