From 69e7a86875f97dc12e941302fabe417122f2cb7e Mon Sep 17 00:00:00 2001 From: Raphaƫl Barrois Date: Tue, 2 Apr 2013 23:07:03 +0200 Subject: Add Factory.FACTORY_HIDDEN_ARGS. Fields listed in this class attributes will be removed from the kwargs dict passed to the associated class for building/creation. --- docs/changelog.rst | 5 ++++- docs/ideas.rst | 3 --- docs/reference.rst | 31 ++++++++++++++++++++++++++++ factory/base.py | 7 +++++++ tests/test_using.py | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 101 insertions(+), 4 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index b9ea79f..9a4a64e 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -10,8 +10,11 @@ ChangeLog - Allow overriding the base factory class for :func:`~factory.make_factory` and friends. - Add support for Python3 (Thanks to `kmike `_ and `nkryptic `_) - - Add support for ``get_or_create`` in :class:`~factory.DjangoModelFactory` - 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` *Removed:* diff --git a/docs/ideas.rst b/docs/ideas.rst index 004f722..914e640 100644 --- a/docs/ideas.rst +++ b/docs/ideas.rst @@ -5,7 +5,4 @@ Ideas This is a list of future features that may be incorporated into factory_boy: * **A 'options' attribute**: instead of adding more class-level constants, use a django-style ``class Meta`` Factory attribute with all options there -* **factory-local fields**: Allow some fields to be available while building attributes, - but not passed to the associated class. - For instance, a ``global_kind`` field would be used to select values for many other fields. diff --git a/docs/reference.rst b/docs/reference.rst index d5b14a9..a2af327 100644 --- a/docs/reference.rst +++ b/docs/reference.rst @@ -50,6 +50,37 @@ The :class:`Factory` class >>> User('john', 'john@example.com', firstname="John") # actual call + .. attribute:: FACTORY_HIDDEN_ARGS + + While writing a :class:`Factory` for some object, it may be useful to + have general fields helping defining others, but that should not be + passed to the target class; for instance, a field named 'now' that would + hold a reference time used by other objects. + + Factory fields whose name are listed in :attr:`FACTORY_HIDDEN_ARGS` will + be removed from the set of args/kwargs passed to the underlying class; + they can be any valid factory_boy declaration: + + .. code-block:: python + + class OrderFactory(factory.Factory): + FACTORY_FOR = Order + FACTORY_HIDDEN_ARGS = ('now',) + + now = factory.LazyAttribute(lambda o: datetime.datetime.utcnow()) + started_at = factory.LazyAttribute(lambda o: o.now - datetime.timedelta(hours=1)) + paid_at = factory.LazyAttribute(lambda o: o.now - datetime.timedelta(minutes=50)) + + .. code-block:: pycon + + >>> OrderFactory() # The value of 'now' isn't passed to Order() + + + >>> # An alternate value may be passed for 'now' + >>> OrderFactory(now=datetime.datetime(2013, 4, 1, 10)) + + + **Base functions:** The :class:`Factory` class provides a few methods for getting objects; diff --git a/factory/base.py b/factory/base.py index ff3e558..58cd50b 100644 --- a/factory/base.py +++ b/factory/base.py @@ -234,6 +234,9 @@ class BaseFactory(object): # List of arguments that should be passed as *args instead of **kwargs FACTORY_ARG_PARAMETERS = () + # List of attributes that should not be passed to the underlying class + FACTORY_HIDDEN_ARGS = () + @classmethod def _setup_next_sequence(cls): """Set up an initial sequence value for Sequence attributes. @@ -305,6 +308,10 @@ class BaseFactory(object): target_class = getattr(cls, CLASS_ATTRIBUTE_ASSOCIATED_CLASS) kwargs = cls._adjust_kwargs(**kwargs) + # Remove 'hidden' arguments. + for arg in cls.FACTORY_HIDDEN_ARGS: + del kwargs[arg] + # Extract *args from **kwargs args = tuple(kwargs.pop(key) for key in cls.FACTORY_ARG_PARAMETERS) diff --git a/tests/test_using.py b/tests/test_using.py index 2e07621..41b666f 100644 --- a/tests/test_using.py +++ b/tests/test_using.py @@ -762,6 +762,65 @@ class UsingFactoryTestCase(unittest.TestCase): self.assertEqual(TestObjectFactory.alt_create(foo=1), {"foo": 1}) + def test_arg_parameters(self): + class TestObject(object): + def __init__(self, *args, **kwargs): + self.args = args + self.kwargs = kwargs + + class TestObjectFactory(factory.Factory): + FACTORY_FOR = TestObject + FACTORY_ARG_PARAMETERS = ('x', 'y') + + x = 1 + y = 2 + z = 3 + t = 4 + + obj = TestObjectFactory.build(x=42, z=5) + self.assertEqual((42, 2), obj.args) + self.assertEqual({'z': 5, 't': 4}, obj.kwargs) + + def test_hidden_args(self): + class TestObject(object): + def __init__(self, *args, **kwargs): + self.args = args + self.kwargs = kwargs + + class TestObjectFactory(factory.Factory): + FACTORY_FOR = TestObject + FACTORY_HIDDEN_ARGS = ('x', 'z') + + x = 1 + y = 2 + z = 3 + t = 4 + + obj = TestObjectFactory.build(x=42, z=5) + self.assertEqual((), obj.args) + self.assertEqual({'y': 2, 't': 4}, obj.kwargs) + + def test_hidden_args_and_arg_parameters(self): + class TestObject(object): + def __init__(self, *args, **kwargs): + self.args = args + self.kwargs = kwargs + + class TestObjectFactory(factory.Factory): + FACTORY_FOR = TestObject + FACTORY_HIDDEN_ARGS = ('x', 'z') + FACTORY_ARG_PARAMETERS = ('y',) + + x = 1 + y = 2 + z = 3 + t = 4 + + obj = TestObjectFactory.build(x=42, z=5) + self.assertEqual((2,), obj.args) + self.assertEqual({'t': 4}, obj.kwargs) + + class NonKwargParametersTestCase(unittest.TestCase): def test_build(self): -- cgit v1.2.3