From c7d92cbfe24abdf03a4258751fb455f12f24eec3 Mon Sep 17 00:00:00 2001 From: Mark Sandstrom Date: Wed, 21 Dec 2011 11:51:49 -0800 Subject: Updated the package description --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index e7eb929..e22a5b4 100644 --- a/setup.py +++ b/setup.py @@ -40,7 +40,7 @@ class test(cmd.Command): setup( name='factory_boy_rbarrois', version=VERSION, - description="A test fixtures replacement based on thoughtbot's factory_girl for Ruby (originally by mark@deliciouslynerdy.com).", + description="A test fixtures replacement based on thoughtbot's factory_girl for Ruby.", author='Mark Sandstrom', author_email='mark@deliciouslynerdy.com', maintainer='Raphaël Barrois', -- cgit v1.2.3 From 41ad5d00acdffaf9c7c59cc3cd7c0dd19c2c7600 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Barrois?= Date: Thu, 22 Dec 2011 01:33:13 +0100 Subject: Fix package name for re-upload to PyPI. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Raphaël Barrois --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index e22a5b4..5625e51 100644 --- a/setup.py +++ b/setup.py @@ -38,7 +38,7 @@ class test(cmd.Command): setup( - name='factory_boy_rbarrois', + name='factory_boy', version=VERSION, description="A test fixtures replacement based on thoughtbot's factory_girl for Ruby.", author='Mark Sandstrom', -- cgit v1.2.3 From 5620221c13398a09cbf2ded1b22ac8304f3f5c27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Barrois?= Date: Mon, 2 Jan 2012 17:05:42 +0100 Subject: Fix kwargs syntax in README custom creation section. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Thanks to 'Lispython' (https://github.com/Lispython) for finding the mistake. Signed-off-by: Raphaël Barrois --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index dba0dbe..7cd80f6 100644 --- a/README.rst +++ b/README.rst @@ -187,7 +187,7 @@ Factory._prepare method:: @classmethod def _prepare(cls, create, **kwargs): password = kwargs.pop('password', None) - user = super(UserFactory, cls)._prepare(create, kwargs) + user = super(UserFactory, cls)._prepare(create, **kwargs) if password: user.set_password(user) if create: -- cgit v1.2.3 From 715276f3a47b34d40574cb22e806ee1fa2d100f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Barrois?= Date: Fri, 6 Jan 2012 23:13:57 +0100 Subject: Add docs for SubFactory. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Raphaël Barrois --- docs/index.rst | 4 ++++ docs/subfactory.rst | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 docs/subfactory.rst diff --git a/docs/index.rst b/docs/index.rst index 69b3b99..17d81c1 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -54,9 +54,13 @@ Once defined, a factory can be instantiated through different methods:: Contents: +:doc:`SubFactory ` + .. toctree:: :maxdepth: 2 + subfactory + Indices and tables ================== diff --git a/docs/subfactory.rst b/docs/subfactory.rst new file mode 100644 index 0000000..8bc1d8f --- /dev/null +++ b/docs/subfactory.rst @@ -0,0 +1,56 @@ +SubFactory +========== + +Some objects may use other complex objects as parameters; in order to simplify this setup, factory_boy +provides the :py:class:`factory.SubFactory` class. + +This should be used when defining a :py:class:`~factory.Factory` attribute that will hold the other complex object:: + + import factory + + # A standard factory + class UserFactory(factory.Factory): + FACTORY_FOR = User + + # Various fields + first_name = 'John' + last_name = factory.Sequence(lambda n: 'D%se' % (o * n)) + email = factory.LazyAttribute(lambda o: '%s.%s@example.org' % (o.first_name.lower(), o.last_name.lower())) + + # A factory for an object with a 'User' field + class CompanyFactory(factory.Factory): + FACTORY_FOR = Company + + name = factory.Sequence(lambda n: 'FactoryBoyz' + z * n) + + # Let's use our UserFactory to create that user, and override its first name. + owner = factory.SubFactory(UserFactory, first_name='Jack') + +Instantiating the external factory will in turn instantiate an object of the internal factory:: + + >>> c = CompanyFactory() + >>> c + + + # Notice that the first_name was overridden + >>> c.owner + + >>> c.owner.email + jack.de@example.org + +Fields of the SubFactory can also be overridden when instantiating the external factory:: + + >>> c = CompanyFactory(owner__first_name='Henry') + >>> c.owner + + + # Notice that the updated first_name was propagated to the email LazyAttribute. + >>> c.owner.email + henry.doe@example.org + + # It is also possible to override other fields of the SubFactory + >>> c = CompanyFactory(owner__last_name='Jones') + >>> c.owner + + >>> c.owner.email + henry.jones@example.org -- cgit v1.2.3 From 105f2fe021a71e1f03650e3bcdd657152fe19a20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Barrois?= Date: Fri, 6 Jan 2012 23:38:51 +0100 Subject: Properly import the 'base' module in test_base. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Raphaël Barrois --- tests/test_base.py | 112 ++++++++++++++++++++++++++--------------------------- 1 file changed, 56 insertions(+), 56 deletions(-) diff --git a/tests/test_base.py b/tests/test_base.py index 68451f3..01855c3 100644 --- a/tests/test_base.py +++ b/tests/test_base.py @@ -22,7 +22,7 @@ import unittest -from factory.base import BaseFactory, Factory, StubFactory, BUILD_STRATEGY, CREATE_STRATEGY, STUB_STRATEGY +from factory import base from factory import declarations class TestObject(object): @@ -52,19 +52,19 @@ class TestModel(FakeDjangoModel): class SafetyTestCase(unittest.TestCase): def testBaseFactory(self): - self.assertRaises(RuntimeError, BaseFactory) + self.assertRaises(RuntimeError, base.BaseFactory) class FactoryTestCase(unittest.TestCase): def testAttribute(self): - class TestObjectFactory(Factory): + class TestObjectFactory(base.Factory): one = 'one' test_object = TestObjectFactory.build() self.assertEqual(test_object.one, 'one') def testSequence(self): - class TestObjectFactory(Factory): + class TestObjectFactory(base.Factory): one = declarations.Sequence(lambda n: 'one' + n) two = declarations.Sequence(lambda n: 'two' + n) @@ -77,7 +77,7 @@ class FactoryTestCase(unittest.TestCase): self.assertEqual(test_object1.two, 'two1') def testSequenceCustomBegin(self): - class TestObjectFactory(Factory): + class TestObjectFactory(base.Factory): @classmethod def _setup_next_sequence(cls): return 42 @@ -94,7 +94,7 @@ class FactoryTestCase(unittest.TestCase): self.assertEqual('two43', test_object1.two) def testLazyAttribute(self): - class TestObjectFactory(Factory): + class TestObjectFactory(base.Factory): one = declarations.LazyAttribute(lambda a: 'abc' ) two = declarations.LazyAttribute(lambda a: a.one + ' xyz') @@ -103,13 +103,13 @@ class FactoryTestCase(unittest.TestCase): self.assertEqual(test_object.two, 'abc xyz') def testLazyAttributeNonExistentParam(self): - class TestObjectFactory(Factory): + class TestObjectFactory(base.Factory): one = declarations.LazyAttribute(lambda a: a.does_not_exist ) self.assertRaises(AttributeError, TestObjectFactory) def testLazyAttributeSequence(self): - class TestObjectFactory(Factory): + class TestObjectFactory(base.Factory): one = declarations.LazyAttributeSequence(lambda a, n: 'abc' + n) two = declarations.LazyAttributeSequence(lambda a, n: a.one + ' xyz' + n) @@ -122,7 +122,7 @@ class FactoryTestCase(unittest.TestCase): self.assertEqual(test_object1.two, 'abc1 xyz1') def testLazyAttributeDecorator(self): - class TestObjectFactory(Factory): + class TestObjectFactory(base.Factory): @declarations.lazy_attribute def one(a): return 'one' @@ -131,7 +131,7 @@ class FactoryTestCase(unittest.TestCase): self.assertEqual(test_object.one, 'one') def testSelfAttribute(self): - class TestObjectFactory(Factory): + class TestObjectFactory(base.Factory): one = 'xx' two = declarations.SelfAttribute('one') @@ -139,7 +139,7 @@ class FactoryTestCase(unittest.TestCase): self.assertEqual(1, test_object.two) def testSequenceDecorator(self): - class TestObjectFactory(Factory): + class TestObjectFactory(base.Factory): @declarations.sequence def one(n): return 'one' + n @@ -148,7 +148,7 @@ class FactoryTestCase(unittest.TestCase): self.assertEqual(test_object.one, 'one0') def testLazyAttributeSequenceDecorator(self): - class TestObjectFactory(Factory): + class TestObjectFactory(base.Factory): @declarations.lazy_attribute_sequence def one(a, n): return 'one' + n @@ -161,7 +161,7 @@ class FactoryTestCase(unittest.TestCase): self.assertEqual(test_object.two, 'one0 two0') def testBuildWithParameters(self): - class TestObjectFactory(Factory): + class TestObjectFactory(base.Factory): one = declarations.Sequence(lambda n: 'one' + n) two = declarations.Sequence(lambda n: 'two' + n) @@ -175,7 +175,7 @@ class FactoryTestCase(unittest.TestCase): self.assertEqual(test_object1.two, 'two1') def testCreate(self): - class TestModelFactory(Factory): + class TestModelFactory(base.Factory): one = 'one' test_model = TestModelFactory.create() @@ -183,7 +183,7 @@ class FactoryTestCase(unittest.TestCase): self.assertTrue(test_model.id) def testInheritance(self): - class TestObjectFactory(Factory): + class TestObjectFactory(base.Factory): one = 'one' two = declarations.LazyAttribute(lambda a: a.one + ' two') @@ -203,7 +203,7 @@ class FactoryTestCase(unittest.TestCase): self.assertEqual(None, test_object_alt.three) def testInheritanceWithInheritedClass(self): - class TestObjectFactory(Factory): + class TestObjectFactory(base.Factory): one = 'one' two = declarations.LazyAttribute(lambda a: a.one + ' two') @@ -219,7 +219,7 @@ class FactoryTestCase(unittest.TestCase): def testInheritanceWithSequence(self): """Tests that sequence IDs are shared between parent and son.""" - class TestObjectFactory(Factory): + class TestObjectFactory(base.Factory): one = declarations.Sequence(lambda a: a) class TestSubFactory(TestObjectFactory): @@ -233,10 +233,10 @@ class FactoryTestCase(unittest.TestCase): self.assertEqual(4, len(ones)) def testDualInheritance(self): - class TestObjectFactory(Factory): + class TestObjectFactory(base.Factory): one = 'one' - class TestOtherFactory(Factory): + class TestOtherFactory(base.Factory): FACTORY_FOR = TestObject two = 'two' four = 'four' @@ -254,7 +254,7 @@ class FactoryTestCase(unittest.TestCase): def creation_function(class_to_create, **kwargs): return "This doesn't even return an instance of {0}".format(class_to_create.__name__) - class TestModelFactory(Factory): + class TestModelFactory(base.Factory): pass TestModelFactory.set_creation_function(creation_function) @@ -264,15 +264,15 @@ class FactoryTestCase(unittest.TestCase): class FactoryDefaultStrategyTestCase(unittest.TestCase): def setUp(self): - self.default_strategy = Factory.default_strategy + self.default_strategy = base.Factory.default_strategy def tearDown(self): - Factory.default_strategy = self.default_strategy + base.Factory.default_strategy = self.default_strategy def testBuildStrategy(self): - Factory.default_strategy = BUILD_STRATEGY + base.Factory.default_strategy = base.BUILD_STRATEGY - class TestModelFactory(Factory): + class TestModelFactory(base.Factory): one = 'one' test_model = TestModelFactory() @@ -282,7 +282,7 @@ class FactoryDefaultStrategyTestCase(unittest.TestCase): def testCreateStrategy(self): # Default default_strategy - class TestModelFactory(Factory): + class TestModelFactory(base.Factory): one = 'one' test_model = TestModelFactory() @@ -293,11 +293,11 @@ class FactoryDefaultStrategyTestCase(unittest.TestCase): class TestModel2(FakeDjangoModel): pass - class TestModelFactory(Factory): + class TestModelFactory(base.Factory): FACTORY_FOR = TestModel one = 3 - class TestModel2Factory(Factory): + class TestModel2Factory(base.Factory): FACTORY_FOR = TestModel2 two = declarations.SubFactory(TestModelFactory, one=1) @@ -310,10 +310,10 @@ class FactoryDefaultStrategyTestCase(unittest.TestCase): class TestModel2(FakeDjangoModel): pass - class TestModelFactory(Factory): + class TestModelFactory(base.Factory): FACTORY_FOR = TestModel - class TestModel2Factory(Factory): + class TestModel2Factory(base.Factory): FACTORY_FOR = TestModel2 two = declarations.SubFactory(TestModelFactory, one=declarations.Sequence(lambda n: 'x%sx' % n), @@ -332,16 +332,16 @@ class FactoryDefaultStrategyTestCase(unittest.TestCase): for k, v in kwargs.iteritems(): setattr(self, k, v) - class TestObjectFactory(Factory): + class TestObjectFactory(base.Factory): FACTORY_FOR = TestObject - class WrappingTestObjectFactory(Factory): + class WrappingTestObjectFactory(base.Factory): FACTORY_FOR = TestObject wrapped = declarations.SubFactory(TestObjectFactory) wrapped_bis = declarations.SubFactory(TestObjectFactory, one=1) - class OuterWrappingTestObjectFactory(Factory): + class OuterWrappingTestObjectFactory(base.Factory): FACTORY_FOR = TestObject wrap = declarations.SubFactory(WrappingTestObjectFactory, wrapped__two=2) @@ -358,17 +358,17 @@ class FactoryDefaultStrategyTestCase(unittest.TestCase): for k, v in kwargs.iteritems(): setattr(self, k, v) - class TestObjectFactory(Factory): + class TestObjectFactory(base.Factory): FACTORY_FOR = TestObject two = 'two' - class WrappingTestObjectFactory(Factory): + class WrappingTestObjectFactory(base.Factory): FACTORY_FOR = TestObject wrapped = declarations.SubFactory(TestObjectFactory) friend = declarations.LazyAttribute(lambda o: o.wrapped.two.four + 1) - class OuterWrappingTestObjectFactory(Factory): + class OuterWrappingTestObjectFactory(base.Factory): FACTORY_FOR = TestObject wrap = declarations.SubFactory(WrappingTestObjectFactory, @@ -379,9 +379,9 @@ class FactoryDefaultStrategyTestCase(unittest.TestCase): self.assertEqual(outer.wrap.friend, 5) def testStubStrategy(self): - Factory.default_strategy = STUB_STRATEGY + base.Factory.default_strategy = base.STUB_STRATEGY - class TestModelFactory(Factory): + class TestModelFactory(base.Factory): one = 'one' test_model = TestModelFactory() @@ -389,54 +389,54 @@ class FactoryDefaultStrategyTestCase(unittest.TestCase): self.assertFalse(hasattr(test_model, 'id')) # We should have a plain old object def testUnknownStrategy(self): - Factory.default_strategy = 'unknown' + base.Factory.default_strategy = 'unknown' - class TestModelFactory(Factory): + class TestModelFactory(base.Factory): one = 'one' - self.assertRaises(Factory.UnknownStrategy, TestModelFactory) + self.assertRaises(base.Factory.UnknownStrategy, TestModelFactory) def testStubWithNonStubStrategy(self): - class TestModelFactory(StubFactory): + class TestModelFactory(base.StubFactory): one = 'one' - TestModelFactory.default_strategy = CREATE_STRATEGY + TestModelFactory.default_strategy = base.CREATE_STRATEGY - self.assertRaises(StubFactory.UnsupportedStrategy, TestModelFactory) + self.assertRaises(base.StubFactory.UnsupportedStrategy, TestModelFactory) - TestModelFactory.default_strategy = BUILD_STRATEGY - self.assertRaises(StubFactory.UnsupportedStrategy, TestModelFactory) + TestModelFactory.default_strategy = base.BUILD_STRATEGY + self.assertRaises(base.StubFactory.UnsupportedStrategy, TestModelFactory) class FactoryCreationTestCase(unittest.TestCase): def testFactoryFor(self): - class TestFactory(Factory): + class TestFactory(base.Factory): FACTORY_FOR = TestObject self.assertTrue(isinstance(TestFactory.build(), TestObject)) def testAutomaticAssociatedClassDiscovery(self): - class TestObjectFactory(Factory): + class TestObjectFactory(base.Factory): pass self.assertTrue(isinstance(TestObjectFactory.build(), TestObject)) def testStub(self): - class TestFactory(StubFactory): + class TestFactory(base.StubFactory): pass - self.assertEqual(TestFactory.default_strategy, STUB_STRATEGY) + self.assertEqual(TestFactory.default_strategy, base.STUB_STRATEGY) def testInheritanceWithStub(self): - class TestObjectFactory(StubFactory): + class TestObjectFactory(base.StubFactory): pass class TestFactory(TestObjectFactory): pass - self.assertEqual(TestFactory.default_strategy, STUB_STRATEGY) + self.assertEqual(TestFactory.default_strategy, base.STUB_STRATEGY) def testCustomCreation(self): - class TestModelFactory(Factory): + class TestModelFactory(base.Factory): @classmethod def _prepare(cls, create, **kwargs): kwargs['four'] = 4 @@ -456,18 +456,18 @@ class FactoryCreationTestCase(unittest.TestCase): def testNoAssociatedClassWithAutodiscovery(self): try: - class TestFactory(Factory): + class TestFactory(base.Factory): pass self.fail() - except Factory.AssociatedClassError as e: + except base.Factory.AssociatedClassError as e: self.assertTrue('autodiscovery' in str(e)) def testNoAssociatedClassWithoutAutodiscovery(self): try: - class Test(Factory): + class Test(base.Factory): pass self.fail() - except Factory.AssociatedClassError as e: + except base.Factory.AssociatedClassError as e: self.assertTrue('autodiscovery' not in str(e)) -- cgit v1.2.3 From 8563a98022b8b3e855bf036a57aad4fd86319eff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Barrois?= Date: Fri, 6 Jan 2012 23:39:33 +0100 Subject: Add factory.build(), factory.create(), factory.stub() methods. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Those are shortcuts for writing a full Factory class. Signed-off-by: Raphaël Barrois --- factory/__init__.py | 4 ++++ factory/base.py | 42 +++++++++++++++++++++++++++++++----------- tests/test_base.py | 21 +++++++++++++++++++++ 3 files changed, 56 insertions(+), 11 deletions(-) diff --git a/factory/__init__.py b/factory/__init__.py index efc0001..f2652f2 100644 --- a/factory/__init__.py +++ b/factory/__init__.py @@ -28,6 +28,10 @@ from base import ( StubFactory, DjangoModelFactory, + build, + create, + stub, + BUILD_STRATEGY, CREATE_STRATEGY, STUB_STRATEGY, diff --git a/factory/base.py b/factory/base.py index 3f3261b..80c49ca 100644 --- a/factory/base.py +++ b/factory/base.py @@ -156,17 +156,18 @@ class FactoryMetaClass(BaseFactoryMetaClass): if FACTORY_CLASS_DECLARATION in attrs: return attrs[FACTORY_CLASS_DECLARATION] - factory_module = sys.modules[attrs['__module__']] - if class_name.endswith('Factory'): - # Try a module lookup - used_auto_discovery = True - associated_class_name = class_name[:-len('Factory')] - if associated_class_name: - # Class name was longer than just 'Factory'. - try: - return getattr(factory_module, associated_class_name) - except AttributeError: - pass + if '__module__' in attrs: + factory_module = sys.modules[attrs['__module__']] + if class_name.endswith('Factory'): + # Try a module lookup + used_auto_discovery = True + associated_class_name = class_name[:-len('Factory')] + if associated_class_name: + # Class name was longer than just 'Factory'. + try: + return getattr(factory_module, associated_class_name) + except AttributeError: + pass # Unable to guess a good option; return the inherited class. if inherited is not None: @@ -439,3 +440,22 @@ class DjangoModelFactory(Factory): ).order_by('-id')[0] except IndexError: return 1 + + +def _make_factory(klass, **kwargs): + factory_name = '%sFactory' % klass.__name__ + kwargs[FACTORY_CLASS_DECLARATION] = klass + factory_class = type(Factory).__new__(type(Factory), factory_name, (Factory,), kwargs) + factory_class.__name__ = '%sFactory' % klass.__name__ + factory_class.__doc__ = 'Auto-generated factory for class %s' % klass + return factory_class + + +def build(klass, **kwargs): + return _make_factory(klass, **kwargs).build() + +def create(klass, **kwargs): + return _make_factory(klass, **kwargs).create() + +def stub(klass, **kwargs): + return _make_factory(klass, **kwargs).stub() diff --git a/tests/test_base.py b/tests/test_base.py index 01855c3..5855682 100644 --- a/tests/test_base.py +++ b/tests/test_base.py @@ -55,6 +55,27 @@ class SafetyTestCase(unittest.TestCase): self.assertRaises(RuntimeError, base.BaseFactory) +class SimpleBuildTestCase(unittest.TestCase): + """Tests the minimalist 'factory.build/create' functions.""" + + def test_build(self): + obj = base.build(TestObject, two=2) + self.assertEqual(obj.one, None) + self.assertEqual(obj.two, 2) + self.assertEqual(obj.three, None) + self.assertEqual(obj.four, None) + + def test_create(self): + obj = base.create(FakeDjangoModel, foo='bar') + self.assertEqual(obj.id, 1) + self.assertEqual(obj.foo, 'bar') + + def test_stub(self): + obj = base.stub(TestObject, three=3) + self.assertEqual(obj.three, 3) + self.assertFalse(hasattr(obj, 'two')) + + class FactoryTestCase(unittest.TestCase): def testAttribute(self): class TestObjectFactory(base.Factory): -- cgit v1.2.3 From bcc1ad6fdce458ae3b24270789ab60ddd2425c4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Barrois?= Date: Fri, 6 Jan 2012 23:43:52 +0100 Subject: Publish the make_factory method, and make sure that complex declarations work with it. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Raphaël Barrois --- factory/__init__.py | 1 + factory/base.py | 12 ++++++++---- tests/test_base.py | 22 ++++++++++++++++++++++ 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/factory/__init__.py b/factory/__init__.py index f2652f2..b673162 100644 --- a/factory/__init__.py +++ b/factory/__init__.py @@ -31,6 +31,7 @@ from base import ( build, create, stub, + make_factory, BUILD_STRATEGY, CREATE_STRATEGY, diff --git a/factory/base.py b/factory/base.py index 80c49ca..19d441b 100644 --- a/factory/base.py +++ b/factory/base.py @@ -442,7 +442,8 @@ class DjangoModelFactory(Factory): return 1 -def _make_factory(klass, **kwargs): +def make_factory(klass, **kwargs): + """Create a new, simple factory for the given class.""" factory_name = '%sFactory' % klass.__name__ kwargs[FACTORY_CLASS_DECLARATION] = klass factory_class = type(Factory).__new__(type(Factory), factory_name, (Factory,), kwargs) @@ -452,10 +453,13 @@ def _make_factory(klass, **kwargs): def build(klass, **kwargs): - return _make_factory(klass, **kwargs).build() + """Create a factory for the given class, and build an instance.""" + return make_factory(klass, **kwargs).build() def create(klass, **kwargs): - return _make_factory(klass, **kwargs).create() + """Create a factory for the given class, and create an instance.""" + return make_factory(klass, **kwargs).create() def stub(klass, **kwargs): - return _make_factory(klass, **kwargs).stub() + """Create a factory for the given class, and stub an instance.""" + return make_factory(klass, **kwargs).stub() diff --git a/tests/test_base.py b/tests/test_base.py index 5855682..b445cb3 100644 --- a/tests/test_base.py +++ b/tests/test_base.py @@ -65,6 +65,13 @@ class SimpleBuildTestCase(unittest.TestCase): self.assertEqual(obj.three, None) self.assertEqual(obj.four, None) + def test_complex(self): + obj = base.build(TestObject, two=2, three=declarations.LazyAttribute(lambda o: o.two + 1)) + self.assertEqual(obj.one, None) + self.assertEqual(obj.two, 2) + self.assertEqual(obj.three, 3) + self.assertEqual(obj.four, None) + def test_create(self): obj = base.create(FakeDjangoModel, foo='bar') self.assertEqual(obj.id, 1) @@ -75,6 +82,21 @@ class SimpleBuildTestCase(unittest.TestCase): self.assertEqual(obj.three, 3) self.assertFalse(hasattr(obj, 'two')) + def test_make_factory(self): + fact = base.make_factory(TestObject, two=2, three=declarations.LazyAttribute(lambda o: o.two + 1)) + + obj = fact.build() + self.assertEqual(obj.one, None) + self.assertEqual(obj.two, 2) + self.assertEqual(obj.three, 3) + self.assertEqual(obj.four, None) + + obj = fact.build(two=4) + self.assertEqual(obj.one, None) + self.assertEqual(obj.two, 4) + self.assertEqual(obj.three, 5) + self.assertEqual(obj.four, None) + class FactoryTestCase(unittest.TestCase): def testAttribute(self): -- cgit v1.2.3 From b81f0282acbb8ee03d56b5ea8957e439efc5bf27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Barrois?= Date: Sun, 8 Jan 2012 21:14:33 +0100 Subject: Add examples to the doc. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Raphaël Barrois --- docs/examples.rst | 136 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ docs/index.rst | 7 ++- 2 files changed, 139 insertions(+), 4 deletions(-) create mode 100644 docs/examples.rst diff --git a/docs/examples.rst b/docs/examples.rst new file mode 100644 index 0000000..cac6bc6 --- /dev/null +++ b/docs/examples.rst @@ -0,0 +1,136 @@ +Examples +======== + +Here are some real-world examples of using FactoryBoy. + + +Objects +------- + +First, let's define a couple of objects:: + + class Account(object): + def __init__(self, username, email): + self.username = username + self.email = email + + def __str__(self): + return '%s (%s)' % (self.username, self.email) + + + class Profile(object): + + GENDER_MALE = 'm' + GENDER_FEMALE = 'f' + GENDER_UNKNOWN = 'u' # If the user refused to give it + + def __init__(self, account, gender, firstname, lastname, planet='Earth'): + self.account = account + self.gender = gender + self.firstname = firstname + self.lastname = lastname + self.planet = planet + + def __unicode__(self): + return u'%s %s (%s)' % ( + unicode(self.firstname), + unicode(self.lastname), + unicode(self.account.accountname), + ) + +Factories +--------- + +And now, we'll define the related factories:: + + import factory + import random + + from . import objects + + + class AccountFactory(factory.Factory): + FACTORY_FOR = objects.Account + + username = factory.Sequence(lambda n: 'john%s' % n) + email = factory.LazyAttribute(lambda o: '%s@example.org' % o.username) + + + class ProfileFactory(factory.Factory): + FACTORY_FOR = objects.Profile + + account = factory.SubFactory(AccountFactory) + gender = random.choice([objects.Profile.GENDER_MALE, objects.Profile.GENDER_FEMALE]) + firstname = u'John' + lastname = u'Doe' + + + +We have now defined basic factories for our :py:class:`~Account` and :py:class:`~Profile` classes. + +If we commonly use a specific variant of our objects, we can refine a factory accordingly:: + + class FemaleProfileFactory(ProfileFactory): + gender = objects.Profile.GENDER_FEMALE + firstname = u'Jane' + user__username = factory.Sequence(lambda n: 'jane%s' % n) + + + +Using the factories +------------------- + +We can now use our factories, for tests:: + + import unittest + + from . import business_logic + from . import factories + from . import objects + + + class MyTestCase(unittest.TestCase): + + def test_send_mail(self): + account = factories.AccountFactory() + email = business_logic.prepare_email(account, subject='Foo', text='Bar') + + self.assertEqual(email.to, account.email) + + def test_get_profile_stats(self): + profiles = [] + + for _ in xrange(4): + profiles.append(factories.ProfileFactory()) + for _ in xrange(2): + profiles.append(factories.FemaleProfileFactory()) + for _ in xrange(2): + profiles.append(factories.ProfileFactory(planet='Tatooine')) + + stats = business_logic.profile_stats(profiles) + self.assertEqual({'Earth': 6, 'Mars': 2}, stats.planets) + self.assertLess(stats.genders[objects.Profile.GENDER_FEMALE], 2) + + +Or for fixtures:: + + from . import factories + + def make_objects(): + for _ in xrange(50): + factories.ProfileFactory() + + # Let's create a few, known objects. + factories.ProfileFactory( + gender=objects.Profile.GENDER_MALE, + firstname='Luke', + lastname='Skywalker', + planet='Tatooine', + ) + + factories.ProfileFactory( + gender=objects.Profile.GENDER_FEMALE, + firstname='Leia', + lastname='Organa', + planet='Alderaan', + ) diff --git a/docs/index.rst b/docs/index.rst index 17d81c1..2e0f4a3 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -54,12 +54,11 @@ Once defined, a factory can be instantiated through different methods:: Contents: -:doc:`SubFactory ` - .. toctree:: - :maxdepth: 2 + :maxdepth: 2 - subfactory + examples + subfactory Indices and tables ================== -- cgit v1.2.3 From 1d2ca8d02e190460f85d20be47bbb1e02f268bd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Barrois?= Date: Sun, 8 Jan 2012 21:14:54 +0100 Subject: Add tests for subfactory field overriding. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Raphaël Barrois --- tests/test_base.py | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/tests/test_base.py b/tests/test_base.py index b445cb3..aaaa65f 100644 --- a/tests/test_base.py +++ b/tests/test_base.py @@ -367,6 +367,28 @@ class FactoryDefaultStrategyTestCase(unittest.TestCase): self.assertEqual('x0x', test_model.two.one) self.assertEqual('x0xx0x', test_model.two.two) + def testSubFactoryOverriding(self): + class TestObject(object): + def __init__(self, **kwargs): + for k, v in kwargs.iteritems(): + setattr(self, k, v) + + class TestObjectFactory(base.Factory): + FACTORY_FOR = TestObject + + class WrappingTestObjectFactory(base.Factory): + FACTORY_FOR = TestObject + + wrapped = declarations.SubFactory(TestObjectFactory, two=2, four=4) + wrapped__two = 4 + wrapped__three = 3 + + wrapping = WrappingTestObjectFactory.build() + self.assertEqual(wrapping.wrapped.two, 4) + self.assertEqual(wrapping.wrapped.three, 3) + self.assertEqual(wrapping.wrapped.four, 4) + + def testNestedSubFactory(self): """Test nested sub-factories.""" @@ -421,6 +443,31 @@ class FactoryDefaultStrategyTestCase(unittest.TestCase): self.assertEqual(outer.wrap.wrapped.two.four, 4) self.assertEqual(outer.wrap.friend, 5) + def testSubFactoryAndInheritance(self): + """Test inheriting from a factory with subfactories, overriding.""" + class TestObject(object): + def __init__(self, **kwargs): + for k, v in kwargs.iteritems(): + setattr(self, k, v) + + class TestObjectFactory(base.Factory): + FACTORY_FOR = TestObject + two = 'two' + + class WrappingTestObjectFactory(base.Factory): + FACTORY_FOR = TestObject + + wrapped = declarations.SubFactory(TestObjectFactory) + friend = declarations.LazyAttribute(lambda o: o.wrapped.two + 1) + + class ExtendedWrappingTestObjectFactory(WrappingTestObjectFactory): + wrapped__two = 4 + + wrapping = ExtendedWrappingTestObjectFactory.build() + self.assertEqual(wrapping.wrapped.two, 4) + self.assertEqual(wrapping.friend, 5) + + def testStubStrategy(self): base.Factory.default_strategy = base.STUB_STRATEGY -- cgit v1.2.3 From 87ff55473bbbbdb8f23f812d1e6e9b0d7c74a34a Mon Sep 17 00:00:00 2001 From: George Hickman Date: Wed, 11 Jan 2012 16:40:51 +0000 Subject: Use pip instead of easy_install --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 7cd80f6..f20b129 100644 --- a/README.rst +++ b/README.rst @@ -19,9 +19,9 @@ Download Github: http://github.com/rbarrois/factory_boy/ -easy_install:: +PyPI:: - easy_install factory_boy + pip install factory_boy Source:: -- cgit v1.2.3 From 86393d4d1117426bc2aafbfb9f11f96c463f05f1 Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Wed, 11 Jan 2012 14:11:45 -0700 Subject: Allow public classmethods on factories. --- factory/containers.py | 4 ++-- tests/test_containers.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/factory/containers.py b/factory/containers.py index e280162..9fe8e46 100644 --- a/factory/containers.py +++ b/factory/containers.py @@ -107,13 +107,13 @@ class DeclarationDict(dict): """Updates the DeclarationDict from a class definition dict. Takes into account all public attributes and OrderedDeclaration - instances; ignores all attributes starting with '_'. + instances; ignores all classmethods and attributes starting with '_'. Returns a dict containing all remaining elements. """ remaining = {} for k, v in d.iteritems(): - if k.startswith('_') and not isinstance(v, declarations.OrderedDeclaration): + if isinstance(v, classmethod) or k.startswith('_') and not isinstance(v, declarations.OrderedDeclaration): remaining[k] = v else: self[k] = v diff --git a/tests/test_containers.py b/tests/test_containers.py index 34b61d4..b9eaab6 100644 --- a/tests/test_containers.py +++ b/tests/test_containers.py @@ -134,7 +134,7 @@ class DeclarationDictTestCase(unittest.TestCase): def test_update_with_public(self): d = containers.DeclarationDict() - d.update_with_public({'one': 1, '_two': 2, 'three': 3}) + d.update_with_public({'one': 1, '_two': 2, 'three': 3, 'four': classmethod(lambda c: 1)}) self.assertEqual(set(['one', 'three']), set(d)) self.assertEqual(set([1, 3]), set(d.values())) -- cgit v1.2.3 From 4964327f517202ecc99a0bf1f90d548eb9f9f5a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Barrois?= Date: Thu, 12 Jan 2012 08:39:47 +0100 Subject: Add support for 'LazyContainerAttribute', when a SubFactory field needs access to attributes from its parent. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Raphaël Barrois --- factory/containers.py | 30 ++++++++++++++++++++------- factory/declarations.py | 49 +++++++++++++++++++++++++++++++++++++------ tests/test_base.py | 54 ++++++++++++++++++++++++++++++++++++++++++++++++ tests/test_containers.py | 27 +++++++++++++++++++++++- 4 files changed, 145 insertions(+), 15 deletions(-) diff --git a/factory/containers.py b/factory/containers.py index e280162..4127851 100644 --- a/factory/containers.py +++ b/factory/containers.py @@ -49,10 +49,11 @@ class LazyStub(object): __initialized = False - def __init__(self, attrs): + def __init__(self, attrs, containers=()): self.__attrs = attrs self.__values = {} self.__pending = [] + self.__containers = containers self.__initialized = True def __fill__(self): @@ -82,7 +83,7 @@ class LazyStub(object): val = self.__attrs[name] if isinstance(val, LazyValue): self.__pending.append(name) - val = val.evaluate(self) + val = val.evaluate(self, self.__containers) assert name == self.__pending.pop() self.__values[name] = val return val @@ -135,7 +136,7 @@ class DeclarationDict(dict): class LazyValue(object): """Some kind of "lazy evaluating" object.""" - def evaluate(self, obj): + def evaluate(self, obj, containers=()): """Compute the value, using the given object.""" raise NotImplementedError("This is an abstract method.") @@ -156,8 +157,12 @@ class SubFactoryWrapper(LazyValue): self.subfields = subfields self.create = create - def evaluate(self, obj): - return self.subfactory.evaluate(self.create, self.subfields) + def evaluate(self, obj, containers=()): + expanded_containers = (obj,) + if containers: + expanded_containers += tuple(containers) + return self.subfactory.evaluate(self.create, self.subfields, + expanded_containers) class OrderedDeclarationWrapper(LazyValue): @@ -175,8 +180,15 @@ class OrderedDeclarationWrapper(LazyValue): self.declaration = declaration self.sequence = sequence - def evaluate(self, obj): - return self.declaration.evaluate(self.sequence, obj) + def evaluate(self, obj, containers=()): + """Lazily evaluate the attached OrderedDeclaration. + + Args: + obj (LazyStub): the object being built + containers (object list): the chain of containers of the object + being built, its immediate holder being first. + """ + return self.declaration.evaluate(self.sequence, obj, containers) class AttributeBuilder(object): @@ -195,7 +207,9 @@ class AttributeBuilder(object): if not extra: extra = {} + self.factory = factory + self._containers = extra.pop('__containers', None) self._attrs = factory.declarations(extra) self._subfields = self._extract_subfields() @@ -229,7 +243,7 @@ class AttributeBuilder(object): v = OrderedDeclarationWrapper(v, self.factory.sequence) wrapped_attrs[k] = v - return LazyStub(wrapped_attrs).__fill__() + return LazyStub(wrapped_attrs, containers=self._containers).__fill__() class StubObject(object): diff --git a/factory/declarations.py b/factory/declarations.py index 4a5bf97..c28e6af 100644 --- a/factory/declarations.py +++ b/factory/declarations.py @@ -28,7 +28,7 @@ class OrderedDeclaration(object): in the same factory. """ - def evaluate(self, sequence, obj): + def evaluate(self, sequence, obj, containers=()): """Evaluate this declaration. Args: @@ -36,6 +36,8 @@ class OrderedDeclaration(object): the current instance obj (containers.LazyStub): The object holding currently computed attributes + containers (list of containers.LazyStub): The chain of SubFactory + which led to building this object. """ raise NotImplementedError('This is an abstract method') @@ -52,7 +54,7 @@ class LazyAttribute(OrderedDeclaration): super(LazyAttribute, self).__init__(*args, **kwargs) self.function = function - def evaluate(self, sequence, obj): + def evaluate(self, sequence, obj, containers=()): return self.function(obj) @@ -67,7 +69,7 @@ class SelfAttribute(OrderedDeclaration): super(SelfAttribute, self).__init__(*args, **kwargs) self.attribute_name = attribute_name - def evaluate(self, sequence, obj): + def evaluate(self, sequence, obj, containers=()): # TODO(rbarrois): allow the use of ATTR_SPLITTER to fetch fields of # subfactories. return getattr(obj, self.attribute_name) @@ -89,7 +91,7 @@ class Sequence(OrderedDeclaration): self.function = function self.type = type - def evaluate(self, sequence, obj): + def evaluate(self, sequence, obj, containers=()): return self.function(self.type(sequence)) @@ -102,10 +104,41 @@ class LazyAttributeSequence(Sequence): type (function): A function converting an integer into the expected kind of counter for the 'function' attribute. """ - def evaluate(self, sequence, obj): + def evaluate(self, sequence, obj, containers=()): return self.function(obj, self.type(sequence)) +class LazyContainerAttribute(OrderedDeclaration): + """Variant of LazyAttribute, also receives the containers of the object. + + Attributes: + function (function): A function, expecting the current LazyStub and the + (optional) object having a subfactory containing this attribute. + strict (bool): Whether evaluating should fail when the containers are + not passed in (i.e used outside a SubFactory). + """ + def __init__(self, function, strict=True, *args, **kwargs): + super(LazyContainerAttribute, self).__init__(*args, **kwargs) + self.function = function + self.strict = strict + + def evaluate(self, sequence, obj, containers=()): + """Evaluate the current LazyContainerAttribute. + + Args: + obj (LazyStub): a lazy stub of the object being constructed, if + needed. + containers (LazyStub): a lazy stub of a factory being evaluated, with + a SubFactory building 'obj'. + """ + if self.strict and not containers: + raise TypeError( + "A LazyContainerAttribute in 'strict' mode can only be used " + "within a SubFactory.") + + return self.function(obj, containers) + + class SubFactory(OrderedDeclaration): """Base class for attributes based upon a sub-factory. @@ -120,7 +153,7 @@ class SubFactory(OrderedDeclaration): self.defaults = kwargs self.factory = factory - def evaluate(self, create, extra): + def evaluate(self, create, extra, containers): """Evaluate the current definition and fill its attributes. Uses attributes definition in the following order: @@ -132,6 +165,7 @@ class SubFactory(OrderedDeclaration): defaults = dict(self.defaults) if extra: defaults.update(extra) + defaults['__containers'] = containers attrs = self.factory.attributes(create, defaults) @@ -151,3 +185,6 @@ def sequence(func): def lazy_attribute_sequence(func): return LazyAttributeSequence(func) + +def lazy_container_attribute(func): + return LazyContainerAttribute(func, strict=False) diff --git a/tests/test_base.py b/tests/test_base.py index aaaa65f..9e61378 100644 --- a/tests/test_base.py +++ b/tests/test_base.py @@ -467,6 +467,60 @@ class FactoryDefaultStrategyTestCase(unittest.TestCase): self.assertEqual(wrapping.wrapped.two, 4) self.assertEqual(wrapping.friend, 5) + def testDiamondSubFactory(self): + """Tests the case where an object has two fields with a common field.""" + class InnerMost(object): + def __init__(self, a, b): + self.a = a + self.b = b + + class SideA(object): + def __init__(self, inner_from_a): + self.inner_from_a = inner_from_a + + class SideB(object): + def __init__(self, inner_from_b): + self.inner_from_b = inner_from_b + + class OuterMost(object): + def __init__(self, foo, side_a, side_b): + self.foo = foo + self.side_a = side_a + self.side_b = side_b + + class InnerMostFactory(base.Factory): + FACTORY_FOR = InnerMost + a = 15 + b = 20 + + class SideAFactory(base.Factory): + FACTORY_FOR = SideA + inner_from_a = declarations.SubFactory(InnerMostFactory, a=20) + + class SideBFactory(base.Factory): + FACTORY_FOR = SideB + inner_from_b = declarations.SubFactory(InnerMostFactory, b=15) + + class OuterMostFactory(base.Factory): + FACTORY_FOR = OuterMost + + foo = 30 + side_a = declarations.SubFactory(SideAFactory, + inner_from_a__a=declarations.LazyContainerAttribute(lambda obj, containers: containers[1].foo * 2)) + side_b = declarations.SubFactory(SideBFactory, + inner_from_b=declarations.LazyContainerAttribute(lambda obj, containers: containers[0].side_a.inner_from_a)) + + outer = OuterMostFactory.build() + self.assertEqual(outer.foo, 30) + self.assertEqual(outer.side_a.inner_from_a, outer.side_b.inner_from_b) + self.assertEqual(outer.side_a.inner_from_a.a, outer.foo * 2) + self.assertEqual(outer.side_a.inner_from_a.b, 20) + + outer = OuterMostFactory.build(side_a__inner_from_a__b = 4) + self.assertEqual(outer.foo, 30) + self.assertEqual(outer.side_a.inner_from_a, outer.side_b.inner_from_b) + self.assertEqual(outer.side_a.inner_from_a.a, outer.foo * 2) + self.assertEqual(outer.side_a.inner_from_a.b, 4) def testStubStrategy(self): base.Factory.default_strategy = base.STUB_STRATEGY diff --git a/tests/test_containers.py b/tests/test_containers.py index 34b61d4..57c06cf 100644 --- a/tests/test_containers.py +++ b/tests/test_containers.py @@ -43,12 +43,37 @@ class LazyStubTestCase(unittest.TestCase): self.assertRaises(AttributeError, getattr, stub, 'three') + def test_accessing_container(self): + class LazyAttr(containers.LazyValue): + def __init__(self, obj_attr, container_attr): + self.obj_attr = obj_attr + self.container_attr = container_attr + + def evaluate(self, obj, containers=()): + if containers: + add = getattr(containers[0], self.container_attr) + else: + add = 0 + return getattr(obj, self.obj_attr) + add + + class DummyContainer(object): + three = 3 + + stub = containers.LazyStub({'one': LazyAttr('two', 'three'), 'two': 2, 'three': 42}, + containers=(DummyContainer(),)) + + self.assertEqual(5, stub.one) + + stub = containers.LazyStub({'one': LazyAttr('two', 'three'), 'two': 2, 'three': 42}, + containers=()) + self.assertEqual(2, stub.one) + def test_cyclic_definition(self): class LazyAttr(containers.LazyValue): def __init__(self, attrname): self.attrname = attrname - def evaluate(self, obj): + def evaluate(self, obj, container=None): return 1 + getattr(obj, self.attrname) stub = containers.LazyStub({'one': LazyAttr('two'), 'two': LazyAttr('one')}) -- cgit v1.2.3 From 363fa7b470286f2b38ab33a7e986bfd000f25d86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Barrois?= Date: Thu, 12 Jan 2012 08:50:29 +0100 Subject: Rearrange tests. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Raphaël Barrois --- factory/__init__.py | 3 + tests/__init__.py | 1 + tests/test_base.py | 418 --------------------------------------------- tests/test_using.py | 477 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 481 insertions(+), 418 deletions(-) create mode 100644 tests/test_using.py diff --git a/factory/__init__.py b/factory/__init__.py index b673162..7f40d6f 100644 --- a/factory/__init__.py +++ b/factory/__init__.py @@ -46,10 +46,13 @@ from declarations import ( LazyAttribute, Sequence, LazyAttributeSequence, + SelfAttribute, + LazyContainerAttribute, SubFactory, lazy_attribute, sequence, lazy_attribute_sequence, + lazy_container_attribute, ) diff --git a/tests/__init__.py b/tests/__init__.py index 14f1e9d..7ab3567 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -4,3 +4,4 @@ from .test_base import * from .test_containers import * from .test_declarations import * +from .test_using import * diff --git a/tests/test_base.py b/tests/test_base.py index 9e61378..97749f6 100644 --- a/tests/test_base.py +++ b/tests/test_base.py @@ -55,211 +55,13 @@ class SafetyTestCase(unittest.TestCase): self.assertRaises(RuntimeError, base.BaseFactory) -class SimpleBuildTestCase(unittest.TestCase): - """Tests the minimalist 'factory.build/create' functions.""" - - def test_build(self): - obj = base.build(TestObject, two=2) - self.assertEqual(obj.one, None) - self.assertEqual(obj.two, 2) - self.assertEqual(obj.three, None) - self.assertEqual(obj.four, None) - - def test_complex(self): - obj = base.build(TestObject, two=2, three=declarations.LazyAttribute(lambda o: o.two + 1)) - self.assertEqual(obj.one, None) - self.assertEqual(obj.two, 2) - self.assertEqual(obj.three, 3) - self.assertEqual(obj.four, None) - - def test_create(self): - obj = base.create(FakeDjangoModel, foo='bar') - self.assertEqual(obj.id, 1) - self.assertEqual(obj.foo, 'bar') - - def test_stub(self): - obj = base.stub(TestObject, three=3) - self.assertEqual(obj.three, 3) - self.assertFalse(hasattr(obj, 'two')) - - def test_make_factory(self): - fact = base.make_factory(TestObject, two=2, three=declarations.LazyAttribute(lambda o: o.two + 1)) - - obj = fact.build() - self.assertEqual(obj.one, None) - self.assertEqual(obj.two, 2) - self.assertEqual(obj.three, 3) - self.assertEqual(obj.four, None) - - obj = fact.build(two=4) - self.assertEqual(obj.one, None) - self.assertEqual(obj.two, 4) - self.assertEqual(obj.three, 5) - self.assertEqual(obj.four, None) - - class FactoryTestCase(unittest.TestCase): - def testAttribute(self): - class TestObjectFactory(base.Factory): - one = 'one' - - test_object = TestObjectFactory.build() - self.assertEqual(test_object.one, 'one') - - def testSequence(self): - class TestObjectFactory(base.Factory): - one = declarations.Sequence(lambda n: 'one' + n) - two = declarations.Sequence(lambda n: 'two' + n) - - test_object0 = TestObjectFactory.build() - self.assertEqual(test_object0.one, 'one0') - self.assertEqual(test_object0.two, 'two0') - - test_object1 = TestObjectFactory.build() - self.assertEqual(test_object1.one, 'one1') - self.assertEqual(test_object1.two, 'two1') - - def testSequenceCustomBegin(self): - class TestObjectFactory(base.Factory): - @classmethod - def _setup_next_sequence(cls): - return 42 - - one = declarations.Sequence(lambda n: 'one' + n) - two = declarations.Sequence(lambda n: 'two' + n) - - test_object0 = TestObjectFactory.build() - self.assertEqual('one42', test_object0.one) - self.assertEqual('two42', test_object0.two) - - test_object1 = TestObjectFactory.build() - self.assertEqual('one43', test_object1.one) - self.assertEqual('two43', test_object1.two) - - def testLazyAttribute(self): - class TestObjectFactory(base.Factory): - one = declarations.LazyAttribute(lambda a: 'abc' ) - two = declarations.LazyAttribute(lambda a: a.one + ' xyz') - - test_object = TestObjectFactory.build() - self.assertEqual(test_object.one, 'abc') - self.assertEqual(test_object.two, 'abc xyz') - def testLazyAttributeNonExistentParam(self): class TestObjectFactory(base.Factory): one = declarations.LazyAttribute(lambda a: a.does_not_exist ) self.assertRaises(AttributeError, TestObjectFactory) - def testLazyAttributeSequence(self): - class TestObjectFactory(base.Factory): - one = declarations.LazyAttributeSequence(lambda a, n: 'abc' + n) - two = declarations.LazyAttributeSequence(lambda a, n: a.one + ' xyz' + n) - - test_object0 = TestObjectFactory.build() - self.assertEqual(test_object0.one, 'abc0') - self.assertEqual(test_object0.two, 'abc0 xyz0') - - test_object1 = TestObjectFactory.build() - self.assertEqual(test_object1.one, 'abc1') - self.assertEqual(test_object1.two, 'abc1 xyz1') - - def testLazyAttributeDecorator(self): - class TestObjectFactory(base.Factory): - @declarations.lazy_attribute - def one(a): - return 'one' - - test_object = TestObjectFactory.build() - self.assertEqual(test_object.one, 'one') - - def testSelfAttribute(self): - class TestObjectFactory(base.Factory): - one = 'xx' - two = declarations.SelfAttribute('one') - - test_object = TestObjectFactory.build(one=1) - self.assertEqual(1, test_object.two) - - def testSequenceDecorator(self): - class TestObjectFactory(base.Factory): - @declarations.sequence - def one(n): - return 'one' + n - - test_object = TestObjectFactory.build() - self.assertEqual(test_object.one, 'one0') - - def testLazyAttributeSequenceDecorator(self): - class TestObjectFactory(base.Factory): - @declarations.lazy_attribute_sequence - def one(a, n): - return 'one' + n - @declarations.lazy_attribute_sequence - def two(a, n): - return a.one + ' two' + n - - test_object = TestObjectFactory.build() - self.assertEqual(test_object.one, 'one0') - self.assertEqual(test_object.two, 'one0 two0') - - def testBuildWithParameters(self): - class TestObjectFactory(base.Factory): - one = declarations.Sequence(lambda n: 'one' + n) - two = declarations.Sequence(lambda n: 'two' + n) - - test_object0 = TestObjectFactory.build(three='three') - self.assertEqual(test_object0.one, 'one0') - self.assertEqual(test_object0.two, 'two0') - self.assertEqual(test_object0.three, 'three') - - test_object1 = TestObjectFactory.build(one='other') - self.assertEqual(test_object1.one, 'other') - self.assertEqual(test_object1.two, 'two1') - - def testCreate(self): - class TestModelFactory(base.Factory): - one = 'one' - - test_model = TestModelFactory.create() - self.assertEqual(test_model.one, 'one') - self.assertTrue(test_model.id) - - def testInheritance(self): - class TestObjectFactory(base.Factory): - one = 'one' - two = declarations.LazyAttribute(lambda a: a.one + ' two') - - class TestObjectFactory2(TestObjectFactory): - FACTORY_FOR = TestObject - - three = 'three' - four = declarations.LazyAttribute(lambda a: a.three + ' four') - - test_object = TestObjectFactory2.build() - self.assertEqual(test_object.one, 'one') - self.assertEqual(test_object.two, 'one two') - self.assertEqual(test_object.three, 'three') - self.assertEqual(test_object.four, 'three four') - - test_object_alt = TestObjectFactory.build() - self.assertEqual(None, test_object_alt.three) - - def testInheritanceWithInheritedClass(self): - class TestObjectFactory(base.Factory): - one = 'one' - two = declarations.LazyAttribute(lambda a: a.one + ' two') - - class TestFactory(TestObjectFactory): - three = 'three' - four = declarations.LazyAttribute(lambda a: a.three + ' four') - - test_object = TestFactory.build() - self.assertEqual(test_object.one, 'one') - self.assertEqual(test_object.two, 'one two') - self.assertEqual(test_object.three, 'three') - self.assertEqual(test_object.four, 'three four') - def testInheritanceWithSequence(self): """Tests that sequence IDs are shared between parent and son.""" class TestObjectFactory(base.Factory): @@ -275,36 +77,6 @@ class FactoryTestCase(unittest.TestCase): ones = set([x.one for x in (parent, alt_parent, sub, alt_sub)]) self.assertEqual(4, len(ones)) - def testDualInheritance(self): - class TestObjectFactory(base.Factory): - one = 'one' - - class TestOtherFactory(base.Factory): - FACTORY_FOR = TestObject - two = 'two' - four = 'four' - - class TestFactory(TestObjectFactory, TestOtherFactory): - three = 'three' - - obj = TestFactory.build(two=2) - self.assertEqual('one', obj.one) - self.assertEqual(2, obj.two) - self.assertEqual('three', obj.three) - self.assertEqual('four', obj.four) - - def testSetCreationFunction(self): - def creation_function(class_to_create, **kwargs): - return "This doesn't even return an instance of {0}".format(class_to_create.__name__) - - class TestModelFactory(base.Factory): - pass - - TestModelFactory.set_creation_function(creation_function) - - test_object = TestModelFactory.create() - self.assertEqual(test_object, "This doesn't even return an instance of TestModel") - class FactoryDefaultStrategyTestCase(unittest.TestCase): def setUp(self): self.default_strategy = base.Factory.default_strategy @@ -332,196 +104,6 @@ class FactoryDefaultStrategyTestCase(unittest.TestCase): self.assertEqual(test_model.one, 'one') self.assertTrue(test_model.id) - def testSubFactory(self): - class TestModel2(FakeDjangoModel): - pass - - class TestModelFactory(base.Factory): - FACTORY_FOR = TestModel - one = 3 - - class TestModel2Factory(base.Factory): - FACTORY_FOR = TestModel2 - two = declarations.SubFactory(TestModelFactory, one=1) - - test_model = TestModel2Factory(two__one=4) - self.assertEqual(4, test_model.two.one) - self.assertEqual(1, test_model.id) - self.assertEqual(1, test_model.two.id) - - def testSubFactoryWithLazyFields(self): - class TestModel2(FakeDjangoModel): - pass - - class TestModelFactory(base.Factory): - FACTORY_FOR = TestModel - - class TestModel2Factory(base.Factory): - FACTORY_FOR = TestModel2 - two = declarations.SubFactory(TestModelFactory, - one=declarations.Sequence(lambda n: 'x%sx' % n), - two=declarations.LazyAttribute( - lambda o: '%s%s' % (o.one, o.one))) - - test_model = TestModel2Factory(one=42) - self.assertEqual('x0x', test_model.two.one) - self.assertEqual('x0xx0x', test_model.two.two) - - def testSubFactoryOverriding(self): - class TestObject(object): - def __init__(self, **kwargs): - for k, v in kwargs.iteritems(): - setattr(self, k, v) - - class TestObjectFactory(base.Factory): - FACTORY_FOR = TestObject - - class WrappingTestObjectFactory(base.Factory): - FACTORY_FOR = TestObject - - wrapped = declarations.SubFactory(TestObjectFactory, two=2, four=4) - wrapped__two = 4 - wrapped__three = 3 - - wrapping = WrappingTestObjectFactory.build() - self.assertEqual(wrapping.wrapped.two, 4) - self.assertEqual(wrapping.wrapped.three, 3) - self.assertEqual(wrapping.wrapped.four, 4) - - - def testNestedSubFactory(self): - """Test nested sub-factories.""" - - class TestObject(object): - def __init__(self, **kwargs): - for k, v in kwargs.iteritems(): - setattr(self, k, v) - - class TestObjectFactory(base.Factory): - FACTORY_FOR = TestObject - - class WrappingTestObjectFactory(base.Factory): - FACTORY_FOR = TestObject - - wrapped = declarations.SubFactory(TestObjectFactory) - wrapped_bis = declarations.SubFactory(TestObjectFactory, one=1) - - class OuterWrappingTestObjectFactory(base.Factory): - FACTORY_FOR = TestObject - - wrap = declarations.SubFactory(WrappingTestObjectFactory, wrapped__two=2) - - outer = OuterWrappingTestObjectFactory.build() - self.assertEqual(outer.wrap.wrapped.two, 2) - self.assertEqual(outer.wrap.wrapped_bis.one, 1) - - def testNestedSubFactoryWithOverriddenSubFactories(self): - """Test nested sub-factories, with attributes overridden with subfactories.""" - - class TestObject(object): - def __init__(self, **kwargs): - for k, v in kwargs.iteritems(): - setattr(self, k, v) - - class TestObjectFactory(base.Factory): - FACTORY_FOR = TestObject - two = 'two' - - class WrappingTestObjectFactory(base.Factory): - FACTORY_FOR = TestObject - - wrapped = declarations.SubFactory(TestObjectFactory) - friend = declarations.LazyAttribute(lambda o: o.wrapped.two.four + 1) - - class OuterWrappingTestObjectFactory(base.Factory): - FACTORY_FOR = TestObject - - 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 testSubFactoryAndInheritance(self): - """Test inheriting from a factory with subfactories, overriding.""" - class TestObject(object): - def __init__(self, **kwargs): - for k, v in kwargs.iteritems(): - setattr(self, k, v) - - class TestObjectFactory(base.Factory): - FACTORY_FOR = TestObject - two = 'two' - - class WrappingTestObjectFactory(base.Factory): - FACTORY_FOR = TestObject - - wrapped = declarations.SubFactory(TestObjectFactory) - friend = declarations.LazyAttribute(lambda o: o.wrapped.two + 1) - - class ExtendedWrappingTestObjectFactory(WrappingTestObjectFactory): - wrapped__two = 4 - - wrapping = ExtendedWrappingTestObjectFactory.build() - self.assertEqual(wrapping.wrapped.two, 4) - self.assertEqual(wrapping.friend, 5) - - def testDiamondSubFactory(self): - """Tests the case where an object has two fields with a common field.""" - class InnerMost(object): - def __init__(self, a, b): - self.a = a - self.b = b - - class SideA(object): - def __init__(self, inner_from_a): - self.inner_from_a = inner_from_a - - class SideB(object): - def __init__(self, inner_from_b): - self.inner_from_b = inner_from_b - - class OuterMost(object): - def __init__(self, foo, side_a, side_b): - self.foo = foo - self.side_a = side_a - self.side_b = side_b - - class InnerMostFactory(base.Factory): - FACTORY_FOR = InnerMost - a = 15 - b = 20 - - class SideAFactory(base.Factory): - FACTORY_FOR = SideA - inner_from_a = declarations.SubFactory(InnerMostFactory, a=20) - - class SideBFactory(base.Factory): - FACTORY_FOR = SideB - inner_from_b = declarations.SubFactory(InnerMostFactory, b=15) - - class OuterMostFactory(base.Factory): - FACTORY_FOR = OuterMost - - foo = 30 - side_a = declarations.SubFactory(SideAFactory, - inner_from_a__a=declarations.LazyContainerAttribute(lambda obj, containers: containers[1].foo * 2)) - side_b = declarations.SubFactory(SideBFactory, - inner_from_b=declarations.LazyContainerAttribute(lambda obj, containers: containers[0].side_a.inner_from_a)) - - outer = OuterMostFactory.build() - self.assertEqual(outer.foo, 30) - self.assertEqual(outer.side_a.inner_from_a, outer.side_b.inner_from_b) - self.assertEqual(outer.side_a.inner_from_a.a, outer.foo * 2) - self.assertEqual(outer.side_a.inner_from_a.b, 20) - - outer = OuterMostFactory.build(side_a__inner_from_a__b = 4) - self.assertEqual(outer.foo, 30) - self.assertEqual(outer.side_a.inner_from_a, outer.side_b.inner_from_b) - self.assertEqual(outer.side_a.inner_from_a.a, outer.foo * 2) - self.assertEqual(outer.side_a.inner_from_a.b, 4) - def testStubStrategy(self): base.Factory.default_strategy = base.STUB_STRATEGY diff --git a/tests/test_using.py b/tests/test_using.py new file mode 100644 index 0000000..e0e59cd --- /dev/null +++ b/tests/test_using.py @@ -0,0 +1,477 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2011 Raphaël Barrois +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +"""Tests using factory.""" + +import unittest + +import factory + + + +class TestObject(object): + def __init__(self, one=None, two=None, three=None, four=None): + self.one = one + self.two = two + self.three = three + self.four = four + +class FakeDjangoModel(object): + class FakeDjangoManager(object): + def create(self, **kwargs): + fake_model = FakeDjangoModel(**kwargs) + fake_model.id = 1 + return fake_model + + objects = FakeDjangoManager() + + def __init__(self, **kwargs): + for name, value in kwargs.iteritems(): + setattr(self, name, value) + self.id = None + +class TestModel(FakeDjangoModel): + pass + + +class SimpleBuildTestCase(unittest.TestCase): + """Tests the minimalist 'factory.build/create' functions.""" + + def test_build(self): + obj = factory.build(TestObject, two=2) + self.assertEqual(obj.one, None) + self.assertEqual(obj.two, 2) + self.assertEqual(obj.three, None) + self.assertEqual(obj.four, None) + + def test_complex(self): + obj = factory.build(TestObject, two=2, three=factory.LazyAttribute(lambda o: o.two + 1)) + self.assertEqual(obj.one, None) + self.assertEqual(obj.two, 2) + self.assertEqual(obj.three, 3) + self.assertEqual(obj.four, None) + + def test_create(self): + obj = factory.create(FakeDjangoModel, foo='bar') + self.assertEqual(obj.id, 1) + self.assertEqual(obj.foo, 'bar') + + def test_stub(self): + obj = factory.stub(TestObject, three=3) + self.assertEqual(obj.three, 3) + self.assertFalse(hasattr(obj, 'two')) + + def test_make_factory(self): + fact = factory.make_factory(TestObject, two=2, three=factory.LazyAttribute(lambda o: o.two + 1)) + + obj = fact.build() + self.assertEqual(obj.one, None) + self.assertEqual(obj.two, 2) + self.assertEqual(obj.three, 3) + self.assertEqual(obj.four, None) + + obj = fact.build(two=4) + self.assertEqual(obj.one, None) + self.assertEqual(obj.two, 4) + self.assertEqual(obj.three, 5) + self.assertEqual(obj.four, None) + + +class FactoryTestCase(unittest.TestCase): + def testAttribute(self): + class TestObjectFactory(factory.Factory): + one = 'one' + + test_object = TestObjectFactory.build() + self.assertEqual(test_object.one, 'one') + + def testSequence(self): + class TestObjectFactory(factory.Factory): + one = factory.Sequence(lambda n: 'one' + n) + two = factory.Sequence(lambda n: 'two' + n) + + test_object0 = TestObjectFactory.build() + self.assertEqual(test_object0.one, 'one0') + self.assertEqual(test_object0.two, 'two0') + + test_object1 = TestObjectFactory.build() + self.assertEqual(test_object1.one, 'one1') + self.assertEqual(test_object1.two, 'two1') + + def testSequenceCustomBegin(self): + class TestObjectFactory(factory.Factory): + @classmethod + def _setup_next_sequence(cls): + return 42 + + one = factory.Sequence(lambda n: 'one' + n) + two = factory.Sequence(lambda n: 'two' + n) + + test_object0 = TestObjectFactory.build() + self.assertEqual('one42', test_object0.one) + self.assertEqual('two42', test_object0.two) + + test_object1 = TestObjectFactory.build() + self.assertEqual('one43', test_object1.one) + self.assertEqual('two43', test_object1.two) + + def testLazyAttribute(self): + class TestObjectFactory(factory.Factory): + one = factory.LazyAttribute(lambda a: 'abc' ) + two = factory.LazyAttribute(lambda a: a.one + ' xyz') + + test_object = TestObjectFactory.build() + self.assertEqual(test_object.one, 'abc') + self.assertEqual(test_object.two, 'abc xyz') + + def testLazyAttributeSequence(self): + class TestObjectFactory(factory.Factory): + one = factory.LazyAttributeSequence(lambda a, n: 'abc' + n) + two = factory.LazyAttributeSequence(lambda a, n: a.one + ' xyz' + n) + + test_object0 = TestObjectFactory.build() + self.assertEqual(test_object0.one, 'abc0') + self.assertEqual(test_object0.two, 'abc0 xyz0') + + test_object1 = TestObjectFactory.build() + self.assertEqual(test_object1.one, 'abc1') + self.assertEqual(test_object1.two, 'abc1 xyz1') + + def testLazyAttributeDecorator(self): + class TestObjectFactory(factory.Factory): + @factory.lazy_attribute + def one(a): + return 'one' + + test_object = TestObjectFactory.build() + self.assertEqual(test_object.one, 'one') + + def testSelfAttribute(self): + class TestObjectFactory(factory.Factory): + one = 'xx' + two = factory.SelfAttribute('one') + + test_object = TestObjectFactory.build(one=1) + self.assertEqual(1, test_object.two) + + def testSequenceDecorator(self): + class TestObjectFactory(factory.Factory): + @factory.sequence + def one(n): + return 'one' + n + + test_object = TestObjectFactory.build() + self.assertEqual(test_object.one, 'one0') + + def testLazyAttributeSequenceDecorator(self): + class TestObjectFactory(factory.Factory): + @factory.lazy_attribute_sequence + def one(a, n): + return 'one' + n + @factory.lazy_attribute_sequence + def two(a, n): + return a.one + ' two' + n + + test_object = TestObjectFactory.build() + self.assertEqual(test_object.one, 'one0') + self.assertEqual(test_object.two, 'one0 two0') + + def testBuildWithParameters(self): + class TestObjectFactory(factory.Factory): + one = factory.Sequence(lambda n: 'one' + n) + two = factory.Sequence(lambda n: 'two' + n) + + test_object0 = TestObjectFactory.build(three='three') + self.assertEqual(test_object0.one, 'one0') + self.assertEqual(test_object0.two, 'two0') + self.assertEqual(test_object0.three, 'three') + + test_object1 = TestObjectFactory.build(one='other') + self.assertEqual(test_object1.one, 'other') + self.assertEqual(test_object1.two, 'two1') + + def testCreate(self): + class TestModelFactory(factory.Factory): + one = 'one' + + test_model = TestModelFactory.create() + self.assertEqual(test_model.one, 'one') + self.assertTrue(test_model.id) + + def testInheritance(self): + class TestObjectFactory(factory.Factory): + one = 'one' + two = factory.LazyAttribute(lambda a: a.one + ' two') + + class TestObjectFactory2(TestObjectFactory): + FACTORY_FOR = TestObject + + three = 'three' + four = factory.LazyAttribute(lambda a: a.three + ' four') + + test_object = TestObjectFactory2.build() + self.assertEqual(test_object.one, 'one') + self.assertEqual(test_object.two, 'one two') + self.assertEqual(test_object.three, 'three') + self.assertEqual(test_object.four, 'three four') + + test_object_alt = TestObjectFactory.build() + self.assertEqual(None, test_object_alt.three) + + def testInheritanceWithInheritedClass(self): + class TestObjectFactory(factory.Factory): + one = 'one' + two = factory.LazyAttribute(lambda a: a.one + ' two') + + class TestFactory(TestObjectFactory): + three = 'three' + four = factory.LazyAttribute(lambda a: a.three + ' four') + + test_object = TestFactory.build() + self.assertEqual(test_object.one, 'one') + self.assertEqual(test_object.two, 'one two') + self.assertEqual(test_object.three, 'three') + self.assertEqual(test_object.four, 'three four') + + def testDualInheritance(self): + class TestObjectFactory(factory.Factory): + one = 'one' + + class TestOtherFactory(factory.Factory): + FACTORY_FOR = TestObject + two = 'two' + four = 'four' + + class TestFactory(TestObjectFactory, TestOtherFactory): + three = 'three' + + obj = TestFactory.build(two=2) + self.assertEqual('one', obj.one) + self.assertEqual(2, obj.two) + self.assertEqual('three', obj.three) + self.assertEqual('four', obj.four) + + def testSetCreationFunction(self): + def creation_function(class_to_create, **kwargs): + return "This doesn't even return an instance of {0}".format(class_to_create.__name__) + + class TestModelFactory(factory.Factory): + pass + + TestModelFactory.set_creation_function(creation_function) + + test_object = TestModelFactory.create() + self.assertEqual(test_object, "This doesn't even return an instance of TestModel") + + +class SubFactoryTestCase(unittest.TestCase): + def testSubFactory(self): + class TestModel2(FakeDjangoModel): + pass + + class TestModelFactory(factory.Factory): + FACTORY_FOR = TestModel + one = 3 + + class TestModel2Factory(factory.Factory): + FACTORY_FOR = TestModel2 + two = factory.SubFactory(TestModelFactory, one=1) + + test_model = TestModel2Factory(two__one=4) + self.assertEqual(4, test_model.two.one) + self.assertEqual(1, test_model.id) + self.assertEqual(1, test_model.two.id) + + def testSubFactoryWithLazyFields(self): + class TestModel2(FakeDjangoModel): + pass + + class TestModelFactory(factory.Factory): + FACTORY_FOR = TestModel + + class TestModel2Factory(factory.Factory): + FACTORY_FOR = TestModel2 + two = factory.SubFactory(TestModelFactory, + one=factory.Sequence(lambda n: 'x%sx' % n), + two=factory.LazyAttribute( + lambda o: '%s%s' % (o.one, o.one))) + + test_model = TestModel2Factory(one=42) + self.assertEqual('x0x', test_model.two.one) + self.assertEqual('x0xx0x', test_model.two.two) + + def testSubFactoryOverriding(self): + class TestObject(object): + def __init__(self, **kwargs): + for k, v in kwargs.iteritems(): + setattr(self, k, v) + + class TestObjectFactory(factory.Factory): + FACTORY_FOR = TestObject + + class WrappingTestObjectFactory(factory.Factory): + FACTORY_FOR = TestObject + + wrapped = factory.SubFactory(TestObjectFactory, two=2, four=4) + wrapped__two = 4 + wrapped__three = 3 + + wrapping = WrappingTestObjectFactory.build() + self.assertEqual(wrapping.wrapped.two, 4) + self.assertEqual(wrapping.wrapped.three, 3) + self.assertEqual(wrapping.wrapped.four, 4) + + + def testNestedSubFactory(self): + """Test nested sub-factories.""" + + class TestObject(object): + def __init__(self, **kwargs): + for k, v in kwargs.iteritems(): + setattr(self, k, v) + + class TestObjectFactory(factory.Factory): + FACTORY_FOR = TestObject + + class WrappingTestObjectFactory(factory.Factory): + FACTORY_FOR = TestObject + + wrapped = factory.SubFactory(TestObjectFactory) + wrapped_bis = factory.SubFactory(TestObjectFactory, one=1) + + class OuterWrappingTestObjectFactory(factory.Factory): + FACTORY_FOR = TestObject + + wrap = factory.SubFactory(WrappingTestObjectFactory, wrapped__two=2) + + outer = OuterWrappingTestObjectFactory.build() + self.assertEqual(outer.wrap.wrapped.two, 2) + self.assertEqual(outer.wrap.wrapped_bis.one, 1) + + def testNestedSubFactoryWithOverriddenSubFactories(self): + """Test nested sub-factories, with attributes overridden with subfactories.""" + + class TestObject(object): + def __init__(self, **kwargs): + for k, v in kwargs.iteritems(): + setattr(self, k, v) + + class TestObjectFactory(factory.Factory): + FACTORY_FOR = TestObject + two = 'two' + + class WrappingTestObjectFactory(factory.Factory): + FACTORY_FOR = TestObject + + wrapped = factory.SubFactory(TestObjectFactory) + friend = factory.LazyAttribute(lambda o: o.wrapped.two.four + 1) + + class OuterWrappingTestObjectFactory(factory.Factory): + FACTORY_FOR = TestObject + + wrap = factory.SubFactory(WrappingTestObjectFactory, + wrapped__two=factory.SubFactory(TestObjectFactory, four=4)) + + outer = OuterWrappingTestObjectFactory.build() + self.assertEqual(outer.wrap.wrapped.two.four, 4) + self.assertEqual(outer.wrap.friend, 5) + + def testSubFactoryAndInheritance(self): + """Test inheriting from a factory with subfactories, overriding.""" + class TestObject(object): + def __init__(self, **kwargs): + for k, v in kwargs.iteritems(): + setattr(self, k, v) + + class TestObjectFactory(factory.Factory): + FACTORY_FOR = TestObject + two = 'two' + + class WrappingTestObjectFactory(factory.Factory): + FACTORY_FOR = TestObject + + wrapped = factory.SubFactory(TestObjectFactory) + friend = factory.LazyAttribute(lambda o: o.wrapped.two + 1) + + class ExtendedWrappingTestObjectFactory(WrappingTestObjectFactory): + wrapped__two = 4 + + wrapping = ExtendedWrappingTestObjectFactory.build() + self.assertEqual(wrapping.wrapped.two, 4) + self.assertEqual(wrapping.friend, 5) + + def testDiamondSubFactory(self): + """Tests the case where an object has two fields with a common field.""" + class InnerMost(object): + def __init__(self, a, b): + self.a = a + self.b = b + + class SideA(object): + def __init__(self, inner_from_a): + self.inner_from_a = inner_from_a + + class SideB(object): + def __init__(self, inner_from_b): + self.inner_from_b = inner_from_b + + class OuterMost(object): + def __init__(self, foo, side_a, side_b): + self.foo = foo + self.side_a = side_a + self.side_b = side_b + + class InnerMostFactory(factory.Factory): + FACTORY_FOR = InnerMost + a = 15 + b = 20 + + class SideAFactory(factory.Factory): + FACTORY_FOR = SideA + inner_from_a = factory.SubFactory(InnerMostFactory, a=20) + + class SideBFactory(factory.Factory): + FACTORY_FOR = SideB + inner_from_b = factory.SubFactory(InnerMostFactory, b=15) + + class OuterMostFactory(factory.Factory): + FACTORY_FOR = OuterMost + + foo = 30 + side_a = factory.SubFactory(SideAFactory, + inner_from_a__a=factory.LazyContainerAttribute(lambda obj, containers: containers[1].foo * 2)) + side_b = factory.SubFactory(SideBFactory, + inner_from_b=factory.LazyContainerAttribute(lambda obj, containers: containers[0].side_a.inner_from_a)) + + outer = OuterMostFactory.build() + self.assertEqual(outer.foo, 30) + self.assertEqual(outer.side_a.inner_from_a, outer.side_b.inner_from_b) + self.assertEqual(outer.side_a.inner_from_a.a, outer.foo * 2) + self.assertEqual(outer.side_a.inner_from_a.b, 20) + + outer = OuterMostFactory.build(side_a__inner_from_a__b = 4) + self.assertEqual(outer.foo, 30) + self.assertEqual(outer.side_a.inner_from_a, outer.side_b.inner_from_b) + self.assertEqual(outer.side_a.inner_from_a.a, outer.foo * 2) + self.assertEqual(outer.side_a.inner_from_a.b, 4) + + +if __name__ == '__main__': + unittest.main() -- cgit v1.2.3 From bf57308c9dcc2d96161993026e138ec181550653 Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Thu, 12 Jan 2012 08:50:27 -0700 Subject: Added API test for classmethod on factory. --- tests/test_using.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/test_using.py b/tests/test_using.py index e0e59cd..4256cc3 100644 --- a/tests/test_using.py +++ b/tests/test_using.py @@ -280,6 +280,14 @@ class FactoryTestCase(unittest.TestCase): test_object = TestModelFactory.create() self.assertEqual(test_object, "This doesn't even return an instance of TestModel") + def testClassMethodAccessible(self): + class TestObjectFactory(factory.Factory): + @classmethod + def alt_create(cls, **kwargs): + return kwargs + + self.assertEqual(TestObjectFactory.alt_create(foo=1), {"foo": 1}) + class SubFactoryTestCase(unittest.TestCase): def testSubFactory(self): -- cgit v1.2.3 From 8aec45386aaf410a36bdf57ebf24cfe9f1fdf3b2 Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Thu, 12 Jan 2012 08:59:36 -0700 Subject: Clarify extended if check in DeclarationDict.update_with_public. --- factory/containers.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/factory/containers.py b/factory/containers.py index 0218e47..847b2c0 100644 --- a/factory/containers.py +++ b/factory/containers.py @@ -104,6 +104,20 @@ class LazyStub(object): class DeclarationDict(dict): """Slightly extended dict to work with OrderedDeclaration.""" + def is_declaration(self, name, value): + """Determines if a class attribute is a field value declaration. + + Based on the name and value of the class attribute, return ``True`` if + it looks like a declaration of a default field value, ``False`` if it + is private (name starts with '_') or a classmethod or staticmethod. + + """ + if isinstance(value, classmethod): + return False + elif isinstance(value, declarations.OrderedDeclaration): + return True + return (not name.startswith("_")) + def update_with_public(self, d): """Updates the DeclarationDict from a class definition dict. @@ -114,10 +128,10 @@ class DeclarationDict(dict): """ remaining = {} for k, v in d.iteritems(): - if isinstance(v, classmethod) or k.startswith('_') and not isinstance(v, declarations.OrderedDeclaration): - remaining[k] = v - else: + if self.is_declaration(k, v): self[k] = v + else: + remaining[k] = v return remaining def copy(self, extra=None): -- cgit v1.2.3 From 99ffa8c49df4baa9681d649ce0b19b6d3b5ad99a Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Thu, 12 Jan 2012 09:01:46 -0700 Subject: Added support for staticmethods on factories as well. --- factory/containers.py | 2 +- tests/test_containers.py | 8 +++++++- tests/test_using.py | 8 ++++++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/factory/containers.py b/factory/containers.py index 847b2c0..68e1e9f 100644 --- a/factory/containers.py +++ b/factory/containers.py @@ -112,7 +112,7 @@ class DeclarationDict(dict): is private (name starts with '_') or a classmethod or staticmethod. """ - if isinstance(value, classmethod): + if isinstance(value, (classmethod, staticmethod)): return False elif isinstance(value, declarations.OrderedDeclaration): return True diff --git a/tests/test_containers.py b/tests/test_containers.py index b4c5c52..effb060 100644 --- a/tests/test_containers.py +++ b/tests/test_containers.py @@ -159,7 +159,13 @@ class DeclarationDictTestCase(unittest.TestCase): def test_update_with_public(self): d = containers.DeclarationDict() - d.update_with_public({'one': 1, '_two': 2, 'three': 3, 'four': classmethod(lambda c: 1)}) + d.update_with_public({ + 'one': 1, + '_two': 2, + 'three': 3, + 'classmethod': classmethod(lambda c: 1), + 'staticmethod': staticmethod(lambda: 1), + }) self.assertEqual(set(['one', 'three']), set(d)) self.assertEqual(set([1, 3]), set(d.values())) diff --git a/tests/test_using.py b/tests/test_using.py index 4256cc3..d1e4262 100644 --- a/tests/test_using.py +++ b/tests/test_using.py @@ -288,6 +288,14 @@ class FactoryTestCase(unittest.TestCase): self.assertEqual(TestObjectFactory.alt_create(foo=1), {"foo": 1}) + def testStaticMethodAccessible(self): + class TestObjectFactory(factory.Factory): + @staticmethod + def alt_create(**kwargs): + return kwargs + + self.assertEqual(TestObjectFactory.alt_create(foo=1), {"foo": 1}) + class SubFactoryTestCase(unittest.TestCase): def testSubFactory(self): -- cgit v1.2.3 From 5bdc19ccbde3934a05b11a0d0edf50d3b31e930d Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Thu, 12 Jan 2012 09:03:22 -0700 Subject: Correct out-of-date docstring. --- factory/containers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/factory/containers.py b/factory/containers.py index 68e1e9f..497e98c 100644 --- a/factory/containers.py +++ b/factory/containers.py @@ -122,7 +122,8 @@ class DeclarationDict(dict): """Updates the DeclarationDict from a class definition dict. Takes into account all public attributes and OrderedDeclaration - instances; ignores all classmethods and attributes starting with '_'. + instances; ignores all class/staticmethods and private attributes + (starting with '_'). Returns a dict containing all remaining elements. """ -- cgit v1.2.3 From 0c4b9334c93170ce6df17506f48e3937090aec05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Barrois?= Date: Thu, 12 Jan 2012 23:11:58 +0100 Subject: Rename LazyContainerAttibute to ContainerAttribute. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Raphaël Barrois --- factory/__init__.py | 4 ++-- factory/declarations.py | 12 ++++++------ tests/test_using.py | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/factory/__init__.py b/factory/__init__.py index 7f40d6f..cc5b788 100644 --- a/factory/__init__.py +++ b/factory/__init__.py @@ -47,12 +47,12 @@ from declarations import ( Sequence, LazyAttributeSequence, SelfAttribute, - LazyContainerAttribute, + ContainerAttribute, SubFactory, lazy_attribute, sequence, lazy_attribute_sequence, - lazy_container_attribute, + container_attribute, ) diff --git a/factory/declarations.py b/factory/declarations.py index c28e6af..0ce7071 100644 --- a/factory/declarations.py +++ b/factory/declarations.py @@ -108,7 +108,7 @@ class LazyAttributeSequence(Sequence): return self.function(obj, self.type(sequence)) -class LazyContainerAttribute(OrderedDeclaration): +class ContainerAttribute(OrderedDeclaration): """Variant of LazyAttribute, also receives the containers of the object. Attributes: @@ -118,12 +118,12 @@ class LazyContainerAttribute(OrderedDeclaration): not passed in (i.e used outside a SubFactory). """ def __init__(self, function, strict=True, *args, **kwargs): - super(LazyContainerAttribute, self).__init__(*args, **kwargs) + super(ContainerAttribute, self).__init__(*args, **kwargs) self.function = function self.strict = strict def evaluate(self, sequence, obj, containers=()): - """Evaluate the current LazyContainerAttribute. + """Evaluate the current ContainerAttribute. Args: obj (LazyStub): a lazy stub of the object being constructed, if @@ -133,7 +133,7 @@ class LazyContainerAttribute(OrderedDeclaration): """ if self.strict and not containers: raise TypeError( - "A LazyContainerAttribute in 'strict' mode can only be used " + "A ContainerAttribute in 'strict' mode can only be used " "within a SubFactory.") return self.function(obj, containers) @@ -186,5 +186,5 @@ def sequence(func): def lazy_attribute_sequence(func): return LazyAttributeSequence(func) -def lazy_container_attribute(func): - return LazyContainerAttribute(func, strict=False) +def container_attribute(func): + return ContainerAttribute(func, strict=False) diff --git a/tests/test_using.py b/tests/test_using.py index d1e4262..a93c968 100644 --- a/tests/test_using.py +++ b/tests/test_using.py @@ -472,9 +472,9 @@ class SubFactoryTestCase(unittest.TestCase): foo = 30 side_a = factory.SubFactory(SideAFactory, - inner_from_a__a=factory.LazyContainerAttribute(lambda obj, containers: containers[1].foo * 2)) + inner_from_a__a=factory.ContainerAttribute(lambda obj, containers: containers[1].foo * 2)) side_b = factory.SubFactory(SideBFactory, - inner_from_b=factory.LazyContainerAttribute(lambda obj, containers: containers[0].side_a.inner_from_a)) + inner_from_b=factory.ContainerAttribute(lambda obj, containers: containers[0].side_a.inner_from_a)) outer = OuterMostFactory.build() self.assertEqual(outer.foo, 30) -- cgit v1.2.3 From c60c3106c700f5f42ad4fcf82f327da98ec5eb9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Barrois?= Date: Thu, 12 Jan 2012 23:21:38 +0100 Subject: Add __repr__ / __str__ to a couple of objects. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Raphaël Barrois --- factory/base.py | 4 ++++ factory/containers.py | 14 ++++++++++++-- tests/test_base.py | 7 +++++++ tests/test_containers.py | 9 +++++++++ 4 files changed, 32 insertions(+), 2 deletions(-) diff --git a/factory/base.py b/factory/base.py index 19d441b..b2f437d 100644 --- a/factory/base.py +++ b/factory/base.py @@ -341,6 +341,10 @@ class Factory(BaseFactory): # from turning it into an instance method. _creation_function = (DJANGO_CREATION,) + def __str__(self): + return '<%s for %s>' % (self.__class__.__name__, + getattr(self, CLASS_ATTRIBUTE_ASSOCIATED_CLASS).__name__) + @classmethod def set_creation_function(cls, creation_function): """Set the creation function for this class. diff --git a/factory/containers.py b/factory/containers.py index 497e98c..dd11f5f 100644 --- a/factory/containers.py +++ b/factory/containers.py @@ -49,13 +49,21 @@ class LazyStub(object): __initialized = False - def __init__(self, attrs, containers=()): + def __init__(self, attrs, containers=(), target_class=object): self.__attrs = attrs self.__values = {} self.__pending = [] self.__containers = containers + self.__target_class = target_class self.__initialized = True + def __repr__(self): + return '' % self.__target_class.__name__ + + def __str__(self): + return '' % ( + self.__target_class.__name__, self.__attrs.keys()) + def __fill__(self): """Fill this LazyStub, computing values of all defined attributes. @@ -258,7 +266,9 @@ class AttributeBuilder(object): v = OrderedDeclarationWrapper(v, self.factory.sequence) wrapped_attrs[k] = v - return LazyStub(wrapped_attrs, containers=self._containers).__fill__() + stub = LazyStub(wrapped_attrs, containers=self._containers, + target_class=self.factory) + return stub.__fill__() class StubObject(object): diff --git a/tests/test_base.py b/tests/test_base.py index 97749f6..4f77421 100644 --- a/tests/test_base.py +++ b/tests/test_base.py @@ -56,6 +56,13 @@ class SafetyTestCase(unittest.TestCase): class FactoryTestCase(unittest.TestCase): + def testDisplay(self): + class TestObjectFactory(base.Factory): + FACTORY_FOR = FakeDjangoModel + + self.assertIn('TestObjectFactory', str(TestObjectFactory)) + self.assertIn('FakeDjangoModel', str(TestObjectFactory)) + def testLazyAttributeNonExistentParam(self): class TestObjectFactory(base.Factory): one = declarations.LazyAttribute(lambda a: a.does_not_exist ) diff --git a/tests/test_containers.py b/tests/test_containers.py index effb060..6e58573 100644 --- a/tests/test_containers.py +++ b/tests/test_containers.py @@ -80,6 +80,15 @@ class LazyStubTestCase(unittest.TestCase): self.assertRaises(containers.CyclicDefinitionError, getattr, stub, 'one') + def test_representation(self): + class RandomObj(object): + pass + + stub = containers.LazyStub({'one': 1, 'two': 2}, target_class=RandomObj) + self.assertIn('RandomObj', repr(stub)) + self.assertIn('RandomObj', str(stub)) + self.assertIn('one', str(stub)) + class OrderedDeclarationMock(declarations.OrderedDeclaration): pass -- cgit v1.2.3 From 6329a894c0b148f1c8bba2ebf224201419cf44ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Barrois?= Date: Fri, 13 Jan 2012 00:01:01 +0100 Subject: Add a PendingDeprecationWarning on associated class auto-discovery. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Raphaël Barrois --- factory/base.py | 30 +++++++++++++++++++----------- tests/test_base.py | 16 ++++++++++++++++ 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/factory/base.py b/factory/base.py index b2f437d..c17b7ce 100644 --- a/factory/base.py +++ b/factory/base.py @@ -22,6 +22,7 @@ import re import sys +import warnings from factory import containers @@ -156,29 +157,36 @@ class FactoryMetaClass(BaseFactoryMetaClass): if FACTORY_CLASS_DECLARATION in attrs: return attrs[FACTORY_CLASS_DECLARATION] + # No specific associated calss was given, and one was defined for our + # parent, use it. + if inherited is not None: + return inherited + if '__module__' in attrs: factory_module = sys.modules[attrs['__module__']] if class_name.endswith('Factory'): # Try a module lookup used_auto_discovery = True - associated_class_name = class_name[:-len('Factory')] - if associated_class_name: - # Class name was longer than just 'Factory'. - try: - return getattr(factory_module, associated_class_name) - except AttributeError: - pass + associated_name = class_name[:-len('Factory')] + if associated_name and hasattr(factory_module, associated_name): + warnings.warn( + "Auto-discovery of associated class is deprecated, and " + "will be removed in the future. Please set '%s = %s' " + "in the %s class definition." % ( + FACTORY_CLASS_DECLARATION, + associated_name, + class_name, + ), PendingDeprecationWarning) + + return getattr(factory_module, associated_name) # Unable to guess a good option; return the inherited class. - if inherited is not None: - return inherited - # Unable to find an associated class; fail. if used_auto_discovery: raise Factory.AssociatedClassError( FactoryMetaClass.ERROR_MESSAGE_AUTODISCOVERY.format( FACTORY_CLASS_DECLARATION, - associated_class_name, + associated_name, class_name, factory_module,)) else: diff --git a/tests/test_base.py b/tests/test_base.py index 4f77421..8da655e 100644 --- a/tests/test_base.py +++ b/tests/test_base.py @@ -21,6 +21,7 @@ # THE SOFTWARE. import unittest +import warnings from factory import base from factory import declarations @@ -153,6 +154,21 @@ class FactoryCreationTestCase(unittest.TestCase): self.assertTrue(isinstance(TestObjectFactory.build(), TestObject)) + def testDeprecationWarning(self): + """Make sure the 'auto-discovery' deprecation warning is issued.""" + + with warnings.catch_warnings(record=True) as w: + # Clear the warning registry. + if hasattr(base, '__warningregistry__'): + base.__warningregistry__.clear() + + warnings.simplefilter('always') + class TestObjectFactory(base.Factory): + pass + + self.assertEqual(1, len(w)) + self.assertIn('deprecated', str(w[0].message)) + def testStub(self): class TestFactory(base.StubFactory): pass -- cgit v1.2.3 From aaf283a81b39d6fc3d2ff493abaf14c318887a79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Barrois?= Date: Fri, 13 Jan 2012 00:04:29 +0100 Subject: Remove deprecated examples from the doc -- explicit is better than implicit, etc. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Raphaël Barrois --- README.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index f20b129..c3e0b0a 100644 --- a/README.rst +++ b/README.rst @@ -32,18 +32,19 @@ Source:: Defining factories ------------------ -Factories declare a set of attributes used to instantiate an object. The name of the factory is used to guess the class of the object by default, but it's possible to explicitly specify it:: +Factories declare a set of attributes used to instantiate an object. The class of the object must be defined in the FACTORY_FOR attribute:: import factory from models import User - # This will guess the User class class UserFactory(factory.Factory): + FACTORY_FOR = User + first_name = 'John' last_name = 'Doe' admin = False - # This will use the User class (Admin would have been guessed) + # Another, different, factory for the same object class AdminFactory(factory.Factory): FACTORY_FOR = User -- cgit v1.2.3 From 33d5a1ee95c78f2d67fcb4f5d217edb9b1a13301 Mon Sep 17 00:00:00 2001 From: Andrey Voronov Date: Thu, 2 Feb 2012 00:40:14 +0200 Subject: Update README.rst --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index c3e0b0a..e41e07a 100644 --- a/README.rst +++ b/README.rst @@ -190,7 +190,7 @@ Factory._prepare method:: password = kwargs.pop('password', None) user = super(UserFactory, cls)._prepare(create, **kwargs) if password: - user.set_password(user) + user.set_password(password) if create: user.save() return user -- cgit v1.2.3 From 2a1138550b3220b6f8cd23bae5fed03f0fb448cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Barrois?= Date: Fri, 24 Feb 2012 00:07:53 +0100 Subject: Allow using '__' in factory.SelfAttribute. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Raphaël Barrois --- factory/containers.py | 2 +- factory/declarations.py | 33 ++++++++++++++++++++++++++++++--- tests/test_declarations.py | 41 ++++++++++++++++++++++++++++++++++++++++- 3 files changed, 71 insertions(+), 5 deletions(-) diff --git a/factory/containers.py b/factory/containers.py index dd11f5f..ef97548 100644 --- a/factory/containers.py +++ b/factory/containers.py @@ -26,7 +26,7 @@ from factory import declarations #: String for splitting an attribute name into a #: (subfactory_name, subfactory_field) tuple. -ATTR_SPLITTER = '__' +ATTR_SPLITTER = declarations.ATTR_SPLITTER class CyclicDefinitionError(Exception): diff --git a/factory/declarations.py b/factory/declarations.py index 0ce7071..60425c3 100644 --- a/factory/declarations.py +++ b/factory/declarations.py @@ -20,6 +20,12 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. + +#: String for splitting an attribute name into a +#: (subfactory_name, subfactory_field) tuple. +ATTR_SPLITTER = '__' + + class OrderedDeclaration(object): """A factory declaration. @@ -58,6 +64,29 @@ class LazyAttribute(OrderedDeclaration): return self.function(obj) +def dig(obj, name): + """Try to retrieve the given attribute of an object, using ATTR_SPLITTER. + + If ATTR_SPLITTER is '__', dig(foo, 'a__b__c') is equivalent to foo.a.b.c. + + Args: + obj (object): the object of which an attribute should be read + name (str): the name of an attribute to look up. + + Returns: + the attribute pointed to by 'name', according to ATTR_SPLITTER. + + Raises: + AttributeError: if obj has no 'name' attribute. + """ + may_split = (ATTR_SPLITTER in name and not name.startswith(ATTR_SPLITTER)) + if may_split and not hasattr(obj, name): + attr, subname = name.split(ATTR_SPLITTER, 1) + return dig(getattr(obj, attr), subname) + else: + return getattr(obj, name) + + class SelfAttribute(OrderedDeclaration): """Specific OrderedDeclaration copying values from other fields. @@ -70,9 +99,7 @@ class SelfAttribute(OrderedDeclaration): self.attribute_name = attribute_name def evaluate(self, sequence, obj, containers=()): - # TODO(rbarrois): allow the use of ATTR_SPLITTER to fetch fields of - # subfactories. - return getattr(obj, self.attribute_name) + return dig(obj, self.attribute_name) class Sequence(OrderedDeclaration): diff --git a/tests/test_declarations.py b/tests/test_declarations.py index dcee38b..0fcdf10 100644 --- a/tests/test_declarations.py +++ b/tests/test_declarations.py @@ -22,12 +22,51 @@ import unittest -from factory.declarations import OrderedDeclaration, Sequence +from factory.declarations import dig, OrderedDeclaration, Sequence class OrderedDeclarationTestCase(unittest.TestCase): def test_errors(self): decl = OrderedDeclaration() self.assertRaises(NotImplementedError, decl.evaluate, None, {}) + +class DigTestCase(unittest.TestCase): + class MyObj(object): + def __init__(self, n): + self.n = n + + def test_parentattr(self): + obj = self.MyObj(1) + obj.a__b__c = self.MyObj(2) + obj.a = self.MyObj(3) + obj.a.b = self.MyObj(4) + obj.a.b.c = self.MyObj(5) + + self.assertEqual(2, dig(obj, 'a__b__c').n) + + def test_private(self): + obj = self.MyObj(1) + self.assertEqual(obj.__class__, dig(obj, '__class__')) + + def test_chaining(self): + obj = self.MyObj(1) + obj.a = self.MyObj(2) + obj.a__c = self.MyObj(3) + obj.a.b = self.MyObj(4) + obj.a.b.c = self.MyObj(5) + + self.assertEqual(2, dig(obj, 'a').n) + self.assertRaises(AttributeError, dig, obj, 'b') + self.assertEqual(2, dig(obj, 'a__n')) + self.assertEqual(3, dig(obj, 'a__c').n) + self.assertRaises(AttributeError, dig, obj, 'a__c__n') + self.assertRaises(AttributeError, dig, obj, 'a__d') + self.assertEqual(4, dig(obj, 'a__b').n) + self.assertEqual(4, dig(obj, 'a__b__n')) + self.assertEqual(5, dig(obj, 'a__b__c').n) + self.assertEqual(5, dig(obj, 'a__b__c__n')) + + + if __name__ == '__main__': unittest.main() -- cgit v1.2.3 From 826312ad0629ab49cab436d7cb1d27a898e7d864 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Barrois?= Date: Fri, 24 Feb 2012 00:17:04 +0100 Subject: Improve documentation. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Raphaël Barrois --- factory/containers.py | 4 ++++ factory/declarations.py | 13 +++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/factory/containers.py b/factory/containers.py index ef97548..fda9073 100644 --- a/factory/containers.py +++ b/factory/containers.py @@ -45,6 +45,10 @@ class LazyStub(object): __values (dict): maps attribute name to computed value __pending (str list): names of the attributes whose value is being computed. This allows to detect cyclic lazy attribute definition. + __containers (LazyStub list): "parents" of the LazyStub being built. + This allows to have the field of a field depend on the value of + another field + __target_class (type): the target class to build. """ __initialized = False diff --git a/factory/declarations.py b/factory/declarations.py index 60425c3..08598e5 100644 --- a/factory/declarations.py +++ b/factory/declarations.py @@ -155,8 +155,9 @@ class ContainerAttribute(OrderedDeclaration): Args: obj (LazyStub): a lazy stub of the object being constructed, if needed. - containers (LazyStub): a lazy stub of a factory being evaluated, with - a SubFactory building 'obj'. + containers (list of LazyStub): a list of lazy stubs of factories + being evaluated in a chain, each item being a future field of + next one. """ if self.strict and not containers: raise TypeError( @@ -187,6 +188,14 @@ class SubFactory(OrderedDeclaration): - attributes defined in the wrapped factory class - values defined when defining the SubFactory - additional values defined in attributes + + Args: + create (bool): whether the subfactory should call 'build' or + 'create' + extra (containers.DeclarationDict): extra values that should + override the wrapped factory's defaults + containers (list of LazyStub): List of LazyStub for the chain of + factories being evaluated, the calling stub being first. """ defaults = dict(self.defaults) -- cgit v1.2.3 From b297d69b2d7027793994d14381bdb8457b8bd8cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Barrois?= Date: Fri, 24 Feb 2012 00:18:53 +0100 Subject: Upgrade version. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Raphaël Barrois --- factory/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/factory/__init__.py b/factory/__init__.py index cc5b788..e58dc51 100644 --- a/factory/__init__.py +++ b/factory/__init__.py @@ -20,7 +20,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -__version__ = '1.0.4' # Remember to change in setup.py as well! +__version__ = '1.1.0' # Remember to change in setup.py as well! __author__ = 'Raphaël Barrois ' from base import ( diff --git a/setup.py b/setup.py index 5625e51..61970fa 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ from distutils.core import setup from distutils import cmd # Remember to change in factory/__init__.py as well! -VERSION = '1.0.4' +VERSION = '1.1.0' class test(cmd.Command): -- cgit v1.2.3 From 3663c1090f6ce016ebc0e9266c4cd4e85796984b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Barrois?= Date: Fri, 24 Feb 2012 00:19:31 +0100 Subject: Add README. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Raphaël Barrois --- README | 223 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ README.rst | 224 +------------------------------------------------------------ 2 files changed, 224 insertions(+), 223 deletions(-) create mode 100644 README mode change 100644 => 120000 README.rst diff --git a/README b/README new file mode 100644 index 0000000..e41e07a --- /dev/null +++ b/README @@ -0,0 +1,223 @@ +factory_boy +=========== + +factory_boy is a fixtures replacement based on thoughtbot's `factory_girl `_ . Like factory_girl it has a straightforward definition syntax, support for multiple build strategies (saved instances, unsaved instances, attribute dicts, and stubbed objects), and support for multiple factories for the same class, including factory inheritance. Django support is included, and support for other ORMs can be easily added. + +The official repository is at http://github.com/rbarrois/factory_boy. + +Credits +------- + +This README parallels the factory_girl README as much as possible; text and examples are reproduced for comparison purposes. Ruby users of factory_girl should feel right at home with factory_boy in Python. + +factory_boy was originally written by Mark Sandstrom, and improved by Raphaël Barrois. + +Thank you Joe Ferris and thoughtbot for creating factory_girl. + +Download +-------- + +Github: http://github.com/rbarrois/factory_boy/ + +PyPI:: + + pip install factory_boy + +Source:: + + # Download the source and run + python setup.py install + + +Defining factories +------------------ + +Factories declare a set of attributes used to instantiate an object. The class of the object must be defined in the FACTORY_FOR attribute:: + + import factory + from models import User + + class UserFactory(factory.Factory): + FACTORY_FOR = User + + first_name = 'John' + last_name = 'Doe' + admin = False + + # Another, different, factory for the same object + class AdminFactory(factory.Factory): + FACTORY_FOR = User + + first_name = 'Admin' + last_name = 'User' + admin = True + +Using factories +--------------- + +factory_boy supports several different build strategies: build, create, attributes and stub:: + + # Returns a User instance that's not saved + user = UserFactory.build() + + # Returns a saved User instance + user = UserFactory.create() + + # Returns a dict of attributes that can be used to build a User instance + attributes = UserFactory.attributes() + + # Returns an object with all defined attributes stubbed out: + stub = UserFactory.stub() + +You can use the Factory class as a shortcut for the default build strategy:: + + # Same as UserFactory.create() + user = UserFactory() + +The default strategy can be overridden:: + + UserFactory.default_strategy = factory.BUILD_STRATEGY + user = UserFactory() + +The default strategy can also be overridden for all factories:: + + # This will set the default strategy for all factories that don't define a default build strategy + factory.Factory.default_strategy = factory.BUILD_STRATEGY + +No matter which strategy is used, it's possible to override the defined attributes by passing keyword arguments:: + + # Build a User instance and override first_name + user = UserFactory.build(first_name='Joe') + user.first_name + # => 'Joe' + +Lazy Attributes +--------------- + +Most factory attributes can be added using static values that are evaluated when the factory is defined, but some attributes (such as associations and other attributes that must be dynamically generated) will need values assigned each time an instance is generated. These "lazy" attributes can be added as follows:: + + class UserFactory(factory.Factory): + first_name = 'Joe' + last_name = 'Blow' + email = factory.LazyAttribute(lambda a: '{0}.{1}@example.com'.format(a.first_name, a.last_name).lower()) + + UserFactory().email + # => 'joe.blow@example.com' + +The function passed to ``LazyAttribute`` is given the attributes defined for the factory up to the point of the LazyAttribute declaration. If a lambda won't cut it, the ``lazy_attribute`` decorator can be used to wrap a function:: + + # Stub factories don't have an associated class. + class SumFactory(factory.StubFactory): + lhs = 1 + rhs = 1 + + @lazy_attribute + def sum(a): + result = a.lhs + a.rhs # Or some other fancy calculation + return result + +Associations +------------ + +Associated instances can also be generated using ``LazyAttribute``:: + + from models import Post + + class PostFactory(factory.Factory): + author = factory.LazyAttribute(lambda a: UserFactory()) + +The associated object's default strategy is always used:: + + # Builds and saves a User and a Post + post = PostFactory() + post.id == None # => False + post.author.id == None # => False + + # Builds and saves a User, and then builds but does not save a Post + post = PostFactory.build() + post.id == None # => True + post.author.id == None # => False + +Inheritance +----------- + +You can easily create multiple factories for the same class without repeating common attributes by using inheritance:: + + class PostFactory(factory.Factory): + title = 'A title' + + class ApprovedPost(PostFactory): + approved = True + approver = factory.LazyAttribute(lambda a: UserFactory()) + +Sequences +--------- + +Unique values in a specific format (for example, e-mail addresses) can be generated using sequences. Sequences are defined by using ``Sequence`` or the decorator ``sequence``:: + + class UserFactory(factory.Factory): + email = factory.Sequence(lambda n: 'person{0}@example.com'.format(n)) + + UserFactory().email # => 'person0@example.com' + UserFactory().email # => 'person1@example.com' + +Sequences can be combined with lazy attributes:: + + class UserFactory(factory.Factory): + name = 'Mark' + email = factory.LazyAttributeSequence(lambda a, n: '{0}+{1}@example.com'.format(a.name, n).lower()) + + UserFactory().email # => mark+0@example.com + +If you wish to use a custom method to set the initial ID for a sequence, you can override the ``_setup_next_sequence`` class method:: + + class MyFactory(factory.Factory): + + @classmethod + def _setup_next_sequence(cls): + return cls._associated_class.objects.values_list('id').order_by('-id')[0] + 1 + +Customizing creation +-------------------- + +Sometimes, the default build/create by keyword arguments doesn't allow for enough +customization of the generated objects. In such cases, you should override the +Factory._prepare method:: + + class UserFactory(factory.Factory): + @classmethod + def _prepare(cls, create, **kwargs): + password = kwargs.pop('password', None) + user = super(UserFactory, cls)._prepare(create, **kwargs) + if password: + user.set_password(password) + if create: + user.save() + return user + +Subfactories +------------ + +If one of your factories has a field which is another factory, you can declare it as a ``SubFactory``. This allows to define attributes of that field when calling +the global factory, using a simple syntax : ``field__attr=42`` will set the attribute ``attr`` of the ``SubFactory`` defined in ``field`` to 42:: + + class InnerFactory(factory.Factory): + foo = 'foo' + bar = factory.LazyAttribute(lambda o: foo * 2) + + class ExternalFactory(factory.Factory): + inner = factory.SubFactory(InnerFactory, foo='bar') + + >>> e = ExternalFactory() + >>> e.foo + 'bar' + >>> e.bar + 'barbar' + + >>> e2 : ExternalFactory(inner__bar='baz') + >>> e2.foo + 'bar' + >>> e2.bar + 'baz' + + diff --git a/README.rst b/README.rst deleted file mode 100644 index e41e07a..0000000 --- a/README.rst +++ /dev/null @@ -1,223 +0,0 @@ -factory_boy -=========== - -factory_boy is a fixtures replacement based on thoughtbot's `factory_girl `_ . Like factory_girl it has a straightforward definition syntax, support for multiple build strategies (saved instances, unsaved instances, attribute dicts, and stubbed objects), and support for multiple factories for the same class, including factory inheritance. Django support is included, and support for other ORMs can be easily added. - -The official repository is at http://github.com/rbarrois/factory_boy. - -Credits -------- - -This README parallels the factory_girl README as much as possible; text and examples are reproduced for comparison purposes. Ruby users of factory_girl should feel right at home with factory_boy in Python. - -factory_boy was originally written by Mark Sandstrom, and improved by Raphaël Barrois. - -Thank you Joe Ferris and thoughtbot for creating factory_girl. - -Download --------- - -Github: http://github.com/rbarrois/factory_boy/ - -PyPI:: - - pip install factory_boy - -Source:: - - # Download the source and run - python setup.py install - - -Defining factories ------------------- - -Factories declare a set of attributes used to instantiate an object. The class of the object must be defined in the FACTORY_FOR attribute:: - - import factory - from models import User - - class UserFactory(factory.Factory): - FACTORY_FOR = User - - first_name = 'John' - last_name = 'Doe' - admin = False - - # Another, different, factory for the same object - class AdminFactory(factory.Factory): - FACTORY_FOR = User - - first_name = 'Admin' - last_name = 'User' - admin = True - -Using factories ---------------- - -factory_boy supports several different build strategies: build, create, attributes and stub:: - - # Returns a User instance that's not saved - user = UserFactory.build() - - # Returns a saved User instance - user = UserFactory.create() - - # Returns a dict of attributes that can be used to build a User instance - attributes = UserFactory.attributes() - - # Returns an object with all defined attributes stubbed out: - stub = UserFactory.stub() - -You can use the Factory class as a shortcut for the default build strategy:: - - # Same as UserFactory.create() - user = UserFactory() - -The default strategy can be overridden:: - - UserFactory.default_strategy = factory.BUILD_STRATEGY - user = UserFactory() - -The default strategy can also be overridden for all factories:: - - # This will set the default strategy for all factories that don't define a default build strategy - factory.Factory.default_strategy = factory.BUILD_STRATEGY - -No matter which strategy is used, it's possible to override the defined attributes by passing keyword arguments:: - - # Build a User instance and override first_name - user = UserFactory.build(first_name='Joe') - user.first_name - # => 'Joe' - -Lazy Attributes ---------------- - -Most factory attributes can be added using static values that are evaluated when the factory is defined, but some attributes (such as associations and other attributes that must be dynamically generated) will need values assigned each time an instance is generated. These "lazy" attributes can be added as follows:: - - class UserFactory(factory.Factory): - first_name = 'Joe' - last_name = 'Blow' - email = factory.LazyAttribute(lambda a: '{0}.{1}@example.com'.format(a.first_name, a.last_name).lower()) - - UserFactory().email - # => 'joe.blow@example.com' - -The function passed to ``LazyAttribute`` is given the attributes defined for the factory up to the point of the LazyAttribute declaration. If a lambda won't cut it, the ``lazy_attribute`` decorator can be used to wrap a function:: - - # Stub factories don't have an associated class. - class SumFactory(factory.StubFactory): - lhs = 1 - rhs = 1 - - @lazy_attribute - def sum(a): - result = a.lhs + a.rhs # Or some other fancy calculation - return result - -Associations ------------- - -Associated instances can also be generated using ``LazyAttribute``:: - - from models import Post - - class PostFactory(factory.Factory): - author = factory.LazyAttribute(lambda a: UserFactory()) - -The associated object's default strategy is always used:: - - # Builds and saves a User and a Post - post = PostFactory() - post.id == None # => False - post.author.id == None # => False - - # Builds and saves a User, and then builds but does not save a Post - post = PostFactory.build() - post.id == None # => True - post.author.id == None # => False - -Inheritance ------------ - -You can easily create multiple factories for the same class without repeating common attributes by using inheritance:: - - class PostFactory(factory.Factory): - title = 'A title' - - class ApprovedPost(PostFactory): - approved = True - approver = factory.LazyAttribute(lambda a: UserFactory()) - -Sequences ---------- - -Unique values in a specific format (for example, e-mail addresses) can be generated using sequences. Sequences are defined by using ``Sequence`` or the decorator ``sequence``:: - - class UserFactory(factory.Factory): - email = factory.Sequence(lambda n: 'person{0}@example.com'.format(n)) - - UserFactory().email # => 'person0@example.com' - UserFactory().email # => 'person1@example.com' - -Sequences can be combined with lazy attributes:: - - class UserFactory(factory.Factory): - name = 'Mark' - email = factory.LazyAttributeSequence(lambda a, n: '{0}+{1}@example.com'.format(a.name, n).lower()) - - UserFactory().email # => mark+0@example.com - -If you wish to use a custom method to set the initial ID for a sequence, you can override the ``_setup_next_sequence`` class method:: - - class MyFactory(factory.Factory): - - @classmethod - def _setup_next_sequence(cls): - return cls._associated_class.objects.values_list('id').order_by('-id')[0] + 1 - -Customizing creation --------------------- - -Sometimes, the default build/create by keyword arguments doesn't allow for enough -customization of the generated objects. In such cases, you should override the -Factory._prepare method:: - - class UserFactory(factory.Factory): - @classmethod - def _prepare(cls, create, **kwargs): - password = kwargs.pop('password', None) - user = super(UserFactory, cls)._prepare(create, **kwargs) - if password: - user.set_password(password) - if create: - user.save() - return user - -Subfactories ------------- - -If one of your factories has a field which is another factory, you can declare it as a ``SubFactory``. This allows to define attributes of that field when calling -the global factory, using a simple syntax : ``field__attr=42`` will set the attribute ``attr`` of the ``SubFactory`` defined in ``field`` to 42:: - - class InnerFactory(factory.Factory): - foo = 'foo' - bar = factory.LazyAttribute(lambda o: foo * 2) - - class ExternalFactory(factory.Factory): - inner = factory.SubFactory(InnerFactory, foo='bar') - - >>> e = ExternalFactory() - >>> e.foo - 'bar' - >>> e.bar - 'barbar' - - >>> e2 : ExternalFactory(inner__bar='baz') - >>> e2.foo - 'bar' - >>> e2.bar - 'baz' - - diff --git a/README.rst b/README.rst new file mode 120000 index 0000000..100b938 --- /dev/null +++ b/README.rst @@ -0,0 +1 @@ +README \ No newline at end of file -- cgit v1.2.3 From dc16881ebf674295a3e855bfc4798a0ce8bd94d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Barrois?= Date: Fri, 24 Feb 2012 01:14:30 +0100 Subject: Improve the 'SelfAttribute' syntax. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Raphaël Barrois --- factory/containers.py | 2 +- factory/declarations.py | 42 +++++++++++++++++++++++++----------------- setup.py | 2 +- tests/test_declarations.py | 43 +++++++++++++++---------------------------- tests/test_using.py | 12 +++++++++++- 5 files changed, 53 insertions(+), 48 deletions(-) diff --git a/factory/containers.py b/factory/containers.py index fda9073..2f92f62 100644 --- a/factory/containers.py +++ b/factory/containers.py @@ -26,7 +26,7 @@ from factory import declarations #: String for splitting an attribute name into a #: (subfactory_name, subfactory_field) tuple. -ATTR_SPLITTER = declarations.ATTR_SPLITTER +ATTR_SPLITTER = '__' class CyclicDefinitionError(Exception): diff --git a/factory/declarations.py b/factory/declarations.py index 08598e5..5fe427c 100644 --- a/factory/declarations.py +++ b/factory/declarations.py @@ -21,11 +21,6 @@ # THE SOFTWARE. -#: String for splitting an attribute name into a -#: (subfactory_name, subfactory_field) tuple. -ATTR_SPLITTER = '__' - - class OrderedDeclaration(object): """A factory declaration. @@ -64,27 +59,37 @@ class LazyAttribute(OrderedDeclaration): return self.function(obj) -def dig(obj, name): - """Try to retrieve the given attribute of an object, using ATTR_SPLITTER. +class _UNSPECIFIED(object): + pass + - If ATTR_SPLITTER is '__', dig(foo, 'a__b__c') is equivalent to foo.a.b.c. +def deepgetattr(obj, name, default=_UNSPECIFIED): + """Try to retrieve the given attribute of an object, digging on '.'. + + This is an extended getattr, digging deeper if '.' is found. Args: obj (object): the object of which an attribute should be read name (str): the name of an attribute to look up. + default (object): the default value to use if the attribute wasn't found Returns: - the attribute pointed to by 'name', according to ATTR_SPLITTER. + the attribute pointed to by 'name', splitting on '.'. Raises: AttributeError: if obj has no 'name' attribute. """ - may_split = (ATTR_SPLITTER in name and not name.startswith(ATTR_SPLITTER)) - if may_split and not hasattr(obj, name): - attr, subname = name.split(ATTR_SPLITTER, 1) - return dig(getattr(obj, attr), subname) - else: - return getattr(obj, name) + try: + if '.' in name: + attr, subname = name.split('.', 1) + return deepgetattr(getattr(obj, attr), subname, default) + else: + return getattr(obj, name) + except AttributeError: + if default is _UNSPECIFIED: + raise + else: + return default class SelfAttribute(OrderedDeclaration): @@ -92,14 +97,17 @@ class SelfAttribute(OrderedDeclaration): Attributes: attribute_name (str): the name of the attribute to copy. + default (object): the default value to use if the attribute doesn't + exist. """ - def __init__(self, attribute_name, *args, **kwargs): + def __init__(self, attribute_name, default=_UNSPECIFIED, *args, **kwargs): super(SelfAttribute, self).__init__(*args, **kwargs) self.attribute_name = attribute_name + self.default = default def evaluate(self, sequence, obj, containers=()): - return dig(obj, self.attribute_name) + return deepgetattr(obj, self.attribute_name, self.default) class Sequence(OrderedDeclaration): diff --git a/setup.py b/setup.py index 61970fa..99aa123 100644 --- a/setup.py +++ b/setup.py @@ -40,7 +40,7 @@ class test(cmd.Command): setup( name='factory_boy', version=VERSION, - description="A test fixtures replacement based on thoughtbot's factory_girl for Ruby.", + description="A verstile test fixtures replacement based on thoughtbot's factory_girl for Ruby.", author='Mark Sandstrom', author_email='mark@deliciouslynerdy.com', maintainer='Raphaël Barrois', diff --git a/tests/test_declarations.py b/tests/test_declarations.py index 0fcdf10..7215a54 100644 --- a/tests/test_declarations.py +++ b/tests/test_declarations.py @@ -22,7 +22,7 @@ import unittest -from factory.declarations import dig, OrderedDeclaration, Sequence +from factory.declarations import deepgetattr, OrderedDeclaration, Sequence class OrderedDeclarationTestCase(unittest.TestCase): def test_errors(self): @@ -35,36 +35,23 @@ class DigTestCase(unittest.TestCase): def __init__(self, n): self.n = n - def test_parentattr(self): - obj = self.MyObj(1) - obj.a__b__c = self.MyObj(2) - obj.a = self.MyObj(3) - obj.a.b = self.MyObj(4) - obj.a.b.c = self.MyObj(5) - - self.assertEqual(2, dig(obj, 'a__b__c').n) - - def test_private(self): - obj = self.MyObj(1) - self.assertEqual(obj.__class__, dig(obj, '__class__')) - def test_chaining(self): obj = self.MyObj(1) obj.a = self.MyObj(2) - obj.a__c = self.MyObj(3) - obj.a.b = self.MyObj(4) - obj.a.b.c = self.MyObj(5) - - self.assertEqual(2, dig(obj, 'a').n) - self.assertRaises(AttributeError, dig, obj, 'b') - self.assertEqual(2, dig(obj, 'a__n')) - self.assertEqual(3, dig(obj, 'a__c').n) - self.assertRaises(AttributeError, dig, obj, 'a__c__n') - self.assertRaises(AttributeError, dig, obj, 'a__d') - self.assertEqual(4, dig(obj, 'a__b').n) - self.assertEqual(4, dig(obj, 'a__b__n')) - self.assertEqual(5, dig(obj, 'a__b__c').n) - self.assertEqual(5, dig(obj, 'a__b__c__n')) + obj.a.b = self.MyObj(3) + obj.a.b.c = self.MyObj(4) + + self.assertEqual(2, deepgetattr(obj, 'a').n) + self.assertRaises(AttributeError, deepgetattr, obj, 'b') + self.assertEqual(2, deepgetattr(obj, 'a.n')) + self.assertEqual(3, deepgetattr(obj, 'a.c', 3)) + self.assertRaises(AttributeError, deepgetattr, obj, 'a.c.n') + self.assertRaises(AttributeError, deepgetattr, obj, 'a.d') + self.assertEqual(3, deepgetattr(obj, 'a.b').n) + self.assertEqual(3, deepgetattr(obj, 'a.b.n')) + self.assertEqual(4, deepgetattr(obj, 'a.b.c').n) + self.assertEqual(4, deepgetattr(obj, 'a.b.c.n')) + self.assertEqual(42, deepgetattr(obj, 'a.b.c.n.x', 42)) diff --git a/tests/test_using.py b/tests/test_using.py index a93c968..85a12ca 100644 --- a/tests/test_using.py +++ b/tests/test_using.py @@ -27,11 +27,12 @@ import factory class TestObject(object): - def __init__(self, one=None, two=None, three=None, four=None): + def __init__(self, one=None, two=None, three=None, four=None, five=None): self.one = one self.two = two self.three = three self.four = four + self.five = five class FakeDjangoModel(object): class FakeDjangoManager(object): @@ -164,12 +165,21 @@ class FactoryTestCase(unittest.TestCase): self.assertEqual(test_object.one, 'one') def testSelfAttribute(self): + class TmpObj(object): + n = 3 + class TestObjectFactory(factory.Factory): one = 'xx' two = factory.SelfAttribute('one') + three = TmpObj() + four = factory.SelfAttribute('three.n') + five = factory.SelfAttribute('three.nnn', 5) test_object = TestObjectFactory.build(one=1) self.assertEqual(1, test_object.two) + self.assertEqual(3, test_object.three.n) + self.assertEqual(3, test_object.four) + self.assertEqual(5, test_object.five) def testSequenceDecorator(self): class TestObjectFactory(factory.Factory): -- cgit v1.2.3 From 59274330932d5eae3b708c5e944a7ae452cdddaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Barrois?= Date: Fri, 24 Feb 2012 01:28:19 +0100 Subject: Improve docs. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Raphaël Barrois --- docs/conf.py | 1 + docs/index.rst | 1 + docs/internals.rst | 25 +++++++++++++++++++++++++ 3 files changed, 27 insertions(+) create mode 100644 docs/internals.rst diff --git a/docs/conf.py b/docs/conf.py index f50b1a6..7a67dfd 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -17,6 +17,7 @@ import sys, os # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.insert(0, os.path.abspath('.')) +sys.path.insert(0, os.path.dirname(os.path.abspath('.'))) # -- General configuration ----------------------------------------------------- diff --git a/docs/index.rst b/docs/index.rst index 2e0f4a3..eff5ac6 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -59,6 +59,7 @@ Contents: examples subfactory + internals Indices and tables ================== diff --git a/docs/internals.rst b/docs/internals.rst new file mode 100644 index 0000000..75de63d --- /dev/null +++ b/docs/internals.rst @@ -0,0 +1,25 @@ +Factoy Boy's internals +====================== + + +declarations +------------ + +.. automodule:: factory.declarations + :members: + + +containers +---------- + +.. automodule:: factory.containers + :members: + + + +base +---- + +.. automodule:: factory.base + :members: + -- cgit v1.2.3 From 1643e56a6e4ca0e90621b9c39928b2c6bfb63063 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Barrois?= Date: Fri, 24 Feb 2012 09:49:49 +0100 Subject: Add batches. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Raphaël Barrois --- factory/__init__.py | 4 ++++ factory/base.py | 54 ++++++++++++++++++++++++++++++++++++++++++ tests/test_using.py | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 125 insertions(+), 1 deletion(-) diff --git a/factory/__init__.py b/factory/__init__.py index e58dc51..4acd380 100644 --- a/factory/__init__.py +++ b/factory/__init__.py @@ -33,6 +33,10 @@ from base import ( stub, make_factory, + build_batch, + create_batch, + stub_batch, + BUILD_STRATEGY, CREATE_STRATEGY, STUB_STRATEGY, diff --git a/factory/base.py b/factory/base.py index c17b7ce..99cf49d 100644 --- a/factory/base.py +++ b/factory/base.py @@ -308,11 +308,35 @@ class BaseFactory(object): """Build an instance of the associated class, with overriden attrs.""" raise cls.UnsupportedStrategy() + @classmethod + def build_batch(cls, size, **kwargs): + """Build a batch of instances of the given class, with overriden attrs. + + Args: + size (int): the number of instances to build + + Returns: + object list: the built instances + """ + return [cls.build(**kwargs) for _ in xrange(size)] + @classmethod def create(cls, **kwargs): """Create an instance of the associated class, with overriden attrs.""" raise cls.UnsupportedStrategy() + @classmethod + def create_batch(cls, size, **kwargs): + """Create a batch of instances of the given class, with overriden attrs. + + Args: + size (int): the number of instances to create + + Returns: + object list: the created instances + """ + return [cls.create(**kwargs) for _ in xrange(size)] + @classmethod def stub(cls, **kwargs): """Retrieve a stub of the associated class, with overriden attrs. @@ -325,6 +349,18 @@ class BaseFactory(object): setattr(stub_object, name, value) return stub_object + @classmethod + def stub_batch(cls, size, **kwargs): + """Stub a batch of instances of the given class, with overriden attrs. + + Args: + size (int): the number of instances to stub + + Returns: + object list: the stubbed instances + """ + return [cls.stub(**kwargs) for _ in xrange(size)] + class StubFactory(BaseFactory): __metaclass__ = BaseFactoryMetaClass @@ -468,10 +504,28 @@ def build(klass, **kwargs): """Create a factory for the given class, and build an instance.""" return make_factory(klass, **kwargs).build() + +def build_batch(klass, size, **kwargs): + """Create a factory for the given class, and build a batch of instances.""" + return make_factory(klass, **kwargs).build_batch(size) + + def create(klass, **kwargs): """Create a factory for the given class, and create an instance.""" return make_factory(klass, **kwargs).create() + +def create_batch(klass, size, **kwargs): + """Create a factory for the given class, and create a batch of instances.""" + return make_factory(klass, **kwargs).create_batch(size) + + def stub(klass, **kwargs): """Create a factory for the given class, and stub an instance.""" return make_factory(klass, **kwargs).stub() + + +def stub_batch(klass, size, **kwargs): + """Create a factory for the given class, and stub a batch of instances.""" + return make_factory(klass, **kwargs).stub_batch(size) + diff --git a/tests/test_using.py b/tests/test_using.py index 85a12ca..7ed94e2 100644 --- a/tests/test_using.py +++ b/tests/test_using.py @@ -69,11 +69,34 @@ class SimpleBuildTestCase(unittest.TestCase): self.assertEqual(obj.three, 3) self.assertEqual(obj.four, None) + def test_build_batch(self): + objs = factory.build_batch(TestObject, 4, two=2, + three=factory.LazyAttribute(lambda o: o.two + 1)) + + self.assertEqual(4, len(objs)) + self.assertEqual(4, len(set(objs))) + + for obj in objs: + self.assertEqual(obj.one, None) + self.assertEqual(obj.two, 2) + self.assertEqual(obj.three, 3) + self.assertEqual(obj.four, None) + def test_create(self): obj = factory.create(FakeDjangoModel, foo='bar') self.assertEqual(obj.id, 1) self.assertEqual(obj.foo, 'bar') + def test_create_batch(self): + objs = factory.create_batch(FakeDjangoModel, 4, foo='bar') + + self.assertEqual(4, len(objs)) + self.assertEqual(4, len(set(objs))) + + for obj in objs: + self.assertEqual(obj.id, 1) + self.assertEqual(obj.foo, 'bar') + def test_stub(self): obj = factory.stub(TestObject, three=3) self.assertEqual(obj.three, 3) @@ -133,6 +156,19 @@ class FactoryTestCase(unittest.TestCase): self.assertEqual('one43', test_object1.one) self.assertEqual('two43', test_object1.two) + def test_sequence_batch(self): + class TestObjectFactory(factory.Factory): + one = factory.Sequence(lambda n: 'one' + n) + two = factory.Sequence(lambda n: 'two' + n) + + objs = TestObjectFactory.build_batch(20) + + self.assertEqual(20, len(objs)) + self.assertEqual(20, len(set(objs))) + for i, obj in enumerate(objs): + self.assertEqual('one%d' % i, obj.one) + self.assertEqual('two%d' % i, obj.two) + def testLazyAttribute(self): class TestObjectFactory(factory.Factory): one = factory.LazyAttribute(lambda a: 'abc' ) @@ -225,6 +261,37 @@ class FactoryTestCase(unittest.TestCase): self.assertEqual(test_model.one, 'one') self.assertTrue(test_model.id) + def test_create_batch(self): + class TestModelFactory(factory.Factory): + one = 'one' + + objs = TestModelFactory.create_batch(20, two=factory.Sequence(int)) + + self.assertEqual(20, len(objs)) + self.assertEqual(20, len(set(objs))) + + for i, obj in enumerate(objs): + self.assertEqual('one', obj.one) + self.assertEqual(i, obj.two) + self.assertTrue(obj.id) + + def test_stub_batch(self): + class TestObjectFactory(factory.Factory): + one = 'one' + two = factory.LazyAttribute(lambda a: a.one + ' two') + three = factory.Sequence(lambda n: int(n)) + + objs = TestObjectFactory.stub_batch(20, + one=factory.Sequence(lambda n: n)) + + self.assertEqual(20, len(objs)) + self.assertEqual(20, len(set(objs))) + + for i, obj in enumerate(objs): + self.assertEqual(str(i), obj.one) + self.assertEqual('%d two' % i, obj.two) + self.assertEqual(i, obj.three) + def testInheritance(self): class TestObjectFactory(factory.Factory): one = 'one' @@ -364,7 +431,6 @@ class SubFactoryTestCase(unittest.TestCase): self.assertEqual(wrapping.wrapped.three, 3) self.assertEqual(wrapping.wrapped.four, 4) - def testNestedSubFactory(self): """Test nested sub-factories.""" -- cgit v1.2.3 From 86cd8c2472dff6fe62e3c1f83bd9a1a3523ce08e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Barrois?= Date: Fri, 24 Feb 2012 09:50:24 +0100 Subject: Version bump MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Raphaël Barrois --- docs/conf.py | 2 +- factory/__init__.py | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 7a67dfd..2326d73 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -51,7 +51,7 @@ copyright = u'2011, Raphaël Barrois, Mark Sandstrom' # The short X.Y version. version = '1.1' # The full version, including alpha/beta/rc tags. -release = '1.1.0' +release = '1.1.1' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/factory/__init__.py b/factory/__init__.py index 4acd380..990f39f 100644 --- a/factory/__init__.py +++ b/factory/__init__.py @@ -20,7 +20,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -__version__ = '1.1.0' # Remember to change in setup.py as well! +__version__ = '1.1.1' # Remember to change in setup.py as well! __author__ = 'Raphaël Barrois ' from base import ( diff --git a/setup.py b/setup.py index 99aa123..2b1d54b 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ from distutils.core import setup from distutils import cmd # Remember to change in factory/__init__.py as well! -VERSION = '1.1.0' +VERSION = '1.1.1' class test(cmd.Command): -- cgit v1.2.3 From cbbe5cc359412c8e6c49e06d5d1f35680ad88c40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Barrois?= Date: Fri, 24 Feb 2012 23:31:02 +0100 Subject: Add support for 'generate' and 'simple_generate' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Raphaël Barrois --- factory/__init__.py | 4 ++ factory/base.py | 85 +++++++++++++++++++++++ tests/test_using.py | 195 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 284 insertions(+) diff --git a/factory/__init__.py b/factory/__init__.py index 990f39f..cf9cc3e 100644 --- a/factory/__init__.py +++ b/factory/__init__.py @@ -31,11 +31,15 @@ from base import ( build, create, stub, + generate, + simple_generate, make_factory, build_batch, create_batch, stub_batch, + generate_batch, + simple_generate_batch, BUILD_STRATEGY, CREATE_STRATEGY, diff --git a/factory/base.py b/factory/base.py index 99cf49d..62131fb 100644 --- a/factory/base.py +++ b/factory/base.py @@ -361,6 +361,72 @@ class BaseFactory(object): """ return [cls.stub(**kwargs) for _ in xrange(size)] + @classmethod + def generate(cls, strategy, **kwargs): + """Generate a new instance. + + The instance will be created with the given strategy (one of + BUILD_STRATEGY, CREATE_STRATEGY, STUB_STRATEGY). + + Args: + strategy (str): the strategy to use for generating the instance. + + Returns: + object: the generated instance + """ + assert strategy in (STUB_STRATEGY, BUILD_STRATEGY, CREATE_STRATEGY) + action = getattr(cls, strategy) + return action(**kwargs) + + @classmethod + def generate_batch(cls, strategy, size, **kwargs): + """Generate a batch of instances. + + The instances will be created with the given strategy (one of + BUILD_STRATEGY, CREATE_STRATEGY, STUB_STRATEGY). + + Args: + strategy (str): the strategy to use for generating the instance. + size (int): the number of instances to generate + + Returns: + object list: the generated instances + """ + assert strategy in (STUB_STRATEGY, BUILD_STRATEGY, CREATE_STRATEGY) + batch_action = getattr(cls, '%s_batch' % strategy) + return batch_action(size, **kwargs) + + @classmethod + def simple_generate(cls, create, **kwargs): + """Generate a new instance. + + The instance will be either 'built' or 'created'. + + Args: + create (bool): whether to 'build' or 'create' the instance. + + Returns: + object: the generated instance + """ + strategy = CREATE_STRATEGY if create else BUILD_STRATEGY + return cls.generate(strategy, **kwargs) + + @classmethod + def simple_generate_batch(cls, create, size, **kwargs): + """Generate a batch of instances. + + These instances will be either 'built' or 'created'. + + Args: + size (int): the number of instances to generate + create (bool): whether to 'build' or 'create' the instances. + + Returns: + object list: the generated instances + """ + strategy = CREATE_STRATEGY if create else BUILD_STRATEGY + return cls.generate_batch(strategy, size, **kwargs) + class StubFactory(BaseFactory): __metaclass__ = BaseFactoryMetaClass @@ -529,3 +595,22 @@ def stub_batch(klass, size, **kwargs): """Create a factory for the given class, and stub a batch of instances.""" return make_factory(klass, **kwargs).stub_batch(size) + +def generate(klass, strategy, **kwargs): + """Create a factory for the given class, and generate an instance.""" + return make_factory(klass, **kwargs).generate(strategy) + + +def generate_batch(klass, strategy, size, **kwargs): + """Create a factory for the given class, and generate instances.""" + return make_factory(klass, **kwargs).generate_batch(strategy, size) + + +def simple_generate(klass, create, **kwargs): + """Create a factory for the given class, and simple_generate an instance.""" + return make_factory(klass, **kwargs).simple_generate(create) + + +def simple_generate_batch(klass, create, size, **kwargs): + """Create a factory for the given class, and simple_generate instances.""" + return make_factory(klass, **kwargs).simple_generate_batch(create, size) diff --git a/tests/test_using.py b/tests/test_using.py index 7ed94e2..4e69212 100644 --- a/tests/test_using.py +++ b/tests/test_using.py @@ -102,6 +102,91 @@ class SimpleBuildTestCase(unittest.TestCase): self.assertEqual(obj.three, 3) self.assertFalse(hasattr(obj, 'two')) + def test_stub_batch(self): + objs = factory.stub_batch(FakeDjangoModel, 4, foo='bar') + + self.assertEqual(4, len(objs)) + self.assertEqual(4, len(set(objs))) + + for obj in objs: + self.assertFalse(hasattr(obj, 'id')) + self.assertEqual(obj.foo, 'bar') + + def test_generate_build(self): + obj = factory.generate(FakeDjangoModel, factory.BUILD_STRATEGY, foo='bar') + self.assertEqual(obj.id, None) + self.assertEqual(obj.foo, 'bar') + + def test_generate_create(self): + obj = factory.generate(FakeDjangoModel, factory.CREATE_STRATEGY, foo='bar') + self.assertEqual(obj.id, 1) + self.assertEqual(obj.foo, 'bar') + + def test_generate_stub(self): + obj = factory.generate(FakeDjangoModel, factory.STUB_STRATEGY, foo='bar') + self.assertFalse(hasattr(obj, 'id')) + self.assertEqual(obj.foo, 'bar') + + def test_generate_batch_build(self): + objs = factory.generate_batch(FakeDjangoModel, factory.BUILD_STRATEGY, 20, foo='bar') + + self.assertEqual(20, len(objs)) + self.assertEqual(20, len(set(objs))) + + for obj in objs: + self.assertEqual(obj.id, None) + self.assertEqual(obj.foo, 'bar') + + def test_generate_batch_create(self): + objs = factory.generate_batch(FakeDjangoModel, factory.CREATE_STRATEGY, 20, foo='bar') + + self.assertEqual(20, len(objs)) + self.assertEqual(20, len(set(objs))) + + for obj in objs: + self.assertEqual(obj.id, 1) + self.assertEqual(obj.foo, 'bar') + + def test_generate_batch_stub(self): + objs = factory.generate_batch(FakeDjangoModel, factory.STUB_STRATEGY, 20, foo='bar') + + self.assertEqual(20, len(objs)) + self.assertEqual(20, len(set(objs))) + + for obj in objs: + self.assertFalse(hasattr(obj, 'id')) + self.assertEqual(obj.foo, 'bar') + + def test_simple_generate_build(self): + obj = factory.simple_generate(FakeDjangoModel, False, foo='bar') + self.assertEqual(obj.id, None) + self.assertEqual(obj.foo, 'bar') + + def test_simple_generate_create(self): + obj = factory.simple_generate(FakeDjangoModel, True, foo='bar') + self.assertEqual(obj.id, 1) + self.assertEqual(obj.foo, 'bar') + + def test_simple_generate_batch_build(self): + objs = factory.simple_generate_batch(FakeDjangoModel, False, 20, foo='bar') + + self.assertEqual(20, len(objs)) + self.assertEqual(20, len(set(objs))) + + for obj in objs: + self.assertEqual(obj.id, None) + self.assertEqual(obj.foo, 'bar') + + def test_simple_generate_batch_create(self): + objs = factory.simple_generate_batch(FakeDjangoModel, True, 20, foo='bar') + + self.assertEqual(20, len(objs)) + self.assertEqual(20, len(set(objs))) + + for obj in objs: + self.assertEqual(obj.id, 1) + self.assertEqual(obj.foo, 'bar') + def test_make_factory(self): fact = factory.make_factory(TestObject, two=2, three=factory.LazyAttribute(lambda o: o.two + 1)) @@ -275,6 +360,116 @@ class FactoryTestCase(unittest.TestCase): self.assertEqual(i, obj.two) self.assertTrue(obj.id) + def test_generate_build(self): + class TestModelFactory(factory.Factory): + one = 'one' + + test_model = TestModelFactory.generate(factory.BUILD_STRATEGY) + self.assertEqual(test_model.one, 'one') + self.assertFalse(test_model.id) + + def test_generate_create(self): + class TestModelFactory(factory.Factory): + one = 'one' + + test_model = TestModelFactory.generate(factory.CREATE_STRATEGY) + self.assertEqual(test_model.one, 'one') + self.assertTrue(test_model.id) + + def test_generate_stub(self): + class TestModelFactory(factory.Factory): + one = 'one' + + test_model = TestModelFactory.generate(factory.STUB_STRATEGY) + self.assertEqual(test_model.one, 'one') + self.assertFalse(hasattr(test_model, 'id')) + + def test_generate_batch_build(self): + class TestModelFactory(factory.Factory): + one = 'one' + + objs = TestModelFactory.generate_batch(factory.BUILD_STRATEGY, 20, two='two') + + self.assertEqual(20, len(objs)) + self.assertEqual(20, len(set(objs))) + + for i, obj in enumerate(objs): + self.assertEqual('one', obj.one) + self.assertEqual('two', obj.two) + self.assertFalse(obj.id) + + def test_generate_batch_create(self): + class TestModelFactory(factory.Factory): + one = 'one' + + objs = TestModelFactory.generate_batch(factory.CREATE_STRATEGY, 20, two='two') + + self.assertEqual(20, len(objs)) + self.assertEqual(20, len(set(objs))) + + for i, obj in enumerate(objs): + self.assertEqual('one', obj.one) + self.assertEqual('two', obj.two) + self.assertTrue(obj.id) + + def test_generate_batch_stub(self): + class TestModelFactory(factory.Factory): + one = 'one' + + objs = TestModelFactory.generate_batch(factory.STUB_STRATEGY, 20, two='two') + + self.assertEqual(20, len(objs)) + self.assertEqual(20, len(set(objs))) + + for i, obj in enumerate(objs): + self.assertEqual('one', obj.one) + self.assertEqual('two', obj.two) + self.assertFalse(hasattr(obj, 'id')) + + def test_simple_generate_build(self): + class TestModelFactory(factory.Factory): + one = 'one' + + test_model = TestModelFactory.simple_generate(False) + self.assertEqual(test_model.one, 'one') + self.assertFalse(test_model.id) + + def test_simple_generate_create(self): + class TestModelFactory(factory.Factory): + one = 'one' + + test_model = TestModelFactory.simple_generate(True) + self.assertEqual(test_model.one, 'one') + self.assertTrue(test_model.id) + + def test_simple_generate_batch_build(self): + class TestModelFactory(factory.Factory): + one = 'one' + + objs = TestModelFactory.simple_generate_batch(False, 20, two='two') + + self.assertEqual(20, len(objs)) + self.assertEqual(20, len(set(objs))) + + for i, obj in enumerate(objs): + self.assertEqual('one', obj.one) + self.assertEqual('two', obj.two) + self.assertFalse(obj.id) + + def test_simple_generate_batch_create(self): + class TestModelFactory(factory.Factory): + one = 'one' + + objs = TestModelFactory.simple_generate_batch(True, 20, two='two') + + self.assertEqual(20, len(objs)) + self.assertEqual(20, len(set(objs))) + + for i, obj in enumerate(objs): + self.assertEqual('one', obj.one) + self.assertEqual('two', obj.two) + self.assertTrue(obj.id) + def test_stub_batch(self): class TestObjectFactory(factory.Factory): one = 'one' -- cgit v1.2.3 From 8a459c5e26a14a531f78d740b325c996044df760 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Barrois?= Date: Fri, 24 Feb 2012 23:58:54 +0100 Subject: Add the Iterator and InfiniteIterator attribute kinds. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Raphaël Barrois --- factory/__init__.py | 4 ++++ factory/declarations.py | 39 +++++++++++++++++++++++++++++++++++++++ tests/test_using.py | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 89 insertions(+) diff --git a/factory/__init__.py b/factory/__init__.py index cf9cc3e..12f297e 100644 --- a/factory/__init__.py +++ b/factory/__init__.py @@ -52,6 +52,8 @@ from base import ( from declarations import ( LazyAttribute, + Iterator, + InfiniteIterator, Sequence, LazyAttributeSequence, SelfAttribute, @@ -59,6 +61,8 @@ from declarations import ( SubFactory, lazy_attribute, + iterator, + infinite_iterator, sequence, lazy_attribute_sequence, container_attribute, diff --git a/factory/declarations.py b/factory/declarations.py index 5fe427c..41d99a3 100644 --- a/factory/declarations.py +++ b/factory/declarations.py @@ -21,6 +21,9 @@ # THE SOFTWARE. +import itertools + + class OrderedDeclaration(object): """A factory declaration. @@ -110,6 +113,34 @@ class SelfAttribute(OrderedDeclaration): return deepgetattr(obj, self.attribute_name, self.default) +class Iterator(OrderedDeclaration): + """Fill this value using the values returned by an iterator. + + Warning: the iterator should not end ! + + Attributes: + iterator (iterable): the iterator whose value should be used. + """ + + def __init__(self, iterator): + super(Iterator, self).__init__() + self.iterator = iter(iterator) + + def evaluate(self, sequence, obj, containers=()): + return self.iterator.next() + + +class InfiniteIterator(Iterator): + """Same as Iterator, but make the iterator infinite by cycling at the end. + + Attributes: + iterator (iterable): the iterator, once made infinite. + """ + + def __init__(self, iterator): + return super(InfiniteIterator, self).__init__(itertools.cycle(iterator)) + + class Sequence(OrderedDeclaration): """Specific OrderedDeclaration to use for 'sequenced' fields. @@ -224,6 +255,14 @@ class SubFactory(OrderedDeclaration): def lazy_attribute(func): return LazyAttribute(func) +def iterator(func): + """Turn a generator function into an iterator attribute.""" + return Iterator(func()) + +def infinite_iterator(func): + """Turn a generator function into an infinite iterator attribute.""" + return InfiniteIterator(func()) + def sequence(func): return Sequence(func) diff --git a/tests/test_using.py b/tests/test_using.py index 4e69212..41b34ea 100644 --- a/tests/test_using.py +++ b/tests/test_using.py @@ -760,5 +760,51 @@ class SubFactoryTestCase(unittest.TestCase): self.assertEqual(outer.side_a.inner_from_a.b, 4) +class IteratorTestCase(unittest.TestCase): + + def test_iterator(self): + class TestObjectFactory(factory.Factory): + one = factory.Iterator(xrange(10, 30)) + + objs = TestObjectFactory.build_batch(20) + + for i, obj in enumerate(objs): + self.assertEqual(i + 10, obj.one) + + def test_infinite_iterator(self): + class TestObjectFactory(factory.Factory): + one = factory.InfiniteIterator(xrange(5)) + + objs = TestObjectFactory.build_batch(20) + + for i, obj in enumerate(objs): + self.assertEqual(i % 5, obj.one) + + def test_iterator_decorator(self): + class TestObjectFactory(factory.Factory): + @factory.iterator + def one(): + for i in xrange(10, 50): + yield i + + objs = TestObjectFactory.build_batch(20) + + for i, obj in enumerate(objs): + self.assertEqual(i + 10, obj.one) + + def test_infinite_iterator_decorator(self): + class TestObjectFactory(factory.Factory): + @factory.infinite_iterator + def one(): + for i in xrange(5): + yield i + + objs = TestObjectFactory.build_batch(20) + + for i, obj in enumerate(objs): + self.assertEqual(i % 5, obj.one) + + + if __name__ == '__main__': unittest.main() -- cgit v1.2.3 From b1682f428d29ea947604447c328b1a09a79313c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Barrois?= Date: Fri, 24 Feb 2012 23:59:18 +0100 Subject: Version bump. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Raphaël Barrois --- docs/conf.py | 2 +- factory/__init__.py | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 2326d73..db523c3 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -51,7 +51,7 @@ copyright = u'2011, Raphaël Barrois, Mark Sandstrom' # The short X.Y version. version = '1.1' # The full version, including alpha/beta/rc tags. -release = '1.1.1' +release = '1.1.2' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/factory/__init__.py b/factory/__init__.py index 12f297e..1d4408f 100644 --- a/factory/__init__.py +++ b/factory/__init__.py @@ -20,7 +20,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -__version__ = '1.1.1' # Remember to change in setup.py as well! +__version__ = '1.1.2' # Remember to change in setup.py as well! __author__ = 'Raphaël Barrois ' from base import ( diff --git a/setup.py b/setup.py index 2b1d54b..ae0a0d4 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ from distutils.core import setup from distutils import cmd # Remember to change in factory/__init__.py as well! -VERSION = '1.1.1' +VERSION = '1.1.2' class test(cmd.Command): -- cgit v1.2.3