Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

factory: LazyFunction to just call a function in the simplest case #267

Merged
merged 1 commit into from Feb 14, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.rst
Expand Up @@ -226,13 +226,18 @@ 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

>>> UserFactory().email
"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
"""""""""

Expand Down
2 changes: 2 additions & 0 deletions docs/examples.rst
Expand Up @@ -49,6 +49,7 @@ And now, we'll define the related factories:

.. code-block:: python

import datetime
import factory
import random

Expand All @@ -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):
Expand Down
29 changes: 29 additions & 0 deletions docs/introduction.rst
Expand Up @@ -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
-------------

Expand Down
40 changes: 38 additions & 2 deletions docs/reference.rst
Expand Up @@ -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))

Expand Down Expand Up @@ -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
"""""""""""""

Expand Down Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions factory/__init__.py
Expand Up @@ -43,6 +43,7 @@
from .faker import Faker

from .declarations import (
LazyFunction,
LazyAttribute,
Iterator,
Sequence,
Expand Down
17 changes: 17 additions & 0 deletions factory/declarations.py
Expand Up @@ -57,6 +57,23 @@ def evaluate(self, sequence, obj, create, extra=None, containers=()):
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.

Expand Down
11 changes: 7 additions & 4 deletions tests/test_base.py
Expand Up @@ -134,25 +134,28 @@ class AbstractFactory(base.Factory):
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()
Expand All @@ -178,7 +181,7 @@ class OtherFactory(AbstractFactory):
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()
Expand Down
23 changes: 23 additions & 0 deletions tests/test_containers.py
Expand Up @@ -215,6 +215,29 @@ def _generate_next_sequence(cls):
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)

Expand Down