diff options
l--------- | ChangeLog | 1 | ||||
-rw-r--r-- | README | 12 | ||||
-rw-r--r-- | docs/changelog.rst | 247 | ||||
-rw-r--r-- | docs/conf.py | 19 | ||||
l---------[-rw-r--r--] | docs/index.rst | 72 | ||||
-rw-r--r-- | factory/__init__.py | 1 | ||||
-rw-r--r-- | factory/containers.py | 4 | ||||
-rw-r--r-- | factory/declarations.py | 29 | ||||
-rw-r--r-- | factory/utils.py | 32 | ||||
-rwxr-xr-x | setup.py | 18 | ||||
-rw-r--r-- | tests/cyclic/__init__.py | 0 | ||||
-rw-r--r-- | tests/cyclic/bar.py | 37 | ||||
-rw-r--r-- | tests/cyclic/foo.py | 38 | ||||
-rw-r--r-- | tests/test_base.py | 46 | ||||
-rw-r--r-- | tests/test_containers.py | 11 | ||||
-rw-r--r-- | tests/test_using.py | 222 | ||||
-rw-r--r-- | tests/test_utils.py | 34 |
17 files changed, 665 insertions, 158 deletions
diff --git a/ChangeLog b/ChangeLog new file mode 120000 index 0000000..6f0024c --- /dev/null +++ b/ChangeLog @@ -0,0 +1 @@ +docs/changelog.rst
\ No newline at end of file @@ -53,7 +53,9 @@ Usage 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:: +Factories declare a set of attributes used to instantiate an object. The class of the object must be defined in the FACTORY_FOR attribute: + +.. code-block:: python import factory from . import models @@ -77,7 +79,9 @@ Factories declare a set of attributes used to instantiate an object. The class o Using factories """"""""""""""" -factory_boy supports several different build strategies: build, create, attributes and stub:: +factory_boy supports several different build strategies: build, create, attributes and stub: + +.. code-block:: python # Returns a User instance that's not saved user = UserFactory.build() @@ -89,7 +93,9 @@ factory_boy supports several different build strategies: build, create, attribut attributes = UserFactory.attributes() -You can use the Factory class as a shortcut for the default build strategy:: +You can use the Factory class as a shortcut for the default build strategy: + +.. code-block:: python # Same as UserFactory.create() user = UserFactory() diff --git a/docs/changelog.rst b/docs/changelog.rst new file mode 100644 index 0000000..173c40f --- /dev/null +++ b/docs/changelog.rst @@ -0,0 +1,247 @@ +ChangeLog +========= + +2.0.2 (2013-04-16) +------------------ + +*New:* + + - When :attr:`~factory.DjangoModelFactory.FACTORY_DJANGO_GET_OR_CREATE` is + empty, use ``Model.objects.create()`` instead of ``Model.objects.get_or_create``. + + +2.0.1 (2013-04-16) +------------------ + +*New:* + + - Don't push ``defaults`` to ``get_or_create`` when + :attr:`~factory.DjangoModelFactory.FACTORY_DJANGO_GET_OR_CREATE` is not set. + + +2.0.0 (2013-04-15) +------------------ + +*New:* + + - Allow overriding the base factory class for :func:`~factory.make_factory` and friends. + - Add support for Python3 (Thanks to `kmike <https://github.com/kmike>`_ and `nkryptic <https://github.com/nkryptic>`_) + - The default :attr:`~factory.Sequence.type` for :class:`~factory.Sequence` is now :obj:`int` + - Fields listed in :attr:`~factory.Factory.FACTORY_HIDDEN_ARGS` won't be passed to + the associated class' constructor + - Add support for ``get_or_create`` in :class:`~factory.DjangoModelFactory`, + through :attr:`~factory.DjangoModelFactory.FACTORY_DJANGO_GET_OR_CREATE`. + - Add support for :mod:`~factory.fuzzy` attribute definitions. + - The :class:`Sequence` counter can be overridden when calling a generating function + - Add :class:`~factory.Dict` and :class:`~factory.List` declarations (Closes #18). + +*Removed:* + + - Remove associated class discovery + - Remove :class:`~factory.InfiniteIterator` and :func:`~factory.infinite_iterator` + - Remove :class:`~factory.CircularSubFactory` + - Remove ``extract_prefix`` kwarg to post-generation hooks. + - Stop defaulting to Django's ``Foo.objects.create()`` when "creating" instances + - Remove STRATEGY_* + - Remove :meth:`~factory.Factory.set_building_function` / :meth:`~factory.Factory.set_creation_function` + + +1.3.0 (2013-03-11) +------------------ + +.. warning:: This version deprecates many magic or unexplicit features that will be + removed in v2.0.0. + + Please read the :ref:`changelog-1-3-0-upgrading` section, then run your + tests with ``python -W default`` to see all remaining warnings. + +New +""" + +- **Global:** + - Rewrite the whole documentation + - Provide a dedicated :class:`~factory.MogoFactory` subclass of :class:`~factory.Factory` + +- **The Factory class:** + - Better creation/building customization hooks at :meth:`factory.Factory._build` and :meth:`factory.Factory.create` + - Add support for passing non-kwarg parameters to a :class:`~factory.Factory` + wrapped class through :attr:`~factory.Factory.FACTORY_ARG_PARAMETERS`. + - Keep the :attr:`~factory.Factory.FACTORY_FOR` attribute in :class:`~factory.Factory` classes + +- **Declarations:** + - Allow :class:`~factory.SubFactory` to solve circular dependencies between factories + - Enhance :class:`~factory.SelfAttribute` to handle "container" attribute fetching + - Add a :attr:`~factory.Iterator.getter` to :class:`~factory.Iterator` + declarations + - A :class:`~factory.Iterator` may be prevented from cycling by setting + its :attr:`~factory.Iterator.cycle` argument to ``False`` + - Allow overriding default arguments in a :class:`~factory.PostGenerationMethodCall` + when generating an instance of the factory + - An object created by a :class:`~factory.DjangoModelFactory` will be saved + again after :class:`~factory.PostGeneration` hooks execution + + +Pending deprecation +""""""""""""""""""" + +The following features have been deprecated and will be removed in an upcoming release. + +- **Declarations:** + - :class:`~factory.InfiniteIterator` is deprecated in favor of :class:`~factory.Iterator` + - :class:`~factory.CircularSubFactory` is deprecated in favor of :class:`~factory.SubFactory` + - The ``extract_prefix`` argument to :meth:`~factory.post_generation` is now deprecated + +- **Factory:** + - Usage of :meth:`~factory.Factory.set_creation_function` and :meth:`~factory.Factory.set_building_function` + are now deprecated + - Implicit associated class discovery is no longer supported, you must set the :attr:`~factory.Factory.FACTORY_FOR` + attribute on all :class:`~factory.Factory` subclasses + + +.. _changelog-1-3-0-upgrading: + +Upgrading +""""""""" + +This version deprecates a few magic or undocumented features. +All warnings will turn into errors starting from v2.0.0. + +In order to upgrade client code, apply the following rules: + +- Add a ``FACTORY_FOR`` attribute pointing to the target class to each + :class:`~factory.Factory`, instead of relying on automagic associated class + discovery +- When using factory_boy for Django models, have each factory inherit from + :class:`~factory.DjangoModelFactory` +- Replace ``factory.CircularSubFactory('some.module', 'Symbol')`` with + ``factory.SubFactory('some.module.Symbol')`` +- Replace ``factory.InfiniteIterator(iterable)`` with ``factory.Iterator(iterable)`` +- Replace ``@factory.post_generation()`` with ``@factory.post_generation`` +- Replace ``factory.set_building_function(SomeFactory, building_function)`` with + an override of the :meth:`~factory.Factory._build` method of ``SomeFactory`` +- Replace ``factory.set_creation_function(SomeFactory, creation_function)`` with + an override of the :meth:`~factory.Factory._create` method of ``SomeFactory`` + + + +1.2.0 (2012-09-08) +------------------ + +*New:* + + - Add :class:`~factory.CircularSubFactory` to solve circular dependencies between factories + +1.1.5 (2012-07-09) +------------------ + +*Bugfix:* + + - Fix :class:`~factory.PostGenerationDeclaration` and derived classes. + +1.1.4 (2012-06-19) +------------------ + +*New:* + + - Add :meth:`~factory.use_strategy` decorator to override a + :class:`~factory.Factory`'s default strategy + - Improve test running (tox, python2.6/2.7) + - Introduce :class:`~factory.PostGeneration` and + :class:`~factory.RelatedFactory` + +1.1.3 (2012-03-09) +------------------ + +*Bugfix:* + + - Fix packaging rules + +1.1.2 (2012-02-25) +------------------ + +*New:* + + - Add :class:`~factory.Iterator` and :class:`~factory.InfiniteIterator` for :class:`~factory.Factory` attribute declarations. + - Provide :func:`~factory.Factory.generate` and :func:`~factory.Factory.simple_generate`, that allow specifying the instantiation strategy directly. + Also provides :func:`~factory.Factory.generate_batch` and :func:`~factory.Factory.simple_generate_batch`. + +1.1.1 (2012-02-24) +------------------ + +*New:* + + - Add :func:`~factory.Factory.build_batch`, :func:`~factory.Factory.create_batch` and :func:`~factory.Factory.stub_batch`, to instantiate factories in batch + +1.1.0 (2012-02-24) +------------------ + +*New:* + + - Improve the :class:`~factory.SelfAttribute` syntax to fetch sub-attributes using the ``foo.bar`` syntax; + - Add :class:`~factory.ContainerAttribute` to fetch attributes from the container of a :class:`~factory.SubFactory`. + - Provide the :func:`~factory.make_factory` helper: ``MyClassFactory = make_factory(MyClass, x=3, y=4)`` + - Add :func:`~factory.build`, :func:`~factory.create`, :func:`~factory.stub` helpers + +*Bugfix:* + + - Allow classmethod/staticmethod on factories + +*Deprecation:* + + - Auto-discovery of :attr:`~factory.Factory.FACTORY_FOR` based on class name is now deprecated + +1.0.4 (2011-12-21) +------------------ + +*New:* + + - Improve the algorithm for populating a :class:`~factory.Factory` attributes dict + - Add ``python setup.py test`` command to run the test suite + - Allow custom build functions + - Introduce :data:`~factory.MOGO_BUILD` build function + - Add support for inheriting from multiple :class:`~factory.Factory` + - Base :class:`~factory.Factory` classes can now be declared :attr:`abstract <factory.Factory.ABSTRACT_FACTORY>`. + - Provide :class:`~factory.DjangoModelFactory`, whose :class:`~factory.Sequence` counter starts at the next free database id + - Introduce :class:`~factory.SelfAttribute`, a shortcut for ``factory.LazyAttribute(lambda o: o.foo.bar.baz``. + +*Bugfix:* + + - Handle nested :class:`~factory.SubFactory` + - Share sequence counter between parent and subclasses + - Fix :class:`~factory.SubFactory` / :class:`~factory.Sequence` interferences + +1.0.2 (2011-05-16) +------------------ + +*New:* + + - Introduce :class:`~factory.SubFactory` + +1.0.1 (2011-05-13) +------------------ + +*New:* + + - Allow :class:`~factory.Factory` inheritance + - Improve handling of custom build/create functions + +*Bugfix:* + + - Fix concurrency between :class:`~factory.LazyAttribute` and :class:`~factory.Sequence` + +1.0.0 (2010-08-22) +------------------ + +*New:* + + - First version of factory_boy + + +Credits +------- + +* Initial version by Mark Sandstrom (2010) +* Developed by Raphaël Barrois since 2011 + + +.. vim:et:ts=4:sw=4:tw=119:ft=rst: diff --git a/docs/conf.py b/docs/conf.py index 47630d3..0ccaf29 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -48,10 +48,23 @@ copyright = u'2011-2013, Raphaël Barrois, Mark Sandstrom' # |version| and |release|, also used in various other places throughout the # built documents. # -# The short X.Y version. -version = '1.1' +root = os.path.abspath(os.path.dirname(__file__)) +def get_version(*module_dir_components): + import re + version_re = re.compile(r"^__version__ = ['\"](.*)['\"]$") + module_root = os.path.join(root, os.pardir, *module_dir_components) + module_init = os.path.join(module_root, '__init__.py') + with open(module_init, 'r') as f: + for line in f: + match = version_re.match(line[:-1]) + if match: + return match.groups()[0] + return '0.1.0' + # The full version, including alpha/beta/rc tags. -release = '1.1.2' +release = get_version('factory') +# The short X.Y version. +version = '.'.join(release.split('.')[:2]) # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/docs/index.rst b/docs/index.rst index f0b5dcc..89a0106 100644..120000 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,71 +1 @@ -Welcome to Factory Boy's documentation! -======================================= - -factory_boy provides easy replacement for fixtures, based on thoughtbot's `factory_girl <http://github.com/thoughtbot/factory_girl>`_. - -It allows for an easy definition of factories, various build factories, factory inheritance, ... - - -Example -------- - -Defining a factory -"""""""""""""""""" - -Simply subclass the :py:class:`~factory.Factory` class, adding various class attributes which will be used as defaults:: - - import factory - - class MyUserFactory(factory.Factory): - FACTORY_FOR = MyUser # Define the related object - - # A simple attribute - first_name = 'Foo' - - # A 'sequential' attribute: each instance of the factory will have a different 'n' - last_name = factory.Sequence(lambda n: 'Bar' + n) - - # A 'lazy' attribute: computed from the values of other attributes - email = factory.LazyAttribute(lambda o: '%s.%s@example.org' % (o.first_name.lower(), o.last_name.lower())) - -Using a factory -""""""""""""""" - -Once defined, a factory can be instantiated through different methods:: - - # Calls MyUser(first_name='Foo', last_name='Bar0', email='foo.bar0@example.org') - >>> user = MyUserFactory.build() - - # Calls MyUser.objects.create(first_name='Foo', last_name='Bar1', email='foo.bar1@example.org') - >>> user = MyUserFactory.create() - - # Values can be overridden - >>> user = MyUserFactory.build(first_name='Baz') - >>> user.email - 'baz.bar2@example.org' - - # Additional values can be specified - >>> user = MyUserFactory.build(some_other_var=42) - >>> user.some_other_var - 42 - - - - -Contents: - -.. toctree:: - :maxdepth: 2 - - examples - subfactory - post_generation - internals - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` - +../README.rst
\ No newline at end of file diff --git a/factory/__init__.py b/factory/__init__.py index 88865ba..e1138fa 100644 --- a/factory/__init__.py +++ b/factory/__init__.py @@ -50,6 +50,7 @@ from .declarations import ( Dict, List, PostGeneration, + PostGenerationMethodCall, RelatedFactory, ) diff --git a/factory/containers.py b/factory/containers.py index c8be431..ee2ad82 100644 --- a/factory/containers.py +++ b/factory/containers.py @@ -124,7 +124,7 @@ class DeclarationDict(dict): return False elif isinstance(value, declarations.OrderedDeclaration): return True - return (not name.startswith("_")) + return (not name.startswith("_") and not name.startswith("FACTORY_")) def update_with_public(self, d): """Updates the DeclarationDict from a class definition dict. @@ -167,7 +167,7 @@ class PostGenerationDeclarationDict(DeclarationDict): class LazyValue(object): """Some kind of "lazy evaluating" object.""" - def evaluate(self, obj, containers=()): + def evaluate(self, obj, containers=()): # pragma: no cover """Compute the value, using the given object.""" raise NotImplementedError("This is an abstract method.") diff --git a/factory/declarations.py b/factory/declarations.py index 969d780..974b4ac 100644 --- a/factory/declarations.py +++ b/factory/declarations.py @@ -231,13 +231,17 @@ class ContainerAttribute(OrderedDeclaration): return self.function(obj, containers) -class SubFactory(OrderedDeclaration): - """Base class for attributes based upon a sub-factory. +class ParameteredAttribute(OrderedDeclaration): + """Base class for attributes expecting parameters. Attributes: - defaults (dict): Overrides to the defaults defined in the wrapped - factory - factory (base.Factory): the wrapped factory + defaults (dict): Default values for the paramters. + May be overridden by call-time parameters. + + Class attributes: + CONTAINERS_FIELD (str): name of the field, if any, where container + information (e.g for SubFactory) should be stored. If empty, + containers data isn't merged into generate() parameters. """ CONTAINERS_FIELD = '__containers' @@ -248,7 +252,6 @@ class SubFactory(OrderedDeclaration): def __init__(self, **kwargs): super(ParameteredAttribute, self).__init__() self.defaults = kwargs - self.factory = factory def _prepare_containers(self, obj, containers=()): if self.EXTEND_CONTAINERS: @@ -260,19 +263,17 @@ class SubFactory(OrderedDeclaration): """Evaluate the current definition and fill its attributes. Uses attributes definition in the following order: - - attributes defined in the wrapped factory class - - values defined when defining the SubFactory - - additional values defined in attributes + - values defined when defining the ParameteredAttribute + - additional values defined when instantiating the containing factory Args: - create (bool): whether the subfactory should call 'build' or - 'create' + create (bool): whether the parent factory is being 'built' or + 'created' extra (containers.DeclarationDict): extra values that should - override the wrapped factory's defaults + override the defaults containers (list of LazyStub): List of LazyStub for the chain of factories being evaluated, the calling stub being first. """ - defaults = dict(self.defaults) if extra: defaults.update(extra) @@ -393,7 +394,7 @@ class PostGenerationDeclaration(object): kwargs = utils.extract_dict(name, attrs) return extracted, kwargs - def call(self, obj, create, extracted=None, **kwargs): + def call(self, obj, create, extracted=None, **kwargs): # pragma: no cover """Call this hook; no return value is expected. Args: diff --git a/factory/utils.py b/factory/utils.py index f845f46..fb8cfef 100644 --- a/factory/utils.py +++ b/factory/utils.py @@ -58,20 +58,16 @@ def extract_dict(prefix, kwargs, pop=True, exclude=()): return extracted -def declength_compare(a, b): - """Compare objects, choosing longest first.""" - if len(a) > len(b): - return -1 - elif len(a) < len(b): - return 1 - else: - return cmp(a, b) - - def multi_extract_dict(prefixes, kwargs, pop=True, exclude=()): """Extracts all values from a given list of prefixes. - Arguments have the same meaning as for extract_dict. + Extraction will start with longer prefixes. + + Args: + prefixes (str list): the prefixes to use for lookups + kwargs (dict): the dict from which values should be extracted + pop (bool): whether to use pop (True) or get (False) + exclude (iterable): list of prefixed keys that shouldn't be extracted Returns: dict(str => dict): a dict mapping each prefix to the dict of extracted @@ -79,10 +75,22 @@ def multi_extract_dict(prefixes, kwargs, pop=True, exclude=()): """ results = {} exclude = list(exclude) - for prefix in sorted(prefixes, cmp=declength_compare): + for prefix in sorted(prefixes, key=lambda x: -len(x)): extracted = extract_dict(prefix, kwargs, pop=pop, exclude=exclude) results[prefix] = extracted exclude.extend( ['%s%s%s' % (prefix, ATTR_SPLITTER, key) for key in extracted]) return results + + +def import_object(module_name, attribute_name): + """Import an object from its absolute path. + + Example: + >>> import_object('datetime', 'datetime') + <type 'datetime.datetime'> + """ + module = __import__(module_name, {}, {}, [attribute_name], 0) + return getattr(module, attribute_name) + @@ -1,12 +1,26 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +import os +import re import sys from distutils.core import setup from distutils import cmd -# Remember to change in factory/__init__.py as well! -VERSION = '1.1.5' +root = os.path.abspath(os.path.dirname(__file__)) + +def get_version(*module_dir_components): + version_re = re.compile(r"^__version__ = ['\"](.*)['\"]$") + module_root = os.path.join(root, *module_dir_components) + module_init = os.path.join(module_root, '__init__.py') + with open(module_init, 'r') as f: + for line in f: + match = version_re.match(line[:-1]) + if match: + return match.groups()[0] + return '0.1.0' + +VERSION = get_version('factory') class test(cmd.Command): diff --git a/tests/cyclic/__init__.py b/tests/cyclic/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/cyclic/__init__.py diff --git a/tests/cyclic/bar.py b/tests/cyclic/bar.py new file mode 100644 index 0000000..fed0602 --- /dev/null +++ b/tests/cyclic/bar.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2011-2013 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. + +"""Helper to test circular factory dependencies.""" + +import factory + +class Bar(object): + def __init__(self, foo, y): + self.foo = foo + self.y = y + + +class BarFactory(factory.Factory): + FACTORY_FOR = Bar + + y = 13 + foo = factory.SubFactory('cyclic.foo.FooFactory') + diff --git a/tests/cyclic/foo.py b/tests/cyclic/foo.py new file mode 100644 index 0000000..7f00f12 --- /dev/null +++ b/tests/cyclic/foo.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2011-2013 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. + +"""Helper to test circular factory dependencies.""" + +import factory + +from cyclic import bar + +class Foo(object): + def __init__(self, bar, x): + self.bar = bar + self.x = x + + +class FooFactory(factory.Factory): + FACTORY_FOR = Foo + + x = 42 + bar = factory.SubFactory(bar.BarFactory) diff --git a/tests/test_base.py b/tests/test_base.py index 216711a..73e59fa 100644 --- a/tests/test_base.py +++ b/tests/test_base.py @@ -35,19 +35,25 @@ class TestObject(object): 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() + @classmethod + def create(cls, **kwargs): + instance = cls(**kwargs) + instance.id = 1 + return instance def __init__(self, **kwargs): for name, value in kwargs.items(): setattr(self, name, value) self.id = None +class FakeModelFactory(base.Factory): + ABSTRACT_FACTORY = True + + @classmethod + def _create(cls, target_class, *args, **kwargs): + return target_class.create(**kwargs) + + class TestModel(FakeDjangoModel): pass @@ -84,6 +90,8 @@ class FactoryTestCase(unittest.TestCase): def test_lazy_attribute_non_existent_param(self): class TestObjectFactory(base.Factory): + FACTORY_FOR = TestObject + one = declarations.LazyAttribute(lambda a: a.does_not_exist ) self.assertRaises(AttributeError, TestObjectFactory) @@ -91,9 +99,13 @@ class FactoryTestCase(unittest.TestCase): def test_inheritance_with_sequence(self): """Tests that sequence IDs are shared between parent and son.""" class TestObjectFactory(base.Factory): + FACTORY_FOR = TestObject + one = declarations.Sequence(lambda a: a) class TestSubFactory(TestObjectFactory): + FACTORY_FOR = TestObject + pass parent = TestObjectFactory.build() @@ -115,6 +127,8 @@ class FactoryDefaultStrategyTestCase(unittest.TestCase): base.Factory.FACTORY_STRATEGY = base.BUILD_STRATEGY class TestModelFactory(base.Factory): + FACTORY_FOR = TestModel + one = 'one' test_model = TestModelFactory() @@ -124,7 +138,9 @@ class FactoryDefaultStrategyTestCase(unittest.TestCase): def test_create_strategy(self): # Default FACTORY_STRATEGY - class TestModelFactory(base.Factory): + class TestModelFactory(FakeModelFactory): + FACTORY_FOR = TestModel + one = 'one' test_model = TestModelFactory() @@ -135,6 +151,8 @@ class FactoryDefaultStrategyTestCase(unittest.TestCase): base.Factory.FACTORY_STRATEGY = base.STUB_STRATEGY class TestModelFactory(base.Factory): + FACTORY_FOR = TestModel + one = 'one' test_model = TestModelFactory() @@ -145,12 +163,16 @@ class FactoryDefaultStrategyTestCase(unittest.TestCase): base.Factory.FACTORY_STRATEGY = 'unknown' class TestModelFactory(base.Factory): + FACTORY_FOR = TestModel + one = 'one' self.assertRaises(base.Factory.UnknownStrategy, TestModelFactory) def test_stub_with_non_stub_strategy(self): class TestModelFactory(base.StubFactory): + FACTORY_FOR = TestModel + one = 'one' TestModelFactory.FACTORY_STRATEGY = base.CREATE_STRATEGY @@ -163,6 +185,8 @@ class FactoryDefaultStrategyTestCase(unittest.TestCase): def test_change_strategy(self): @base.use_strategy(base.CREATE_STRATEGY) class TestModelFactory(base.StubFactory): + FACTORY_FOR = TestModel + one = 'one' self.assertEqual(base.CREATE_STRATEGY, TestModelFactory.FACTORY_STRATEGY) @@ -183,6 +207,8 @@ class FactoryCreationTestCase(unittest.TestCase): def test_inheritance_with_stub(self): class TestObjectFactory(base.StubFactory): + FACTORY_FOR = TestObject + pass class TestFactory(TestObjectFactory): @@ -224,12 +250,16 @@ class PostGenerationParsingTestCase(unittest.TestCase): def test_extraction(self): class TestObjectFactory(base.Factory): + FACTORY_FOR = TestObject + foo = declarations.PostGenerationDeclaration() self.assertIn('foo', TestObjectFactory._postgen_declarations) def test_classlevel_extraction(self): class TestObjectFactory(base.Factory): + FACTORY_FOR = TestObject + foo = declarations.PostGenerationDeclaration() foo__bar = 42 diff --git a/tests/test_containers.py b/tests/test_containers.py index 7c8d829..70ed885 100644 --- a/tests/test_containers.py +++ b/tests/test_containers.py @@ -178,6 +178,17 @@ class DeclarationDictTestCase(unittest.TestCase): self.assertEqual(set(['one', 'three']), set(d)) self.assertEqual(set([1, 3]), set(d.values())) + def test_update_with_public_ignores_factory_attributes(self): + """Ensure that a DeclarationDict ignores FACTORY_ keys.""" + d = containers.DeclarationDict() + d.update_with_public({ + 'one': 1, + 'FACTORY_FOR': 2, + 'FACTORY_ARG_PARAMETERS': 3, + }) + self.assertEqual(['one'], list(d)) + self.assertEqual([1], list(d.values())) + class AttributeBuilderTestCase(unittest.TestCase): def test_empty(self): diff --git a/tests/test_using.py b/tests/test_using.py index 497e206..821fad3 100644 --- a/tests/test_using.py +++ b/tests/test_using.py @@ -21,6 +21,11 @@ """Tests using factory.""" +import functools +import os +import sys +import warnings + import factory from .compat import is_python2, unittest @@ -71,7 +76,16 @@ class FakeModel(object): setattr(self, name, value) self.id = None -class TestModel(FakeDjangoModel): + +class FakeModelFactory(factory.Factory): + ABSTRACT_FACTORY = True + + @classmethod + def _create(cls, target_class, *args, **kwargs): + return target_class.create(**kwargs) + + +class TestModel(FakeModel): pass @@ -116,7 +130,7 @@ class SimpleBuildTestCase(unittest.TestCase): self.assertEqual(obj.foo, 'bar') def test_create_batch(self): - objs = factory.create_batch(FakeDjangoModel, 4, foo='bar') + objs = factory.create_batch(FakeModel, 4, foo='bar') self.assertEqual(4, len(objs)) self.assertEqual(4, len(set(objs))) @@ -142,7 +156,7 @@ class SimpleBuildTestCase(unittest.TestCase): self.assertFalse(hasattr(obj, 'two')) def test_stub_batch(self): - objs = factory.stub_batch(FakeDjangoModel, 4, foo='bar') + objs = factory.stub_batch(FakeModel, 4, foo='bar') self.assertEqual(4, len(objs)) self.assertEqual(4, len(set(objs))) @@ -152,7 +166,7 @@ class SimpleBuildTestCase(unittest.TestCase): self.assertEqual(obj.foo, 'bar') def test_generate_build(self): - obj = factory.generate(FakeDjangoModel, factory.BUILD_STRATEGY, foo='bar') + obj = factory.generate(FakeModel, factory.BUILD_STRATEGY, foo='bar') self.assertEqual(obj.id, None) self.assertEqual(obj.foo, 'bar') @@ -168,12 +182,12 @@ class SimpleBuildTestCase(unittest.TestCase): self.assertEqual(obj.foo, 'bar') def test_generate_stub(self): - obj = factory.generate(FakeDjangoModel, factory.STUB_STRATEGY, foo='bar') + obj = factory.generate(FakeModel, 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') + objs = factory.generate_batch(FakeModel, factory.BUILD_STRATEGY, 20, foo='bar') self.assertEqual(20, len(objs)) self.assertEqual(20, len(set(objs))) @@ -183,7 +197,7 @@ class SimpleBuildTestCase(unittest.TestCase): self.assertEqual(obj.foo, 'bar') def test_generate_batch_create(self): - objs = factory.generate_batch(FakeDjangoModel, factory.CREATE_STRATEGY, 20, foo='bar') + objs = factory.generate_batch(FakeModel, factory.CREATE_STRATEGY, 20, foo='bar') self.assertEqual(20, len(objs)) self.assertEqual(20, len(set(objs))) @@ -204,7 +218,7 @@ class SimpleBuildTestCase(unittest.TestCase): self.assertEqual(obj.foo, 'bar') def test_generate_batch_stub(self): - objs = factory.generate_batch(FakeDjangoModel, factory.STUB_STRATEGY, 20, foo='bar') + objs = factory.generate_batch(FakeModel, factory.STUB_STRATEGY, 20, foo='bar') self.assertEqual(20, len(objs)) self.assertEqual(20, len(set(objs))) @@ -214,7 +228,7 @@ class SimpleBuildTestCase(unittest.TestCase): self.assertEqual(obj.foo, 'bar') def test_simple_generate_build(self): - obj = factory.simple_generate(FakeDjangoModel, False, foo='bar') + obj = factory.simple_generate(FakeModel, False, foo='bar') self.assertEqual(obj.id, None) self.assertEqual(obj.foo, 'bar') @@ -229,7 +243,7 @@ class SimpleBuildTestCase(unittest.TestCase): self.assertEqual(obj.foo, 'bar') def test_simple_generate_batch_build(self): - objs = factory.simple_generate_batch(FakeDjangoModel, False, 20, foo='bar') + objs = factory.simple_generate_batch(FakeModel, False, 20, foo='bar') self.assertEqual(20, len(objs)) self.assertEqual(20, len(set(objs))) @@ -239,7 +253,7 @@ class SimpleBuildTestCase(unittest.TestCase): self.assertEqual(obj.foo, 'bar') def test_simple_generate_batch_create(self): - objs = factory.simple_generate_batch(FakeDjangoModel, True, 20, foo='bar') + objs = factory.simple_generate_batch(FakeModel, True, 20, foo='bar') self.assertEqual(20, len(objs)) self.assertEqual(20, len(set(objs))) @@ -278,6 +292,8 @@ class SimpleBuildTestCase(unittest.TestCase): class UsingFactoryTestCase(unittest.TestCase): def test_attribute(self): class TestObjectFactory(factory.Factory): + FACTORY_FOR = TestObject + one = 'one' test_object = TestObjectFactory.build() @@ -321,6 +337,8 @@ class UsingFactoryTestCase(unittest.TestCase): def test_sequence_custom_begin(self): class TestObjectFactory(factory.Factory): + FACTORY_FOR = TestObject + @classmethod def _setup_next_sequence(cls): return 42 @@ -402,6 +420,8 @@ class UsingFactoryTestCase(unittest.TestCase): def test_lazy_attribute(self): class TestObjectFactory(factory.Factory): + FACTORY_FOR = TestObject + one = factory.LazyAttribute(lambda a: 'abc' ) two = factory.LazyAttribute(lambda a: a.one + ' xyz') @@ -426,6 +446,8 @@ class UsingFactoryTestCase(unittest.TestCase): def test_lazy_attribute_decorator(self): class TestObjectFactory(factory.Factory): + FACTORY_FOR = TestObject + @factory.lazy_attribute def one(a): return 'one' @@ -438,6 +460,8 @@ class UsingFactoryTestCase(unittest.TestCase): n = 3 class TestObjectFactory(factory.Factory): + FACTORY_FOR = TestObject + one = 'xx' two = factory.SelfAttribute('one') three = TmpObj() @@ -469,6 +493,8 @@ class UsingFactoryTestCase(unittest.TestCase): def test_sequence_decorator(self): class TestObjectFactory(factory.Factory): + FACTORY_FOR = TestObject + @factory.sequence def one(n): return 'one%d' % n @@ -478,6 +504,8 @@ class UsingFactoryTestCase(unittest.TestCase): def test_lazy_attribute_sequence_decorator(self): class TestObjectFactory(factory.Factory): + FACTORY_FOR = TestObject + @factory.lazy_attribute_sequence def one(a, n): return 'one%d' % n @@ -516,7 +544,9 @@ class UsingFactoryTestCase(unittest.TestCase): self.assertTrue(test_model.id) def test_create_batch(self): - class TestModelFactory(factory.Factory): + class TestModelFactory(FakeModelFactory): + FACTORY_FOR = TestModel + one = 'one' objs = TestModelFactory.create_batch(20, two=factory.Sequence(int)) @@ -530,7 +560,9 @@ class UsingFactoryTestCase(unittest.TestCase): self.assertTrue(obj.id) def test_generate_build(self): - class TestModelFactory(factory.Factory): + class TestModelFactory(FakeModelFactory): + FACTORY_FOR = TestModel + one = 'one' test_model = TestModelFactory.generate(factory.BUILD_STRATEGY) @@ -538,7 +570,9 @@ class UsingFactoryTestCase(unittest.TestCase): self.assertFalse(test_model.id) def test_generate_create(self): - class TestModelFactory(factory.Factory): + class TestModelFactory(FakeModelFactory): + FACTORY_FOR = TestModel + one = 'one' test_model = TestModelFactory.generate(factory.CREATE_STRATEGY) @@ -546,7 +580,9 @@ class UsingFactoryTestCase(unittest.TestCase): self.assertTrue(test_model.id) def test_generate_stub(self): - class TestModelFactory(factory.Factory): + class TestModelFactory(FakeModelFactory): + FACTORY_FOR = TestModel + one = 'one' test_model = TestModelFactory.generate(factory.STUB_STRATEGY) @@ -554,7 +590,9 @@ class UsingFactoryTestCase(unittest.TestCase): self.assertFalse(hasattr(test_model, 'id')) def test_generate_batch_build(self): - class TestModelFactory(factory.Factory): + class TestModelFactory(FakeModelFactory): + FACTORY_FOR = TestModel + one = 'one' objs = TestModelFactory.generate_batch(factory.BUILD_STRATEGY, 20, two='two') @@ -568,7 +606,9 @@ class UsingFactoryTestCase(unittest.TestCase): self.assertFalse(obj.id) def test_generate_batch_create(self): - class TestModelFactory(factory.Factory): + class TestModelFactory(FakeModelFactory): + FACTORY_FOR = TestModel + one = 'one' objs = TestModelFactory.generate_batch(factory.CREATE_STRATEGY, 20, two='two') @@ -582,7 +622,9 @@ class UsingFactoryTestCase(unittest.TestCase): self.assertTrue(obj.id) def test_generate_batch_stub(self): - class TestModelFactory(factory.Factory): + class TestModelFactory(FakeModelFactory): + FACTORY_FOR = TestModel + one = 'one' objs = TestModelFactory.generate_batch(factory.STUB_STRATEGY, 20, two='two') @@ -596,7 +638,9 @@ class UsingFactoryTestCase(unittest.TestCase): self.assertFalse(hasattr(obj, 'id')) def test_simple_generate_build(self): - class TestModelFactory(factory.Factory): + class TestModelFactory(FakeModelFactory): + FACTORY_FOR = TestModel + one = 'one' test_model = TestModelFactory.simple_generate(False) @@ -604,7 +648,9 @@ class UsingFactoryTestCase(unittest.TestCase): self.assertFalse(test_model.id) def test_simple_generate_create(self): - class TestModelFactory(factory.Factory): + class TestModelFactory(FakeModelFactory): + FACTORY_FOR = TestModel + one = 'one' test_model = TestModelFactory.simple_generate(True) @@ -612,7 +658,9 @@ class UsingFactoryTestCase(unittest.TestCase): self.assertTrue(test_model.id) def test_simple_generate_batch_build(self): - class TestModelFactory(factory.Factory): + class TestModelFactory(FakeModelFactory): + FACTORY_FOR = TestModel + one = 'one' objs = TestModelFactory.simple_generate_batch(False, 20, two='two') @@ -626,7 +674,9 @@ class UsingFactoryTestCase(unittest.TestCase): self.assertFalse(obj.id) def test_simple_generate_batch_create(self): - class TestModelFactory(factory.Factory): + class TestModelFactory(FakeModelFactory): + FACTORY_FOR = TestModel + one = 'one' objs = TestModelFactory.simple_generate_batch(True, 20, two='two') @@ -641,6 +691,8 @@ class UsingFactoryTestCase(unittest.TestCase): def test_stub_batch(self): class TestObjectFactory(factory.Factory): + FACTORY_FOR = TestObject + one = 'one' two = factory.LazyAttribute(lambda a: a.one + ' two') three = factory.Sequence(lambda n: int(n)) @@ -658,6 +710,8 @@ class UsingFactoryTestCase(unittest.TestCase): def test_inheritance(self): class TestObjectFactory(factory.Factory): + FACTORY_FOR = TestObject + one = 'one' two = factory.LazyAttribute(lambda a: a.one + ' two') @@ -678,6 +732,8 @@ class UsingFactoryTestCase(unittest.TestCase): def test_inheritance_with_inherited_class(self): class TestObjectFactory(factory.Factory): + FACTORY_FOR = TestObject + one = 'one' two = factory.LazyAttribute(lambda a: a.one + ' two') @@ -693,6 +749,8 @@ class UsingFactoryTestCase(unittest.TestCase): def test_dual_inheritance(self): class TestObjectFactory(factory.Factory): + FACTORY_FOR = TestObject + one = 'one' class TestOtherFactory(factory.Factory): @@ -711,6 +769,8 @@ class UsingFactoryTestCase(unittest.TestCase): def test_class_method_accessible(self): class TestObjectFactory(factory.Factory): + FACTORY_FOR = TestObject + @classmethod def alt_create(cls, **kwargs): return kwargs @@ -719,6 +779,8 @@ class UsingFactoryTestCase(unittest.TestCase): def test_static_method_accessible(self): class TestObjectFactory(factory.Factory): + FACTORY_FOR = TestObject + @staticmethod def alt_create(**kwargs): return kwargs @@ -861,11 +923,11 @@ class SubFactoryTestCase(unittest.TestCase): class TestModel2(FakeModel): pass - class TestModelFactory(factory.Factory): + class TestModelFactory(FakeModelFactory): FACTORY_FOR = TestModel one = 3 - class TestModel2Factory(factory.Factory): + class TestModel2Factory(FakeModelFactory): FACTORY_FOR = TestModel2 two = factory.SubFactory(TestModelFactory, one=1) @@ -878,10 +940,10 @@ class SubFactoryTestCase(unittest.TestCase): class TestModel2(FakeModel): pass - class TestModelFactory(factory.Factory): + class TestModelFactory(FakeModelFactory): FACTORY_FOR = TestModel - class TestModel2Factory(factory.Factory): + class TestModel2Factory(FakeModelFactory): FACTORY_FOR = TestModel2 two = factory.SubFactory(TestModelFactory, one=factory.Sequence(lambda n: 'x%dx' % n), @@ -1073,6 +1135,78 @@ class SubFactoryTestCase(unittest.TestCase): self.assertEqual(outer.side_a.inner_from_a.a, outer.foo * 2) self.assertEqual(outer.side_a.inner_from_a.b, 4) + def test_nonstrict_container_attribute(self): + class TestModel2(FakeModel): + pass + + class TestModelFactory(FakeModelFactory): + FACTORY_FOR = TestModel + one = 3 + two = factory.ContainerAttribute(lambda obj, containers: len(containers or []), strict=False) + + class TestModel2Factory(FakeModelFactory): + FACTORY_FOR = TestModel2 + one = 1 + two = factory.SubFactory(TestModelFactory, one=1) + + obj = TestModel2Factory.build() + self.assertEqual(1, obj.one) + self.assertEqual(1, obj.two.one) + self.assertEqual(1, obj.two.two) + + obj = TestModelFactory() + self.assertEqual(3, obj.one) + self.assertEqual(0, obj.two) + + def test_strict_container_attribute(self): + class TestModel2(FakeModel): + pass + + class TestModelFactory(FakeModelFactory): + FACTORY_FOR = TestModel + one = 3 + two = factory.ContainerAttribute(lambda obj, containers: len(containers or []), strict=True) + + class TestModel2Factory(FakeModelFactory): + FACTORY_FOR = TestModel2 + one = 1 + two = factory.SubFactory(TestModelFactory, one=1) + + obj = TestModel2Factory.build() + self.assertEqual(1, obj.one) + self.assertEqual(1, obj.two.one) + self.assertEqual(1, obj.two.two) + + self.assertRaises(TypeError, TestModelFactory.build) + + def test_function_container_attribute(self): + class TestModel2(FakeModel): + pass + + class TestModelFactory(FakeModelFactory): + FACTORY_FOR = TestModel + one = 3 + + @factory.container_attribute + def two(self, containers): + if containers: + return len(containers) + return 42 + + class TestModel2Factory(FakeModelFactory): + FACTORY_FOR = TestModel2 + one = 1 + two = factory.SubFactory(TestModelFactory, one=1) + + obj = TestModel2Factory.build() + self.assertEqual(1, obj.one) + self.assertEqual(1, obj.two.one) + self.assertEqual(1, obj.two.two) + + obj = TestModelFactory() + self.assertEqual(3, obj.one) + self.assertEqual(42, obj.two) + class IteratorTestCase(unittest.TestCase): @@ -1115,6 +1249,8 @@ class IteratorTestCase(unittest.TestCase): def test_iterator_decorator(self): class TestObjectFactory(factory.Factory): + FACTORY_FOR = TestObject + @factory.iterator def one(): for i in range(10, 50): @@ -1257,6 +1393,8 @@ class DjangoModelFactoryTestCase(unittest.TestCase): class PostGenerationTestCase(unittest.TestCase): def test_post_generation(self): class TestObjectFactory(factory.Factory): + FACTORY_FOR = TestObject + one = 1 @factory.post_generation @@ -1294,6 +1432,8 @@ class PostGenerationTestCase(unittest.TestCase): def test_post_generation_extraction(self): class TestObjectFactory(factory.Factory): + FACTORY_FOR = TestObject + one = 1 @factory.post_generation @@ -1317,10 +1457,40 @@ class PostGenerationTestCase(unittest.TestCase): self.assertEqual(kwargs, {'foo': 13}) class TestObjectFactory(factory.Factory): + FACTORY_FOR = TestObject + bar = factory.PostGeneration(my_lambda) obj = TestObjectFactory.build(bar=42, bar__foo=13) + def test_post_generation_method_call(self): + calls = [] + + class TestObject(object): + def __init__(self, one=None, two=None): + self.one = one + self.two = two + self.extra = None + + def call(self, *args, **kwargs): + self.extra = (args, kwargs) + + class TestObjectFactory(factory.Factory): + FACTORY_FOR = TestObject + one = 3 + two = 2 + post_call = factory.PostGenerationMethodCall('call', one=1) + + obj = TestObjectFactory.build() + self.assertEqual(3, obj.one) + self.assertEqual(2, obj.two) + self.assertEqual(((), {'one': 1}), obj.extra) + + obj = TestObjectFactory.build(post_call__one=2, post_call__two=3) + self.assertEqual(3, obj.one) + self.assertEqual(2, obj.two) + self.assertEqual(((), {'one': 2, 'two': 3}), obj.extra) + def test_related_factory(self): class TestRelatedObject(object): def __init__(self, obj=None, one=None, two=None): diff --git a/tests/test_utils.py b/tests/test_utils.py index 787164a..b353c9d 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -25,23 +25,6 @@ from factory import utils from .compat import unittest -class DecLengthCompareTestCase(unittest.TestCase): - def test_reciprocity(self): - self.assertEqual(1, utils.declength_compare('a', 'bb')) - self.assertEqual(-1, utils.declength_compare('aa', 'b')) - - def test_not_lexical(self): - self.assertEqual(1, utils.declength_compare('abc', 'aaaa')) - self.assertEqual(-1, utils.declength_compare('aaaa', 'abc')) - - def test_same_length(self): - self.assertEqual(-1, utils.declength_compare('abc', 'abd')) - self.assertEqual(1, utils.declength_compare('abe', 'abd')) - - def test_equality(self): - self.assertEqual(0, utils.declength_compare('abc', 'abc')) - self.assertEqual(0, utils.declength_compare([1, 2, 3], [1, 2, 3])) - class ExtractDictTestCase(unittest.TestCase): def test_empty_dict(self): @@ -102,6 +85,7 @@ class ExtractDictTestCase(unittest.TestCase): self.assertIn('foo__bar', d) self.assertNotIn('foo__foo__bar', d) + class MultiExtractDictTestCase(unittest.TestCase): def test_empty_dict(self): self.assertEqual({'foo': {}}, utils.multi_extract_dict(['foo'], {})) @@ -230,3 +214,19 @@ class MultiExtractDictTestCase(unittest.TestCase): self.assertNotIn('foo__foo__bar', d) self.assertNotIn('bar__foo', d) self.assertNotIn('bar__bar__baz', d) + + +class ImportObjectTestCase(unittest.TestCase): + def test_datetime(self): + imported = utils.import_object('datetime', 'date') + import datetime + d = datetime.date + self.assertEqual(d, imported) + + def test_unknown_attribute(self): + self.assertRaises(AttributeError, utils.import_object, + 'datetime', 'foo') + + def test_invalid_module(self): + self.assertRaises(ImportError, utils.import_object, + 'this-is-an-invalid-module', '__name__') |