summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHervé Cauwelier <herve.cauwelier@polyconseil.fr>2016-02-12 17:31:04 +0100
committerHervé Cauwelier <herve.cauwelier@polyconseil.fr>2016-02-12 17:31:04 +0100
commitf2c075c40fd331b7d26a9db72aad249b2165eac4 (patch)
tree9a9de1a7c1a608b40b2ec18610edad361be28602
parent38f4a69db8f71cb52b9e7fd8d6e20e7d052a5b8d (diff)
downloadfactory-boy-f2c075c40fd331b7d26a9db72aad249b2165eac4.tar
factory-boy-f2c075c40fd331b7d26a9db72aad249b2165eac4.tar.gz
factory: LazyFunction to just call a function in the simplest case
No need to wrap it in a lambda to strip the object argument from LazyAttribute or the sequence argument from Sequence.
-rw-r--r--README.rst5
-rw-r--r--docs/examples.rst2
-rw-r--r--docs/introduction.rst29
-rw-r--r--docs/reference.rst40
-rw-r--r--factory/__init__.py1
-rw-r--r--factory/declarations.py17
-rw-r--r--tests/test_base.py11
-rw-r--r--tests/test_containers.py23
8 files changed, 122 insertions, 6 deletions
diff --git a/README.rst b/README.rst
index 4d114e5..762fedb 100644
--- a/README.rst
+++ b/README.rst
@@ -226,6 +226,7 @@ These "lazy" attributes can be added as follows:
first_name = 'Joe'
last_name = 'Blow'
email = factory.LazyAttribute(lambda a: '{0}.{1}@example.com'.format(a.first_name, a.last_name).lower())
+ date_joined = factory.LazyFunction(datetime.now)
.. code-block:: pycon
@@ -233,6 +234,10 @@ These "lazy" attributes can be added as follows:
"joe.blow@example.com"
+.. note:: ``LazyAttribute`` calls the function with the object being constructed as an argument, when
+ ``LazyFunction`` does not send any argument.
+
+
Sequences
"""""""""
diff --git a/docs/examples.rst b/docs/examples.rst
index e7f6057..6f26b7e 100644
--- a/docs/examples.rst
+++ b/docs/examples.rst
@@ -49,6 +49,7 @@ And now, we'll define the related factories:
.. code-block:: python
+ import datetime
import factory
import random
@@ -61,6 +62,7 @@ And now, we'll define the related factories:
username = factory.Sequence(lambda n: 'john%s' % n)
email = factory.LazyAttribute(lambda o: '%s@example.org' % o.username)
+ date_joined = factory.LazyFunction(datetime.datetime.now)
class ProfileFactory(factory.Factory):
diff --git a/docs/introduction.rst b/docs/introduction.rst
index d00154d..9a16c39 100644
--- a/docs/introduction.rst
+++ b/docs/introduction.rst
@@ -117,6 +117,35 @@ This is achieved with the :class:`~factory.Sequence` declaration:
return 'user%d' % n
+LazyFunction
+------------
+
+In simple cases, calling a function is enough to compute the value. If that function doesn't depend on the object
+being built, use :class:`~factory.LazyFunction` to call that function; it should receive a function taking no
+argument and returning the value for the field:
+
+.. code-block:: python
+
+ class LogFactory(factory.Factory):
+ class Meta:
+ model = models.Log
+
+ timestamp = factory.LazyFunction(datetime.now)
+
+.. code-block:: pycon
+
+ >>> LogFactory()
+ <Log: log at 2016-02-12 17:02:34>
+
+ >>> # The LazyFunction can be overriden
+ >>> LogFactory(timestamp=now - timedelta(days=1))
+ <Log: log at 2016-02-11 17:02:34>
+
+
+.. note:: For complex cases when you happen to write a specific function,
+ the :meth:`~factory.@lazy_attribute` decorator should be more appropriate.
+
+
LazyAttribute
-------------
diff --git a/docs/reference.rst b/docs/reference.rst
index b5ccd16..9e01213 100644
--- a/docs/reference.rst
+++ b/docs/reference.rst
@@ -90,7 +90,7 @@ The :class:`Factory` class
model = Order
exclude = ('now',)
- now = factory.LazyAttribute(lambda o: datetime.datetime.utcnow())
+ now = factory.LazyFunction(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))
@@ -551,6 +551,42 @@ Faker
smiley = factory.Faker('smiley')
+LazyFunction
+""""""""""""
+
+.. class:: LazyFunction(method_to_call)
+
+The :class:`LazyFunction` is the simplest case where the value of an attribute
+does not depend on the object being built.
+
+It takes as argument a method to call (function, lambda...); that method should
+not take any argument, though keyword arguments are safe but unused,
+and return a value.
+
+.. code-block:: python
+
+ class LogFactory(factory.Factory):
+ class Meta:
+ model = models.Log
+
+ timestamp = factory.LazyFunction(datetime.now)
+
+.. code-block:: pycon
+
+ >>> LogFactory()
+ <Log: log at 2016-02-12 17:02:34>
+
+ >>> # The LazyFunction can be overriden
+ >>> LogFactory(timestamp=now - timedelta(days=1))
+ <Log: log at 2016-02-11 17:02:34>
+
+Decorator
+~~~~~~~~~
+
+The class :class:`LazyFunction` does not provide a decorator.
+
+For complex cases, use :meth:`LazyAttribute.lazy_attribute` directly.
+
LazyAttribute
"""""""""""""
@@ -1041,7 +1077,7 @@ gains an "upward" semantic through the double-dot notation, as used in Python im
>>> company.owner.language
'fr'
-Obviously, this "follow parents" hability also handles overriding some attributes on call:
+Obviously, this "follow parents" ability also handles overriding some attributes on call:
.. code-block:: pycon
diff --git a/factory/__init__.py b/factory/__init__.py
index c8bc396..1fa581b 100644
--- a/factory/__init__.py
+++ b/factory/__init__.py
@@ -43,6 +43,7 @@ from .base import (
from .faker import Faker
from .declarations import (
+ LazyFunction,
LazyAttribute,
Iterator,
Sequence,
diff --git a/factory/declarations.py b/factory/declarations.py
index f0dbfe5..9ab7462 100644
--- a/factory/declarations.py
+++ b/factory/declarations.py
@@ -57,6 +57,23 @@ class OrderedDeclaration(object):
raise NotImplementedError('This is an abstract method')
+class LazyFunction(OrderedDeclaration):
+ """Simplest OrderedDeclaration computed by calling the given function.
+
+ Attributes:
+ function (function): a function without arguments and
+ returning the computed value.
+ """
+
+ def __init__(self, function, *args, **kwargs):
+ super(LazyFunction, self).__init__(*args, **kwargs)
+ self.function = function
+
+ def evaluate(self, sequence, obj, create, extra=None, containers=()):
+ logger.debug("LazyFunction: Evaluating %r on %r", self.function, obj)
+ return self.function()
+
+
class LazyAttribute(OrderedDeclaration):
"""Specific OrderedDeclaration computed using a lambda.
diff --git a/tests/test_base.py b/tests/test_base.py
index 24f64e5..dd74e35 100644
--- a/tests/test_base.py
+++ b/tests/test_base.py
@@ -134,25 +134,28 @@ class OptionsTests(unittest.TestCase):
self.assertEqual(AbstractFactory, AbstractFactory._meta.counter_reference)
def test_declaration_collecting(self):
- lazy = declarations.LazyAttribute(lambda _o: 1)
+ lazy = declarations.LazyFunction(int)
+ lazy2 = declarations.LazyAttribute(lambda _o: 1)
postgen = declarations.PostGenerationDeclaration()
class AbstractFactory(base.Factory):
x = 1
y = lazy
+ y2 = lazy2
z = postgen
# Declarations aren't removed
self.assertEqual(1, AbstractFactory.x)
self.assertEqual(lazy, AbstractFactory.y)
+ self.assertEqual(lazy2, AbstractFactory.y2)
self.assertEqual(postgen, AbstractFactory.z)
# And are available in class Meta
- self.assertEqual({'x': 1, 'y': lazy}, AbstractFactory._meta.declarations)
+ self.assertEqual({'x': 1, 'y': lazy, 'y2': lazy2}, AbstractFactory._meta.declarations)
self.assertEqual({'z': postgen}, AbstractFactory._meta.postgen_declarations)
def test_inherited_declaration_collecting(self):
- lazy = declarations.LazyAttribute(lambda _o: 1)
+ lazy = declarations.LazyFunction(int)
lazy2 = declarations.LazyAttribute(lambda _o: 2)
postgen = declarations.PostGenerationDeclaration()
postgen2 = declarations.PostGenerationDeclaration()
@@ -178,7 +181,7 @@ class OptionsTests(unittest.TestCase):
self.assertEqual({'z': postgen, 'b': postgen2}, OtherFactory._meta.postgen_declarations)
def test_inherited_declaration_shadowing(self):
- lazy = declarations.LazyAttribute(lambda _o: 1)
+ lazy = declarations.LazyFunction(int)
lazy2 = declarations.LazyAttribute(lambda _o: 2)
postgen = declarations.PostGenerationDeclaration()
postgen2 = declarations.PostGenerationDeclaration()
diff --git a/tests/test_containers.py b/tests/test_containers.py
index 083b306..825e897 100644
--- a/tests/test_containers.py
+++ b/tests/test_containers.py
@@ -215,6 +215,29 @@ class AttributeBuilderTestCase(unittest.TestCase):
ab = containers.AttributeBuilder(FakeFactory, extra={'one': seq2})
self.assertEqual({'one': 'yy1'}, ab.build(create=False))
+ def test_lazy_function(self):
+ lf = declarations.LazyFunction(int)
+
+ class FakeFactory(object):
+ @classmethod
+ def declarations(cls, extra):
+ d = {'one': 1, 'two': lf}
+ d.update(extra)
+ return d
+
+ @classmethod
+ def _generate_next_sequence(cls):
+ return 1
+
+ ab = containers.AttributeBuilder(FakeFactory)
+ self.assertEqual({'one': 1, 'two': 0}, ab.build(create=False))
+
+ ab = containers.AttributeBuilder(FakeFactory, {'one': 4})
+ self.assertEqual({'one': 4, 'two': 0}, ab.build(create=False))
+
+ ab = containers.AttributeBuilder(FakeFactory, {'one': 4, 'three': lf})
+ self.assertEqual({'one': 4, 'two': 0, 'three': 0}, ab.build(create=False))
+
def test_lazy_attribute(self):
la = declarations.LazyAttribute(lambda a: a.one * 2)