summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.travis.yml2
-rw-r--r--Makefile4
-rw-r--r--README.rst38
-rw-r--r--dev_requirements.txt3
-rw-r--r--docs/changelog.rst9
-rw-r--r--docs/conf.py2
-rw-r--r--docs/examples.rst2
-rw-r--r--docs/fuzzy.rst2
-rw-r--r--docs/introduction.rst29
-rw-r--r--docs/orms.rst8
-rw-r--r--docs/recipes.rst2
-rw-r--r--docs/reference.rst57
-rw-r--r--factory/__init__.py3
-rw-r--r--factory/alchemy.py3
-rw-r--r--factory/compat.py8
-rw-r--r--factory/declarations.py17
-rw-r--r--factory/fuzzy.py5
-rw-r--r--factory/utils.py2
-rwxr-xr-xsetup.py1
-rw-r--r--tests/test_alchemy.py32
-rw-r--r--tests/test_base.py11
-rw-r--r--tests/test_containers.py23
-rw-r--r--tests/test_fuzzy.py13
-rw-r--r--tests/test_using.py30
24 files changed, 274 insertions, 32 deletions
diff --git a/.travis.yml b/.travis.yml
index e1600bd..938787b 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,8 +1,10 @@
+sudo: false
language: python
python:
- "2.7"
- "3.4"
+ - "3.5"
- "pypy"
script:
diff --git a/Makefile b/Makefile
index 35f635c..da8ac88 100644
--- a/Makefile
+++ b/Makefile
@@ -7,13 +7,13 @@ EXAMPLES_DIR=examples
COVERAGE = python $(shell which coverage)
# Dependencies
-DJANGO ?= 1.8
+DJANGO ?= 1.9
NEXT_DJANGO = $(shell python -c "v='$(DJANGO)'; parts=v.split('.'); parts[-1]=str(int(parts[-1])+1); print('.'.join(parts))")
ALCHEMY ?= 1.0
NEXT_ALCHEMY = $(shell python -c "v='$(ALCHEMY)'; parts=v.split('.'); parts[-1]=str(int(parts[-1])+1); print('.'.join(parts))")
-MONGOENGINE ?= 0.9
+MONGOENGINE ?= 0.10
NEXT_MONGOENGINE = $(shell python -c "v='$(MONGOENGINE)'; parts=v.split('.'); parts[-1]=str(int(parts[-1])+1); print('.'.join(parts))")
REQ_FILE = auto_dev_requirements_django$(DJANGO)_alchemy$(ALCHEMY)_mongoengine$(MONGOENGINE).txt
diff --git a/README.rst b/README.rst
index 9b82406..762fedb 100644
--- a/README.rst
+++ b/README.rst
@@ -1,20 +1,24 @@
factory_boy
===========
-.. image:: https://pypip.in/version/factory_boy/badge.svg
+.. image:: https://secure.travis-ci.org/rbarrois/factory_boy.png?branch=master
+ :target: http://travis-ci.org/rbarrois/factory_boy/
+
+.. image:: https://img.shields.io/pypi/v/factory_boy.svg
:target: http://factoryboy.readthedocs.org/en/latest/changelog.html
:alt: Latest Version
-.. image:: https://pypip.in/py_versions/factory_boy/badge.svg
+.. image:: https://img.shields.io/pypi/pyversions/factory_boy.svg
:target: https://pypi.python.org/pypi/factory_boy/
:alt: Supported Python versions
-.. image:: https://pypip.in/wheel/factory_boy/badge.svg
+.. image:: https://img.shields.io/pypi/wheel/factory_boy.svg
:target: https://pypi.python.org/pypi/factory_boy/
:alt: Wheel status
-.. image:: https://secure.travis-ci.org/rbarrois/factory_boy.png?branch=master
- :target: http://travis-ci.org/rbarrois/factory_boy/
+.. image:: https://img.shields.io/pypi/l/factory_boy.svg
+ :target: https://pypi.python.org/pypi/factory_boy/
+ :alt: License
factory_boy is a fixtures replacement based on thoughtbot's `factory_girl <http://github.com/thoughtbot/factory_girl>`_.
@@ -74,8 +78,9 @@ Links
* Documentation: http://factoryboy.readthedocs.org/
* Repository: https://github.com/rbarrois/factory_boy
* Package: https://pypi.python.org/pypi/factory_boy/
+* Mailing-list: `factoryboy@googlegroups.com <mailto:factoryboy@googlegroups.com>`_ | https://groups.google.com/forum/#!forum/factoryboy
-factory_boy supports Python 2.6, 2.7, 3.2 and 3.3, as well as PyPy; it requires only the standard Python library.
+factory_boy supports Python 2.6, 2.7, 3.2 to 3.5, as well as PyPy; it requires only the standard Python library.
Download
@@ -181,7 +186,7 @@ It is also possible to create a bunch of objects in a single call:
Realistic, random values
""""""""""""""""""""""""
-Tests look better with random yet realistic values.
+Demos look better with random yet realistic values; and those realistic values can also help discover bugs.
For this, factory_boy relies on the excellent `fake-factory <https://pypi.python.org/pypi/fake-factory>`_ library:
.. code-block:: python
@@ -199,6 +204,10 @@ For this, factory_boy relies on the excellent `fake-factory <https://pypi.python
<User: Lucy Murray>
+.. note:: Use of fully randomized data in tests is quickly a problem for reproducing broken builds.
+ To that purpose, factory_boy provides helpers to handle the random seeds it uses.
+
+
Lazy Attributes
"""""""""""""""
@@ -217,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
@@ -224,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
"""""""""
@@ -328,6 +342,7 @@ Contributing
factory_boy is distributed under the MIT License.
Issues should be opened through `GitHub Issues <http://github.com/rbarrois/factory_boy/issues/>`_; whenever possible, a pull request should be included.
+Questions and suggestions are welcome on the `mailing-list <mailto:factoryboy@googlegroups.com>`_.
All pull request should pass the test suite, which can be launched simply with:
@@ -348,7 +363,7 @@ To test with a specific framework version, you may use:
.. code-block:: sh
- $ make DJANGO=1.7 test
+ $ make DJANGO=1.9 test
Valid options are:
@@ -357,6 +372,13 @@ Valid options are:
* ``ALCHEMY`` for ``SQLAlchemy``
+To avoid running ``mongoengine`` tests (e.g no mongo server installed), run:
+
+.. code-block:: sh
+
+ $ make SKIP_MONGOENGINE=1 test
+
+
Contents, indices and tables
----------------------------
diff --git a/dev_requirements.txt b/dev_requirements.txt
index 22261a1..c78aa9d 100644
--- a/dev_requirements.txt
+++ b/dev_requirements.txt
@@ -8,3 +8,6 @@ SQLAlchemy
mongoengine
mock
wheel
+
+Sphinx
+sphinx_rtd_theme
diff --git a/docs/changelog.rst b/docs/changelog.rst
index fa542f4..dc3f967 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -1,6 +1,15 @@
ChangeLog
=========
+.. _v2.6.1:
+
+2.6.1 (2016-02-10)
+------------------
+
+*New:*
+
+ - :issue:`262`: Allow optional forced flush on SQLAlchemy, courtesy of `Minjung <https://github.com/Minjung>`_.
+
.. _v2.6.0:
2.6.0 (2015-10-20)
diff --git a/docs/conf.py b/docs/conf.py
index c3512e0..d5b86f4 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -114,7 +114,7 @@ pygments_style = 'sphinx'
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
-html_theme = 'default'
+html_theme = 'sphinx_rtd_theme'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
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/fuzzy.rst b/docs/fuzzy.rst
index 6b06608..fde1af1 100644
--- a/docs/fuzzy.rst
+++ b/docs/fuzzy.rst
@@ -199,7 +199,7 @@ FuzzyDate
FuzzyDateTime
-------------
-.. class:: FuzzyDateTime(start_dt[, end_dt], tz=UTC, force_year=None, force_month=None, force_day=None, force_hour=None, force_minute=None, force_second=None, force_microsecond=None)
+.. class:: FuzzyDateTime(start_dt[, end_dt], force_year=None, force_month=None, force_day=None, force_hour=None, force_minute=None, force_second=None, force_microsecond=None)
The :class:`FuzzyDateTime` fuzzer generates random timezone-aware datetime within a given
inclusive range.
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/orms.rst b/docs/orms.rst
index 9b209bc..af20917 100644
--- a/docs/orms.rst
+++ b/docs/orms.rst
@@ -15,7 +15,7 @@ Django
The first versions of factory_boy were designed specifically for Django,
-but the library has now evolved to be framework-independant.
+but the library has now evolved to be framework-independent.
Most features should thus feel quite familiar to Django users.
@@ -126,7 +126,7 @@ Extra fields
:param str from_path: Use data from the file located at ``from_path``,
and keep its filename
:param file from_file: Use the contents of the provided file object; use its filename
- if available
+ if available, unless ``filename`` is also provided.
:param bytes data: Use the provided bytes as file contents
:param str filename: The filename for the FileField
@@ -333,6 +333,10 @@ To work, this class needs an `SQLAlchemy`_ session object affected to the :attr:
SQLAlchemy session to use to communicate with the database when creating
an object through this :class:`SQLAlchemyModelFactory`.
+ .. attribute:: force_flush
+
+ Force a session flush() at the end of :func:`~factory.alchemy.SQLAlchemyModelFactory._create()`.
+
A (very) simple example:
.. code-block:: python
diff --git a/docs/recipes.rst b/docs/recipes.rst
index df86bac..a627e8b 100644
--- a/docs/recipes.rst
+++ b/docs/recipes.rst
@@ -88,7 +88,7 @@ When a :class:`UserFactory` is instantiated, factory_boy will call
Example: Django's Profile
-"""""""""""""""""""""""""
+~~~~~~~~~~~~~~~~~~~~~~~~~
Django (<1.5) provided a mechanism to attach a ``Profile`` to a ``User`` instance,
using a :class:`~django.db.models.OneToOneField` from the ``Profile`` to the ``User``.
diff --git a/docs/reference.rst b/docs/reference.rst
index 44300a5..e2f63db 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
"""""""""""""
@@ -1042,7 +1078,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
@@ -1413,6 +1449,23 @@ If a value if passed for the :class:`RelatedFactory` attribute, this disables
1
+.. note:: The target of the :class:`RelatedFactory` is evaluated *after* the initial factory has been instantiated.
+ This means that calls to :class:`factory.SelfAttribute` cannot go higher than this :class:`RelatedFactory`:
+
+ .. code-block:: python
+
+ class CountryFactory(factory.Factory):
+ class Meta:
+ model = Country
+
+ lang = 'fr'
+ capital_city = factory.RelatedFactory(CityFactory, 'capital_of',
+ # factory.SelfAttribute('..lang') will crash, since the context of
+ # ``CountryFactory`` has already been evaluated.
+ main_lang=factory.SelfAttribute('capital_of.lang'),
+ )
+
+
PostGeneration
""""""""""""""
diff --git a/factory/__init__.py b/factory/__init__.py
index 4a4a09f..1fa581b 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__ = '2.6.0'
+__version__ = '2.6.1'
__author__ = 'Raphaƫl Barrois <raphael.barrois+fboy@polytechnique.org>'
@@ -43,6 +43,7 @@ from .base import (
from .faker import Faker
from .declarations import (
+ LazyFunction,
LazyAttribute,
Iterator,
Sequence,
diff --git a/factory/alchemy.py b/factory/alchemy.py
index 20da6cf..a9aab23 100644
--- a/factory/alchemy.py
+++ b/factory/alchemy.py
@@ -27,6 +27,7 @@ class SQLAlchemyOptions(base.FactoryOptions):
def _build_default_options(self):
return super(SQLAlchemyOptions, self)._build_default_options() + [
base.OptionDefault('sqlalchemy_session', None, inherit=True),
+ base.OptionDefault('force_flush', False, inherit=True),
]
@@ -43,4 +44,6 @@ class SQLAlchemyModelFactory(base.Factory):
session = cls._meta.sqlalchemy_session
obj = model_class(*args, **kwargs)
session.add(obj)
+ if cls._meta.force_flush:
+ session.flush()
return obj
diff --git a/factory/compat.py b/factory/compat.py
index 785d174..737d91a 100644
--- a/factory/compat.py
+++ b/factory/compat.py
@@ -42,14 +42,6 @@ else: # pragma: no cover
from io import BytesIO
-if sys.version_info[:2] == (2, 6): # pragma: no cover
- def float_to_decimal(fl):
- return decimal.Decimal(str(fl))
-else: # pragma: no cover
- def float_to_decimal(fl):
- return decimal.Decimal(fl)
-
-
try: # pragma: no cover
# Python >= 3.2
UTC = datetime.timezone.utc
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/factory/fuzzy.py b/factory/fuzzy.py
index 923d8b7..71d1884 100644
--- a/factory/fuzzy.py
+++ b/factory/fuzzy.py
@@ -164,7 +164,7 @@ class FuzzyDecimal(BaseFuzzyAttribute):
super(FuzzyDecimal, self).__init__(**kwargs)
def fuzz(self):
- base = compat.float_to_decimal(_random.uniform(self.low, self.high))
+ base = decimal.Decimal(str(_random.uniform(self.low, self.high)))
return base.quantize(decimal.Decimal(10) ** -self.precision)
@@ -217,6 +217,9 @@ class BaseFuzzyDateTime(BaseFuzzyAttribute):
"""%s boundaries should have start <= end, got %r > %r""" % (
self.__class__.__name__, start_dt, end_dt))
+ def _now(self):
+ raise NotImplementedError()
+
def __init__(self, start_dt, end_dt=None,
force_year=None, force_month=None, force_day=None,
force_hour=None, force_minute=None, force_second=None,
diff --git a/factory/utils.py b/factory/utils.py
index 806b1ec..15dba0a 100644
--- a/factory/utils.py
+++ b/factory/utils.py
@@ -101,7 +101,7 @@ def import_object(module_name, attribute_name):
def _safe_repr(obj):
try:
obj_repr = repr(obj)
- except UnicodeError:
+ except Exception:
return '<bad_repr object at %s>' % id(obj)
try: # Convert to "text type" (= unicode)
diff --git a/setup.py b/setup.py
index 8ca7e4b..003dd08 100755
--- a/setup.py
+++ b/setup.py
@@ -67,6 +67,7 @@ setup(
"Programming Language :: Python :: 3.2",
"Programming Language :: Python :: 3.3",
"Programming Language :: Python :: 3.4",
+ "Programming Language :: Python :: 3.5",
"Programming Language :: Python :: Implementation :: PyPy",
"Topic :: Software Development :: Testing",
"Topic :: Software Development :: Libraries :: Python Modules",
diff --git a/tests/test_alchemy.py b/tests/test_alchemy.py
index 9d7288a..5d8f275 100644
--- a/tests/test_alchemy.py
+++ b/tests/test_alchemy.py
@@ -23,6 +23,7 @@
import factory
from .compat import unittest
+import mock
try:
@@ -55,6 +56,16 @@ class StandardFactory(SQLAlchemyModelFactory):
foo = factory.Sequence(lambda n: 'foo%d' % n)
+class ForceFlushingStandardFactory(SQLAlchemyModelFactory):
+ class Meta:
+ model = models.StandardModel
+ sqlalchemy_session = mock.MagicMock()
+ force_flush = True
+
+ id = factory.Sequence(lambda n: n)
+ foo = factory.Sequence(lambda n: 'foo%d' % n)
+
+
class NonIntegerPkFactory(SQLAlchemyModelFactory):
class Meta:
model = models.NonIntegerPk
@@ -103,6 +114,27 @@ class SQLAlchemyPkSequenceTestCase(unittest.TestCase):
@unittest.skipIf(sqlalchemy is None, "SQLalchemy not installed.")
+class SQLAlchemyForceFlushTestCase(unittest.TestCase):
+ def setUp(self):
+ super(SQLAlchemyForceFlushTestCase, self).setUp()
+ ForceFlushingStandardFactory.reset_sequence(1)
+ ForceFlushingStandardFactory._meta.sqlalchemy_session.rollback()
+ ForceFlushingStandardFactory._meta.sqlalchemy_session.reset_mock()
+
+ def test_force_flush_called(self):
+ self.assertFalse(ForceFlushingStandardFactory._meta.sqlalchemy_session.flush.called)
+ ForceFlushingStandardFactory.create()
+ self.assertTrue(ForceFlushingStandardFactory._meta.sqlalchemy_session.flush.called)
+
+ def test_force_flush_not_called(self):
+ ForceFlushingStandardFactory._meta.force_flush = False
+ self.assertFalse(ForceFlushingStandardFactory._meta.sqlalchemy_session.flush.called)
+ ForceFlushingStandardFactory.create()
+ self.assertFalse(ForceFlushingStandardFactory._meta.sqlalchemy_session.flush.called)
+ ForceFlushingStandardFactory._meta.force_flush = True
+
+
+@unittest.skipIf(sqlalchemy is None, "SQLalchemy not installed.")
class SQLAlchemyNonIntegerPkTestCase(unittest.TestCase):
def setUp(self):
super(SQLAlchemyNonIntegerPkTestCase, self).setUp()
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)
diff --git a/tests/test_fuzzy.py b/tests/test_fuzzy.py
index 3f9c434..4c3873a 100644
--- a/tests/test_fuzzy.py
+++ b/tests/test_fuzzy.py
@@ -189,6 +189,19 @@ class FuzzyDecimalTestCase(unittest.TestCase):
self.assertEqual(decimal.Decimal('8.001').quantize(decimal.Decimal(10) ** -3), res)
+ @unittest.skipIf(compat.PY2, "decimal.FloatOperation was added in Py3")
+ def test_no_approximation(self):
+ """We should not go through floats in our fuzzy calls unless actually needed."""
+ fuzz = fuzzy.FuzzyDecimal(0, 10)
+
+ decimal_context = decimal.getcontext()
+ old_traps = decimal_context.traps[decimal.FloatOperation]
+ try:
+ decimal_context.traps[decimal.FloatOperation] = True
+ fuzz.evaluate(2, None, None)
+ finally:
+ decimal_context.traps[decimal.FloatOperation] = old_traps
+
class FuzzyDateTestCase(unittest.TestCase):
@classmethod
diff --git a/tests/test_using.py b/tests/test_using.py
index c7d2b85..0a893c1 100644
--- a/tests/test_using.py
+++ b/tests/test_using.py
@@ -1924,6 +1924,36 @@ class PostGenerationTestCase(unittest.TestCase):
self.assertEqual(3, related.one)
self.assertEqual(4, related.two)
+ def test_related_factory_selfattribute(self):
+ class TestRelatedObject(object):
+ def __init__(self, obj=None, one=None, two=None):
+ obj.related = self
+ self.one = one
+ self.two = two
+ self.three = obj
+
+ class TestRelatedObjectFactory(factory.Factory):
+ class Meta:
+ model = TestRelatedObject
+ one = 1
+ two = factory.LazyAttribute(lambda o: o.one + 1)
+
+ class TestObjectFactory(factory.Factory):
+ class Meta:
+ model = TestObject
+ one = 3
+ two = 2
+ three = factory.RelatedFactory(TestRelatedObjectFactory, 'obj',
+ two=factory.SelfAttribute('obj.two'),
+ )
+
+ obj = TestObjectFactory.build(two=4)
+ self.assertEqual(3, obj.one)
+ self.assertEqual(4, obj.two)
+ self.assertEqual(1, obj.related.one)
+ self.assertEqual(4, obj.related.two)
+
+
class RelatedFactoryExtractionTestCase(unittest.TestCase):
def setUp(self):