pax_global_header00006660000000000000000000000064130251051370014507gustar00rootroot0000000000000052 comment=110317e8438d571deca8c6a910d13390fd91d394 factory_boy-2.8.1/000077500000000000000000000000001302510513700140375ustar00rootroot00000000000000factory_boy-2.8.1/.flake8000066400000000000000000000001161302510513700152100ustar00rootroot00000000000000[flake8] # Ignore "and" at start of line. ignore = W503 max-line-length = 120 factory_boy-2.8.1/.gitignore000066400000000000000000000002431302510513700160260ustar00rootroot00000000000000# Temporary files .*.swp *.pyc *.pyo # Build-related files docs/_build/ auto_dev_requirements*.txt .coverage .tox *.egg-info *.egg build/ dist/ htmlcov/ MANIFEST factory_boy-2.8.1/.travis.yml000066400000000000000000000011521302510513700161470ustar00rootroot00000000000000sudo: false language: python script: - tox install: - pip install tox matrix: include: - python: "2.7" env: TOXENV=py27-django110-alchemy10-mongoengine010 - python: "3.4" env: TOXENV=py34-django110-alchemy10-mongoengine010 - python: "3.5" env: TOXENV=py35-django110-alchemy10-mongoengine010 # Pypy - python: "pypy" env: TOXENV=py27-django110-alchemy10-mongoengine010 # Linting - python: "3.5" env: TOXENV=examples - python: "3.5" env: TOXENV=lint services: - mongodb notifications: email: false irc: "irc.freenode.org#factory_boy" factory_boy-2.8.1/ChangeLog000077700000000000000000000000001302510513700212152docs/changelog.rstustar00rootroot00000000000000factory_boy-2.8.1/LICENSE000066400000000000000000000021131302510513700150410ustar00rootroot00000000000000Copyright (c) 2010 Mark Sandstrom Copyright (c) 2011-2015 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. factory_boy-2.8.1/MANIFEST.in000066400000000000000000000002751302510513700156010ustar00rootroot00000000000000include ChangeLog LICENSE README.rst include requirements*.txt graft factory prune docs prune examples prune tests global-exclude *.py[cod] __pycache__ exclude Makefile tox.ini .flake8 factory_boy-2.8.1/Makefile000066400000000000000000000022511302510513700154770ustar00rootroot00000000000000PACKAGE=factory TESTS_DIR=tests DOC_DIR=docs EXAMPLES_DIR=examples # Use current python binary instead of system default. COVERAGE = python $(shell which coverage) FLAKE8 = flake8 all: default default: clean: find . -type f -name '*.pyc' -delete find . -type f -path '*/__pycache__/*' -delete find . -type d -empty -delete @rm -rf tmp_test/ install-deps: pip install --upgrade pip setuptools pip install --upgrade -r requirements_dev.txt pip freeze testall: tox test: python -Wdefault -m unittest $(TESTS_DIR) example-test: $(MAKE) -C $(EXAMPLES_DIR) test # Note: we run the linter in two runs, because our __init__.py files has specific warnings we want to exclude lint: check-manifest $(FLAKE8) --config .flake8 --exclude $(PACKAGE)/__init__.py $(PACKAGE) $(FLAKE8) --config .flake8 --ignore F401 $(PACKAGE)/__init__.py coverage: $(COVERAGE) erase $(COVERAGE) run "--include=$(PACKAGE)/*.py,$(TESTS_DIR)/*.py" --branch setup.py test $(COVERAGE) report "--include=$(PACKAGE)/*.py,$(TESTS_DIR)/*.py" $(COVERAGE) html "--include=$(PACKAGE)/*.py,$(TESTS_DIR)/*.py" doc: $(MAKE) -C $(DOC_DIR) html .PHONY: all default clean coverage doc install-deps lint test factory_boy-2.8.1/README.rst000066400000000000000000000254171302510513700155370ustar00rootroot00000000000000factory_boy =========== .. image:: https://secure.travis-ci.org/FactoryBoy/factory_boy.png?branch=master :target: http://travis-ci.org/FactoryBoy/factory_boy/ .. image:: https://img.shields.io/pypi/v/factory_boy.svg :target: https://factoryboy.readthedocs.io/en/latest/changelog.html :alt: Latest Version .. image:: https://img.shields.io/pypi/pyversions/factory_boy.svg :target: https://pypi.python.org/pypi/factory_boy/ :alt: Supported Python versions .. image:: https://img.shields.io/pypi/wheel/factory_boy.svg :target: https://pypi.python.org/pypi/factory_boy/ :alt: Wheel status .. 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 `_. As a fixtures replacement tool, it aims to replace static, hard to maintain fixtures with easy-to-use factories for complex object. Instead of building an exhaustive test setup with every possible combination of corner cases, ``factory_boy`` allows you to use objects customized for the current test, while only declaring the test-specific fields: .. code-block:: python class FooTests(unittest.TestCase): def test_with_factory_boy(self): # We need a 200€, paid order, shipping to australia, for a VIP customer order = OrderFactory( amount=200, status='PAID', customer__is_vip=True, address__country='AU', ) # Run the tests here def test_without_factory_boy(self): address = Address( street="42 fubar street", zipcode="42Z42", city="Sydney", country="AU", ) customer = Customer( first_name="John", last_name="Doe", phone="+1234", email="john.doe@example.org", active=True, is_vip=True, address=address, ) # etc. factory_boy is designed to work well with various ORMs (Django, Mongo, SQLAlchemy), and can easily be extended for other libraries. Its main features include: - Straightforward declarative syntax - Chaining factory calls while retaining the global context - Support for multiple build strategies (saved/unsaved instances, stubbed objects) - Multiple factories per class support, including inheritance Links ----- * Documentation: https://factoryboy.readthedocs.io/ * Repository: https://github.com/FactoryBoy/factory_boy * Package: https://pypi.python.org/pypi/factory_boy/ * Mailing-list: `factoryboy@googlegroups.com `_ | https://groups.google.com/forum/#!forum/factoryboy factory_boy supports Python 2.7, 3.2 to 3.5, as well as PyPy; it requires only the standard Python library. Download -------- PyPI: https://pypi.python.org/pypi/factory_boy/ .. code-block:: sh $ pip install factory_boy Source: https://github.com/FactoryBoy/factory_boy/ .. code-block:: sh $ git clone git://github.com/FactoryBoy/factory_boy/ $ python setup.py install Usage ----- .. note:: This section provides a quick summary of factory_boy features. A more detailed listing is available in the full documentation. Defining factories """""""""""""""""" Factories declare a set of attributes used to instantiate an object. The class of the object must be defined in the ``model`` field of a ``class Meta:`` attribute: .. code-block:: python import factory from . import models class UserFactory(factory.Factory): class Meta: model = models.User first_name = 'John' last_name = 'Doe' admin = False # Another, different, factory for the same object class AdminFactory(factory.Factory): class Meta: model = models.User first_name = 'Admin' last_name = 'User' admin = True Using factories """"""""""""""" factory_boy supports several different build strategies: build, create, and stub: .. code-block:: python # Returns a User instance that's not saved user = UserFactory.build() # Returns a saved User instance user = UserFactory.create() # Returns a stub object (just a bunch of attributes) obj = UserFactory.stub() You can use the Factory class as a shortcut for the default build strategy: .. code-block:: python # Same as UserFactory.create() user = UserFactory() No matter which strategy is used, it's possible to override the defined attributes by passing keyword arguments: .. code-block:: pycon # Build a User instance and override first_name >>> user = UserFactory.build(first_name='Joe') >>> user.first_name "Joe" It is also possible to create a bunch of objects in a single call: .. code-block:: pycon >>> users = UserFactory.build_batch(10, first_name="Joe") >>> len(users) 10 >>> [user.first_name for user in users] ["Joe", "Joe", "Joe", "Joe", "Joe", "Joe", "Joe", "Joe", "Joe", "Joe"] Realistic, random 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 `_ library: .. code-block:: python class RandomUserFactory(factory.Factory): class Meta: model = models.User first_name = factory.Faker('first_name') last_name = factory.Faker('last_name') .. code-block:: pycon >>> UserFactory() .. 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 """"""""""""""" Most factory attributes can be added using static values that are evaluated when the factory is defined, but some attributes (such as fields whose value is computed from other elements) will need values assigned each time an instance is generated. These "lazy" attributes can be added as follows: .. code-block:: python class UserFactory(factory.Factory): class Meta: model = models.User 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 """"""""" Unique values in a specific format (for example, e-mail addresses) can be generated using sequences. Sequences are defined by using ``Sequence`` or the decorator ``sequence``: .. code-block:: python class UserFactory(factory.Factory): class Meta: model = models.User email = factory.Sequence(lambda n: 'person{0}@example.com'.format(n)) >>> UserFactory().email 'person0@example.com' >>> UserFactory().email 'person1@example.com' Associations """""""""""" Some objects have a complex field, that should itself be defined from a dedicated factories. This is handled by the ``SubFactory`` helper: .. code-block:: python class PostFactory(factory.Factory): class Meta: model = models.Post author = factory.SubFactory(UserFactory) The associated object's strategy will be used: .. code-block:: python # Builds and saves a User and a Post >>> post = PostFactory() >>> post.id is None # Post has been 'saved' False >>> post.author.id is None # post.author has been saved False # Builds but does not save a User, and then builds but does not save a Post >>> post = PostFactory.build() >>> post.id is None True >>> post.author.id is None True ORM Support """"""""""" factory_boy has specific support for a few ORMs, through specific ``factory.Factory`` subclasses: * Django, with ``factory.django.DjangoModelFactory`` * Mogo, with ``factory.mogo.MogoFactory`` * MongoEngine, with ``factory.mongoengine.MongoEngineFactory`` * SQLAlchemy, with ``factory.alchemy.SQLAlchemyModelFactory`` Debugging factory_boy """"""""""""""""""""" Debugging factory_boy can be rather complex due to the long chains of calls. Detailed logging is available through the ``factory`` logger. A helper, `factory.debug()`, is available to ease debugging: .. code-block:: python with factory.debug(): obj = TestModel2Factory() import logging logger = logging.getLogger('factory') logger.addHandler(logging.StreamHandler()) logger.setLevel(logging.DEBUG) This will yield messages similar to those (artificial indentation): .. code-block:: ini BaseFactory: Preparing tests.test_using.TestModel2Factory(extra={}) LazyStub: Computing values for tests.test_using.TestModel2Factory(two=>) SubFactory: Instantiating tests.test_using.TestModelFactory(__containers=(,), one=4), create=True BaseFactory: Preparing tests.test_using.TestModelFactory(extra={'__containers': (,), 'one': 4}) LazyStub: Computing values for tests.test_using.TestModelFactory(one=4) LazyStub: Computed values, got tests.test_using.TestModelFactory(one=4) BaseFactory: Generating tests.test_using.TestModelFactory(one=4) LazyStub: Computed values, got tests.test_using.TestModel2Factory(two=) BaseFactory: Generating tests.test_using.TestModel2Factory(two=) Contributing ------------ factory_boy is distributed under the MIT License. Issues should be opened through `GitHub Issues `_; whenever possible, a pull request should be included. Questions and suggestions are welcome on the `mailing-list `_. All pull request should pass the test suite, which can be launched simply with: .. code-block:: sh $ make test In order to test coverage, please use: .. code-block:: sh $ make coverage To test with a specific framework version, you may use: .. code-block:: sh $ make DJANGO=1.9 test Valid options are: * ``DJANGO`` for ``Django`` * ``MONGOENGINE`` for ``mongoengine`` * ``ALCHEMY`` for ``SQLAlchemy`` To avoid running ``mongoengine`` tests (e.g no mongo server installed), run: .. code-block:: sh $ make SKIP_MONGOENGINE=1 test factory_boy-2.8.1/docs/000077500000000000000000000000001302510513700147675ustar00rootroot00000000000000factory_boy-2.8.1/docs/Makefile000066400000000000000000000107761302510513700164420ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/FactoryBoy.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/FactoryBoy.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/FactoryBoy" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/FactoryBoy" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." make -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." factory_boy-2.8.1/docs/_static/000077500000000000000000000000001302510513700164155ustar00rootroot00000000000000factory_boy-2.8.1/docs/_static/.keep_dir000066400000000000000000000000001302510513700201660ustar00rootroot00000000000000factory_boy-2.8.1/docs/changelog.rst000066400000000000000000000467441302510513700174670ustar00rootroot00000000000000ChangeLog ========= .. _v2.8.1: 2.8.1 (2016-12-17) ------------------ *Bugfix:* - Fix packaging issues. .. _v2.8.0: 2.8.0 (2016-12-17) ------------------ *New:* - :issue:`240`: Call post-generation declarations in the order they were declared, thanks to `Oleg Pidsadnyi `_. - :issue:`309`: Provide new options for SQLAlchemy session persistence *Bugfix:* - :issue:`334`: Adjust for the package change in ``faker`` .. _v2.7.0: 2.7.0 (2016-04-19) ------------------ *New:* - :issue:`267`: Add :class:`factory.LazyFunction` to remove unneeded lambda parameters, thanks to `Hervé Cauwelier `_. - :issue:`251`: Add :ref:`parameterized factories ` and :class:`traits ` - :issue:`256`, :issue:`292`: Improve error messages in corner cases *Removed:* - :issue:`278`: Formally drop support for Python2.6 .. _v2.6.1: 2.6.1 (2016-02-10) ------------------ *New:* - :issue:`262`: Allow optional forced flush on SQLAlchemy, courtesy of `Minjung `_. .. _v2.6.0: 2.6.0 (2015-10-20) ------------------ *New:* - Add :attr:`factory.FactoryOptions.rename` to help handle conflicting names (:issue:`206`) - Add support for random-yet-realistic values through `fake-factory `_, through the :class:`factory.Faker` class. - :class:`factory.Iterator` no longer begins iteration of its argument at import time, thus allowing to pass in a lazy iterator such as a Django queryset (i.e ``factory.Iterator(models.MyThingy.objects.all())``). - Simplify imports for ORM layers, now available through a simple ``factory`` import, at ``factory.alchemy.SQLAlchemyModelFactory`` / ``factory.django.DjangoModelFactory`` / ``factory.mongoengine.MongoEngineFactory``. *Bugfix:* - :issue:`201`: Properly handle custom Django managers when dealing with abstract Django models. - :issue:`212`: Fix :meth:`factory.django.mute_signals` to handle Django's signal caching - :issue:`228`: Don't load :func:`django.apps.apps.get_model()` until required - :issue:`219`: Stop using :meth:`mogo.model.Model.new()`, deprecated 4 years ago. .. _v2.5.2: 2.5.2 (2015-04-21) ------------------ *Bugfix:* - Add support for Django 1.7/1.8 - Add support for mongoengine>=0.9.0 / pymongo>=2.1 .. _v2.5.1: 2.5.1 (2015-03-27) ------------------ *Bugfix:* - Respect custom managers in :class:`~factory.django.DjangoModelFactory` (see :issue:`192`) - Allow passing declarations (e.g :class:`~factory.Sequence`) as parameters to :class:`~factory.django.FileField` and :class:`~factory.django.ImageField`. .. _v2.5.0: 2.5.0 (2015-03-26) ------------------ *New:* - Add support for getting/setting :mod:`factory.fuzzy`'s random state (see :issue:`175`, :issue:`185`). - Support lazy evaluation of iterables in :class:`factory.fuzzy.FuzzyChoice` (see :issue:`184`). - Support non-default databases at the factory level (see :issue:`171`) - Make :class:`factory.django.FileField` and :class:`factory.django.ImageField` non-post_generation, i.e normal fields also available in ``save()`` (see :issue:`141`). *Bugfix:* - Avoid issues when using :meth:`factory.django.mute_signals` on a base factory class (see :issue:`183`). - Fix limitations of :class:`factory.StubFactory`, that can now use :class:`factory.SubFactory` and co (see :issue:`131`). *Deprecation:* - Remove deprecated features from :ref:`v2.4.0` - Remove the auto-magical sequence setup (based on the latest primary key value in the database) for Django and SQLAlchemy; this relates to issues :issue:`170`, :issue:`153`, :issue:`111`, :issue:`103`, :issue:`92`, :issue:`78`. See https://github.com/FactoryBoy/factory_boy/commit/13d310f for technical details. .. warning:: Version 2.5.0 removes the 'auto-magical sequence setup' bug-and-feature. This could trigger some bugs when tests expected a non-zero sequence reference. Upgrading """"""""" .. warning:: Version 2.5.0 removes features that were marked as deprecated in :ref:`v2.4.0 `. All ``FACTORY_*``-style attributes are now declared in a ``class Meta:`` section: .. code-block:: python # Old-style, deprecated class MyFactory(factory.Factory): FACTORY_FOR = models.MyModel FACTORY_HIDDEN_ARGS = ['a', 'b', 'c'] # New-style class MyFactory(factory.Factory): class Meta: model = models.MyModel exclude = ['a', 'b', 'c'] A simple shell command to upgrade the code would be: .. code-block:: sh # sed -i: inplace update # grep -l: only file names, not matching lines sed -i 's/FACTORY_FOR =/class Meta:\n model =/' $(grep -l FACTORY_FOR $(find . -name '*.py')) This takes care of all ``FACTORY_FOR`` occurences; the files containing other attributes to rename can be found with ``grep -R FACTORY .`` .. _v2.4.1: 2.4.1 (2014-06-23) ------------------ *Bugfix:* - Fix overriding deeply inherited attributes (set in one factory, overridden in a subclass, used in a sub-sub-class). .. _v2.4.0: 2.4.0 (2014-06-21) ------------------ *New:* - Add support for :attr:`factory.fuzzy.FuzzyInteger.step`, thanks to `ilya-pirogov `_ (:issue:`120`) - Add :meth:`~factory.django.mute_signals` decorator to temporarily disable some signals, thanks to `ilya-pirogov `_ (:issue:`122`) - Add :class:`~factory.fuzzy.FuzzyFloat` (:issue:`124`) - Declare target model and other non-declaration fields in a ``class Meta`` section. *Deprecation:* - Use of ``FACTORY_FOR`` and other ``FACTORY`` class-level attributes is deprecated and will be removed in 2.5. Those attributes should now declared within the :class:`class Meta ` attribute: For :class:`factory.Factory`: * Rename :attr:`~factory.Factory.FACTORY_FOR` to :attr:`~factory.FactoryOptions.model` * Rename :attr:`~factory.Factory.ABSTRACT_FACTORY` to :attr:`~factory.FactoryOptions.abstract` * Rename :attr:`~factory.Factory.FACTORY_STRATEGY` to :attr:`~factory.FactoryOptions.strategy` * Rename :attr:`~factory.Factory.FACTORY_ARG_PARAMETERS` to :attr:`~factory.FactoryOptions.inline_args` * Rename :attr:`~factory.Factory.FACTORY_HIDDEN_ARGS` to :attr:`~factory.FactoryOptions.exclude` For :class:`factory.django.DjangoModelFactory`: * Rename :attr:`~factory.django.DjangoModelFactory.FACTORY_DJANGO_GET_OR_CREATE` to :attr:`~factory.django.DjangoOptions.django_get_or_create` For :class:`factory.alchemy.SQLAlchemyModelFactory`: * Rename :attr:`~factory.alchemy.SQLAlchemyModelFactory.FACTORY_SESSION` to :attr:`~factory.alchemy.SQLAlchemyOptions.sqlalchemy_session` .. _v2.3.1: 2.3.1 (2014-01-22) ------------------ *Bugfix:* - Fix badly written assert containing state-changing code, spotted by `chsigi `_ (:issue:`126`) - Don't crash when handling objects whose __repr__ is non-pure-ascii bytes on Py2, discovered by `mbertheau `_ (:issue:`123`) and `strycore `_ (:issue:`127`) .. _v2.3.0: 2.3.0 (2013-12-25) ------------------ *New:* - Add :class:`~factory.fuzzy.FuzzyText`, thanks to `jdufresne `_ (:issue:`97`) - Add :class:`~factory.fuzzy.FuzzyDecimal`, thanks to `thedrow `_ (:issue:`94`) - Add support for :class:`~mongoengine.EmbeddedDocument`, thanks to `imiric `_ (:issue:`100`) .. _v2.2.1: 2.2.1 (2013-09-24) ------------------ *Bugfix:* - Fixed sequence counter for :class:`~factory.django.DjangoModelFactory` when a factory inherits from another factory relating to an abstract model. .. _v2.2.0: 2.2.0 (2013-09-24) ------------------ *Bugfix:* - Removed duplicated :class:`~factory.alchemy.SQLAlchemyModelFactory` lurking in :mod:`factory` (:issue:`83`) - Properly handle sequences within object inheritance chains. If FactoryA inherits from FactoryB, and their associated classes share the same link, sequence counters will be shared (:issue:`93`) - Properly handle nested :class:`~factory.SubFactory` overrides *New:* - The :class:`~factory.django.DjangoModelFactory` now supports the ``FACTORY_FOR = 'myapp.MyModel'`` syntax, making it easier to shove all factories in a single module (:issue:`66`). - Add :meth:`factory.debug()` helper for easier backtrace analysis - Adding factory support for mongoengine with :class:`~factory.mongoengine.MongoEngineFactory`. .. _v2.1.2: 2.1.2 (2013-08-14) ------------------ *New:* - The :class:`~factory.Factory.ABSTRACT_FACTORY` keyword is now optional, and automatically set to ``True`` if neither the :class:`~factory.Factory` subclass nor its parent declare the :class:`~factory.Factory.FACTORY_FOR` attribute (:issue:`74`) .. _v2.1.1: 2.1.1 (2013-07-02) ------------------ *Bugfix:* - Properly retrieve the ``color`` keyword argument passed to :class:`~factory.django.ImageField` .. _v2.1.0: 2.1.0 (2013-06-26) ------------------ *New:* - Add :class:`~factory.fuzzy.FuzzyDate` thanks to `saulshanabrook `_ - Add :class:`~factory.fuzzy.FuzzyDateTime` and :class:`~factory.fuzzy.FuzzyNaiveDateTime`. - Add a :attr:`~factory.containers.LazyStub.factory_parent` attribute to the :class:`~factory.containers.LazyStub` passed to :class:`~factory.LazyAttribute`, in order to access fields defined in wrapping factories. - Move :class:`~factory.django.DjangoModelFactory` and :class:`~factory.mogo.MogoFactory` to their own modules (:mod:`factory.django` and :mod:`factory.mogo`) - Add the :meth:`~factory.Factory.reset_sequence` classmethod to :class:`~factory.Factory` to ease resetting the sequence counter for a given factory. - Add debug messages to ``factory`` logger. - Add a :meth:`~factory.Iterator.reset` method to :class:`~factory.Iterator` (:issue:`63`) - Add support for the SQLAlchemy ORM through :class:`~factory.alchemy.SQLAlchemyModelFactory` (:issue:`64`, thanks to `Romain Commandé `_) - Add :class:`factory.django.FileField` and :class:`factory.django.ImageField` hooks for related Django model fields (:issue:`52`) *Bugfix* - Properly handle non-integer pks in :class:`~factory.django.DjangoModelFactory` (:issue:`57`). - Disable :class:`~factory.RelatedFactory` generation when a specific value was passed (:issue:`62`, thanks to `Gabe Koscky `_) *Deprecation:* - Rename :class:`~factory.RelatedFactory`'s ``name`` argument to ``factory_related_name`` (See :issue:`58`) .. _v2.0.2: 2.0.2 (2013-04-16) ------------------ *New:* - When :attr:`~factory.django.DjangoModelFactory.FACTORY_DJANGO_GET_OR_CREATE` is empty, use ``Model.objects.create()`` instead of ``Model.objects.get_or_create``. .. _v2.0.1: 2.0.1 (2013-04-16) ------------------ *New:* - Don't push ``defaults`` to ``get_or_create`` when :attr:`~factory.django.DjangoModelFactory.FACTORY_DJANGO_GET_OR_CREATE` is not set. .. _v2.0.0: 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 `_ and `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.django.DjangoModelFactory`, through :attr:`~factory.django.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 :issue:`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` .. _v1.3.0: 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.mogo.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.django.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.django.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`` .. _v1.2.0: 1.2.0 (2012-09-08) ------------------ *New:* - Add :class:`~factory.CircularSubFactory` to solve circular dependencies between factories .. _v1.1.5: 1.1.5 (2012-07-09) ------------------ *Bugfix:* - Fix :class:`~factory.PostGenerationDeclaration` and derived classes. .. _v1.1.4: 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` .. _v1.1.3: 1.1.3 (2012-03-09) ------------------ *Bugfix:* - Fix packaging rules .. _v1.1.2: 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`. .. _v1.1.1: 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 .. _v1.1.0: 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 .. _v1.0.4: 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 `. - Provide :class:`~factory.django.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 .. _v1.0.2: 1.0.2 (2011-05-16) ------------------ *New:* - Introduce :class:`~factory.SubFactory` .. _v1.0.1: 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` .. _v1.0.0: 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: factory_boy-2.8.1/docs/conf.py000066400000000000000000000201351302510513700162670ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # Factory Boy documentation build configuration file, created by # sphinx-quickstart on Thu Sep 15 23:51:15 2011. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys, os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.insert(0, os.path.abspath('.')) sys.path.insert(0, os.path.dirname(os.path.abspath('.'))) # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.extlinks', 'sphinx.ext.intersphinx', 'sphinx.ext.viewcode', ] extlinks = { 'issue': ('https://github.com/FactoryBoy/factory_boy/issues/%s', 'issue #'), } # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'Factory Boy' copyright = u'2011-2015, Raphaël Barrois, Mark Sandstrom' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # 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 = 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. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['_build'] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. 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 # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'FactoryBoydoc' # -- Options for LaTeX output -------------------------------------------------- # The paper size ('letter' or 'a4'). #latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). #latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'FactoryBoy.tex', u'Factory Boy Documentation', u'Raphaël Barrois, Mark Sandstrom', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Additional stuff for the LaTeX preamble. #latex_preamble = '' # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'factoryboy', u'Factory Boy Documentation', [u'Raphaël Barrois, Mark Sandstrom'], 1) ] # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = { 'http://docs.python.org/': None, 'django': ( 'http://docs.djangoproject.com/en/dev/', 'http://docs.djangoproject.com/en/dev/_objects/', ), 'sqlalchemy': ( 'http://docs.sqlalchemy.org/en/rel_0_9/', 'http://docs.sqlalchemy.org/en/rel_0_9/objects.inv', ), } factory_boy-2.8.1/docs/examples.rst000066400000000000000000000072721302510513700173470ustar00rootroot00000000000000Examples ======== Here are some real-world examples of using FactoryBoy. Objects ------- First, let's define a couple of objects: .. code-block:: python class Account(object): def __init__(self, username, email): self.username = username self.email = email def __str__(self): return '%s (%s)' % (self.username, self.email) class Profile(object): GENDER_MALE = 'm' GENDER_FEMALE = 'f' GENDER_UNKNOWN = 'u' # If the user refused to give it def __init__(self, account, gender, firstname, lastname, planet='Earth'): self.account = account self.gender = gender self.firstname = firstname self.lastname = lastname self.planet = planet def __unicode__(self): return u'%s %s (%s)' % ( unicode(self.firstname), unicode(self.lastname), unicode(self.account.accountname), ) Factories --------- And now, we'll define the related factories: .. code-block:: python import datetime import factory import random from . import objects class AccountFactory(factory.Factory): class Meta: model = objects.Account 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): class Meta: model = objects.Profile account = factory.SubFactory(AccountFactory) gender = factory.Iterator([objects.Profile.GENDER_MALE, objects.Profile.GENDER_FEMALE]) firstname = u'John' lastname = u'Doe' We have now defined basic factories for our :class:`~Account` and :class:`~Profile` classes. If we commonly use a specific variant of our objects, we can refine a factory accordingly: .. code-block:: python class FemaleProfileFactory(ProfileFactory): gender = objects.Profile.GENDER_FEMALE firstname = u'Jane' user__username = factory.Sequence(lambda n: 'jane%s' % n) Using the factories ------------------- We can now use our factories, for tests: .. code-block:: python import unittest from . import business_logic from . import factories from . import objects class MyTestCase(unittest.TestCase): def test_send_mail(self): account = factories.AccountFactory() email = business_logic.prepare_email(account, subject='Foo', text='Bar') self.assertEqual(email.to, account.email) def test_get_profile_stats(self): profiles = [] profiles.extend(factories.ProfileFactory.create_batch(4)) profiles.extend(factories.FemaleProfileFactory.create_batch(2)) profiles.extend(factories.ProfileFactory.create_batch(2, planet="Tatooine")) stats = business_logic.profile_stats(profiles) self.assertEqual({'Earth': 6, 'Mars': 2}, stats.planets) self.assertLess(stats.genders[objects.Profile.GENDER_FEMALE], 2) Or for fixtures: .. code-block:: python from . import factories def make_objects(): factories.ProfileFactory.create_batch(size=50) # Let's create a few, known objects. factories.ProfileFactory( gender=objects.Profile.GENDER_MALE, firstname='Luke', lastname='Skywalker', planet='Tatooine', ) factories.ProfileFactory( gender=objects.Profile.GENDER_FEMALE, firstname='Leia', lastname='Organa', planet='Alderaan', ) factory_boy-2.8.1/docs/fuzzy.rst000066400000000000000000000252401302510513700167130ustar00rootroot00000000000000Fuzzy attributes ================ .. module:: factory.fuzzy .. note:: Now that FactoryBoy includes the :class:`factory.Faker` class, most of these built-in fuzzers are deprecated in favor of their `Faker `_ equivalents. Further discussion here: ``_ Some tests may be interested in testing with fuzzy, random values. This is handled by the :mod:`factory.fuzzy` module, which provides a few random declarations. .. note:: Use ``import factory.fuzzy`` to load this module. FuzzyAttribute -------------- .. class:: FuzzyAttribute The :class:`FuzzyAttribute` uses an arbitrary callable as fuzzer. It is expected that successive calls of that function return various values. .. attribute:: fuzzer The callable that generates random values FuzzyText --------- .. class:: FuzzyText(length=12, chars=string.ascii_letters, prefix='') The :class:`FuzzyText` fuzzer yields random strings beginning with the given :attr:`prefix`, followed by :attr:`length` charactes chosen from the :attr:`chars` character set, and ending with the given :attr:`suffix`. .. attribute:: length int, the length of the random part .. attribute:: prefix text, an optional prefix to prepend to the random part .. attribute:: suffix text, an optional suffix to append to the random part .. attribute:: chars char iterable, the chars to choose from; defaults to the list of ascii letters and numbers. FuzzyChoice ----------- .. class:: FuzzyChoice(choices) The :class:`FuzzyChoice` fuzzer yields random choices from the given iterable. .. note:: The passed in :attr:`choices` will be converted into a list upon first use, not at declaration time. This allows passing in, for instance, a Django queryset that will only hit the database during the database, not at import time. .. attribute:: choices The list of choices to select randomly FuzzyInteger ------------ .. class:: FuzzyInteger(low[, high[, step]]) The :class:`FuzzyInteger` fuzzer generates random integers within a given inclusive range. The :attr:`low` bound may be omitted, in which case it defaults to 0: .. code-block:: pycon >>> fi = FuzzyInteger(0, 42) >>> fi.low, fi.high 0, 42 >>> fi = FuzzyInteger(42) >>> fi.low, fi.high 0, 42 .. attribute:: low int, the inclusive lower bound of generated integers .. attribute:: high int, the inclusive higher bound of generated integers .. attribute:: step int, the step between values in the range; for instance, a ``FuzzyInteger(0, 42, step=3)`` might only yield values from ``[0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42]``. FuzzyDecimal ------------ .. class:: FuzzyDecimal(low[, high[, precision=2]]) The :class:`FuzzyDecimal` fuzzer generates random :class:`decimals ` within a given inclusive range. The :attr:`low` bound may be omitted, in which case it defaults to 0: .. code-block:: pycon >>> FuzzyDecimal(0.5, 42.7) >>> fi.low, fi.high 0.5, 42.7 >>> fi = FuzzyDecimal(42.7) >>> fi.low, fi.high 0.0, 42.7 >>> fi = FuzzyDecimal(0.5, 42.7, 3) >>> fi.low, fi.high, fi.precision 0.5, 42.7, 3 .. attribute:: low decimal, the inclusive lower bound of generated decimals .. attribute:: high decimal, the inclusive higher bound of generated decimals .. attribute:: precision int, the number of digits to generate after the dot. The default is 2 digits. FuzzyFloat ---------- .. class:: FuzzyFloat(low[, high]) The :class:`FuzzyFloat` fuzzer provides random :class:`float` objects within a given inclusive range. .. code-block:: pycon >>> FuzzyFloat(0.5, 42.7) >>> fi.low, fi.high 0.5, 42.7 >>> fi = FuzzyFloat(42.7) >>> fi.low, fi.high 0.0, 42.7 .. attribute:: low decimal, the inclusive lower bound of generated floats .. attribute:: high decimal, the inclusive higher bound of generated floats FuzzyDate --------- .. class:: FuzzyDate(start_date[, end_date]) The :class:`FuzzyDate` fuzzer generates random dates within a given inclusive range. The :attr:`end_date` bound may be omitted, in which case it defaults to the current date: .. code-block:: pycon >>> fd = FuzzyDate(datetime.date(2008, 1, 1)) >>> fd.start_date, fd.end_date datetime.date(2008, 1, 1), datetime.date(2013, 4, 16) .. attribute:: start_date :class:`datetime.date`, the inclusive lower bound of generated dates .. attribute:: end_date :class:`datetime.date`, the inclusive higher bound of generated dates FuzzyDateTime ------------- .. 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. The :attr:`end_dt` bound may be omitted, in which case it defaults to ``datetime.datetime.now()`` localized into the UTC timezone. .. code-block:: pycon >>> fdt = FuzzyDateTime(datetime.datetime(2008, 1, 1, tzinfo=UTC)) >>> fdt.start_dt, fdt.end_dt datetime.datetime(2008, 1, 1, tzinfo=UTC), datetime.datetime(2013, 4, 21, 19, 13, 32, 458487, tzinfo=UTC) The ``force_XXX`` keyword arguments force the related value of generated datetimes: .. code-block:: pycon >>> fdt = FuzzyDateTime(datetime.datetime(2008, 1, 1, tzinfo=UTC), datetime.datetime(2009, 1, 1, tzinfo=UTC), ... force_day=3, force_second=42) >>> fdt.evaluate(2, None, False) # Actual code used by ``SomeFactory.build()`` datetime.datetime(2008, 5, 3, 12, 13, 42, 124848, tzinfo=UTC) .. attribute:: start_dt :class:`datetime.datetime`, the inclusive lower bound of generated datetimes .. attribute:: end_dt :class:`datetime.datetime`, the inclusive upper bound of generated datetimes .. attribute:: force_year int or None; if set, forces the :attr:`~datetime.datetime.year` of generated datetime. .. attribute:: force_month int or None; if set, forces the :attr:`~datetime.datetime.month` of generated datetime. .. attribute:: force_day int or None; if set, forces the :attr:`~datetime.datetime.day` of generated datetime. .. attribute:: force_hour int or None; if set, forces the :attr:`~datetime.datetime.hour` of generated datetime. .. attribute:: force_minute int or None; if set, forces the :attr:`~datetime.datetime.minute` of generated datetime. .. attribute:: force_second int or None; if set, forces the :attr:`~datetime.datetime.second` of generated datetime. .. attribute:: force_microsecond int or None; if set, forces the :attr:`~datetime.datetime.microsecond` of generated datetime. FuzzyNaiveDateTime ------------------ .. class:: FuzzyNaiveDateTime(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:`FuzzyNaiveDateTime` fuzzer generates random naive datetime within a given inclusive range. The :attr:`end_dt` bound may be omitted, in which case it defaults to ``datetime.datetime.now()``: .. code-block:: pycon >>> fdt = FuzzyNaiveDateTime(datetime.datetime(2008, 1, 1)) >>> fdt.start_dt, fdt.end_dt datetime.datetime(2008, 1, 1), datetime.datetime(2013, 4, 21, 19, 13, 32, 458487) The ``force_XXX`` keyword arguments force the related value of generated datetimes: .. code-block:: pycon >>> fdt = FuzzyNaiveDateTime(datetime.datetime(2008, 1, 1), datetime.datetime(2009, 1, 1), ... force_day=3, force_second=42) >>> fdt.evaluate(2, None, False) # Actual code used by ``SomeFactory.build()`` datetime.datetime(2008, 5, 3, 12, 13, 42, 124848) .. attribute:: start_dt :class:`datetime.datetime`, the inclusive lower bound of generated datetimes .. attribute:: end_dt :class:`datetime.datetime`, the inclusive upper bound of generated datetimes .. attribute:: force_year int or None; if set, forces the :attr:`~datetime.datetime.year` of generated datetime. .. attribute:: force_month int or None; if set, forces the :attr:`~datetime.datetime.month` of generated datetime. .. attribute:: force_day int or None; if set, forces the :attr:`~datetime.datetime.day` of generated datetime. .. attribute:: force_hour int or None; if set, forces the :attr:`~datetime.datetime.hour` of generated datetime. .. attribute:: force_minute int or None; if set, forces the :attr:`~datetime.datetime.minute` of generated datetime. .. attribute:: force_second int or None; if set, forces the :attr:`~datetime.datetime.second` of generated datetime. .. attribute:: force_microsecond int or None; if set, forces the :attr:`~datetime.datetime.microsecond` of generated datetime. Custom fuzzy fields ------------------- Alternate fuzzy fields may be defined. They should inherit from the :class:`BaseFuzzyAttribute` class, and override its :meth:`~BaseFuzzyAttribute.fuzz` method. .. class:: BaseFuzzyAttribute Base class for all fuzzy attributes. .. method:: fuzz(self) The method responsible for generating random values. *Must* be overridden in subclasses. Managing randomness ------------------- Using :mod:`random` in factories allows to "fuzz" a program efficiently. However, it's sometimes required to *reproduce* a failing test. :mod:`factory.fuzzy` uses a separate instance of :class:`random.Random`, and provides a few helpers for this: .. method:: get_random_state() Call :meth:`get_random_state` to retrieve the random generator's current state. .. method:: set_random_state(state) Use :meth:`set_random_state` to set a custom state into the random generator (fetched from :meth:`get_random_state` in a previous run, for instance) .. method:: reseed_random(seed) The :meth:`reseed_random` function allows to load a chosen seed into the random generator. Custom :class:`BaseFuzzyAttribute` subclasses **SHOULD** use :obj:`factory.fuzzy._random` as a randomness source; this ensures that data they generate can be regenerated using the simple state from :meth:`get_random_state`. factory_boy-2.8.1/docs/ideas.rst000066400000000000000000000006401302510513700166060ustar00rootroot00000000000000Ideas ===== This is a list of future features that may be incorporated into factory_boy: * When a :class:`Factory` is built or created, pass the calling context throughout the calling chain instead of custom solutions everywhere * Define a proper set of rules for the support of third-party ORMs * Properly evaluate nested declarations (e.g ``factory.fuzzy.FuzzyDate(start_date=factory.SelfAttribute('since'))``) factory_boy-2.8.1/docs/index.rst000066400000000000000000000004371302510513700166340ustar00rootroot00000000000000.. include:: ../README.rst Contents, indices and tables ---------------------------- .. toctree:: :maxdepth: 2 introduction reference orms recipes fuzzy examples internals changelog ideas * :ref:`genindex` * :ref:`modindex` * :ref:`search` factory_boy-2.8.1/docs/internals.rst000066400000000000000000000000241302510513700175140ustar00rootroot00000000000000Internals ========= factory_boy-2.8.1/docs/introduction.rst000066400000000000000000000222521302510513700202450ustar00rootroot00000000000000Introduction ============ The purpose of factory_boy is to provide a default way of getting a new instance, while still being able to override some fields on a per-call basis. .. note:: This section will drive you through an overview of factory_boy's feature. New users are advised to spend a few minutes browsing through this list of useful helpers. Users looking for quick helpers may take a look at :doc:`recipes`, while those needing detailed documentation will be interested in the :doc:`reference` section. Basic usage ----------- Factories declare a set of attributes used to instantiate an object, whose class is defined in the ``class Meta``'s ``model`` attribute: - Subclass ``factory.Factory`` (or a more suitable subclass) - Add a ``class Meta:`` block - Set its ``model`` attribute to the target class - Add defaults for keyword args to pass to the associated class' ``__init__`` method .. code-block:: python import factory from . import base class UserFactory(factory.Factory): class Meta: model = base.User firstname = "John" lastname = "Doe" You may now get ``base.User`` instances trivially: .. code-block:: pycon >>> john = UserFactory() It is also possible to override the defined attributes by passing keyword arguments to the factory: .. code-block:: pycon >>> jack = UserFactory(firstname="Jack") A given class may be associated to many :class:`~factory.Factory` subclasses: .. code-block:: python class EnglishUserFactory(factory.Factory): class Meta: model = base.User firstname = "John" lastname = "Doe" lang = 'en' class FrenchUserFactory(factory.Factory): class Meta: model = base.User firstname = "Jean" lastname = "Dupont" lang = 'fr' .. code-block:: pycon >>> EnglishUserFactory() >>> FrenchUserFactory() Sequences --------- When a field has a unique key, each object generated by the factory should have a different value for that field. This is achieved with the :class:`~factory.Sequence` declaration: .. code-block:: python class UserFactory(factory.Factory): class Meta: model = models.User username = factory.Sequence(lambda n: 'user%d' % n) .. code-block:: pycon >>> UserFactory() >>> UserFactory() .. note:: For more complex situations, you may also use the :meth:`~factory.@sequence` decorator (note that ``self`` is not added as first parameter): .. code-block:: python class UserFactory(factory.Factory): class Meta: model = models.User @factory.sequence def username(n): 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() >>> # The LazyFunction can be overriden >>> LogFactory(timestamp=now - timedelta(days=1)) .. note:: For complex cases when you happen to write a specific function, the :meth:`~factory.@lazy_attribute` decorator should be more appropriate. LazyAttribute ------------- Some fields may be deduced from others, for instance the email based on the username. The :class:`~factory.LazyAttribute` handles such cases: it should receive a function taking the object being built and returning the value for the field: .. code-block:: python class UserFactory(factory.Factory): class Meta: model = models.User username = factory.Sequence(lambda n: 'user%d' % n) email = factory.LazyAttribute(lambda obj: '%s@example.com' % obj.username) .. code-block:: pycon >>> UserFactory() >>> # The LazyAttribute handles overridden fields >>> UserFactory(username='john') >>> # They can be directly overridden as well >>> UserFactory(email='doe@example.com') .. note:: As for :class:`~factory.Sequence`, a :meth:`~factory.@lazy_attribute` decorator is available: .. code-block:: python class UserFactory(factory.Factory): class Meta: model = models.User username = factory.Sequence(lambda n: 'user%d' % n) @factory.lazy_attribute def email(self): return '%s@example.com' % self.username Inheritance ----------- Once a "base" factory has been defined for a given class, alternate versions can be easily defined through subclassing. The subclassed :class:`~factory.Factory` will inherit all declarations from its parent, and update them with its own declarations: .. code-block:: python class UserFactory(factory.Factory): class Meta: model = base.User firstname = "John" lastname = "Doe" group = 'users' class AdminFactory(UserFactory): admin = True group = 'admins' .. code-block:: pycon >>> user = UserFactory() >>> user >>> user.group 'users' >>> admin = AdminFactory() >>> admin >>> admin.group # The AdminFactory field has overridden the base field 'admins' Any argument of all factories in the chain can easily be overridden: .. code-block:: pycon >>> super_admin = AdminFactory(group='superadmins', lastname="Lennon") >>> super_admin >>> super_admin.group # Overridden at call time 'superadmins' Non-kwarg arguments ------------------- Some classes take a few, non-kwarg arguments first. This is handled by the :data:`~factory.FactoryOptions.inline_args` attribute: .. code-block:: python class MyFactory(factory.Factory): class Meta: model = MyClass inline_args = ('x', 'y') x = 1 y = 2 z = 3 .. code-block:: pycon >>> MyFactory(y=4) Altering a factory's behaviour: parameters and traits ----------------------------------------------------- Some classes are better described with a few, simple parameters, that aren't fields on the actual model. In that case, use a :attr:`~factory.Factory.Params` declaration: .. code-block:: python class RentalFactory(factory.Factory): class Meta: model = Rental begin = factory.fuzzy.FuzzyDate(start_date=datetime.date(2000, 1, 1)) end = factory.LazyAttribute(lambda o: o.begin + o.duration) class Params: duration = 12 .. code-block:: pycon >>> RentalFactory(duration=0) 2012-03-03> >>> RentalFactory(duration=10) 2012-12-26> When many fields should be updated based on a flag, use :class:`Traits ` instead: .. code-block:: python class OrderFactory(factory.Factory): status = 'pending' shipped_by = None shipped_on = None class Meta: model = Order class Params: shipped = factory.Trait( status='shipped', shipped_by=factory.SubFactory(EmployeeFactory), shipped_on=factory.LazyFunction(datetime.date.today), ) A trait is toggled by a single boolean value: .. code-block:: pycon >>> OrderFactory() >>> OrderFactory(shipped=True) Strategies ---------- All factories support two built-in strategies: * ``build`` provides a local object * ``create`` instantiates a local object, and saves it to the database. .. note:: For 1.X versions, the ``create`` will actually call ``AssociatedClass.objects.create``, as for a Django model. Starting from 2.0, :meth:`factory.Factory.create` simply calls ``AssociatedClass(**kwargs)``. You should use :class:`~factory.django.DjangoModelFactory` for Django models. When a :class:`~factory.Factory` includes related fields (:class:`~factory.SubFactory`, :class:`~factory.RelatedFactory`), the parent's strategy will be pushed onto related factories. Calling a :class:`~factory.Factory` subclass will provide an object through the default strategy: .. code-block:: python class MyFactory(factory.Factory): class Meta: model = MyClass .. code-block:: pycon >>> MyFactory.create() >>> MyFactory.build() >>> MyFactory() # equivalent to MyFactory.create() The default strategy can be changed by setting the ``class Meta`` :attr:`~factory.FactoryOptions.strategy` attribute. factory_boy-2.8.1/docs/logo.png000066400000000000000000000522061302510513700164420ustar00rootroot00000000000000PNG  IHDR1sBIT|d pHYs B(xtEXtSoftwarewww.inkscape.org< IDATxy%Gyy{oݭJU}/-6Vao`f=n?cl`،nc{ I,ff@HBjRUݺ9'#揈Ȍ̌̓sJ sϚy|o/[(!v?lxRoW ب>O<7}(Oj"lRxؔ_ [nG~rmz`J6lnJ-'㽷j[mN>F* )=?[mͶbJ O{M9!` @DHX ("zD"ӿI+|߹G }u!j[mem{GxD@!0DM@ڈz'~f}տj[m}6n(Za,=}+#^?:WV9}PT78zBgFw7]m)R#B!z`VCD2D@QI(#(٣@"ffD#@n5$>[VAoV1%F=8,HFI0bQ+TKx=!o@5<rة*3zU n=3|նl3)רjAdD@T1s% |Kw,U{AtGNTm9oOXs@@#v̠?05U{ P?PV{>|j[m1 !z=V偨GFve BXa樒mOT_.: XMXmT9AC@Dl)p=Pg QG.`55c7Y<<<D#A( L™m(ݽ8 0 VTjjj4uq9 D# aPW\I }~Cجn}h8vB:lE\&-**Rxw}Zɳ={ooqW95R-o{B.+jcBj+(ȸM""ҏAIS 6;Oݷy|+wqT»>9[ _u1E/+j'7 Bry # "x=ռRT f{wlH5HR3҅~qiu;?~3/d~~&3Iq8`hk޿ϋRTnwW|c[YZӧjeB_-BHAR@HQb1(tgE^磙SN, >u_/vޡ]睢ӧjF[[`YrHnS Dw)"((ez3G+'77#G//y OA:L?#Be>e o&uy_/,-[T73FAG@F!LpA@J};=V?t2讛>|xVƸ+t3 5/C;xp=jo_O#Lh@L\!n=.MGz#r<@D#I֚%!tEج?#wZ>ku_`u][EƑQ^=}q7B7R3N[G}U]3 $gmnluB03wqmTo׏ AHR?X ŁIlhBpy v 쟸[{9^ y$<#'A-w gSEڅH,۶r"^pgm5ln<{jr_pӗi*R!9YoO\!~DG$Zgh5pn3 `v:HlٌSGnx)N_kkC3G  ;KDwiߎoi4{\˄y!\v{Ddg71*]6quD:A.q"EBQ<(pMܖ/!x`;c,"h9dDpׯǸmvlm875 $:woYIܟ}%YZ us'׮a: đQ+F"NW)<+.fYBXlv "8UA 1 ΐv|o|z%̜ՊWK`%vI%v]Z teg?AreD˰VCq˖Om^"S~JJPpe@5 E"TlJ@BPn E0'ZMϒu/y% 4%@wifNh4 :K\i׼&?ae]#ȸIQV'^3mZMre*@JxJL ÎTw){- ,びO N~3dMgp,Y r~<3l? <9hK"hh1t81Zܵsf;P聨A/vjqVLh;f`b,|u~@~ؚ2H\!X~ $LTǛl*BpOΌhC B$NP-0Џu#Hu: %.HbE0%~ ,]gݛ ay@2nf "[ [1YdR5_tok8.#!VQ!7SxFN@At0Bhyy]J@7^?T/4j۾u/?2J@:$u %k1 ܚ5[z]'H^wf&[6ۍz"!N3;-PV2>_ɉiOTxy@B^跔,_HE۷wݭfwyTG&0$LI KMo]='HQnZzZ=\ڽm򏧦 Syjfg< <@bIzMK]F`&[J_q|'-hWH";m\30vWu]WŮP@[*$uZtiE`{5 YBNy,N|@K@*(~_Ru"A"V]!R i\!昑S( yyP$xiVUx YJS%.rpDMSx rxb/@~k*<]?;_n@T+$#?~X+.rx@*_ȦJ@zP_E qQlZၠPP L0G'F:uz4ti_J9D|@B4<`Pp,7uZNWF#3UtFZ*QۊIzb"' ťU>(q'c=B<@*,LM⬎ϙL| .TjI.{@=]=Sm<5qaq۽ӕ Em UT-l+!9T$PuH#q'!Oc×nXĮ m7>I06(P2+$M|<"BQ ˍf ъO9RͧN+TQM:<#D6:W{z$ݮ3{]g١Y'8pYynn/#߽ձKR&$~XB->BN>41޼{7<;EԓiЮ6DDd\ȸB#]!WHIߟR%*4< ) ʈ@PU+oŹ!y`b.K-tREAg]+%v;︥;9בZEV(G =xĹ5e'ZNw6ߦ*~^~1ER֣ K=~>pĺ]mX;s& +,o+&]}x Ba괏(JPRQ$ څJ\HWHRa)Lr);_2szj8UB>vjvjJRUrPAYi8uS{RJEp̙?ϟ<7:%wbi>"H9֍ 7U]hK~EF*-dj1Aq%(X*_NiGCM^VZN|R_Ji_P>3;y,wDIQ$Hb쑃>qj,>¿+ԔO^WQ5>!0'|>>< UK-n׉Z<}+|*.vbaTS=6֬R xR@=_30Y(Ig$)B>>j<"!hf-db< 9 IDAT'w?~;T-1j'5b*v}b>։z|fݚ5Pģ$>@cqV#T+V+;5Z=>(xJ ^M𡷧Ep?7q6 .uQ/~Bn͚XHeK)H'>@J:2.MY| ] EP5:N:94iC{\JfvdYK'ozƎK 37[/'O(Ĵu|fn@z(]JY"@TF閛>Rz0>aqSz]w:$@A|@KѹAm#ݾ-/z1yD_?G @kӤeb=;ĩ:x7<j4RW)2uFϒJ >-dkpD*i`(_Υ@%oZ5>,9K)i֬ V<[~NHI3⿊m>FSp<@_|@@hi|R#uYma K-2xVRʥH6\ctrhy|wi=?1+:7;z@Rse<x@wУYX GClyyJ(R"?|\sO/QƝ7#Ri5 <] p(#J)bzZ@O]JnͪLkJN~|SVLQ*4])}<ɡf]^EUF2;:AjNPU`ye< To!J-zxh=qs?o>sf\Fv=1yBdRy<EmZH.Y$[3 ,B0JxRݥ=vs<}Zǵ{O??j!Nvj*Ri_&)@>њǤ4:-~ NnDW(; $<~$͹WK% ̯Hw)eT饋z](QЃRUp)e~Lf >c#JFR158K)%ENVI\'/d}x+dHfuZn˹F|ŕؚh+&(*SZE n?V(+^z}<qxԑiZQ- VLԨȥN+QK[q2pԞix4\L@ 5 0 +rFkfw+&f/}r[JaITm3@?><aVz@hD)D U|B%׋+5ku.fwbEbJ>/o0p| n| ]jr|>@Ѫe\I ťUz]JYeWHHR;vX7]mJ%pW$ ("ٴEI|g^Y$gȻBIЫ<NC(LVp+$=}-G%RKM":-L- P *LDOG׎p]N wWNv??}v/޵:Ɣ=#U1"0x!+e"@N9 ,tkR =UKrk,$3#pHڑɍwQ(vH((꟭xN<Ƨ:Uye  qnx@J3HRIP G@T @TDM4;qrFS"&O!*VYA~#_>йӍveӐsAA/k+s`kv6_6PZP j⡓e Kdm<ux'kO,X5<;n/{Hc#Ó%m̡LO,*3VhlM=׏c>v>*(L_@Vi f6ʈP#*naJG5/=~sa2![q\; Ӥe1dvJ-ڜc@!<~rWr9BL}П%M049SOϦ[nXY=[1-wkV:{ w=Z(0^gEbJBeRg}#`bʞY'31Tf?S&m:8Z3}͜NX^|>Q*>ˆ%0gŶf]VL%[_N'k0~$O1T{,~PU;g_=Y?P%F6a9GS>|!; T|P Y?P^(6B$K{b܊`k;tOR5S:?r6i2%Z4d֬#O +m;~jx`]|(+T" + ek֣|ɛTJ=r^sHV+&߈bOA$5z(z D~E y tTW(['B;{F[JfʷV$KS?/l_RM8-koe<7)])ksRq 3:@֬Wvk;?o~a!C#Wýb򋠺{&-l"a }N) zC*MN*&ByCK֬<6[k k?20ӨRK%""dָ@~NyޣŰF=s0b*%ݍrk]unsEq7P{p?`=9 ڶ_{7{1qt-lv!lbPJCʌ2m͚_2[1uhyU"D8Ӥ೫)i_Q^ui (@g|L 0y])^g1Y<\ȹBj9pzjc0Ḇ=UϹo(}T=Ki8e7}frHjZ+!;y3#0 F]ģ@§O@ 5J@v+Oq*5{61zƗ_Z_ qn(8-{n\=a< :k= G5ry ә"03b?|ӤH3Mk~?oHF`Sin /_C*=8|LjRfe̚&1UN E<˭'oZ:ە(SZⒺ$eiT~ISJTz-|/Oo}J']\ey93.(-#A4q5 ZTjHHeFiv)W֕g45qe k+$]Fu{ťw+r@BSG݊\tWcDqg5?KgO*ϬK7KS,PMIT*B橈d z 욈l a*B*(VLV'lPz} O X6B+%7J>'{Z- O_u[~?%e]-k quJ\!WHψ~6^]cz-rBXqО5k"" EE=3:REv^:c_=d{6*Z-8;;97޻fّݕR^%tiʀ<7 (硱9m.4H֧r\()e-%)RRBqQ53Y bjl bBSe])^"aSi/SX ON57dXuH?fJ,RX@XD@gz>hPXkaӭv$#偦z$xn%?+aޓ~s Iy26'K-`!Ĺ?o߻wKXtm go*:aWQ[d8UyEOL(EɲW^iC-*eHQ`؋Ш'; Zo#TtU[XX80s]\ mїDtkJȁy ejy@=IsNWh[>bS=(݊) yB_W׸DuH~ G([7pyg>U\vvΰTügX'REv) R%VWHS9#ٚVL<VLcDQ_uGx:||~sDJq3u s]Ngl.7[j|YpD`\!9Ts[<@ Dn- E <c7Ϥo}>e|^S'-Xyx ڗb&rRȋ N.X? &z{F#Ee#Mi( }ccy],8KޗC/f=vjڱO},z {"d)#ZwPygPJR=L38ה.T$ETB"mߦ[~IFv)e/=hK-8j0@? ])N !NZdzv׸KݶqV|?Uf҃l:Z^W{__*J{sPRJU*f /V6K$<`Dٖ{bޚ 2/rg b1@Q=;Fj_6@glEnWG)ÈHIgJZ,tV"Tb A;#"׌_4;ZRK/F2Ƨ!=4Hȡy g#BDy.$Cb@\25h@/j &Jm}=wqW<> 2K7[x8<)EK)G@!+xW_/jﵛGN>xW{L$ s22_LD+Ķᎇe⁔+4FxB@*Sb @d](]O? #2.P]\#z5'O=t#q;Z FpPF@FRhE5Bi@#Z Pϻj_~ tj: )%@&u@hZt <^I[Ttr#c =m)Z4 c;!:ERnm$tiTPP(3 [ԟ -e3+$/R!*B$)AnrPi]!Y,@7/f(~C (^ֈR҃VxTЛ vwt+Lci*ʈ@2!p{jO}!DDJ9’tJWH sxxg4Pe"Dz s:9Ky@$(!iS%I:BvDeT؈Q4+)]ƟE LO,fICxf+5E*,Q2f}h44=o^A`f6DeAK [z]6WxO'6]K/?z*R)i3+̉2Bꘈ$)AO᳏ Xjfb l=@Ay' .a]% EY85xx qԪaJQd {pp|k=fYEQ'9FBb5~ry@P9x<)LBR%Үu[)y@P PݢحY4 n͚8>,dyR@XjZF2Ž>.4<1?if!q%bWh<$ z}5IM,_(INp$tLCk6lTXX v԰3-2?L@m@{,T+R<`A=ΔlQPaiؔ 8U+ RVR6ˆ> ';\qr6UB ٥F1'x>0W5VL0[1!.- yzG͞^(^?yJ'RiSe2[jQ)V*@&_(@\߭LO/@0<y~d&FFF_ɗQI!v5\=J1u8gO|k5!G8kdXvD5OxHxM@ *K#}J J|2bYE@2?>ٚE|֬#P <>) <)nk_#C$Nwn>SOXl A)"s w@Yݥ|@Ҕ+7> )`x M<x\!H"ӧxt)JF5x$"(#NV:Krkw?K,g C#d@@.Uh{pryxͼQ) ӣG+|?G& >9bT~<.}슂AxCaR7 cN|M0p|0@Bܹ6@)U0). ^p իJy`)U9*-L-* s,(.R L"73NBatV-ؚ7_&qx`zHd)p%K)+ldmnxէFpE`O*VBy v|<$z-%c=)B\Wg'cWLmTG*щUǍv+xjK)ه̐Y؝'R@0zq}<O"{Rm+1 !nMɲ}>!Q]T]+^w^[?`<`]3ؔt./؟|Ӏ`\wIiE72quQ)qB Th}B8"^p߅`mrjjU,|<xR>=xN# d/P<"8v\ZڀG UEe/U y +]?~8Cv`@tLOڂ^3< HzH!iFNgN&!ťƆYyqWhxȻBozx u ϩ = ]ߪ'C +ojDc&vF]ՕTBTDj\좜YU/$3 N6N`$<`G(mʅN("xtyTjє|30Iּ GwYQ &0-tK-`=1Qr</ty@l.XW(t:Ayhq[>V)>"pyie ٪DǥE &N`KwvԱȈ@8?Y?@T}4[FBv$,YO7> 8B/׋)=E9!#$>$2#8u,X 1hRLK)_?`+H$BcR .֬}yy@_'}G G1?"Xcdq4'j{.U*KrT m"UAdDx!]X׀q^}F<< %@|!xBdH-で֬+0B6HF;$4 NR<0֨yqyi3((@z}-hAg+[1yy`+Z>@lYP>< qd image/svg+xml factory_boy-2.8.1/docs/make.bat000066400000000000000000000106471302510513700164040ustar00rootroot00000000000000@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=_build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. singlehtml to make a single large HTML file echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages echo. changes to make an overview over all changed/added/deprecated items echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\FactoryBoy.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\FactoryBoy.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp if errorlevel 1 exit /b 1 echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex if errorlevel 1 exit /b 1 echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text if errorlevel 1 exit /b 1 echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man if errorlevel 1 exit /b 1 echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes if errorlevel 1 exit /b 1 echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck if errorlevel 1 exit /b 1 echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest if errorlevel 1 exit /b 1 echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) :end factory_boy-2.8.1/docs/orms.rst000066400000000000000000000350301302510513700165020ustar00rootroot00000000000000Using factory_boy with ORMs =========================== .. currentmodule:: factory factory_boy provides custom :class:`Factory` subclasses for various ORMs, adding dedicated features. Django ------ .. currentmodule:: factory.django The first versions of factory_boy were designed specifically for Django, but the library has now evolved to be framework-independent. Most features should thus feel quite familiar to Django users. The :class:`DjangoModelFactory` subclass """"""""""""""""""""""""""""""""""""""""" All factories for a Django :class:`~django.db.models.Model` should use the :class:`DjangoModelFactory` base class. .. class:: DjangoModelFactory(factory.Factory) Dedicated class for Django :class:`~django.db.models.Model` factories. This class provides the following features: * The :attr:`~factory.FactoryOptions.model` attribute also supports the ``'app.Model'`` syntax * :func:`~factory.Factory.create()` uses :meth:`Model.objects.create() ` * When using :class:`~factory.RelatedFactory` or :class:`~factory.PostGeneration` attributes, the base object will be :meth:`saved ` once all post-generation hooks have run. .. note:: With Django versions 1.8.0 to 1.8.3, it was no longer possible to call ``.build()`` on a factory if this factory used a :class:`~factory.SubFactory` pointing to another model: Django refused to set a :class:`~djang.db.models.ForeignKey` to an unsaved :class:`~django.db.models.Model` instance. See https://code.djangoproject.com/ticket/10811 and https://code.djangoproject.com/ticket/25160 for details. .. class:: DjangoOptions(factory.base.FactoryOptions) The ``class Meta`` on a :class:`~DjangoModelFactory` supports extra parameters: .. attribute:: database .. versionadded:: 2.5.0 All queries to the related model will be routed to the given database. It defaults to ``'default'``. .. attribute:: django_get_or_create .. versionadded:: 2.4.0 Fields whose name are passed in this list will be used to perform a :meth:`Model.objects.get_or_create() ` instead of the usual :meth:`Model.objects.create() `: .. code-block:: python class UserFactory(factory.django.DjangoModelFactory): class Meta: model = 'myapp.User' # Equivalent to ``model = myapp.models.User`` django_get_or_create = ('username',) username = 'john' .. code-block:: pycon >>> User.objects.all() [] >>> UserFactory() # Creates a new user >>> User.objects.all() [] >>> UserFactory() # Fetches the existing user >>> User.objects.all() # No new user! [] >>> UserFactory(username='jack') # Creates another user >>> User.objects.all() [, ] Extra fields """""""""""" .. class:: FileField Custom declarations for :class:`django.db.models.FileField` .. method:: __init__(self, from_path='', from_file='', data=b'', filename='example.dat') :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, unless ``filename`` is also provided. :param func from_func: Use function that returns a file object :param bytes data: Use the provided bytes as file contents :param str filename: The filename for the FileField .. note:: If the value ``None`` was passed for the :class:`FileField` field, this will disable field generation: .. code-block:: python class MyFactory(factory.django.DjangoModelFactory): class Meta: model = models.MyModel the_file = factory.django.FileField(filename='the_file.dat') .. code-block:: pycon >>> MyFactory(the_file__data=b'uhuh').the_file.read() b'uhuh' >>> MyFactory(the_file=None).the_file None .. class:: ImageField Custom declarations for :class:`django.db.models.ImageField` .. method:: __init__(self, from_path='', from_file='', filename='example.jpg', width=100, height=100, color='green', format='JPEG') :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 :param func from_func: Use function that returns a file object :param str filename: The filename for the ImageField :param int width: The width of the generated image (default: ``100``) :param int height: The height of the generated image (default: ``100``) :param str color: The color of the generated image (default: ``'green'``) :param str format: The image format (as supported by PIL) (default: ``'JPEG'``) .. note:: If the value ``None`` was passed for the :class:`FileField` field, this will disable field generation: .. note:: Just as Django's :class:`django.db.models.ImageField` requires the Python Imaging Library, this :class:`ImageField` requires it too. .. code-block:: python class MyFactory(factory.django.DjangoModelFactory): class Meta: model = models.MyModel the_image = factory.django.ImageField(color='blue') .. code-block:: pycon >>> MyFactory(the_image__width=42).the_image.width 42 >>> MyFactory(the_image=None).the_image None Disabling signals """"""""""""""""" Signals are often used to plug some custom code into external components code; for instance to create ``Profile`` objects on-the-fly when a new ``User`` object is saved. This may interfere with finely tuned :class:`factories `, which would create both using :class:`~factory.RelatedFactory`. To work around this problem, use the :meth:`mute_signals()` decorator/context manager: .. method:: mute_signals(signal1, ...) Disable the list of selected signals when calling the factory, and reactivate them upon leaving. .. code-block:: python # foo/factories.py import factory import factory.django from . import models from . import signals @factory.django.mute_signals(signals.pre_save, signals.post_save) class FooFactory(factory.django.DjangoModelFactory): class Meta: model = models.Foo # ... def make_chain(): with factory.django.mute_signals(signals.pre_save, signals.post_save): # pre_save/post_save won't be called here. return SomeFactory(), SomeOtherFactory() Mogo ---- .. currentmodule:: factory.mogo factory_boy supports `Mogo`_-style models, through the :class:`MogoFactory` class. `Mogo`_ is a wrapper around the ``pymongo`` library for MongoDB. .. _Mogo: https://github.com/joshmarshall/mogo .. class:: MogoFactory(factory.Factory) Dedicated class for `Mogo`_ models. This class provides the following features: * :func:`~factory.Factory.build()` calls a model's ``new()`` method * :func:`~factory.Factory.create()` builds an instance through ``new()`` then saves it. MongoEngine ----------- .. currentmodule:: factory.mongoengine factory_boy supports `MongoEngine`_-style models, through the :class:`MongoEngineFactory` class. `mongoengine`_ is a wrapper around the ``pymongo`` library for MongoDB. .. _mongoengine: http://mongoengine.org/ .. class:: MongoEngineFactory(factory.Factory) Dedicated class for `MongoEngine`_ models. This class provides the following features: * :func:`~factory.Factory.build()` calls a model's ``__init__`` method * :func:`~factory.Factory.create()` builds an instance through ``__init__`` then saves it. .. note:: If the :attr:`associated class ` attribute. .. _SQLAlchemy: http://www.sqlalchemy.org/ .. class:: SQLAlchemyModelFactory(factory.Factory) Dedicated class for `SQLAlchemy`_ models. This class provides the following features: * :func:`~factory.Factory.create()` uses :meth:`sqlalchemy.orm.session.Session.add` .. class:: SQLAlchemyOptions(factory.base.FactoryOptions) In addition to the usual parameters available in :class:`class Meta `, a :class:`SQLAlchemyModelFactory` also supports the following settings: .. attribute:: sqlalchemy_session 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 from sqlalchemy import Column, Integer, Unicode, create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import scoped_session, sessionmaker engine = create_engine('sqlite://') session = scoped_session(sessionmaker(bind=engine)) Base = declarative_base() class User(Base): """ A SQLAlchemy simple model class who represents a user """ __tablename__ = 'UserTable' id = Column(Integer(), primary_key=True) name = Column(Unicode(20)) Base.metadata.create_all(engine) import factory class UserFactory(factory.alchemy.SQLAlchemyModelFactory): class Meta: model = User sqlalchemy_session = session # the SQLAlchemy session object id = factory.Sequence(lambda n: n) name = factory.Sequence(lambda n: u'User %d' % n) .. code-block:: pycon >>> session.query(User).all() [] >>> UserFactory() >>> session.query(User).all() [] Managing sessions """"""""""""""""" Since `SQLAlchemy`_ is a general purpose library, there is no "global" session management system. The most common pattern when working with unit tests and ``factory_boy`` is to use `SQLAlchemy`_'s :class:`sqlalchemy.orm.scoping.scoped_session`: * The test runner configures some project-wide :class:`~sqlalchemy.orm.scoping.scoped_session` * Each :class:`~SQLAlchemyModelFactory` subclass uses this :class:`~sqlalchemy.orm.scoping.scoped_session` as its :attr:`~SQLAlchemyOptions.sqlalchemy_session` * The :meth:`~unittest.TestCase.tearDown` method of tests calls :meth:`Session.remove ` to reset the session. .. note:: See the excellent :ref:`SQLAlchemy guide on scoped_session ` for details of :class:`~sqlalchemy.orm.scoping.scoped_session`'s usage. The basic idea is that declarative parts of the code (including factories) need a simple way to access the "current session", but that session will only be created and configured at a later point. The :class:`~sqlalchemy.orm.scoping.scoped_session` handles this, by virtue of only creating the session when a query is sent to the database. Here is an example layout: - A global (test-only?) file holds the :class:`~sqlalchemy.orm.scoping.scoped_session`: .. code-block:: python # myprojet/test/common.py from sqlalchemy import orm Session = orm.scoped_session(orm.sessionmaker()) - All factory access it: .. code-block:: python # myproject/factories.py import factory import factory.alchemy from . import models from .test import common class UserFactory(factory.alchemy.SQLAlchemyModelFactory): class Meta: model = models.User # Use the not-so-global scoped_session # Warning: DO NOT USE common.Session()! sqlalchemy_session = common.Session name = factory.Sequence(lambda n: "User %d" % n) - The test runner configures the :class:`~sqlalchemy.orm.scoping.scoped_session` when it starts: .. code-block:: python # myproject/test/runtests.py import sqlalchemy from . import common def runtests(): engine = sqlalchemy.create_engine('sqlite://') # It's a scoped_session, and now is the time to configure it. common.Session.configure(bind=engine) run_the_tests - :class:`test cases ` use this ``scoped_session``, and clear it after each test (for isolation): .. code-block:: python # myproject/test/test_stuff.py import unittest from . import common class MyTest(unittest.TestCase): def setUp(self): # Prepare a new, clean session self.session = common.Session() def test_something(self): u = factories.UserFactory() self.assertEqual([u], self.session.query(User).all()) def tearDown(self): # Rollback the session => no changes to the database self.session.rollback() # Remove it, so that the next test gets a new Session() common.Session.remove() factory_boy-2.8.1/docs/recipes.rst000066400000000000000000000360641302510513700171640ustar00rootroot00000000000000Common recipes ============== .. note:: Most recipes below take on Django model examples, but can also be used on their own. Dependent objects (ForeignKey) ------------------------------ When one attribute is actually a complex field (e.g a :class:`~django.db.models.ForeignKey` to another :class:`~django.db.models.Model`), use the :class:`~factory.SubFactory` declaration: .. code-block:: python # models.py class User(models.Model): first_name = models.CharField() group = models.ForeignKey(Group) # factories.py import factory from . import models class UserFactory(factory.django.DjangoModelFactory): class Meta: model = models.User first_name = factory.Sequence(lambda n: "Agent %03d" % n) group = factory.SubFactory(GroupFactory) Choosing from a populated table ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If the target of the :class:`~django.db.models.ForeignKey` should be chosen from a pre-populated table (e.g :class:`django.contrib.contenttypes.models.ContentType`), simply use a :class:`factory.Iterator` on the chosen queryset: .. code-block:: python import factory, factory.django from . import models class UserFactory(factory.django.DjangoModelFactory): class Meta: model = models.User language = factory.Iterator(models.Language.objects.all()) Here, ``models.Language.objects.all()`` won't be evaluated until the first call to ``UserFactory``; thus avoiding DB queries at import time. Reverse dependencies (reverse ForeignKey) ----------------------------------------- When a related object should be created upon object creation (e.g a reverse :class:`~django.db.models.ForeignKey` from another :class:`~django.db.models.Model`), use a :class:`~factory.RelatedFactory` declaration: .. code-block:: python # models.py class User(models.Model): pass class UserLog(models.Model): user = models.ForeignKey(User) action = models.CharField() # factories.py class UserFactory(factory.django.DjangoModelFactory): class Meta: model = models.User log = factory.RelatedFactory(UserLogFactory, 'user', action=models.UserLog.ACTION_CREATE) When a :class:`UserFactory` is instantiated, factory_boy will call ``UserLogFactory(user=that_user, action=...)`` just before returning the created ``User``. 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``. A typical way to create those profiles was to hook a post-save signal to the ``User`` model. factory_boy allows to define attributes of such profiles dynamically when creating a ``User``: .. code-block:: python class ProfileFactory(factory.django.DjangoModelFactory): class Meta: model = my_models.Profile title = 'Dr' # We pass in profile=None to prevent UserFactory from creating another profile # (this disables the RelatedFactory) user = factory.SubFactory('app.factories.UserFactory', profile=None) class UserFactory(factory.django.DjangoModelFactory): class Meta: model = auth_models.User username = factory.Sequence(lambda n: "user_%d" % n) # We pass in 'user' to link the generated Profile to our just-generated User # This will call ProfileFactory(user=our_new_user), thus skipping the SubFactory. profile = factory.RelatedFactory(ProfileFactory, 'user') @classmethod def _generate(cls, create, attrs): """Override the default _generate() to disable the post-save signal.""" # Note: If the signal was defined with a dispatch_uid, include that in both calls. post_save.disconnect(handler_create_user_profile, auth_models.User) user = super(UserFactory, cls)._generate(create, attrs) post_save.connect(handler_create_user_profile, auth_models.User) return user .. OHAI_VIM:* .. code-block:: pycon >>> u = UserFactory(profile__title=u"Lord") >>> u.get_profile().title u"Lord" Such behaviour can be extended to other situations where a signal interferes with factory_boy related factories. .. note:: When any :class:`~factory.RelatedFactory` or :class:`~factory.post_generation` attribute is defined on the :class:`~factory.django.DjangoModelFactory` subclass, a second ``save()`` is performed *after* the call to ``_create()``. Code working with signals should thus override the :meth:`~factory.Factory._generate` method. Simple Many-to-many relationship -------------------------------- Building the adequate link between two models depends heavily on the use case; factory_boy doesn't provide a "all in one tools" as for :class:`~factory.SubFactory` or :class:`~factory.RelatedFactory`, users will have to craft their own depending on the model. The base building block for this feature is the :class:`~factory.post_generation` hook: .. code-block:: python # models.py class Group(models.Model): name = models.CharField() class User(models.Model): name = models.CharField() groups = models.ManyToManyField(Group) # factories.py class GroupFactory(factory.django.DjangoModelFactory): class Meta: model = models.Group name = factory.Sequence(lambda n: "Group #%s" % n) class UserFactory(factory.django.DjangoModelFactory): class Meta: model = models.User name = "John Doe" @factory.post_generation def groups(self, create, extracted, **kwargs): if not create: # Simple build, do nothing. return if extracted: # A list of groups were passed in, use them for group in extracted: self.groups.add(group) .. OHAI_VIM** When calling ``UserFactory()`` or ``UserFactory.build()``, no group binding will be created. But when ``UserFactory.create(groups=(group1, group2, group3))`` is called, the ``groups`` declaration will add passed in groups to the set of groups for the user. Many-to-many relation with a 'through' -------------------------------------- If only one link is required, this can be simply performed with a :class:`RelatedFactory`. If more links are needed, simply add more :class:`RelatedFactory` declarations: .. code-block:: python # models.py class User(models.Model): name = models.CharField() class Group(models.Model): name = models.CharField() members = models.ManyToManyField(User, through='GroupLevel') class GroupLevel(models.Model): user = models.ForeignKey(User) group = models.ForeignKey(Group) rank = models.IntegerField() # factories.py class UserFactory(factory.django.DjangoModelFactory): class Meta: model = models.User name = "John Doe" class GroupFactory(factory.django.DjangoModelFactory): class Meta: model = models.Group name = "Admins" class GroupLevelFactory(factory.django.DjangoModelFactory): class Meta: model = models.GroupLevel user = factory.SubFactory(UserFactory) group = factory.SubFactory(GroupFactory) rank = 1 class UserWithGroupFactory(UserFactory): membership = factory.RelatedFactory(GroupLevelFactory, 'user') class UserWith2GroupsFactory(UserFactory): membership1 = factory.RelatedFactory(GroupLevelFactory, 'user', group__name='Group1') membership2 = factory.RelatedFactory(GroupLevelFactory, 'user', group__name='Group2') Whenever the ``UserWithGroupFactory`` is called, it will, as a post-generation hook, call the ``GroupLevelFactory``, passing the generated user as a ``user`` field: 1. ``UserWithGroupFactory()`` generates a ``User`` instance, ``obj`` 2. It calls ``GroupLevelFactory(user=obj)`` 3. It returns ``obj`` When using the ``UserWith2GroupsFactory``, that behavior becomes: 1. ``UserWith2GroupsFactory()`` generates a ``User`` instance, ``obj`` 2. It calls ``GroupLevelFactory(user=obj, group__name='Group1')`` 3. It calls ``GroupLevelFactory(user=obj, group__name='Group2')`` 4. It returns ``obj`` Copying fields to a SubFactory ------------------------------ When a field of a related class should match one of the container: .. code-block:: python # models.py class Country(models.Model): name = models.CharField() lang = models.CharField() class User(models.Model): name = models.CharField() lang = models.CharField() country = models.ForeignKey(Country) class Company(models.Model): name = models.CharField() owner = models.ForeignKey(User) country = models.ForeignKey(Country) Here, we want: - The User to have the lang of its country (``factory.SelfAttribute('country.lang')``) - The Company owner to live in the country of the company (``factory.SelfAttribute('..country')``) .. code-block:: python # factories.py class CountryFactory(factory.django.DjangoModelFactory): class Meta: model = models.Country name = factory.Iterator(["France", "Italy", "Spain"]) lang = factory.Iterator(['fr', 'it', 'es']) class UserFactory(factory.django.DjangoModelFactory): class Meta: model = models.User name = "John" lang = factory.SelfAttribute('country.lang') country = factory.SubFactory(CountryFactory) class CompanyFactory(factory.django.DjangoModelFactory): class Meta: model = models.Company name = "ACME, Inc." country = factory.SubFactory(CountryFactory) owner = factory.SubFactory(UserFactory, country=factory.SelfAttribute('..country')) If the value of a field on the child factory is indirectly derived from a field on the parent factory, you will need to use LazyAttribute and poke the "factory_parent" attribute. This time, we want the company owner to live in a country neighboring the country of the company: .. code-block:: python class CompanyFactory(factory.django.DjangoModelFactory): class Meta: model = models.Company name = "ACME, Inc." country = factory.SubFactory(CountryFactory) owner = factory.SubFactory(UserFactory, country=factory.LazyAttribute(lambda o: get_random_neighbor(o.factory_parent.country))) Custom manager methods ---------------------- Sometimes you need a factory to call a specific manager method other then the default :meth:`Model.objects.create() ` method: .. code-block:: python class UserFactory(factory.DjangoModelFactory): class Meta: model = UserenaSignup username = "l7d8s" email = "my_name@example.com" password = "my_password" @classmethod def _create(cls, model_class, *args, **kwargs): """Override the default ``_create`` with our custom call.""" manager = cls._get_manager(model_class) # The default would use ``manager.create(*args, **kwargs)`` return manager.create_user(*args, **kwargs) Forcing the sequence counter ---------------------------- A common pattern with factory_boy is to use a :class:`factory.Sequence` declaration to provide varying values to attributes declared as unique. However, it is sometimes useful to force a given value to the counter, for instance to ensure that tests are properly reproductible. factory_boy provides a few hooks for this: Forcing the value on a per-call basis In order to force the counter for a specific :class:`~factory.Factory` instantiation, just pass the value in the ``__sequence=42`` parameter: .. code-block:: python class AccountFactory(factory.Factory): class Meta: model = Account uid = factory.Sequence(lambda n: n) name = "Test" .. code-block:: pycon >>> obj1 = AccountFactory(name="John Doe", __sequence=10) >>> obj1.uid # Taken from the __sequence counter 10 >>> obj2 = AccountFactory(name="Jane Doe") >>> obj2.uid # The base sequence counter hasn't changed 1 Resetting the counter globally If all calls for a factory must start from a deterministic number, use :meth:`factory.Factory.reset_sequence`; this will reset the counter to its initial value (as defined by :meth:`factory.Factory._setup_next_sequence`). .. code-block:: pycon >>> AccountFactory().uid 1 >>> AccountFactory().uid 2 >>> AccountFactory.reset_sequence() >>> AccountFactory().uid # Reset to the initial value 1 >>> AccountFactory().uid 2 It is also possible to reset the counter to a specific value: .. code-block:: pycon >>> AccountFactory.reset_sequence(10) >>> AccountFactory().uid 10 >>> AccountFactory().uid 11 This recipe is most useful in a :class:`~unittest.TestCase`'s :meth:`~unittest.TestCase.setUp` method. Forcing the initial value for all projects The sequence counter of a :class:`~factory.Factory` can also be set automatically upon the first call through the :meth:`~factory.Factory._setup_next_sequence` method; this helps when the objects's attributes mustn't conflict with pre-existing data. A typical example is to ensure that running a Python script twice will create non-conflicting objects, by setting up the counter to "max used value plus one": .. code-block:: python class AccountFactory(factory.django.DjangoModelFactory): class Meta: model = models.Account @classmethod def _setup_next_sequence(cls): try: return models.Accounts.objects.latest('uid').uid + 1 except models.Account.DoesNotExist: return 1 .. code-block:: pycon >>> Account.objects.create(uid=42, name="Blah") >>> AccountFactory.create() # Sets up the account number based on the latest uid Converting a factory's output to a dict --------------------------------------- In order to inject some data to, say, a REST API, it can be useful to fetch the factory's data as a dict. Internally, a factory will: 1. Merge declarations and overrides from all sources (class definition, call parameters, ...) 2. Resolve them into a dict 3. Pass that dict as keyword arguments to the model's ``build`` / ``create`` function In order to get a dict, we'll just have to swap the model; the easiest way is to use :meth:`factory.build`: .. code-block:: python class UserFactory(factory.django.DjangoModelFactory): class Meta: model = models.User first_name = factory.Sequence(lambda n: "Agent %03d" % n) username = factory.Faker('username') .. code-block:: pycon >>> factory.build(dict, FACTORY_CLASS=UserFactory) {'first_name': "Agent 001", 'username': 'john_doe'} factory_boy-2.8.1/docs/reference.rst000066400000000000000000001634301302510513700174660ustar00rootroot00000000000000Reference ========= .. currentmodule:: factory This section offers an in-depth description of factory_boy features. For internals and customization points, please refer to the :doc:`internals` section. The :class:`Factory` class -------------------------- Meta options """""""""""" .. class:: FactoryOptions .. versionadded:: 2.4.0 A :class:`Factory`'s behaviour can be tuned through a few settings. For convenience, they are declared in a single ``class Meta`` attribute: .. code-block:: python class MyFactory(factory.Factory): class Meta: model = MyObject abstract = False .. attribute:: model This optional attribute describes the class of objects to generate. If unset, it will be inherited from parent :class:`Factory` subclasses. .. versionadded:: 2.4.0 .. attribute:: abstract This attribute indicates that the :class:`Factory` subclass should not be used to generate objects, but instead provides some extra defaults. It will be automatically set to ``True`` if neither the :class:`Factory` subclass nor its parents define the :attr:`~FactoryOptions.model` attribute. .. warning:: This flag is reset to ``False`` when a :class:`Factory` subclasses another one if a :attr:`~FactoryOptions.model` is set. .. versionadded:: 2.4.0 .. attribute:: inline_args Some factories require non-keyword arguments to their :meth:`~object.__init__`. They should be listed, in order, in the :attr:`inline_args` attribute: .. code-block:: python class UserFactory(factory.Factory): class Meta: model = User inline_args = ('login', 'email') login = 'john' email = factory.LazyAttribute(lambda o: '%s@example.com' % o.login) firstname = "John" .. code-block:: pycon >>> UserFactory() >>> User('john', 'john@example.com', firstname="John") # actual call .. versionadded:: 2.4.0 .. attribute:: exclude 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 model 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:`exclude` 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): class Meta: model = Order exclude = ('now',) 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)) .. 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)) .. versionadded:: 2.4.0 .. attribute:: rename Sometimes, a model expects a field with a name already used by one of :class:`Factory`'s methods. In this case, the :attr:`rename` attributes allows to define renaming rules: the keys of the :attr:`rename` dict are those used in the :class:`Factory` declarations, and their values the new name: .. code-block:: python class ImageFactory(factory.Factory): # The model expects "attributes" form_attributes = ['thumbnail', 'black-and-white'] class Meta: model = Image rename = {'form_attributes': 'attributes'} .. versionadded: 2.6.0 .. attribute:: strategy Use this attribute to change the strategy used by a :class:`Factory`. The default is :data:`CREATE_STRATEGY`. Attributes and methods """""""""""""""""""""" .. class:: Factory **Class-level attributes:** .. attribute:: Meta .. attribute:: _meta .. versionadded:: 2.4.0 The :class:`FactoryOptions` instance attached to a :class:`Factory` class is available as a :attr:`_meta` attribute. .. attribute:: Params .. versionadded:: 2.7.0 The extra parameters attached to a :class:`Factory` are declared through a :attr:`Params` class. See :ref:`the "Parameters" section ` for more information. .. attribute:: _options_class .. versionadded:: 2.4.0 If a :class:`Factory` subclass needs to define additional, extra options, it has to provide a custom :class:`FactoryOptions` subclass. A pointer to that custom class should be provided as :attr:`_options_class` so that the :class:`Factory`-building metaclass can use it instead. **Base functions:** The :class:`Factory` class provides a few methods for getting objects; the usual way being to simply call the class: .. code-block:: pycon >>> UserFactory() # Calls UserFactory.create() >>> UserFactory(login='john') # Calls UserFactory.create(login='john') Under the hood, factory_boy will define the :class:`Factory` :meth:`~object.__new__` method to call the default :ref:`strategy ` of the :class:`Factory`. A specific strategy for getting instance can be selected by calling the adequate method: .. classmethod:: build(cls, **kwargs) Provides a new object, using the 'build' strategy. .. classmethod:: build_batch(cls, size, **kwargs) Provides a list of :obj:`size` instances from the :class:`Factory`, through the 'build' strategy. .. classmethod:: create(cls, **kwargs) Provides a new object, using the 'create' strategy. .. classmethod:: create_batch(cls, size, **kwargs) Provides a list of :obj:`size` instances from the :class:`Factory`, through the 'create' strategy. .. classmethod:: stub(cls, **kwargs) Provides a new stub .. classmethod:: stub_batch(cls, size, **kwargs) Provides a list of :obj:`size` stubs from the :class:`Factory`. .. classmethod:: generate(cls, strategy, **kwargs) Provide a new instance, with the provided :obj:`strategy`. .. classmethod:: generate_batch(cls, strategy, size, **kwargs) Provides a list of :obj:`size` instances using the specified strategy. .. classmethod:: simple_generate(cls, create, **kwargs) Provide a new instance, either built (``create=False``) or created (``create=True``). .. classmethod:: simple_generate_batch(cls, create, size, **kwargs) Provides a list of :obj:`size` instances, either built or created according to :obj:`create`. **Extension points:** A :class:`Factory` subclass may override a couple of class methods to adapt its behaviour: .. classmethod:: _adjust_kwargs(cls, **kwargs) .. OHAI_VIM** The :meth:`_adjust_kwargs` extension point allows for late fields tuning. It is called once keyword arguments have been resolved and post-generation items removed, but before the :attr:`~FactoryOptions.inline_args` extraction phase. .. code-block:: python class UserFactory(factory.Factory): @classmethod def _adjust_kwargs(cls, **kwargs): # Ensure ``lastname`` is upper-case. kwargs['lastname'] = kwargs['lastname'].upper() return kwargs .. OHAI_VIM** .. classmethod:: _setup_next_sequence(cls) This method will compute the first value to use for the sequence counter of this factory. It is called when the first instance of the factory (or one of its subclasses) is created. Subclasses may fetch the next free ID from the database, for instance. .. classmethod:: _build(cls, model_class, *args, **kwargs) .. OHAI_VIM* This class method is called whenever a new instance needs to be built. It receives the model class (provided to :attr:`~FactoryOptions.model`), and the positional and keyword arguments to use for the class once all has been computed. Subclasses may override this for custom APIs. .. classmethod:: _create(cls, model_class, *args, **kwargs) .. OHAI_VIM* The :meth:`_create` method is called whenever an instance needs to be created. It receives the same arguments as :meth:`_build`. Subclasses may override this for specific persistence backends: .. code-block:: python class BaseBackendFactory(factory.Factory): class Meta: abstract = True # Optional def _create(cls, model_class, *args, **kwargs): obj = model_class(*args, **kwargs) obj.save() return obj .. OHAI_VIM* .. classmethod:: _after_postgeneration(cls, obj, create, results=None) :arg object obj: The object just generated :arg bool create: Whether the object was 'built' or 'created' :arg dict results: Map of post-generation declaration name to call result The :meth:`_after_postgeneration` is called once post-generation declarations have been handled. Its arguments allow to handle specifically some post-generation return values, for instance. **Advanced functions:** .. classmethod:: reset_sequence(cls, value=None, force=False) :arg int value: The value to reset the sequence to :arg bool force: Whether to force-reset the sequence Allows to reset the sequence counter for a :class:`~factory.Factory`. The new value can be passed in as the ``value`` argument: .. code-block:: pycon >>> SomeFactory.reset_sequence(4) >>> SomeFactory._next_sequence 4 Since subclasses of a non-:attr:`abstract ` :class:`~factory.Factory` share the same sequence counter, special care needs to be taken when resetting the counter of such a subclass. By default, :meth:`reset_sequence` will raise a :exc:`ValueError` when called on a subclassed :class:`~factory.Factory` subclass. This can be avoided by passing in the ``force=True`` flag: .. code-block:: pycon >>> InheritedFactory.reset_sequence() Traceback (most recent call last): File "factory_boy/tests/test_base.py", line 179, in test_reset_sequence_subclass_parent SubTestObjectFactory.reset_sequence() File "factory_boy/factory/base.py", line 250, in reset_sequence "Cannot reset the sequence of a factory subclass. " ValueError: Cannot reset the sequence of a factory subclass. Please call reset_sequence() on the root factory, or call reset_sequence(forward=True). >>> InheritedFactory.reset_sequence(force=True) >>> This is equivalent to calling :meth:`reset_sequence` on the base factory in the chain. .. _parameters: Parameters """""""""" .. versionadded:: 2.7.0 Some models have many fields that can be summarized by a few parameters; for instance, a train with many cars — each complete with serial number, manufacturer, ...; or an order that can be pending/shipped/received, with a few fields to describe each step. When building instances of such models, a couple of parameters can be enough to determine all other fields; this is handled by the :class:`~Factory.Params` section of a :class:`Factory` declaration. Simple parameters ~~~~~~~~~~~~~~~~~ Some factories only need little data: .. code-block:: python class ConferenceFactory(factory.Factory): class Meta: model = Conference class Params: duration = 'short' # Or 'long' start_date = factory.fuzzy.FuzzyDate() end_date = factory.LazyAttribute( lambda o: o.start_date + datetime.timedelta(days=2 if o.duration == 'short' else 7) ) sprints_start = factory.LazyAttribute( lambda o: o.end_date - datetime.timedelta(days=0 if o.duration == 'short' else 1) ) .. code-block:: pycon >>> Conference(duration='short') >>> Conference(duration='long') Any simple parameter provided to the :class:`Factory.Params` section is available to the whole factory, but not passed to the final class (similar to the :attr:`~FactoryOptions.exclude` behavior). Traits ~~~~~~ .. class:: Trait(**kwargs) .. OHAI VIM** .. versionadded:: 2.7.0 A trait's parameters are the fields it should alter when enabled. For more complex situations, it is helpful to override a few fields at once: .. code-block:: python class OrderFactory(factory.Factory): class Meta: model = Order state = 'pending' shipped_on = None shipped_by = None class Params: shipped = factory.Trait( state='shipped', shipped_on=datetime.date.today(), shipped_by=factory.SubFactory(EmployeeFactory), ) Such a :class:`Trait` is activated or disabled by a single boolean field: .. code-block:: pycon >>> OrderFactory() Order(state='pending') >>> OrderFactory(shipped=True) A :class:`Trait` can be enabled/disabled by a :class:`Factory` subclass: .. code-block:: python class ShippedOrderFactory(OrderFactory): shipped = True Values set in a :class:`Trait` can be overridden by call-time values: .. code-block:: pycon >>> OrderFactory(shipped=True, shipped_on=last_year) :class:`Traits ` can be chained: .. code-block:: python class OrderFactory(factory.Factory): class Meta: model = Order # Can be pending/shipping/received state = 'pending' shipped_on = None shipped_by = None received_on = None received_by = None class Params: shipped = factory.Trait( state='shipped', shipped_on=datetime.date.today, shipped_by=factory.SubFactory(EmployeeFactory), ) received = factory.Trait( shipped=True, state='received', shipped_on=datetime.date.today - datetime.timedelta(days=4), received_on=datetime.date.today, received_by=factory.SubFactory(CustomerFactory), ) .. code-block:: pycon >>> OrderFactory(received=True) A :class:`Trait` might be overridden in :class:`Factory` subclasses: .. code-block:: python class LocalOrderFactory(OrderFactory): class Params: received = factory.Trait( shipped=True, state='received', shipped_on=datetime.date.today - datetime.timedelta(days=1), received_on=datetime.date.today, received_by=factory.SubFactory(CustomerFactory), ) .. code-block:: pycon >>> LocalOrderFactory(received=True) .. note:: When overriding a :class:`Trait`, the whole declaration **MUST** be replaced. .. _strategies: Strategies """""""""" factory_boy supports two main strategies for generating instances, plus stubs. .. data:: BUILD_STRATEGY The 'build' strategy is used when an instance should be created, but not persisted to any datastore. It is usually a simple call to the :meth:`~object.__init__` method of the :attr:`~FactoryOptions.model` class. .. data:: CREATE_STRATEGY The 'create' strategy builds and saves an instance into its appropriate datastore. This is the default strategy of factory_boy; it would typically instantiate an object, then save it: .. code-block:: pycon >>> obj = self._associated_class(*args, **kwargs) >>> obj.save() >>> return obj .. OHAI_VIM* .. warning:: For backward compatibility reasons, the default behaviour of factory_boy is to call ``MyClass.objects.create(*args, **kwargs)`` when using the ``create`` strategy. That policy will be used if the :attr:`associated class ` has an ``objects`` attribute *and* the :meth:`~Factory._create` classmethod of the :class:`Factory` wasn't overridden. .. function:: use_strategy(strategy) *Decorator* Change the default strategy of the decorated :class:`Factory` to the chosen :obj:`strategy`: .. code-block:: python @use_strategy(factory.BUILD_STRATEGY) class UserBuildingFactory(UserFactory): pass .. data:: STUB_STRATEGY The 'stub' strategy is an exception in the factory_boy world: it doesn't return an instance of the :attr:`~FactoryOptions.model` class, and actually doesn't require one to be present. Instead, it returns an instance of :class:`StubObject` whose attributes have been set according to the declarations. .. class:: StubObject(object) A plain, stupid object. No method, no helpers, simply a bunch of attributes. It is typically instantiated, then has its attributes set: .. code-block:: pycon >>> obj = StubObject() >>> obj.x = 1 >>> obj.y = 2 .. class:: StubFactory(Factory) An :attr:`abstract ` :class:`Factory`, with a default strategy set to :data:`STUB_STRATEGY`. .. function:: debug(logger='factory', stream=None) :param str logger: The name of the logger to enable debug for :param file stream: The stream to send debug output to, defaults to :obj:`sys.stderr` Context manager to help debugging factory_boy behavior. It will temporarily put the target logger (e.g ``'factory'``) in debug mode, sending all output to :obj`~sys.stderr`; upon leaving the context, the logging levels are reset. A typical use case is to understand what happens during a single factory call: .. code-block:: python with factory.debug(): obj = TestModel2Factory() This will yield messages similar to those (artificial indentation): .. code-block:: ini BaseFactory: Preparing tests.test_using.TestModel2Factory(extra={}) LazyStub: Computing values for tests.test_using.TestModel2Factory(two=>) SubFactory: Instantiating tests.test_using.TestModelFactory(__containers=(,), one=4), create=True BaseFactory: Preparing tests.test_using.TestModelFactory(extra={'__containers': (,), 'one': 4}) LazyStub: Computing values for tests.test_using.TestModelFactory(one=4) LazyStub: Computed values, got tests.test_using.TestModelFactory(one=4) BaseFactory: Generating tests.test_using.TestModelFactory(one=4) LazyStub: Computed values, got tests.test_using.TestModel2Factory(two=) BaseFactory: Generating tests.test_using.TestModel2Factory(two=) .. _declarations: Declarations ------------ Faker """"" .. class:: Faker(provider, locale=None, **kwargs) .. OHAIVIM** In order to easily define realistic-looking factories, use the :class:`Faker` attribute declaration. This is a wrapper around `fake-factory `_; its argument is the name of a ``fake-factory`` provider: .. code-block:: python class UserFactory(factory.Factory): class Meta: model = User name = factory.Faker('name') .. code-block:: pycon >>> user = UserFactory() >>> user.name 'Lucy Cechtelar' .. attribute:: locale If a custom locale is required for one specific field, use the ``locale`` parameter: .. code-block:: python class UserFactory(factory.Factory): class Meta: model = User name = factory.Faker('name', locale='fr_FR') .. code-block:: pycon >>> user = UserFactory() >>> user.name 'Jean Valjean' .. classmethod:: override_default_locale(cls, locale) If the locale needs to be overridden for a whole test, use :meth:`~factory.Faker.override_default_locale`: .. code-block:: pycon >>> with factory.Faker.override_default_locale('de_DE'): ... UserFactory() .. classmethod:: add_provider(cls, locale=None) Some projects may need to fake fields beyond those provided by ``fake-factory``; in such cases, use :meth:`factory.Faker.add_provider` to declare additional providers for those fields: .. code-block:: python factory.Faker.add_provider(SmileyProvider) class FaceFactory(factory.Factory): class Meta: model = Face 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() >>> # The LazyFunction can be overriden >>> LogFactory(timestamp=now - timedelta(days=1)) Decorator ~~~~~~~~~ The class :class:`LazyFunction` does not provide a decorator. For complex cases, use :meth:`LazyAttribute.lazy_attribute` directly. LazyAttribute """"""""""""" .. class:: LazyAttribute(method_to_call) The :class:`LazyAttribute` is a simple yet extremely powerful building brick for extending a :class:`Factory`. It takes as argument a method to call (usually a lambda); that method should accept the object being built as sole argument, and return a value. .. code-block:: python class UserFactory(factory.Factory): class Meta: model = User username = 'john' email = factory.LazyAttribute(lambda o: '%s@example.com' % o.username) .. code-block:: pycon >>> u = UserFactory() >>> u.email 'john@example.com' >>> u = UserFactory(username='leo') >>> u.email 'leo@example.com' The object passed to :class:`LazyAttribute` is not an instance of the target class, but instead a :class:`~containers.LazyStub`: a temporary container that computes the value of all declared fields. Decorator ~~~~~~~~~ .. function:: lazy_attribute If a simple lambda isn't enough, you may use the :meth:`lazy_attribute` decorator instead. This decorates an instance method that should take a single argument, ``self``; the name of the method will be used as the name of the attribute to fill with the return value of the method: .. code-block:: python class UserFactory(factory.Factory) class Meta: model = User name = u"Jean" @factory.lazy_attribute def email(self): # Convert to plain ascii text clean_name = (unicodedata.normalize('NFKD', self.name) .encode('ascii', 'ignore') .decode('utf8')) return u'%s@example.com' % clean_name .. code-block:: pycon >>> joel = UserFactory(name=u"Joël") >>> joel.email u'joel@example.com' Sequence """""""" .. class:: Sequence(lambda, type=int) If a field should be unique, and thus different for all built instances, use a :class:`Sequence`. This declaration takes a single argument, a function accepting a single parameter - the current sequence counter - and returning the related value. .. note:: An extra kwarg argument, ``type``, may be provided. This feature was deprecated in 1.3.0 and will be removed in 2.0.0. .. code-block:: python class UserFactory(factory.Factory) class Meta: model = User phone = factory.Sequence(lambda n: '123-555-%04d' % n) .. code-block:: pycon >>> UserFactory().phone '123-555-0001' >>> UserFactory().phone '123-555-0002' Decorator ~~~~~~~~~ .. function:: sequence As with :meth:`lazy_attribute`, a decorator is available for complex situations. :meth:`sequence` decorates an instance method, whose ``self`` method will actually be the sequence counter - this might be confusing: .. code-block:: python class UserFactory(factory.Factory) class Meta: model = User @factory.sequence def phone(n): a = n // 10000 b = n % 10000 return '%03d-555-%04d' % (a, b) .. code-block:: pycon >>> UserFactory().phone '000-555-9999' >>> UserFactory().phone '001-555-0000' Sharing ~~~~~~~ The sequence counter is shared across all :class:`Sequence` attributes of the :class:`Factory`: .. code-block:: python class UserFactory(factory.Factory): class Meta: model = User phone = factory.Sequence(lambda n: '%04d' % n) office = factory.Sequence(lambda n: 'A23-B%03d' % n) .. code-block:: pycon >>> u = UserFactory() >>> u.phone, u.office '0041', 'A23-B041' >>> u2 = UserFactory() >>> u2.phone, u2.office '0042', 'A23-B042' Inheritance ~~~~~~~~~~~ When a :class:`Factory` inherits from another :class:`Factory` and the `model` of the subclass inherits from the `model` of the parent, the sequence counter is shared across the :class:`Factory` classes: .. code-block:: python class UserFactory(factory.Factory): class Meta: model = User phone = factory.Sequence(lambda n: '123-555-%04d' % n) class EmployeeFactory(UserFactory): office_phone = factory.Sequence(lambda n: '%04d' % n) .. code-block:: pycon >>> u = UserFactory() >>> u.phone '123-555-0001' >>> e = EmployeeFactory() >>> e.phone, e.office_phone '123-555-0002', '0002' >>> u2 = UserFactory() >>> u2.phone '123-555-0003' Forcing a sequence counter ~~~~~~~~~~~~~~~~~~~~~~~~~~ If a specific value of the sequence counter is required for one instance, the ``__sequence`` keyword argument should be passed to the factory method. This will force the sequence counter during the call, without altering the class-level value. .. code-block:: python class UserFactory(factory.Factory): class Meta: model = User uid = factory.Sequence(int) .. code-block:: pycon >>> UserFactory() >>> UserFactory() >>> UserFactory(__sequence=42) .. warning:: The impact of setting ``__sequence=n`` on a ``_batch`` call is undefined. Each generated instance may share a same counter, or use incremental values starting from the forced value. LazyAttributeSequence """"""""""""""""""""" .. class:: LazyAttributeSequence(method_to_call) The :class:`LazyAttributeSequence` declaration merges features of :class:`Sequence` and :class:`LazyAttribute`. It takes a single argument, a function whose two parameters are, in order: * The object being built * The sequence counter .. code-block:: python class UserFactory(factory.Factory): class Meta: model = User login = 'john' email = factory.LazyAttributeSequence(lambda o, n: '%s@s%d.example.com' % (o.login, n)) .. code-block:: pycon >>> UserFactory().email 'john@s1.example.com' >>> UserFactory(login='jack').email 'jack@s2.example.com' Decorator ~~~~~~~~~ .. function:: lazy_attribute_sequence(method_to_call) As for :meth:`lazy_attribute` and :meth:`sequence`, the :meth:`lazy_attribute_sequence` handles more complex cases: .. code-block:: python class UserFactory(factory.Factory): class Meta: model = User login = 'john' @lazy_attribute_sequence def email(self, n): bucket = n % 10 return '%s@s%d.example.com' % (self.login, bucket) SubFactory """""""""" .. class:: SubFactory(factory, **kwargs) .. OHAI_VIM** This attribute declaration calls another :class:`Factory` subclass, selecting the same build strategy and collecting extra kwargs in the process. The :class:`SubFactory` attribute should be called with: * A :class:`Factory` subclass as first argument, or the fully qualified import path to that :class:`Factory` (see :ref:`Circular imports `) * An optional set of keyword arguments that should be passed when calling that factory .. note:: When passing an actual :class:`~factory.Factory` for the :attr:`~factory.SubFactory.factory` argument, make sure to pass the class and not instance (i.e no ``()`` after the class): .. code-block:: python class FooFactory(factory.Factory): class Meta: model = Foo bar = factory.SubFactory(BarFactory) # Not BarFactory() Definition ~~~~~~~~~~ .. code-block:: python # A standard factory class UserFactory(factory.Factory): class Meta: model = User # Various fields first_name = 'John' last_name = factory.Sequence(lambda n: 'D%se' % ('o' * n)) # De, Doe, Dooe, Doooe, ... email = factory.LazyAttribute(lambda o: '%s.%s@example.org' % (o.first_name.lower(), o.last_name.lower())) # A factory for an object with a 'User' field class CompanyFactory(factory.Factory): class Meta: model = Company name = factory.Sequence(lambda n: 'FactoryBoyz' + 'z' * n) # Let's use our UserFactory to create that user, and override its first name. owner = factory.SubFactory(UserFactory, first_name='Jack') Calling ~~~~~~~ The wrapping factory will call of the inner factory: .. code-block:: pycon >>> c = CompanyFactory() >>> c # Notice that the first_name was overridden >>> c.owner >>> c.owner.email jack.de@example.org Fields of the :class:`~factory.SubFactory` may be overridden from the external factory: .. code-block:: pycon >>> c = CompanyFactory(owner__first_name='Henry') >>> c.owner # Notice that the updated first_name was propagated to the email LazyAttribute. >>> c.owner.email henry.doe@example.org # It is also possible to override other fields of the SubFactory >>> c = CompanyFactory(owner__last_name='Jones') >>> c.owner >>> c.owner.email henry.jones@example.org Strategies ~~~~~~~~~~ The strategy chosen for the external factory will be propagated to all subfactories: .. code-block:: pycon >>> c = CompanyFactory() >>> c.pk # Saved to the database 3 >>> c.owner.pk # Saved to the database 8 >>> c = CompanyFactory.build() >>> c.pk # Not saved None >>> c.owner.pk # Not saved either None .. _subfactory-circular: Circular imports ~~~~~~~~~~~~~~~~ Some factories may rely on each other in a circular manner. This issue can be handled by passing the absolute import path to the target :class:`Factory` to the :class:`SubFactory`. .. versionadded:: 1.3.0 .. code-block:: python class UserFactory(factory.Factory): class Meta: model = User username = 'john' main_group = factory.SubFactory('users.factories.GroupFactory') class GroupFactory(factory.Factory): class Meta: model = Group name = "MyGroup" owner = factory.SubFactory(UserFactory) Obviously, such circular relationships require careful handling of loops: .. code-block:: pycon >>> owner = UserFactory(main_group=None) >>> UserFactory(main_group__owner=owner) SelfAttribute """"""""""""" .. class:: SelfAttribute(dotted_path_to_attribute) Some fields should reference another field of the object being constructed, or an attribute thereof. This is performed by the :class:`~factory.SelfAttribute` declaration. That declaration takes a single argument, a dot-delimited path to the attribute to fetch: .. code-block:: python class UserFactory(factory.Factory) class Meta: model = User birthdate = factory.Sequence(lambda n: datetime.date(2000, 1, 1) + datetime.timedelta(days=n)) birthmonth = factory.SelfAttribute('birthdate.month') .. code-block:: pycon >>> u = UserFactory() >>> u.birthdate date(2000, 3, 15) >>> u.birthmonth 3 Parents ~~~~~~~ When used in conjunction with :class:`~factory.SubFactory`, the :class:`~factory.SelfAttribute` gains an "upward" semantic through the double-dot notation, as used in Python imports. ``factory.SelfAttribute('..country.language')`` means "Select the ``language`` of the ``country`` of the :class:`~factory.Factory` calling me". .. code-block:: python class UserFactory(factory.Factory): class Meta: model = User language = 'en' class CompanyFactory(factory.Factory): class Meta: model = Company country = factory.SubFactory(CountryFactory) owner = factory.SubFactory(UserFactory, language=factory.SelfAttribute('..country.language')) .. code-block:: pycon >>> company = CompanyFactory() >>> company.country.language 'fr' >>> company.owner.language 'fr' Obviously, this "follow parents" ability also handles overriding some attributes on call: .. code-block:: pycon >>> company = CompanyFactory(country=china) >>> company.owner.language 'cn' This feature is also available to :class:`LazyAttribute` and :class:`LazyAttributeSequence`, through the :attr:`~containers.LazyStub.factory_parent` attribute of the passed-in object: .. code-block:: python class CompanyFactory(factory.Factory): class Meta: model = Company country = factory.SubFactory(CountryFactory) owner = factory.SubFactory(UserFactory, language=factory.LazyAttribute(lambda user: user.factory_parent.country.language), ) Iterator """""""" .. class:: Iterator(iterable, cycle=True, getter=None) The :class:`Iterator` declaration takes succesive values from the given iterable. When it is exhausted, it starts again from zero (unless ``cycle=False``). .. attribute:: cycle The ``cycle`` argument is only useful for advanced cases, where the provided iterable has no end (as wishing to cycle it means storing values in memory...). .. versionadded:: 1.3.0 The ``cycle`` argument is available as of v1.3.0; previous versions had a behaviour equivalent to ``cycle=False``. .. attribute:: getter A custom function called on each value returned by the iterable. See the :ref:`iterator-getter` section for details. .. versionadded:: 1.3.0 .. method:: reset() Reset the internal iterator used by the attribute, so that the next value will be the first value generated by the iterator. May be called several times. Each call to the factory will receive the next value from the iterable: .. code-block:: python class UserFactory(factory.Factory) lang = factory.Iterator(['en', 'fr', 'es', 'it', 'de']) .. code-block:: pycon >>> UserFactory().lang 'en' >>> UserFactory().lang 'fr' When a value is passed in for the argument, the iterator will *not* be advanced: .. code-block:: pycon >>> UserFactory().lang 'en' >>> UserFactory(lang='cn').lang 'cn' >>> UserFactory().lang 'fr' .. _iterator-getter: Getter ~~~~~~ Some situations may reuse an existing iterable, using only some component. This is handled by the :attr:`~Iterator.getter` attribute: this is a function that accepts as sole parameter a value from the iterable, and returns an adequate value. .. code-block:: python class UserFactory(factory.Factory): class Meta: model = User # CATEGORY_CHOICES is a list of (key, title) tuples category = factory.Iterator(User.CATEGORY_CHOICES, getter=lambda c: c[0]) Decorator ~~~~~~~~~ .. function:: iterator(func) When generating items of the iterator gets too complex for a simple list comprehension, use the :func:`iterator` decorator: .. warning:: The decorated function takes **no** argument, notably no ``self`` parameter. .. code-block:: python class UserFactory(factory.Factory): class Meta: model = User @factory.iterator def name(): with open('test/data/names.dat', 'r') as f: for line in f: yield line Resetting ~~~~~~~~~ In order to start back at the first value in an :class:`Iterator`, simply call the :meth:`~Iterator.reset` method of that attribute (accessing it from the bare :class:`~Factory` subclass): .. code-block:: pycon >>> UserFactory().lang 'en' >>> UserFactory().lang 'fr' >>> UserFactory.lang.reset() >>> UserFactory().lang 'en' Dict and List """"""""""""" When a factory expects lists or dicts as arguments, such values can be generated through the whole range of factory_boy declarations, with the :class:`Dict` and :class:`List` attributes: .. class:: Dict(params[, dict_factory=factory.DictFactory]) The :class:`Dict` class is used for dict-like attributes. It receives as non-keyword argument a dictionary of fields to define, whose value may be any factory-enabled declarations: .. code-block:: python class UserFactory(factory.Factory): class Meta: model = User is_superuser = False roles = factory.Dict({ 'role1': True, 'role2': False, 'role3': factory.Iterator([True, False]), 'admin': factory.SelfAttribute('..is_superuser'), }) .. note:: Declarations used as a :class:`Dict` values are evaluated within that :class:`Dict`'s context; this means that you must use the ``..foo`` syntax to access fields defined at the factory level. On the other hand, the :class:`Sequence` counter is aligned on the containing factory's one. The :class:`Dict` behaviour can be tuned through the following parameters: .. attribute:: dict_factory The actual factory to use for generating the dict can be set as a keyword argument, if an exotic dictionary-like object (SortedDict, ...) is required. .. class:: List(items[, list_factory=factory.ListFactory]) The :class:`List` can be used for list-like attributes. Internally, the fields are converted into a ``index=value`` dict, which makes it possible to override some values at use time: .. code-block:: python class UserFactory(factory.Factory): class Meta: model = User flags = factory.List([ 'user', 'active', 'admin', ]) .. code-block:: pycon >>> u = UserFactory(flags__2='superadmin') >>> u.flags ['user', 'active', 'superadmin'] The :class:`List` behaviour can be tuned through the following parameters: .. attribute:: list_factory The actual factory to use for generating the list can be set as a keyword argument, if another type (tuple, set, ...) is required. Post-generation hooks """"""""""""""""""""" Some objects expect additional method calls or complex processing for proper definition. For instance, a ``User`` may need to have a related ``Profile``, where the ``Profile`` is built from the ``User`` object. To support this pattern, factory_boy provides the following tools: - :class:`PostGenerationMethodCall`: allows you to hook a particular attribute to a function call - :class:`PostGeneration`: this class allows calling a given function with the generated object as argument - :func:`post_generation`: decorator performing the same functions as :class:`PostGeneration` - :class:`RelatedFactory`: this builds or creates a given factory *after* building/creating the first Factory. Post-generation hooks are called in the same order they are declared in the factory class, so that functions can rely on the side effects applied by the previous post-generation hook. Extracting parameters """"""""""""""""""""" All post-building hooks share a common base for picking parameters from the set of attributes passed to the :class:`Factory`. For instance, a :class:`PostGeneration` hook is declared as ``post``: .. code-block:: python class SomeFactory(factory.Factory): class Meta: model = SomeObject @post_generation def post(obj, create, extracted, **kwargs): obj.set_origin(create) .. OHAI_VIM** When calling the factory, some arguments will be extracted for this method: - If a ``post`` argument is passed, it will be passed as the ``extracted`` field - Any argument starting with ``post__XYZ`` will be extracted, its ``post__`` prefix removed, and added to the kwargs passed to the post-generation hook. Extracted arguments won't be passed to the :attr:`~FactoryOptions.model` class. Thus, in the following call: .. code-block:: pycon >>> SomeFactory( post=1, post_x=2, post__y=3, post__z__t=42, ) The ``post`` hook will receive ``1`` as ``extracted`` and ``{'y': 3, 'z__t': 42}`` as keyword arguments; ``{'post_x': 2}`` will be passed to ``SomeFactory._meta.model``. RelatedFactory """""""""""""" .. class:: RelatedFactory(factory, factory_related_name='', **kwargs) .. OHAI_VIM** A :class:`RelatedFactory` behaves mostly like a :class:`SubFactory`, with the main difference that the related :class:`Factory` will be generated *after* the base :class:`Factory`. .. attribute:: factory As for :class:`SubFactory`, the :attr:`factory` argument can be: - A :class:`Factory` subclass - Or the fully qualified path to a :class:`Factory` subclass (see :ref:`subfactory-circular` for details) .. attribute:: name The generated object (where the :class:`RelatedFactory` attribute will set) may be passed to the related factory if the :attr:`factory_related_name` parameter is set. It will be passed as a keyword argument, using the :attr:`name` value as keyword: .. note:: When passing an actual :class:`~factory.Factory` for the :attr:`~factory.RelatedFactory.factory` argument, make sure to pass the class and not instance (i.e no ``()`` after the class): .. code-block:: python class FooFactory(factory.Factory): class Meta: model = Foo bar = factory.RelatedFactory(BarFactory) # Not BarFactory() .. code-block:: python class CityFactory(factory.Factory): class Meta: model = City capital_of = None name = "Toronto" class CountryFactory(factory.Factory): class Meta: model = Country lang = 'fr' capital_city = factory.RelatedFactory(CityFactory, 'capital_of', name="Paris") .. code-block:: pycon >>> france = CountryFactory() >>> City.objects.get(capital_of=france) Extra kwargs may be passed to the related factory, through the usual ``ATTR__SUBATTR`` syntax: .. code-block:: pycon >>> england = CountryFactory(lang='en', capital_city__name="London") >>> City.objects.get(capital_of=england) If a value if passed for the :class:`RelatedFactory` attribute, this disables :class:`RelatedFactory` generation: .. code-block:: pycon >>> france = CountryFactory() >>> paris = City.objects.get() >>> paris >>> reunion = CountryFactory(capital_city=paris) >>> City.objects.count() # No new capital_city generated 1 >>> guyane = CountryFactory(capital_city=paris, capital_city__name='Kourou') >>> City.objects.count() # No new capital_city generated, ``name`` ignored. 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 """""""""""""" .. class:: PostGeneration(callable) The :class:`PostGeneration` declaration performs actions once the model object has been generated. Its sole argument is a callable, that will be called once the base object has been generated. Once the base object has been generated, the provided callable will be called as ``callable(obj, create, extracted, **kwargs)``, where: - ``obj`` is the base object previously generated - ``create`` is a boolean indicating which strategy was used - ``extracted`` is ``None`` unless a value was passed in for the :class:`PostGeneration` declaration at :class:`Factory` declaration time - ``kwargs`` are any extra parameters passed as ``attr__key=value`` when calling the :class:`Factory`: .. code-block:: python class UserFactory(factory.Factory): class Meta: model = User login = 'john' make_mbox = factory.PostGeneration( lambda obj, create, extracted, **kwargs: os.makedirs(obj.login)) .. OHAI_VIM** Decorator ~~~~~~~~~ .. function:: post_generation A decorator is also provided, decorating a single method accepting the same ``obj``, ``created``, ``extracted`` and keyword arguments as :class:`PostGeneration`. .. code-block:: python class UserFactory(factory.Factory): class Meta: model = User login = 'john' @factory.post_generation def mbox(self, create, extracted, **kwargs): if not create: return path = extracted or os.path.join('/tmp/mbox/', self.login) os.path.makedirs(path) return path .. OHAI_VIM** .. code-block:: pycon >>> UserFactory.build() # Nothing was created >>> UserFactory.create() # Creates dir /tmp/mbox/john >>> UserFactory.create(login='jack') # Creates dir /tmp/mbox/jack >>> UserFactory.create(mbox='/tmp/alt') # Creates dir /tmp/alt PostGenerationMethodCall """""""""""""""""""""""" .. class:: PostGenerationMethodCall(method_name, *args, **kwargs) .. OHAI_VIM* The :class:`PostGenerationMethodCall` declaration will call a method on the generated object just after instantiation. This declaration class provides a friendly means of generating attributes of a factory instance during initialization. The declaration is created using the following arguments: .. attribute:: method_name The name of the method to call on the :attr:`~FactoryOptions.model` object .. attribute:: args The default set of unnamed arguments to pass to the method given in :attr:`method_name` .. attribute:: kwargs The default set of keyword arguments to pass to the method given in :attr:`method_name` Once the factory instance has been generated, the method specified in :attr:`~PostGenerationMethodCall.method_name` will be called on the generated object with any arguments specified in the :class:`PostGenerationMethodCall` declaration, by default. For example, to set a default password on a generated User instance during instantiation, we could make a declaration for a ``password`` attribute like below: .. code-block:: python class UserFactory(factory.Factory): class Meta: model = User username = 'user' password = factory.PostGenerationMethodCall('set_password', 'defaultpassword') When we instantiate a user from the ``UserFactory``, the factory will create a password attribute by calling ``User.set_password('defaultpassword')``. Thus, by default, our users will have a password set to ``'defaultpassword'``. .. code-block:: pycon >>> u = UserFactory() # Calls user.set_password('defaultpassword') >>> u.check_password('defaultpassword') True If the :class:`PostGenerationMethodCall` declaration contained no arguments or one argument, an overriding value can be passed directly to the method through a keyword argument matching the attribute name. For example we can override the default password specified in the declaration above by simply passing in the desired password as a keyword argument to the factory during instantiation. .. code-block:: pycon >>> other_u = UserFactory(password='different') # Calls user.set_password('different') >>> other_u.check_password('defaultpassword') False >>> other_u.check_password('different') True .. note:: For Django models, unless the object method called by :class:`PostGenerationMethodCall` saves the object back to the database, we will have to explicitly remember to save the object back if we performed a ``create()``. .. code-block:: pycon >>> u = UserFactory.create() # u.password has not been saved back to the database >>> u.save() # we must remember to do it ourselves We can avoid this by subclassing from :class:`DjangoModelFactory`, instead, e.g., .. code-block:: python class UserFactory(factory.django.DjangoModelFactory): class Meta: model = User username = 'user' password = factory.PostGenerationMethodCall('set_password', 'defaultpassword') If instead the :class:`PostGenerationMethodCall` declaration uses two or more positional arguments, the overriding value must be an iterable. For example, if we declared the ``password`` attribute like the following, .. code-block:: python class UserFactory(factory.Factory): class Meta: model = User username = 'user' password = factory.PostGenerationMethodCall('set_password', '', 'sha1') then we must be cautious to pass in an iterable for the ``password`` keyword argument when creating an instance from the factory: .. code-block:: pycon >>> UserFactory() # Calls user.set_password('', 'sha1') >>> UserFactory(password=('test', 'md5')) # Calls user.set_password('test', 'md5') >>> # Always pass in a good iterable: >>> UserFactory(password=('test',)) # Calls user.set_password('test') >>> UserFactory(password='test') # Calls user.set_password('t', 'e', 's', 't') .. note:: While this setup provides sane and intuitive defaults for most users, it prevents passing more than one argument when the declaration used zero or one. In such cases, users are advised to either resort to the more powerful :class:`PostGeneration` or to add the second expected argument default value to the :class:`PostGenerationMethodCall` declaration (``PostGenerationMethodCall('method', 'x', 'y_that_is_the_default')``) Keywords extracted from the factory arguments are merged into the defaults present in the :class:`PostGenerationMethodCall` declaration. .. code-block:: pycon >>> UserFactory(password__disabled=True) # Calls user.set_password('', 'sha1', disabled=True) Module-level functions ---------------------- Beyond the :class:`Factory` class and the various :ref:`declarations` classes and methods, factory_boy exposes a few module-level functions, mostly useful for lightweight factory generation. Lightweight factory declaration """"""""""""""""""""""""""""""" .. function:: make_factory(klass, **kwargs) .. OHAI_VIM** The :func:`make_factory` function takes a class, declarations as keyword arguments, and generates a new :class:`Factory` for that class accordingly: .. code-block:: python UserFactory = make_factory(User, login='john', email=factory.LazyAttribute(lambda u: '%s@example.com' % u.login), ) # This is equivalent to: class UserFactory(factory.Factory): class Meta: model = User login = 'john' email = factory.LazyAttribute(lambda u: '%s@example.com' % u.login) An alternate base class to :class:`Factory` can be specified in the ``FACTORY_CLASS`` argument: .. code-block:: python UserFactory = make_factory(models.User, login='john', email=factory.LazyAttribute(lambda u: '%s@example.com' % u.login), FACTORY_CLASS=factory.django.DjangoModelFactory, ) # This is equivalent to: class UserFactory(factory.django.DjangoModelFactory): class Meta: model = models.User login = 'john' email = factory.LazyAttribute(lambda u: '%s@example.com' % u.login) .. versionadded:: 2.0.0 The ``FACTORY_CLASS`` kwarg was added in 2.0.0. Instance building """"""""""""""""" The :mod:`factory` module provides a bunch of shortcuts for creating a factory and extracting instances from them: .. function:: build(klass, FACTORY_CLASS=None, **kwargs) .. function:: build_batch(klass, size, FACTORY_CLASS=None, **kwargs) Create a factory for :obj:`klass` using declarations passed in kwargs; return an instance built from that factory, or a list of :obj:`size` instances (for :func:`build_batch`). :param class klass: Class of the instance to build :param int size: Number of instances to build :param kwargs: Declarations to use for the generated factory :param FACTORY_CLASS: Alternate base class (instead of :class:`Factory`) .. function:: create(klass, FACTORY_CLASS=None, **kwargs) .. function:: create_batch(klass, size, FACTORY_CLASS=None, **kwargs) Create a factory for :obj:`klass` using declarations passed in kwargs; return an instance created from that factory, or a list of :obj:`size` instances (for :func:`create_batch`). :param class klass: Class of the instance to create :param int size: Number of instances to create :param kwargs: Declarations to use for the generated factory :param FACTORY_CLASS: Alternate base class (instead of :class:`Factory`) .. function:: stub(klass, FACTORY_CLASS=None, **kwargs) .. function:: stub_batch(klass, size, FACTORY_CLASS=None, **kwargs) Create a factory for :obj:`klass` using declarations passed in kwargs; return an instance stubbed from that factory, or a list of :obj:`size` instances (for :func:`stub_batch`). :param class klass: Class of the instance to stub :param int size: Number of instances to stub :param kwargs: Declarations to use for the generated factory :param FACTORY_CLASS: Alternate base class (instead of :class:`Factory`) .. function:: generate(klass, strategy, FACTORY_CLASS=None, **kwargs) .. function:: generate_batch(klass, strategy, size, FACTORY_CLASS=None, **kwargs) Create a factory for :obj:`klass` using declarations passed in kwargs; return an instance generated from that factory with the :obj:`strategy` strategy, or a list of :obj:`size` instances (for :func:`generate_batch`). :param class klass: Class of the instance to generate :param str strategy: The strategy to use :param int size: Number of instances to generate :param kwargs: Declarations to use for the generated factory :param FACTORY_CLASS: Alternate base class (instead of :class:`Factory`) .. function:: simple_generate(klass, create, FACTORY_CLASS=None, **kwargs) .. function:: simple_generate_batch(klass, create, size, FACTORY_CLASS=None, **kwargs) Create a factory for :obj:`klass` using declarations passed in kwargs; return an instance generated from that factory according to the :obj:`create` flag, or a list of :obj:`size` instances (for :func:`simple_generate_batch`). :param class klass: Class of the instance to generate :param bool create: Whether to build (``False``) or create (``True``) instances :param int size: Number of instances to generate :param kwargs: Declarations to use for the generated factory :param FACTORY_CLASS: Alternate base class (instead of :class:`Factory`) factory_boy-2.8.1/examples/000077500000000000000000000000001302510513700156555ustar00rootroot00000000000000factory_boy-2.8.1/examples/Makefile000066400000000000000000000002561302510513700173200ustar00rootroot00000000000000EXAMPLES = flask_alchemy TEST_TARGETS = $(addprefix runtest-,$(EXAMPLES)) test: $(TEST_TARGETS) $(TEST_TARGETS): runtest-%: cd $* && PYTHONPATH=../.. python -m unittest factory_boy-2.8.1/examples/flask_alchemy/000077500000000000000000000000001302510513700204575ustar00rootroot00000000000000factory_boy-2.8.1/examples/flask_alchemy/demoapp.py000066400000000000000000000040461302510513700224620ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (c) 2015 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. from flask import Flask from flask.ext.sqlalchemy import SQLAlchemy app = Flask(__name__) app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test.db' db = SQLAlchemy(app) class User(db.Model): id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(80), unique=True) email = db.Column(db.String(120), unique=True) def __init__(self, username, email): self.username = username self.email = email def __repr__(self): return '' % self.username class UserLog(db.Model): id = db.Column(db.Integer, primary_key=True) message = db.Column(db.String(1000)) user_id = db.Column(db.Integer, db.ForeignKey('user.id')) user = db.relationship('User', backref=db.backref('logs', lazy='dynamic')) def __init__(self, message, user): self.message = message self.user = user def __repr__(self): return '' % (self.user, self.message) factory_boy-2.8.1/examples/flask_alchemy/demoapp_factories.py000066400000000000000000000010211302510513700245070ustar00rootroot00000000000000import factory import factory.fuzzy import demoapp class BaseFactory(factory.alchemy.SQLAlchemyModelFactory): class Meta: abstract = True sqlalchemy_session = demoapp.db.session class UserFactory(BaseFactory): class Meta: model = demoapp.User username = factory.fuzzy.FuzzyText() email = factory.fuzzy.FuzzyText() class UserLogFactory(BaseFactory): class Meta: model = demoapp.UserLog message = factory.fuzzy.FuzzyText() user = factory.SubFactory(UserFactory) factory_boy-2.8.1/examples/flask_alchemy/requirements.txt000066400000000000000000000000271302510513700237420ustar00rootroot00000000000000Flask Flask-SQLAlchemy factory_boy-2.8.1/examples/flask_alchemy/test_demoapp.py000066400000000000000000000017561302510513700235260ustar00rootroot00000000000000import os import unittest import tempfile import demoapp import demoapp_factories class DemoAppTestCase(unittest.TestCase): def setUp(self): demoapp.app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite://' demoapp.app.config['TESTING'] = True self.app = demoapp.app.test_client() self.db = demoapp.db self.db.create_all() def tearDown(self): self.db.drop_all() def test_user_factory(self): user = demoapp_factories.UserFactory() self.db.session.commit() self.assertIsNotNone(user.id) self.assertEqual(1, len(demoapp.User.query.all())) def test_userlog_factory(self): userlog = demoapp_factories.UserLogFactory() self.db.session.commit() self.assertIsNotNone(userlog.id) self.assertIsNotNone(userlog.user.id) self.assertEqual(1, len(demoapp.User.query.all())) self.assertEqual(1, len(demoapp.UserLog.query.all())) if __name__ == '__main__': unittest.main() factory_boy-2.8.1/examples/requirements.txt000066400000000000000000000000421302510513700211350ustar00rootroot00000000000000-r flask_alchemy/requirements.txt factory_boy-2.8.1/factory/000077500000000000000000000000001302510513700155065ustar00rootroot00000000000000factory_boy-2.8.1/factory/__init__.py000066400000000000000000000045251302510513700176250ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (c) 2010 Mark Sandstrom # Copyright (c) 2011-2015 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. from .base import ( Factory, BaseDictFactory, DictFactory, BaseListFactory, ListFactory, StubFactory, BUILD_STRATEGY, CREATE_STRATEGY, STUB_STRATEGY, use_strategy, ) from .errors import ( FactoryError, ) from .faker import Faker from .declarations import ( LazyFunction, LazyAttribute, Iterator, Sequence, LazyAttributeSequence, SelfAttribute, Trait, ContainerAttribute, SubFactory, Dict, List, PostGeneration, PostGenerationMethodCall, RelatedFactory, ) from .helpers import ( debug, build, create, stub, generate, simple_generate, make_factory, build_batch, create_batch, stub_batch, generate_batch, simple_generate_batch, lazy_attribute, iterator, sequence, lazy_attribute_sequence, container_attribute, post_generation, ) # Backward compatibility; this should be removed soon. from . import alchemy from . import django from . import mogo from . import mongoengine __version__ = '2.8.1' __author__ = 'Raphaël Barrois ' MogoFactory = mogo.MogoFactory DjangoModelFactory = django.DjangoModelFactory factory_boy-2.8.1/factory/alchemy.py000066400000000000000000000073041302510513700175060ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (c) 2013 Romain Commandé # # 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. from __future__ import unicode_literals from . import base import warnings SESSION_PERSISTENCE_COMMIT = 'commit' SESSION_PERSISTENCE_FLUSH = 'flush' VALID_SESSION_PERSISTENCE_TYPES = [ None, SESSION_PERSISTENCE_COMMIT, SESSION_PERSISTENCE_FLUSH, ] class SQLAlchemyOptions(base.FactoryOptions): def _check_sqlalchemy_session_persistence(self, meta, value): if value not in VALID_SESSION_PERSISTENCE_TYPES: raise TypeError( "%s.sqlalchemy_session_persistence must be one of %s, got %r" % (meta, VALID_SESSION_PERSISTENCE_TYPES, value) ) def _check_force_flush(self, meta, value): if value: warnings.warn( "%(meta)s.force_flush has been deprecated as of 2.8.0 and will be removed in 3.0.0. " "Please set ``%(meta)s.sqlalchemy_session_persistence = 'flush'`` instead." % dict(meta=meta), DeprecationWarning, # Stacklevel: # declaration -> FactoryMetaClass.__new__ -> meta.contribute_to_class # -> meta._fill_from_meta -> option.apply -> option.checker stacklevel=6, ) def _build_default_options(self): return super(SQLAlchemyOptions, self)._build_default_options() + [ base.OptionDefault('sqlalchemy_session', None, inherit=True), base.OptionDefault( 'sqlalchemy_session_persistence', None, inherit=True, checker=self._check_sqlalchemy_session_persistence, ), # DEPRECATED as of 2.8.0, remove in 3.0.0 base.OptionDefault( 'force_flush', False, inherit=True, checker=self._check_force_flush, ), ] class SQLAlchemyModelFactory(base.Factory): """Factory for SQLAlchemy models. """ _options_class = SQLAlchemyOptions class Meta: abstract = True @classmethod def _create(cls, model_class, *args, **kwargs): """Create an instance of the model, and save it to the database.""" session = cls._meta.sqlalchemy_session session_persistence = cls._meta.sqlalchemy_session_persistence if cls._meta.force_flush: session_persistence = SESSION_PERSISTENCE_FLUSH obj = model_class(*args, **kwargs) session.add(obj) if session_persistence == SESSION_PERSISTENCE_FLUSH: session.flush() elif session_persistence == SESSION_PERSISTENCE_COMMIT: session.commit() return obj factory_boy-2.8.1/factory/base.py000066400000000000000000000651071302510513700170030ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (c) 2010 Mark Sandstrom # Copyright (c) 2011-2015 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. from __future__ import unicode_literals import collections import logging from . import containers from . import declarations from . import errors from . import utils logger = logging.getLogger('factory.generate') # Strategies BUILD_STRATEGY = 'build' CREATE_STRATEGY = 'create' STUB_STRATEGY = 'stub' # Factory metaclasses def get_factory_bases(bases): """Retrieve all FactoryMetaClass-derived bases from a list.""" return [b for b in bases if issubclass(b, BaseFactory)] def resolve_attribute(name, bases, default=None): """Find the first definition of an attribute according to MRO order.""" for base in bases: if hasattr(base, name): return getattr(base, name) return default class FactoryMetaClass(type): """Factory metaclass for handling ordered declarations.""" def __call__(cls, **kwargs): """Override the default Factory() syntax to call the default strategy. Returns an instance of the associated class. """ if cls._meta.strategy == BUILD_STRATEGY: return cls.build(**kwargs) elif cls._meta.strategy == CREATE_STRATEGY: return cls.create(**kwargs) elif cls._meta.strategy == STUB_STRATEGY: return cls.stub(**kwargs) else: raise errors.UnknownStrategy('Unknown Meta.strategy: {0}'.format( cls._meta.strategy)) def __new__(mcs, class_name, bases, attrs): """Record attributes as a pattern for later instance construction. This is called when a new Factory subclass is defined; it will collect attribute declaration from the class definition. Args: class_name (str): the name of the class being created bases (list of class): the parents of the class being created attrs (str => obj dict): the attributes as defined in the class definition Returns: A new class """ parent_factories = get_factory_bases(bases) if parent_factories: base_factory = parent_factories[0] else: base_factory = None attrs_meta = attrs.pop('Meta', None) attrs_params = attrs.pop('Params', None) base_meta = resolve_attribute('_meta', bases) options_class = resolve_attribute('_options_class', bases, FactoryOptions) meta = options_class() attrs['_meta'] = meta new_class = super(FactoryMetaClass, mcs).__new__( mcs, class_name, bases, attrs) meta.contribute_to_class( new_class, meta=attrs_meta, base_meta=base_meta, base_factory=base_factory, params=attrs_params, ) return new_class def __str__(cls): if cls._meta.abstract: return '<%s (abstract)>' % cls.__name__ else: return '<%s for %s>' % (cls.__name__, cls._meta.model) class BaseMeta: abstract = True strategy = CREATE_STRATEGY class OptionDefault(object): """The default for an option. Attributes: name: str, the name of the option ('class Meta' attribute) value: object, the default value for the option inherit: bool, whether to inherit the value from the parent factory's `class Meta` when no value is provided checker: callable or None, an optional function used to detect invalid option values at declaration time """ def __init__(self, name, value, inherit=False, checker=None): self.name = name self.value = value self.inherit = inherit self.checker = checker def apply(self, meta, base_meta): value = self.value if self.inherit and base_meta is not None: value = getattr(base_meta, self.name, value) if meta is not None: value = getattr(meta, self.name, value) if self.checker is not None: self.checker(meta, value) return value def __str__(self): return '%s(%r, %r, inherit=%r)' % ( self.__class__.__name__, self.name, self.value, self.inherit) class FactoryOptions(object): def __init__(self): self.factory = None self.base_factory = None self.declarations = {} self.postgen_declarations = {} self.parameters = {} self.parameters_dependencies = {} @property def sorted_postgen_declarations(self): """Get sorted postgen declaration items.""" return sorted( self.postgen_declarations.items(), key=lambda item: item[1].creation_counter, ) def _build_default_options(self): """"Provide the default value for all allowed fields. Custom FactoryOptions classes should override this method to update() its return value. """ return [ OptionDefault('model', None, inherit=True), OptionDefault('abstract', False, inherit=False), OptionDefault('strategy', CREATE_STRATEGY, inherit=True), OptionDefault('inline_args', (), inherit=True), OptionDefault('exclude', (), inherit=True), OptionDefault('rename', {}, inherit=True), ] def _fill_from_meta(self, meta, base_meta): # Exclude private/protected fields from the meta if meta is None: meta_attrs = {} else: meta_attrs = dict( (k, v) for (k, v) in vars(meta).items() if not k.startswith('_') ) for option in self._build_default_options(): assert not hasattr(self, option.name), "Can't override field %s." % option.name value = option.apply(meta, base_meta) meta_attrs.pop(option.name, None) setattr(self, option.name, value) if meta_attrs: # Some attributes in the Meta aren't allowed here raise TypeError( "'class Meta' for %r got unknown attribute(s) %s" % (self.factory, ','.join(sorted(meta_attrs.keys())))) def contribute_to_class(self, factory, meta=None, base_meta=None, base_factory=None, params=None): self.factory = factory self.base_factory = base_factory self._fill_from_meta(meta=meta, base_meta=base_meta) self.model = self.factory._load_model_class(self.model) if self.model is None: self.abstract = True self.counter_reference = self._get_counter_reference() for parent in reversed(self.factory.__mro__[1:]): if not hasattr(parent, '_meta'): continue self.declarations.update(parent._meta.declarations) self.postgen_declarations.update(parent._meta.postgen_declarations) self.parameters.update(parent._meta.parameters) for k, v in vars(self.factory).items(): if self._is_declaration(k, v): self.declarations[k] = v if self._is_postgen_declaration(k, v): self.postgen_declarations[k] = v if params is not None: for k, v in vars(params).items(): if not k.startswith('_'): self.parameters[k] = v self.parameters_dependencies = self._compute_parameter_dependencies(self.parameters) def _get_counter_reference(self): """Identify which factory should be used for a shared counter.""" if (self.model is not None and self.base_factory is not None and self.base_factory._meta.model is not None and issubclass(self.model, self.base_factory._meta.model)): return self.base_factory else: return self.factory def _is_declaration(self, name, value): """Determines if a class attribute is a field value declaration. Based on the name and value of the class attribute, return ``True`` if it looks like a declaration of a default field value, ``False`` if it is private (name starts with '_') or a classmethod or staticmethod. """ if isinstance(value, (classmethod, staticmethod)): return False elif isinstance(value, declarations.OrderedDeclaration): return True elif isinstance(value, declarations.PostGenerationDeclaration): return False return not name.startswith("_") def _is_postgen_declaration(self, name, value): """Captures instances of PostGenerationDeclaration.""" return isinstance(value, declarations.PostGenerationDeclaration) def _compute_parameter_dependencies(self, parameters): """Find out in what order parameters should be called.""" # Warning: parameters only provide reverse dependencies; we reverse them into standard dependencies. # deep_revdeps: set of fields a field depend indirectly upon deep_revdeps = collections.defaultdict(set) # Actual, direct dependencies deps = collections.defaultdict(set) for name, parameter in parameters.items(): if isinstance(parameter, declarations.ComplexParameter): field_revdeps = parameter.get_revdeps(parameters) if not field_revdeps: continue deep_revdeps[name] = set.union(*(deep_revdeps[dep] for dep in field_revdeps)) deep_revdeps[name] |= set(field_revdeps) for dep in field_revdeps: deps[dep].add(name) # Check for cyclical dependencies cyclic = [name for name, field_deps in deep_revdeps.items() if name in field_deps] if cyclic: raise errors.CyclicDefinitionError( "Cyclic definition detected on %s' Params around %s" % (self.factory, ', '.join(cyclic))) return deps def __str__(self): return "<%s for %s>" % (self.__class__.__name__, self.factory.__class__.__name__) def __repr__(self): return str(self) # Factory base classes class _Counter(object): """Simple, naive counter. Attributes: for_class (obj): the class this counter related to seq (int): the next value """ def __init__(self, seq, for_class): self.seq = seq self.for_class = for_class def next(self): value = self.seq self.seq += 1 return value def reset(self, next_value=0): self.seq = next_value def __repr__(self): return '<_Counter for %s.%s, next=%d>' % ( self.for_class.__module__, self.for_class.__name__, self.seq) class BaseFactory(object): """Factory base support for sequences, attributes and stubs.""" # Backwards compatibility UnknownStrategy = errors.UnknownStrategy UnsupportedStrategy = errors.UnsupportedStrategy def __new__(cls, *args, **kwargs): """Would be called if trying to instantiate the class.""" raise errors.FactoryError('You cannot instantiate BaseFactory') _meta = FactoryOptions() # ID to use for the next 'declarations.Sequence' attribute. _counter = None @classmethod def reset_sequence(cls, value=None, force=False): """Reset the sequence counter. Args: value (int or None): the new 'next' sequence value; if None, recompute the next value from _setup_next_sequence(). force (bool): whether to force-reset parent sequence counters in a factory inheritance chain. """ if cls._meta.counter_reference is not cls: if force: cls._meta.base_factory.reset_sequence(value=value) else: raise ValueError( "Cannot reset the sequence of a factory subclass. " "Please call reset_sequence() on the root factory, " "or call reset_sequence(force=True)." ) else: cls._setup_counter() if value is None: value = cls._setup_next_sequence() cls._counter.reset(value) @classmethod def _setup_next_sequence(cls): """Set up an initial sequence value for Sequence attributes. Returns: int: the first available ID to use for instances of this factory. """ return 0 @classmethod def _setup_counter(cls): """Ensures cls._counter is set for this class. Due to the way inheritance works in Python, we need to ensure that the ``_counter`` attribute has been initialized for *this* Factory subclass, not one of its parents. """ if cls._counter is None or cls._counter.for_class != cls: first_seq = cls._setup_next_sequence() cls._counter = _Counter(for_class=cls, seq=first_seq) logger.debug("%s: Setting up next sequence (%d)", cls, first_seq) @classmethod def _generate_next_sequence(cls): """Retrieve a new sequence ID. This will call, in order: - _generate_next_sequence from the base factory, if provided - _setup_next_sequence, if this is the 'toplevel' factory and the sequence counter wasn't initialized yet; then increase it. """ # Rely upon our parents if cls._meta.counter_reference is not cls: logger.debug("%r: reusing sequence from %r", cls, cls._meta.base_factory) return cls._meta.base_factory._generate_next_sequence() # Make sure _counter is initialized cls._setup_counter() # Pick current value, then increase class counter for the next call. return cls._counter.next() @classmethod def attributes(cls, create=False, extra=None): """Build a dict of attribute values, respecting declaration order. The process is: - Handle 'orderless' attributes, overriding defaults with provided kwargs when applicable - Handle ordered attributes, overriding them with provided kwargs when applicable; the current list of computed attributes is available to the currently processed object. """ force_sequence = None if extra: force_sequence = extra.pop('__sequence', None) log_ctx = '%s.%s' % (cls.__module__, cls.__name__) logger.debug( "BaseFactory: Preparing %s.%s(extra=%s)", cls.__module__, cls.__name__, utils.log_repr(extra), ) return containers.AttributeBuilder(cls, extra, log_ctx=log_ctx).build( create=create, force_sequence=force_sequence, ) @classmethod def declarations(cls, extra_defs=None): """Retrieve a copy of the declared attributes. Args: extra_defs (dict): additional definitions to insert into the retrieved DeclarationDict. """ decls = cls._meta.declarations.copy() decls.update(extra_defs or {}) return decls @classmethod def _rename_fields(cls, **kwargs): for old_name, new_name in cls._meta.rename.items(): kwargs[new_name] = kwargs.pop(old_name) return kwargs @classmethod def _adjust_kwargs(cls, **kwargs): """Extension point for custom kwargs adjustment.""" return kwargs @classmethod def _load_model_class(cls, class_definition): """Extension point for loading model classes. This can be overridden in framework-specific subclasses to hook into existing model repositories, for instance. """ return class_definition @classmethod def _get_model_class(cls): """Retrieve the actual, associated model class.""" definition = cls._meta.model return cls._load_model_class(definition) @classmethod def _prepare(cls, create, **kwargs): """Prepare an object for this factory. Args: create: bool, whether to create or to build the object **kwargs: arguments to pass to the creation function """ model_class = cls._get_model_class() kwargs = cls._rename_fields(**kwargs) kwargs = cls._adjust_kwargs(**kwargs) # Remove 'hidden' arguments. for arg in cls._meta.exclude: del kwargs[arg] # Remove parameters, if defined for arg in cls._meta.parameters: kwargs.pop(arg, None) # Extract *args from **kwargs args = tuple(kwargs.pop(key) for key in cls._meta.inline_args) logger.debug( "BaseFactory: Generating %s.%s(%s)", cls.__module__, cls.__name__, utils.log_pprint(args, kwargs), ) if create: return cls._create(model_class, *args, **kwargs) else: return cls._build(model_class, *args, **kwargs) @classmethod def _generate(cls, create, attrs): """generate the object. Args: create (bool): whether to 'build' or 'create' the object attrs (dict): attributes to use for generating the object """ if cls._meta.abstract: raise errors.FactoryError( "Cannot generate instances of abstract factory %(f)s; " "Ensure %(f)s.Meta.model is set and %(f)s.Meta.abstract " "is either not set or False." % dict(f=cls.__name__)) # Extract declarations used for post-generation postgen_attributes = {} for name, decl in cls._meta.sorted_postgen_declarations: postgen_attributes[name] = decl.extract(name, attrs) # Generate the object obj = cls._prepare(create, **attrs) # Handle post-generation attributes results = {} for name, decl in cls._meta.sorted_postgen_declarations: extraction_context = postgen_attributes[name] results[name] = decl.call(obj, create, extraction_context) cls._after_postgeneration(obj, create, results) return obj @classmethod def _after_postgeneration(cls, obj, create, results=None): """Hook called after post-generation declarations have been handled. Args: obj (object): the generated object create (bool): whether the strategy was 'build' or 'create' results (dict or None): result of post-generation declarations """ pass @classmethod def _build(cls, model_class, *args, **kwargs): """Actually build an instance of the model_class. Customization point, will be called once the full set of args and kwargs has been computed. Args: model_class (type): the class for which an instance should be built args (tuple): arguments to use when building the class kwargs (dict): keyword arguments to use when building the class """ return model_class(*args, **kwargs) @classmethod def _create(cls, model_class, *args, **kwargs): """Actually create an instance of the model_class. Customization point, will be called once the full set of args and kwargs has been computed. Args: model_class (type): the class for which an instance should be created args (tuple): arguments to use when creating the class kwargs (dict): keyword arguments to use when creating the class """ return model_class(*args, **kwargs) @classmethod def build(cls, **kwargs): """Build an instance of the associated class, with overriden attrs.""" attrs = cls.attributes(create=False, extra=kwargs) return cls._generate(False, attrs) @classmethod def build_batch(cls, size, **kwargs): """Build a batch of instances of the given class, with overriden attrs. Args: size (int): the number of instances to build Returns: object list: the built instances """ return [cls.build(**kwargs) for _ in range(size)] @classmethod def create(cls, **kwargs): """Create an instance of the associated class, with overriden attrs.""" attrs = cls.attributes(create=True, extra=kwargs) return cls._generate(True, attrs) @classmethod def create_batch(cls, size, **kwargs): """Create a batch of instances of the given class, with overriden attrs. Args: size (int): the number of instances to create Returns: object list: the created instances """ return [cls.create(**kwargs) for _ in range(size)] @classmethod def stub(cls, **kwargs): """Retrieve a stub of the associated class, with overriden attrs. This will return an object whose attributes are those defined in this factory's declarations or in the extra kwargs. """ stub_object = containers.StubObject() for name, value in cls.attributes(create=False, extra=kwargs).items(): setattr(stub_object, name, value) return stub_object @classmethod def stub_batch(cls, size, **kwargs): """Stub a batch of instances of the given class, with overriden attrs. Args: size (int): the number of instances to stub Returns: object list: the stubbed instances """ return [cls.stub(**kwargs) for _ in range(size)] @classmethod def generate(cls, strategy, **kwargs): """Generate a new instance. The instance will be created with the given strategy (one of BUILD_STRATEGY, CREATE_STRATEGY, STUB_STRATEGY). Args: strategy (str): the strategy to use for generating the instance. Returns: object: the generated instance """ assert strategy in (STUB_STRATEGY, BUILD_STRATEGY, CREATE_STRATEGY) action = getattr(cls, strategy) return action(**kwargs) @classmethod def generate_batch(cls, strategy, size, **kwargs): """Generate a batch of instances. The instances will be created with the given strategy (one of BUILD_STRATEGY, CREATE_STRATEGY, STUB_STRATEGY). Args: strategy (str): the strategy to use for generating the instance. size (int): the number of instances to generate Returns: object list: the generated instances """ assert strategy in (STUB_STRATEGY, BUILD_STRATEGY, CREATE_STRATEGY) batch_action = getattr(cls, '%s_batch' % strategy) return batch_action(size, **kwargs) @classmethod def simple_generate(cls, create, **kwargs): """Generate a new instance. The instance will be either 'built' or 'created'. Args: create (bool): whether to 'build' or 'create' the instance. Returns: object: the generated instance """ strategy = CREATE_STRATEGY if create else BUILD_STRATEGY return cls.generate(strategy, **kwargs) @classmethod def simple_generate_batch(cls, create, size, **kwargs): """Generate a batch of instances. These instances will be either 'built' or 'created'. Args: size (int): the number of instances to generate create (bool): whether to 'build' or 'create' the instances. Returns: object list: the generated instances """ strategy = CREATE_STRATEGY if create else BUILD_STRATEGY return cls.generate_batch(strategy, size, **kwargs) # Note: we're calling str() on the class name to avoid issues with Py2's type() expecting bytes # instead of unicode. Factory = FactoryMetaClass(str('Factory'), (BaseFactory,), { 'Meta': BaseMeta, '__doc__': """Factory base with build and create support. This class has the ability to support multiple ORMs by using custom creation functions. """, }) # Backwards compatibility Factory.AssociatedClassError = errors.AssociatedClassError class StubFactory(Factory): class Meta: strategy = STUB_STRATEGY model = containers.StubObject @classmethod def build(cls, **kwargs): return cls.stub(**kwargs) @classmethod def create(cls, **kwargs): raise errors.UnsupportedStrategy() class BaseDictFactory(Factory): """Factory for dictionary-like classes.""" class Meta: abstract = True @classmethod def _build(cls, model_class, *args, **kwargs): if args: raise ValueError( "DictFactory %r does not support Meta.inline_args.", cls) return model_class(**kwargs) @classmethod def _create(cls, model_class, *args, **kwargs): return cls._build(model_class, *args, **kwargs) class DictFactory(BaseDictFactory): class Meta: model = dict class BaseListFactory(Factory): """Factory for list-like classes.""" class Meta: abstract = True @classmethod def _build(cls, model_class, *args, **kwargs): if args: raise ValueError( "ListFactory %r does not support Meta.inline_args.", cls) values = [v for k, v in sorted(kwargs.items())] return model_class(values) @classmethod def _create(cls, model_class, *args, **kwargs): return cls._build(model_class, *args, **kwargs) class ListFactory(BaseListFactory): class Meta: model = list def use_strategy(new_strategy): """Force the use of a different strategy. This is an alternative to setting default_strategy in the class definition. """ def wrapped_class(klass): klass._meta.strategy = new_strategy return klass return wrapped_class factory_boy-2.8.1/factory/compat.py000066400000000000000000000045301302510513700173450ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (c) 2010 Mark Sandstrom # Copyright (c) 2011-2015 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. """Compatibility tools""" import datetime import sys PY2 = (sys.version_info[0] == 2) if PY2: # pragma: no cover def is_string(obj): return isinstance(obj, (str, unicode)) # noqa from StringIO import StringIO as BytesIO # noqa def force_text(str_or_unicode): if isinstance(str_or_unicode, unicode): # noqa return str_or_unicode return str_or_unicode.decode('utf-8') else: # pragma: no cover def is_string(obj): return isinstance(obj, str) from io import BytesIO # noqa def force_text(text): return text try: # pragma: no cover # Python >= 3.2 UTC = datetime.timezone.utc except AttributeError: # pragma: no cover try: # Fallback to pytz from pytz import UTC except ImportError: # Ok, let's write our own. class _UTC(datetime.tzinfo): """The UTC tzinfo.""" def utcoffset(self, dt): return datetime.timedelta(0) def tzname(self, dt): return "UTC" def dst(self, dt): return datetime.timedelta(0) def localize(self, dt): dt.astimezone(self) UTC = _UTC() factory_boy-2.8.1/factory/containers.py000066400000000000000000000266121302510513700202340ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (c) 2010 Mark Sandstrom # Copyright (c) 2011-2015 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. from __future__ import unicode_literals import logging from . import declarations from . import errors from . import utils logger = logging.getLogger(__name__) class LazyStub(object): """A generic container that only allows getting attributes. Attributes are set at instantiation time, values are computed lazily. Attributes: __initialized (bool): whether this object's __init__ as run. If set, setting any attribute will be prevented. __attrs (dict): maps attribute name to their declaration __values (dict): maps attribute name to computed value __pending (str list): names of the attributes whose value is being computed. This allows to detect cyclic lazy attribute definition. __containers (LazyStub list): "parents" of the LazyStub being built. This allows to have the field of a field depend on the value of another field __model_class (type): the model class to build. """ __initialized = False def __init__(self, attrs, containers=(), model_class=object, log_ctx=None): self.__attrs = attrs self.__values = {} self.__pending = [] self.__containers = containers self.__model_class = model_class self.__log_ctx = log_ctx or '%s.%s' % (model_class.__module__, model_class.__name__) self.factory_parent = containers[0] if containers else None self.__initialized = True def __repr__(self): return '' % (self.__model_class.__module__, self.__model_class.__name__) def __str__(self): return '' % ( self.__model_class.__name__, list(self.__attrs.keys())) def __fill__(self): """Fill this LazyStub, computing values of all defined attributes. Retunrs: dict: map of attribute name => computed value """ res = {} logger.debug( "LazyStub: Computing values for %s(%s)", self.__log_ctx, utils.log_pprint(kwargs=self.__attrs), ) for attr in self.__attrs: res[attr] = getattr(self, attr) logger.debug( "LazyStub: Computed values, got %s(%s)", self.__log_ctx, utils.log_pprint(kwargs=res), ) return res def __getattr__(self, name): """Retrieve an attribute's value. This will compute it if needed, unless it is already on the list of attributes being computed. """ if name in self.__pending: raise errors.CyclicDefinitionError( "Cyclic lazy attribute definition for %s; cycle found in %r." % (name, self.__pending)) elif name in self.__values: return self.__values[name] elif name in self.__attrs: val = self.__attrs[name] if isinstance(val, LazyValue): self.__pending.append(name) try: val = val.evaluate(self, self.__containers) finally: last = self.__pending.pop() assert name == last self.__values[name] = val return val else: raise AttributeError( "The parameter %s is unknown. Evaluated attributes are %r, " "definitions are %r." % (name, self.__values, self.__attrs)) def __setattr__(self, name, value): """Prevent setting attributes once __init__ is done.""" if not self.__initialized: return super(LazyStub, self).__setattr__(name, value) else: raise AttributeError('Setting of object attributes is not allowed') class DeclarationStack(object): """An ordered stack of declarations. This is intended to handle declaration precedence among different mutating layers. """ def __init__(self, ordering): self.ordering = ordering self.layers = dict((name, {}) for name in self.ordering) def __getitem__(self, key): return self.layers[key] def __setitem__(self, key, value): assert key in self.ordering self.layers[key] = value def current(self): """Retrieve the current, flattened declarations dict.""" result = {} for layer in self.ordering: result.update(self.layers[layer]) return result class ParameterResolver(object): """Resolve a factory's parameter declarations.""" def __init__(self, parameters, deps): self.parameters = parameters self.deps = deps self.declaration_stack = None self.resolved = set() def resolve_one(self, name): """Compute one field is needed, taking dependencies into accounts.""" if name in self.resolved: return for dep in self.deps.get(name, ()): self.resolve_one(dep) self.compute(name) self.resolved.add(name) def compute(self, name): """Actually compute the value for a given name.""" value = self.parameters[name] if isinstance(value, declarations.ComplexParameter): overrides = value.compute(name, self.declaration_stack.current()) else: overrides = {name: value} self.declaration_stack['overrides'].update(overrides) def resolve(self, declaration_stack): """Resolve parameters for a given declaration stack. Modifies the stack in-place. """ self.declaration_stack = declaration_stack for name in self.parameters: self.resolve_one(name) class LazyValue(object): """Some kind of "lazy evaluating" object.""" def evaluate(self, obj, containers=()): # pragma: no cover """Compute the value, using the given object.""" raise NotImplementedError("This is an abstract method.") class DeclarationWrapper(LazyValue): """Lazy wrapper around an OrderedDeclaration. Attributes: declaration (declarations.OrderedDeclaration): the OrderedDeclaration being wrapped sequence (int): the sequence counter to use when evaluatin the declaration """ def __init__(self, declaration, sequence, create, extra=None, **kwargs): super(DeclarationWrapper, self).__init__(**kwargs) self.declaration = declaration self.sequence = sequence self.create = create self.extra = extra def evaluate(self, obj, containers=()): """Lazily evaluate the attached OrderedDeclaration. Args: obj (LazyStub): the object being built containers (object list): the chain of containers of the object being built, its immediate holder being first. """ return self.declaration.evaluate( self.sequence, obj, create=self.create, extra=self.extra, containers=containers, ) def __repr__(self): return '<%s for %r>' % (self.__class__.__name__, self.declaration) class AttributeBuilder(object): """Builds attributes from a factory and extra data. Attributes: factory (base.Factory): the Factory for which attributes are being built _declarations (DeclarationDict): the attribute declarations for the factory _subfields (dict): dict mapping an attribute name to a dict of overridden default values for the related SubFactory. """ def __init__(self, factory, extra=None, log_ctx=None, **kwargs): super(AttributeBuilder, self).__init__(**kwargs) if not extra: extra = {} self.factory = factory self._containers = extra.pop('__containers', ()) initial_declarations = dict(factory._meta.declarations) self._log_ctx = log_ctx # Parameters # ---------- self._declarations = self.merge_declarations(initial_declarations, extra) # Subfields # --------- attrs_with_subfields = [ k for k, v in initial_declarations.items() if self.has_subfields(v) ] # Extract subfields; THIS MODIFIES self._declarations. self._subfields = utils.multi_extract_dict( attrs_with_subfields, self._declarations) def has_subfields(self, value): return isinstance(value, declarations.ParameteredAttribute) def merge_declarations(self, initial, extra): """Compute the final declarations, taking into account paramter-based overrides.""" # Precedence order: # - Start with class-level declarations # - Add overrides from parameters # - Finally, use callsite-level declarations & values declaration_stack = DeclarationStack(['initial', 'overrides', 'extra']) declaration_stack['initial'] = initial.copy() declaration_stack['extra'] = extra.copy() # Actually compute the final stack resolver = ParameterResolver( parameters=self.factory._meta.parameters, deps=self.factory._meta.parameters_dependencies, ) resolver.resolve(declaration_stack) return declaration_stack.current() def build(self, create, force_sequence=None): """Build a dictionary of attributes. Args: create (bool): whether to 'build' or 'create' the subfactories. force_sequence (int or None): if set to an int, use this value for the sequence counter; don't advance the related counter. """ # Setup factory sequence. if force_sequence is None: sequence = self.factory._generate_next_sequence() else: sequence = force_sequence # Parse attribute declarations, wrapping SubFactory and # OrderedDeclaration. wrapped_attrs = {} for k, v in self._declarations.items(): if isinstance(v, declarations.OrderedDeclaration): v = DeclarationWrapper( v, sequence=sequence, create=create, extra=self._subfields.get(k, {}), ) wrapped_attrs[k] = v stub = LazyStub( wrapped_attrs, containers=self._containers, model_class=self.factory, log_ctx=self._log_ctx) return stub.__fill__() class StubObject(object): """A generic container.""" pass factory_boy-2.8.1/factory/declarations.py000066400000000000000000000556361302510513700205470ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (c) 2010 Mark Sandstrom # Copyright (c) 2011-2015 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. from __future__ import unicode_literals import itertools import logging from . import compat from . import utils logger = logging.getLogger('factory.generate') class OrderedDeclaration(object): """A factory declaration. Ordered declarations mark an attribute as needing lazy evaluation. This allows them to refer to attributes defined by other OrderedDeclarations in the same factory. """ def evaluate(self, sequence, obj, create, extra=None, containers=()): """Evaluate this declaration. Args: sequence (int): the current sequence counter to use when filling the current instance obj (containers.LazyStub): The object holding currently computed attributes containers (list of containers.LazyStub): The chain of SubFactory which led to building this object. create (bool): whether the model class should be 'built' or 'created' extra (DeclarationDict or None): extracted key/value extracted from the attribute prefix """ 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 %s on %s", utils.log_repr(self.function), utils.log_repr(obj)) return self.function() class LazyAttribute(OrderedDeclaration): """Specific OrderedDeclaration computed using a lambda. Attributes: function (function): a function, expecting the current LazyStub and returning the computed value. """ def __init__(self, function, *args, **kwargs): super(LazyAttribute, self).__init__(*args, **kwargs) self.function = function def evaluate(self, sequence, obj, create, extra=None, containers=()): logger.debug("LazyAttribute: Evaluating %s on %s", utils.log_repr(self.function), utils.log_repr(obj)) return self.function(obj) class _UNSPECIFIED(object): pass def deepgetattr(obj, name, default=_UNSPECIFIED): """Try to retrieve the given attribute of an object, digging on '.'. This is an extended getattr, digging deeper if '.' is found. Args: obj (object): the object of which an attribute should be read name (str): the name of an attribute to look up. default (object): the default value to use if the attribute wasn't found Returns: the attribute pointed to by 'name', splitting on '.'. Raises: AttributeError: if obj has no 'name' attribute. """ try: if '.' in name: attr, subname = name.split('.', 1) return deepgetattr(getattr(obj, attr), subname, default) else: return getattr(obj, name) except AttributeError: if default is _UNSPECIFIED: raise else: return default class SelfAttribute(OrderedDeclaration): """Specific OrderedDeclaration copying values from other fields. If the field name starts with two dots or more, the lookup will be anchored in the related 'parent'. Attributes: depth (int): the number of steps to go up in the containers chain attribute_name (str): the name of the attribute to copy. default (object): the default value to use if the attribute doesn't exist. """ def __init__(self, attribute_name, default=_UNSPECIFIED, *args, **kwargs): super(SelfAttribute, self).__init__(*args, **kwargs) depth = len(attribute_name) - len(attribute_name.lstrip('.')) attribute_name = attribute_name[depth:] self.depth = depth self.attribute_name = attribute_name self.default = default def evaluate(self, sequence, obj, create, extra=None, containers=()): if self.depth > 1: # Fetching from a parent target = containers[self.depth - 2] else: target = obj logger.debug("SelfAttribute: Picking attribute %r on %s", self.attribute_name, utils.log_repr(target)) return deepgetattr(target, self.attribute_name, self.default) def __repr__(self): return '<%s(%r, default=%r)>' % ( self.__class__.__name__, self.attribute_name, self.default, ) class Iterator(OrderedDeclaration): """Fill this value using the values returned by an iterator. Warning: the iterator should not end ! Attributes: iterator (iterable): the iterator whose value should be used. getter (callable or None): a function to parse returned values """ def __init__(self, iterator, cycle=True, getter=None): super(Iterator, self).__init__() self.getter = getter self.iterator = None if cycle: self.iterator_builder = lambda: utils.ResetableIterator(itertools.cycle(iterator)) else: self.iterator_builder = lambda: utils.ResetableIterator(iterator) def evaluate(self, sequence, obj, create, extra=None, containers=()): # Begin unrolling as late as possible. # This helps with ResetableIterator(MyModel.objects.all()) if self.iterator is None: self.iterator = self.iterator_builder() logger.debug("Iterator: Fetching next value from %s", utils.log_repr(self.iterator)) value = next(iter(self.iterator)) if self.getter is None: return value return self.getter(value) def reset(self): """Reset the internal iterator.""" self.iterator.reset() class Sequence(OrderedDeclaration): """Specific OrderedDeclaration to use for 'sequenced' fields. These fields are typically used to generate increasing unique values. Attributes: function (function): A function, expecting the current sequence counter and returning the computed value. type (function): A function converting an integer into the expected kind of counter for the 'function' attribute. """ def __init__(self, function, type=int): super(Sequence, self).__init__() self.function = function self.type = type def evaluate(self, sequence, obj, create, extra=None, containers=()): logger.debug("Sequence: Computing next value of %r for seq=%s", self.function, sequence) return self.function(self.type(sequence)) class LazyAttributeSequence(Sequence): """Composite of a LazyAttribute and a Sequence. Attributes: function (function): A function, expecting the current LazyStub and the current sequence counter. type (function): A function converting an integer into the expected kind of counter for the 'function' attribute. """ def evaluate(self, sequence, obj, create, extra=None, containers=()): logger.debug( "LazyAttributeSequence: Computing next value of %r for seq=%s, obj=%s", self.function, sequence, utils.log_repr(obj)) return self.function(obj, self.type(sequence)) class ContainerAttribute(OrderedDeclaration): """Variant of LazyAttribute, also receives the containers of the object. Attributes: function (function): A function, expecting the current LazyStub and the (optional) object having a subfactory containing this attribute. strict (bool): Whether evaluating should fail when the containers are not passed in (i.e used outside a SubFactory). """ def __init__(self, function, strict=True, *args, **kwargs): super(ContainerAttribute, self).__init__(*args, **kwargs) self.function = function self.strict = strict def evaluate(self, sequence, obj, create, extra=None, containers=()): """Evaluate the current ContainerAttribute. Args: obj (LazyStub): a lazy stub of the object being constructed, if needed. containers (list of LazyStub): a list of lazy stubs of factories being evaluated in a chain, each item being a future field of next one. """ if self.strict and not containers: raise TypeError( "A ContainerAttribute in 'strict' mode can only be used " "within a SubFactory.") return self.function(obj, containers) class ParameteredAttribute(OrderedDeclaration): """Base class for attributes expecting parameters. Attributes: 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' # Whether to add the current object to the stack of containers EXTEND_CONTAINERS = False def __init__(self, **kwargs): super(ParameteredAttribute, self).__init__() self.defaults = kwargs def _prepare_containers(self, obj, containers=()): if self.EXTEND_CONTAINERS: return (obj,) + tuple(containers) return containers def evaluate(self, sequence, obj, create, extra=None, containers=()): """Evaluate the current definition and fill its attributes. Uses attributes definition in the following order: - values defined when defining the ParameteredAttribute - additional values defined when instantiating the containing factory Args: create (bool): whether the parent factory is being 'built' or 'created' extra (containers.DeclarationDict): extra values that should 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) if self.CONTAINERS_FIELD: containers = self._prepare_containers(obj, containers) defaults[self.CONTAINERS_FIELD] = containers return self.generate(sequence, obj, create, defaults) def generate(self, sequence, obj, create, params): # pragma: no cover """Actually generate the related attribute. Args: sequence (int): the current sequence number obj (LazyStub): the object being constructed create (bool): whether the calling factory was in 'create' or 'build' mode params (dict): parameters inherited from init and evaluation-time overrides. Returns: Computed value for the current declaration. """ raise NotImplementedError() class _FactoryWrapper(object): """Handle a 'factory' arg. Such args can be either a Factory subclass, or a fully qualified import path for that subclass (e.g 'myapp.factories.MyFactory'). """ def __init__(self, factory_or_path): self.factory = None self.module = self.name = '' if isinstance(factory_or_path, type): self.factory = factory_or_path else: if not (compat.is_string(factory_or_path) and '.' in factory_or_path): raise ValueError( "A factory= argument must receive either a class " "or the fully qualified path to a Factory subclass; got " "%r instead." % factory_or_path) self.module, self.name = factory_or_path.rsplit('.', 1) def get(self): if self.factory is None: self.factory = utils.import_object( self.module, self.name, ) return self.factory def __repr__(self): if self.factory is None: return '<_FactoryImport: %s.%s>' % (self.module, self.name) else: return '<_FactoryImport: %s>' % self.factory.__class__ class SubFactory(ParameteredAttribute): """Base class for attributes based upon a sub-factory. Attributes: defaults (dict): Overrides to the defaults defined in the wrapped factory factory (base.Factory): the wrapped factory """ EXTEND_CONTAINERS = True def __init__(self, factory, **kwargs): super(SubFactory, self).__init__(**kwargs) self.factory_wrapper = _FactoryWrapper(factory) def get_factory(self): """Retrieve the wrapped factory.Factory subclass.""" return self.factory_wrapper.get() def generate(self, sequence, obj, create, params): """Evaluate the current definition and fill its attributes. Args: create (bool): whether the subfactory should call 'build' or 'create' params (containers.DeclarationDict): extra values that should override the wrapped factory's defaults """ subfactory = self.get_factory() logger.debug( "SubFactory: Instantiating %s.%s(%s), create=%r", subfactory.__module__, subfactory.__name__, utils.log_pprint(kwargs=params), create, ) return subfactory.simple_generate(create, **params) class Dict(SubFactory): """Fill a dict with usual declarations.""" def __init__(self, params, dict_factory='factory.DictFactory'): super(Dict, self).__init__(dict_factory, **dict(params)) def generate(self, sequence, obj, create, params): dict_factory = self.get_factory() logger.debug("Dict: Building dict(%s)", utils.log_pprint(kwargs=params)) return dict_factory.simple_generate( create, __sequence=sequence, **params) class List(SubFactory): """Fill a list with standard declarations.""" def __init__(self, params, list_factory='factory.ListFactory'): params = dict((str(i), v) for i, v in enumerate(params)) super(List, self).__init__(list_factory, **params) def generate(self, sequence, obj, create, params): list_factory = self.get_factory() logger.debug( "List: Building list(%s)", utils.log_pprint(args=[v for _i, v in sorted(params.items())]), ) return list_factory.simple_generate( create, __sequence=sequence, **params) # Parameters # ========== class ComplexParameter(object): """A complex parameter, to be used in a Factory.Params section. Must implement: - A "compute" function, performing the actual declaration override - Optionally, a get_revdeps() function (to compute other parameters it may alter) """ def compute(self, field_name, declarations): """Compute the overrides for this parameter. Args: - field_name (str): the field this parameter is installed at - declarations (dict): the global factory declarations Returns: dict: the declarations to override """ raise NotImplementedError() def get_revdeps(self, parameters): """Retrieve the list of other parameters modified by this one.""" return [] class Trait(ComplexParameter): """The simplest complex parameter, it enables a bunch of new declarations based on a boolean flag.""" def __init__(self, **overrides): self.overrides = overrides def compute(self, field_name, declarations): if declarations.get(field_name): return self.overrides else: return {} def get_revdeps(self, parameters): """This might alter fields it's injecting.""" return [param for param in parameters if param in self.overrides] # Post-generation # =============== class ExtractionContext(object): """Private class holding all required context from extraction to postgen.""" def __init__(self, value=None, did_extract=False, extra=None, for_field=''): self.value = value self.did_extract = did_extract self.extra = extra or {} self.for_field = for_field def __repr__(self): return 'ExtractionContext(%r, %r, %r)' % ( self.value, self.did_extract, self.extra, ) class PostGenerationDeclaration(object): """Declarations to be called once the model object has been generated.""" creation_counter = 0 """Global creation counter of the declaration.""" def __init__(self, *args, **kwargs): self.creation_counter = PostGenerationDeclaration.creation_counter PostGenerationDeclaration.creation_counter += 1 def extract(self, name, attrs): """Extract relevant attributes from a dict. Args: name (str): the name at which this PostGenerationDeclaration was defined in the declarations attrs (dict): the attribute dict from which values should be extracted Returns: (object, dict): a tuple containing the attribute at 'name' (if provided) and a dict of extracted attributes """ try: extracted = attrs.pop(name) did_extract = True except KeyError: extracted = None did_extract = False kwargs = utils.extract_dict(name, attrs) return ExtractionContext(extracted, did_extract, kwargs, name) def call(self, obj, create, extraction_context): # pragma: no cover """Call this hook; no return value is expected. Args: obj (object): the newly generated object create (bool): whether the object was 'built' or 'created' extraction_context: An ExtractionContext containing values extracted from the containing factory's declaration """ raise NotImplementedError() class PostGeneration(PostGenerationDeclaration): """Calls a given function once the object has been generated.""" def __init__(self, function): super(PostGeneration, self).__init__() self.function = function def call(self, obj, create, extraction_context): logger.debug( "PostGeneration: Calling %s.%s(%s)", self.function.__module__, self.function.__name__, utils.log_pprint( (obj, create, extraction_context.value), extraction_context.extra, ), ) return self.function( obj, create, extraction_context.value, **extraction_context.extra) class RelatedFactory(PostGenerationDeclaration): """Calls a factory once the object has been generated. Attributes: factory (Factory): the factory to call defaults (dict): extra declarations for calling the related factory name (str): the name to use to refer to the generated object when calling the related factory """ def __init__(self, factory, factory_related_name='', **defaults): super(RelatedFactory, self).__init__() self.name = factory_related_name self.defaults = defaults self.factory_wrapper = _FactoryWrapper(factory) def get_factory(self): """Retrieve the wrapped factory.Factory subclass.""" return self.factory_wrapper.get() def call(self, obj, create, extraction_context): factory = self.get_factory() if extraction_context.did_extract: # The user passed in a custom value logger.debug( "RelatedFactory: Using provided %s instead of generating %s.%s.", utils.log_repr(extraction_context.value), factory.__module__, factory.__name__, ) return extraction_context.value passed_kwargs = dict(self.defaults) passed_kwargs.update(extraction_context.extra) if self.name: passed_kwargs[self.name] = obj logger.debug( "RelatedFactory: Generating %s.%s(%s)", factory.__module__, factory.__name__, utils.log_pprint((create,), passed_kwargs), ) return factory.simple_generate(create, **passed_kwargs) class PostGenerationMethodCall(PostGenerationDeclaration): """Calls a method of the generated object. Attributes: method_name (str): the method to call method_args (list): arguments to pass to the method method_kwargs (dict): keyword arguments to pass to the method Example: class UserFactory(factory.Factory): ... password = factory.PostGenerationMethodCall('set_pass', password='') """ def __init__(self, method_name, *args, **kwargs): super(PostGenerationMethodCall, self).__init__() self.method_name = method_name self.method_args = args self.method_kwargs = kwargs def call(self, obj, create, extraction_context): if not extraction_context.did_extract: passed_args = self.method_args elif len(self.method_args) <= 1: # Max one argument expected passed_args = (extraction_context.value,) else: passed_args = tuple(extraction_context.value) passed_kwargs = dict(self.method_kwargs) passed_kwargs.update(extraction_context.extra) method = getattr(obj, self.method_name) logger.debug( "PostGenerationMethodCall: Calling %s.%s(%s)", utils.log_repr(obj), self.method_name, utils.log_pprint(passed_args, passed_kwargs), ) return method(*passed_args, **passed_kwargs) factory_boy-2.8.1/factory/django.py000066400000000000000000000267321302510513700173340ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (c) 2010 Mark Sandstrom # Copyright (c) 2011-2015 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. """factory_boy extensions for use with the Django framework.""" from __future__ import absolute_import from __future__ import unicode_literals import os import logging import functools try: import django from django.core import files as django_files except ImportError as e: # pragma: no cover django = None django_files = None import_failure = e from . import base from . import declarations from . import errors from .compat import BytesIO, is_string logger = logging.getLogger('factory.generate') DEFAULT_DB_ALIAS = 'default' # Same as django.db.DEFAULT_DB_ALIAS def require_django(): """Simple helper to ensure Django is available.""" if django_files is None: # pragma: no cover raise import_failure _LAZY_LOADS = {} def get_model(app, model): """Wrapper around django's get_model.""" if 'get_model' not in _LAZY_LOADS: _lazy_load_get_model() _get_model = _LAZY_LOADS['get_model'] return _get_model(app, model) def _lazy_load_get_model(): """Lazy loading of get_model. get_model loads django.conf.settings, which may fail if the settings haven't been configured yet. """ if django is None: def _get_model(app, model): raise import_failure elif django.VERSION[:2] < (1, 7): from django.db.models.loading import get_model as _get_model else: from django import apps as django_apps _get_model = django_apps.apps.get_model _LAZY_LOADS['get_model'] = _get_model class DjangoOptions(base.FactoryOptions): def _build_default_options(self): return super(DjangoOptions, self)._build_default_options() + [ base.OptionDefault('django_get_or_create', (), inherit=True), base.OptionDefault('database', DEFAULT_DB_ALIAS, inherit=True), ] def _get_counter_reference(self): counter_reference = super(DjangoOptions, self)._get_counter_reference() if (counter_reference == self.base_factory and self.base_factory._meta.model is not None and self.base_factory._meta.model._meta.abstract and self.model is not None and not self.model._meta.abstract): # Target factory is for an abstract model, yet we're for another, # concrete subclass => don't reuse the counter. return self.factory return counter_reference class DjangoModelFactory(base.Factory): """Factory for Django models. This makes sure that the 'sequence' field of created objects is a new id. Possible improvement: define a new 'attribute' type, AutoField, which would handle those for non-numerical primary keys. """ _options_class = DjangoOptions class Meta: abstract = True # Optional, but explicit. @classmethod def _load_model_class(cls, definition): if is_string(definition) and '.' in definition: app, model = definition.split('.', 1) return get_model(app, model) return definition @classmethod def _get_manager(cls, model_class): if model_class is None: raise errors.AssociatedClassError( "No model set on %s.%s.Meta" % (cls.__module__, cls.__name__)) try: manager = model_class.objects except AttributeError: # When inheriting from an abstract model with a custom # manager, the class has no 'objects' field. manager = model_class._default_manager if cls._meta.database != DEFAULT_DB_ALIAS: manager = manager.using(cls._meta.database) return manager @classmethod def _get_or_create(cls, model_class, *args, **kwargs): """Create an instance of the model through objects.get_or_create.""" manager = cls._get_manager(model_class) assert 'defaults' not in cls._meta.django_get_or_create, ( "'defaults' is a reserved keyword for get_or_create " "(in %s._meta.django_get_or_create=%r)" % (cls, cls._meta.django_get_or_create)) key_fields = {} for field in cls._meta.django_get_or_create: if field not in kwargs: raise errors.FactoryError( "django_get_or_create - " "Unable to find initialization value for '%s' in factory %s" % (field, cls.__name__)) key_fields[field] = kwargs.pop(field) key_fields['defaults'] = kwargs obj, _created = manager.get_or_create(*args, **key_fields) return obj @classmethod def _create(cls, model_class, *args, **kwargs): """Create an instance of the model, and save it to the database.""" manager = cls._get_manager(model_class) if cls._meta.django_get_or_create: return cls._get_or_create(model_class, *args, **kwargs) return manager.create(*args, **kwargs) @classmethod def _after_postgeneration(cls, obj, create, results=None): """Save again the instance if creating and at least one hook ran.""" if create and results: # Some post-generation hooks ran, and may have modified us. obj.save() class FileField(declarations.ParameteredAttribute): """Helper to fill in django.db.models.FileField from a Factory.""" DEFAULT_FILENAME = 'example.dat' EXTEND_CONTAINERS = True def __init__(self, **defaults): require_django() super(FileField, self).__init__(**defaults) def _make_data(self, params): """Create data for the field.""" return params.get('data', b'') def _make_content(self, params): path = '' _content_params = [params.get('from_path'), params.get('from_file'), params.get('from_func')] if len([p for p in _content_params if p]) > 1: raise ValueError( "At most one argument from 'from_file', 'from_path', and 'from_func' should " "be non-empty when calling factory.django.FileField." ) if params.get('from_path'): path = params['from_path'] f = open(path, 'rb') content = django_files.File(f, name=path) elif params.get('from_file'): f = params['from_file'] content = django_files.File(f) path = content.name elif params.get('from_func'): func = params['from_func'] content = django_files.File(func()) path = content.name else: data = self._make_data(params) content = django_files.base.ContentFile(data) if path: default_filename = os.path.basename(path) else: default_filename = self.DEFAULT_FILENAME filename = params.get('filename', default_filename) return filename, content def generate(self, sequence, obj, create, params): """Fill in the field.""" params.setdefault('__sequence', sequence) params = base.DictFactory.simple_generate(create, **params) filename, content = self._make_content(params) return django_files.File(content.file, filename) class ImageField(FileField): DEFAULT_FILENAME = 'example.jpg' def _make_data(self, params): # ImageField (both django's and factory_boy's) require PIL. # Try to import it along one of its known installation paths. try: from PIL import Image except ImportError: import Image width = params.get('width', 100) height = params.get('height', width) color = params.get('color', 'blue') image_format = params.get('format', 'JPEG') thumb = Image.new('RGB', (width, height), color) thumb_io = BytesIO() thumb.save(thumb_io, format=image_format) return thumb_io.getvalue() class mute_signals(object): """Temporarily disables and then restores any django signals. Args: *signals (django.dispatch.dispatcher.Signal): any django signals Examples: with mute_signals(pre_init): user = UserFactory.build() ... @mute_signals(pre_save, post_save) class UserFactory(factory.Factory): ... @mute_signals(post_save) def generate_users(): UserFactory.create_batch(10) """ def __init__(self, *signals): self.signals = signals self.paused = {} def __enter__(self): for signal in self.signals: logger.debug('mute_signals: Disabling signal handlers %r', signal.receivers) # Note that we're using implementation details of # django.signals, since arguments to signal.connect() # are lost in signal.receivers self.paused[signal] = signal.receivers signal.receivers = [] def __exit__(self, exc_type, exc_value, traceback): for signal, receivers in self.paused.items(): logger.debug('mute_signals: Restoring signal handlers %r', receivers) signal.receivers = receivers if django.VERSION[:2] >= (1, 6): with signal.lock: # Django uses some caching for its signals. # Since we're bypassing signal.connect and signal.disconnect, # we have to keep messing with django's internals. signal.sender_receivers_cache.clear() self.paused = {} def copy(self): return mute_signals(*self.signals) def __call__(self, callable_obj): if isinstance(callable_obj, base.FactoryMetaClass): # Retrieve __func__, the *actual* callable object. generate_method = callable_obj._generate.__func__ @classmethod @functools.wraps(generate_method) def wrapped_generate(*args, **kwargs): # A mute_signals() object is not reentrant; use a copy everytime. with self.copy(): return generate_method(*args, **kwargs) callable_obj._generate = wrapped_generate return callable_obj else: @functools.wraps(callable_obj) def wrapper(*args, **kwargs): # A mute_signals() object is not reentrant; use a copy everytime. with self.copy(): return callable_obj(*args, **kwargs) return wrapper factory_boy-2.8.1/factory/errors.py000066400000000000000000000031301302510513700173710ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (c) 2011-2015 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. class FactoryError(Exception): """Any exception raised by factory_boy.""" class AssociatedClassError(FactoryError): """Exception for Factory subclasses lacking Meta.model.""" class UnknownStrategy(FactoryError): """Raised when a factory uses an unknown strategy.""" class UnsupportedStrategy(FactoryError): """Raised when trying to use a strategy on an incompatible Factory.""" class CyclicDefinitionError(FactoryError): """Raised when a cyclical declaration occurs.""" factory_boy-2.8.1/factory/faker.py000066400000000000000000000063101302510513700171500ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (c) 2010 Mark Sandstrom # Copyright (c) 2011-2015 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. """Additional declarations for "faker" attributes. Usage: class MyFactory(factory.Factory): class Meta: model = MyProfile first_name = factory.Faker('name') """ from __future__ import absolute_import from __future__ import unicode_literals import contextlib import faker import faker.config from . import declarations class Faker(declarations.OrderedDeclaration): """Wrapper for 'faker' values. Args: provider (str): the name of the Faker field locale (str): the locale to use for the faker All other kwargs will be passed to the underlying provider (e.g ``factory.Faker('ean', length=10)`` calls ``faker.Faker.ean(length=10)``) Usage: >>> foo = factory.Faker('name') """ def __init__(self, provider, locale=None, **kwargs): self.provider = provider self.provider_kwargs = kwargs self.locale = locale def generate(self, extra_kwargs): kwargs = {} kwargs.update(self.provider_kwargs) kwargs.update(extra_kwargs) subfaker = self._get_faker(self.locale) return subfaker.format(self.provider, **kwargs) def evaluate(self, sequence, obj, create, extra=None, containers=()): return self.generate(extra or {}) _FAKER_REGISTRY = {} _DEFAULT_LOCALE = faker.config.DEFAULT_LOCALE @classmethod @contextlib.contextmanager def override_default_locale(cls, locale): old_locale = cls._DEFAULT_LOCALE cls._DEFAULT_LOCALE = locale try: yield finally: cls._DEFAULT_LOCALE = old_locale @classmethod def _get_faker(cls, locale=None): if locale is None: locale = cls._DEFAULT_LOCALE if locale not in cls._FAKER_REGISTRY: cls._FAKER_REGISTRY[locale] = faker.Faker(locale=locale) return cls._FAKER_REGISTRY[locale] @classmethod def add_provider(cls, provider, locale=None): """Add a new Faker provider for the specified locale""" cls._get_faker(locale).add_provider(provider) factory_boy-2.8.1/factory/fuzzy.py000066400000000000000000000232251302510513700172530ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (c) 2010 Mark Sandstrom # Copyright (c) 2011-2015 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. """Additional declarations for "fuzzy" attribute definitions.""" from __future__ import unicode_literals import decimal import random import string import datetime from . import compat from . import declarations _random = random.Random() def get_random_state(): """Retrieve the state of factory.fuzzy's random generator.""" return _random.getstate() def set_random_state(state): """Force-set the state of factory.fuzzy's random generator.""" return _random.setstate(state) def reseed_random(seed): """Reseed factory.fuzzy's random generator.""" r = random.Random(seed) set_random_state(r.getstate()) class BaseFuzzyAttribute(declarations.OrderedDeclaration): """Base class for fuzzy attributes. Custom fuzzers should override the `fuzz()` method. """ def fuzz(self): # pragma: no cover raise NotImplementedError() def evaluate(self, sequence, obj, create, extra=None, containers=()): return self.fuzz() class FuzzyAttribute(BaseFuzzyAttribute): """Similar to LazyAttribute, but yields random values. Attributes: function (callable): function taking no parameters and returning a random value. """ def __init__(self, fuzzer, **kwargs): super(FuzzyAttribute, self).__init__(**kwargs) self.fuzzer = fuzzer def fuzz(self): return self.fuzzer() class FuzzyText(BaseFuzzyAttribute): """Random string with a given prefix. Generates a random string of the given length from chosen chars. If a prefix or a suffix are supplied, they will be prepended / appended to the generated string. Args: prefix (text): An optional prefix to prepend to the random string length (int): the length of the random part suffix (text): An optional suffix to append to the random string chars (str list): the chars to choose from Useful for generating unique attributes where the exact value is not important. """ def __init__(self, prefix='', length=12, suffix='', chars=string.ascii_letters, **kwargs): super(FuzzyText, self).__init__(**kwargs) self.prefix = prefix self.suffix = suffix self.length = length self.chars = tuple(chars) # Unroll iterators def fuzz(self): chars = [_random.choice(self.chars) for _i in range(self.length)] return self.prefix + ''.join(chars) + self.suffix class FuzzyChoice(BaseFuzzyAttribute): """Handles fuzzy choice of an attribute. Args: choices (iterable): An iterable yielding options; will only be unrolled on the first call. """ def __init__(self, choices, **kwargs): self.choices = None self.choices_generator = choices super(FuzzyChoice, self).__init__(**kwargs) def fuzz(self): if self.choices is None: self.choices = list(self.choices_generator) return _random.choice(self.choices) class FuzzyInteger(BaseFuzzyAttribute): """Random integer within a given range.""" def __init__(self, low, high=None, step=1, **kwargs): if high is None: high = low low = 0 self.low = low self.high = high self.step = step super(FuzzyInteger, self).__init__(**kwargs) def fuzz(self): return _random.randrange(self.low, self.high + 1, self.step) class FuzzyDecimal(BaseFuzzyAttribute): """Random decimal within a given range.""" def __init__(self, low, high=None, precision=2, **kwargs): if high is None: high = low low = 0.0 self.low = low self.high = high self.precision = precision super(FuzzyDecimal, self).__init__(**kwargs) def fuzz(self): base = decimal.Decimal(str(_random.uniform(self.low, self.high))) return base.quantize(decimal.Decimal(10) ** -self.precision) class FuzzyFloat(BaseFuzzyAttribute): """Random float within a given range.""" def __init__(self, low, high=None, **kwargs): if high is None: high = low low = 0 self.low = low self.high = high super(FuzzyFloat, self).__init__(**kwargs) def fuzz(self): return _random.uniform(self.low, self.high) class FuzzyDate(BaseFuzzyAttribute): """Random date within a given date range.""" def __init__(self, start_date, end_date=None, **kwargs): super(FuzzyDate, self).__init__(**kwargs) if end_date is None: end_date = datetime.date.today() if start_date > end_date: raise ValueError( "FuzzyDate boundaries should have start <= end; got %r > %r." % (start_date, end_date)) self.start_date = start_date.toordinal() self.end_date = end_date.toordinal() def fuzz(self): return datetime.date.fromordinal(_random.randint(self.start_date, self.end_date)) class BaseFuzzyDateTime(BaseFuzzyAttribute): """Base class for fuzzy datetime-related attributes. Provides fuzz() computation, forcing year/month/day/hour/... """ def _check_bounds(self, start_dt, end_dt): if start_dt > end_dt: raise ValueError( """%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, force_microsecond=None, **kwargs): super(BaseFuzzyDateTime, self).__init__(**kwargs) if end_dt is None: end_dt = self._now() self._check_bounds(start_dt, end_dt) self.start_dt = start_dt self.end_dt = end_dt self.force_year = force_year self.force_month = force_month self.force_day = force_day self.force_hour = force_hour self.force_minute = force_minute self.force_second = force_second self.force_microsecond = force_microsecond def fuzz(self): delta = self.end_dt - self.start_dt microseconds = delta.microseconds + 1000000 * (delta.seconds + (delta.days * 86400)) offset = _random.randint(0, microseconds) result = self.start_dt + datetime.timedelta(microseconds=offset) if self.force_year is not None: result = result.replace(year=self.force_year) if self.force_month is not None: result = result.replace(month=self.force_month) if self.force_day is not None: result = result.replace(day=self.force_day) if self.force_hour is not None: result = result.replace(hour=self.force_hour) if self.force_minute is not None: result = result.replace(minute=self.force_minute) if self.force_second is not None: result = result.replace(second=self.force_second) if self.force_microsecond is not None: result = result.replace(microsecond=self.force_microsecond) return result class FuzzyNaiveDateTime(BaseFuzzyDateTime): """Random naive datetime within a given range. If no upper bound is given, will default to datetime.datetime.utcnow(). """ def _now(self): return datetime.datetime.now() def _check_bounds(self, start_dt, end_dt): if start_dt.tzinfo is not None: raise ValueError( "FuzzyNaiveDateTime only handles naive datetimes, got start=%r" % start_dt) if end_dt.tzinfo is not None: raise ValueError( "FuzzyNaiveDateTime only handles naive datetimes, got end=%r" % end_dt) super(FuzzyNaiveDateTime, self)._check_bounds(start_dt, end_dt) class FuzzyDateTime(BaseFuzzyDateTime): """Random timezone-aware datetime within a given range. If no upper bound is given, will default to datetime.datetime.now() If no timezone is given, will default to utc. """ def _now(self): return datetime.datetime.now(tz=compat.UTC) def _check_bounds(self, start_dt, end_dt): if start_dt.tzinfo is None: raise ValueError( "FuzzyDateTime requires timezone-aware datetimes, got start=%r" % start_dt) if end_dt.tzinfo is None: raise ValueError( "FuzzyDateTime requires timezone-aware datetimes, got end=%r" % end_dt) super(FuzzyDateTime, self)._check_bounds(start_dt, end_dt) factory_boy-2.8.1/factory/helpers.py000066400000000000000000000106411302510513700175240ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (c) 2010 Mark Sandstrom # Copyright (c) 2011-2015 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. """Simple wrappers around Factory class definition.""" import contextlib import logging from . import base from . import declarations @contextlib.contextmanager def debug(logger='factory', stream=None): logger_obj = logging.getLogger(logger) old_level = logger_obj.level handler = logging.StreamHandler(stream) handler.setLevel(logging.DEBUG) logger_obj.addHandler(handler) logger_obj.setLevel(logging.DEBUG) yield logger_obj.setLevel(old_level) logger_obj.removeHandler(handler) def make_factory(klass, **kwargs): """Create a new, simple factory for the given class.""" factory_name = '%sFactory' % klass.__name__ class Meta: model = klass kwargs['Meta'] = Meta base_class = kwargs.pop('FACTORY_CLASS', base.Factory) factory_class = type(base.Factory).__new__(type(base.Factory), factory_name, (base_class,), kwargs) factory_class.__name__ = '%sFactory' % klass.__name__ factory_class.__doc__ = 'Auto-generated factory for class %s' % klass return factory_class def build(klass, **kwargs): """Create a factory for the given class, and build an instance.""" return make_factory(klass, **kwargs).build() def build_batch(klass, size, **kwargs): """Create a factory for the given class, and build a batch of instances.""" return make_factory(klass, **kwargs).build_batch(size) def create(klass, **kwargs): """Create a factory for the given class, and create an instance.""" return make_factory(klass, **kwargs).create() def create_batch(klass, size, **kwargs): """Create a factory for the given class, and create a batch of instances.""" return make_factory(klass, **kwargs).create_batch(size) def stub(klass, **kwargs): """Create a factory for the given class, and stub an instance.""" return make_factory(klass, **kwargs).stub() def stub_batch(klass, size, **kwargs): """Create a factory for the given class, and stub a batch of instances.""" return make_factory(klass, **kwargs).stub_batch(size) def generate(klass, strategy, **kwargs): """Create a factory for the given class, and generate an instance.""" return make_factory(klass, **kwargs).generate(strategy) def generate_batch(klass, strategy, size, **kwargs): """Create a factory for the given class, and generate instances.""" return make_factory(klass, **kwargs).generate_batch(strategy, size) def simple_generate(klass, create, **kwargs): """Create a factory for the given class, and simple_generate an instance.""" return make_factory(klass, **kwargs).simple_generate(create) def simple_generate_batch(klass, create, size, **kwargs): """Create a factory for the given class, and simple_generate instances.""" return make_factory(klass, **kwargs).simple_generate_batch(create, size) def lazy_attribute(func): return declarations.LazyAttribute(func) def iterator(func): """Turn a generator function into an iterator attribute.""" return declarations.Iterator(func()) def sequence(func): return declarations.Sequence(func) def lazy_attribute_sequence(func): return declarations.LazyAttributeSequence(func) def container_attribute(func): return declarations.ContainerAttribute(func, strict=False) def post_generation(fun): return declarations.PostGeneration(fun) factory_boy-2.8.1/factory/mogo.py000066400000000000000000000032341302510513700170230ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (c) 2010 Mark Sandstrom # Copyright (c) 2011-2015 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. """factory_boy extensions for use with the mogo library (pymongo wrapper).""" from __future__ import unicode_literals from . import base class MogoFactory(base.Factory): """Factory for mogo objects.""" class Meta: abstract = True @classmethod def _build(cls, model_class, *args, **kwargs): return model_class(*args, **kwargs) @classmethod def _create(cls, model_class, *args, **kwargs): instance = model_class(*args, **kwargs) instance.save() return instance factory_boy-2.8.1/factory/mongoengine.py000066400000000000000000000033311302510513700203650ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (c) 2010 Mark Sandstrom # Copyright (c) 2011-2015 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. """factory_boy extensions for use with the mongoengine library (pymongo wrapper).""" from __future__ import unicode_literals from . import base class MongoEngineFactory(base.Factory): """Factory for mongoengine objects.""" class Meta: abstract = True @classmethod def _build(cls, model_class, *args, **kwargs): return model_class(*args, **kwargs) @classmethod def _create(cls, model_class, *args, **kwargs): instance = model_class(*args, **kwargs) if instance._is_document: instance.save() return instance factory_boy-2.8.1/factory/utils.py000066400000000000000000000124541302510513700172260ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (c) 2010 Mark Sandstrom # Copyright (c) 2011-2015 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. from __future__ import unicode_literals import collections from . import compat #: String for splitting an attribute name into a #: (subfactory_name, subfactory_field) tuple. ATTR_SPLITTER = '__' def extract_dict(prefix, kwargs, pop=True, exclude=()): """Extracts all values beginning with a given prefix from a dict. Can either 'pop' or 'get' them; Args: prefix (str): the prefix to use for lookups kwargs (dict): the dict from which values should be extracted; WILL BE MODIFIED. pop (bool): whether to use pop (True) or get (False) exclude (iterable): list of prefixed keys that shouldn't be extracted Returns: A new dict, containing values from kwargs and beginning with prefix + ATTR_SPLITTER. That full prefix is removed from the keys of the returned dict. """ prefix = prefix + ATTR_SPLITTER extracted = {} for key in list(kwargs): if key in exclude: continue if key.startswith(prefix): new_key = key[len(prefix):] if pop: value = kwargs.pop(key) else: value = kwargs[key] extracted[new_key] = value return extracted def multi_extract_dict(prefixes, kwargs, pop=True, exclude=()): """Extracts all values from a given list of prefixes. 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; WILL BE MODIFIED. 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 key/value. """ results = {} exclude = list(exclude) 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') """ # Py2 compatibility: force str (i.e bytes) when importing. module = __import__(str(module_name), {}, {}, [str(attribute_name)], 0) return getattr(module, str(attribute_name)) def _safe_repr(obj): try: return log_repr(obj) except Exception: return '' % id(obj) class log_pprint(object): """Helper for properly printing args / kwargs passed to an object. Since it is only used with factory.debug(), the computation is performed lazily. """ __slots__ = ['args', 'kwargs'] def __init__(self, args=(), kwargs=None): self.args = args self.kwargs = kwargs or {} def __repr__(self): return repr(str(self)) def __str__(self): return ', '.join( [_safe_repr(arg) for arg in self.args] + [ '%s=%s' % (key, _safe_repr(value)) for key, value in self.kwargs.items() ] ) def log_repr(obj): """Generate a text-compatible repr of an object. Some projects have a tendency to generate bytes-style repr in Python2. """ return compat.force_text(repr(obj)) class ResetableIterator(object): """An iterator wrapper that can be 'reset()' to its start.""" def __init__(self, iterator, **kwargs): super(ResetableIterator, self).__init__(**kwargs) self.iterator = iter(iterator) self.past_elements = collections.deque() self.next_elements = collections.deque() def __iter__(self): while True: if self.next_elements: yield self.next_elements.popleft() else: value = next(self.iterator) self.past_elements.append(value) yield value def reset(self): self.next_elements.clear() self.next_elements.extend(self.past_elements) factory_boy-2.8.1/requirements.txt000066400000000000000000000000151302510513700173170ustar00rootroot00000000000000Faker>=0.7.0 factory_boy-2.8.1/requirements_dev.txt000066400000000000000000000002151302510513700201570ustar00rootroot00000000000000-e . -r requirements_test.txt -r examples/requirements.txt coverage Django Pillow SQLAlchemy mongoengine wheel tox Sphinx sphinx_rtd_theme factory_boy-2.8.1/requirements_test.txt000066400000000000000000000000141302510513700203550ustar00rootroot00000000000000mock flake8 factory_boy-2.8.1/setup.cfg000066400000000000000000000000341302510513700156550ustar00rootroot00000000000000[bdist_wheel] universal = 1 factory_boy-2.8.1/setup.py000077500000000000000000000045341302510513700155620ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- import codecs import os import re import sys from setuptools import setup root_dir = os.path.abspath(os.path.dirname(__file__)) def get_version(package_name): version_re = re.compile(r"^__version__ = [\"']([\w_.-]+)[\"']$") package_components = package_name.split('.') init_path = os.path.join(root_dir, *(package_components + ['__init__.py'])) with codecs.open(init_path, 'r', 'utf-8') as f: for line in f: match = version_re.match(line[:-1]) if match: return match.groups()[0] return '0.1.0' if sys.version_info[0:2] < (2, 7): # pragma: no cover test_loader = 'unittest2:TestLoader' else: test_loader = 'unittest:TestLoader' PACKAGE = 'factory' setup( name='factory_boy', version=get_version(PACKAGE), description="A versatile test fixtures replacement based on thoughtbot's factory_girl for Ruby.", long_description=codecs.open(os.path.join(root_dir, 'README.rst'), 'r', 'utf-8').read(), author='Mark Sandstrom', author_email='mark@deliciouslynerdy.com', maintainer='Raphaël Barrois', maintainer_email='raphael.barrois+fboy@polytechnique.org', url='https://github.com/FactoryBoy/factory_boy', keywords=['factory_boy', 'factory', 'fixtures'], packages=['factory'], license='MIT', install_requires=[ 'Faker>=0.7.0', ], setup_requires=[ 'setuptools>=0.8', ], tests_require=[ #'mock', ], classifiers=[ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "Framework :: Django", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "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", ], test_suite='tests', test_loader=test_loader, ) factory_boy-2.8.1/tests/000077500000000000000000000000001302510513700152015ustar00rootroot00000000000000factory_boy-2.8.1/tests/__init__.py000066400000000000000000000006451302510513700173170ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (c) 2011-2015 Raphaël Barrois # factory.django needs a configured Django. from .test_django import * from .test_base import * from .test_containers import * from .test_declarations import * from .test_faker import * from .test_fuzzy import * from .test_helpers import * from .test_using import * from .test_utils import * from .test_alchemy import * from .test_mongoengine import * factory_boy-2.8.1/tests/alchemyapp/000077500000000000000000000000001302510513700173245ustar00rootroot00000000000000factory_boy-2.8.1/tests/alchemyapp/__init__.py000066400000000000000000000000001302510513700214230ustar00rootroot00000000000000factory_boy-2.8.1/tests/alchemyapp/models.py000066400000000000000000000033461302510513700211670ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (c) 2013 Romain Commandé # # 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. """Helpers for testing SQLAlchemy apps.""" from sqlalchemy import Column, Integer, Unicode, create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import scoped_session, sessionmaker session = scoped_session(sessionmaker()) engine = create_engine('sqlite://') session.configure(bind=engine) Base = declarative_base() class StandardModel(Base): __tablename__ = 'StandardModelTable' id = Column(Integer(), primary_key=True) foo = Column(Unicode(20)) class NonIntegerPk(Base): __tablename__ = 'NonIntegerPk' id = Column(Unicode(20), primary_key=True) Base.metadata.create_all(engine) factory_boy-2.8.1/tests/alter_time.py000066400000000000000000000074171302510513700177110ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # This code is in the public domain # Author: Raphaël Barrois from __future__ import print_function import datetime from .compat import mock real_datetime_class = datetime.datetime def mock_datetime_now(target, datetime_module): """Override ``datetime.datetime.now()`` with a custom target value. This creates a new datetime.datetime class, and alters its now()/utcnow() methods. Returns: A mock.patch context, can be used as a decorator or in a with. """ # See http://bugs.python.org/msg68532 # And http://docs.python.org/reference/datamodel.html#customizing-instance-and-subclass-checks class DatetimeSubclassMeta(type): """We need to customize the __instancecheck__ method for isinstance(). This must be performed at a metaclass level. """ @classmethod def __instancecheck__(mcs, obj): return isinstance(obj, real_datetime_class) class BaseMockedDatetime(real_datetime_class): @classmethod def now(cls, tz=None): return target.replace(tzinfo=tz) @classmethod def utcnow(cls): return target # Python2 & Python3-compatible metaclass MockedDatetime = DatetimeSubclassMeta('datetime', (BaseMockedDatetime,), {}) return mock.patch.object(datetime_module, 'datetime', MockedDatetime) real_date_class = datetime.date def mock_date_today(target, datetime_module): """Override ``datetime.date.today()`` with a custom target value. This creates a new datetime.date class, and alters its today() method. Returns: A mock.patch context, can be used as a decorator or in a with. """ # See http://bugs.python.org/msg68532 # And http://docs.python.org/reference/datamodel.html#customizing-instance-and-subclass-checks class DateSubclassMeta(type): """We need to customize the __instancecheck__ method for isinstance(). This must be performed at a metaclass level. """ @classmethod def __instancecheck__(mcs, obj): return isinstance(obj, real_date_class) class BaseMockedDate(real_date_class): @classmethod def today(cls): return target # Python2 & Python3-compatible metaclass MockedDate = DateSubclassMeta('date', (BaseMockedDate,), {}) return mock.patch.object(datetime_module, 'date', MockedDate) def main(): # pragma: no cover """Run a couple of tests""" target_dt = real_datetime_class(2009, 1, 1) target_date = real_date_class(2009, 1, 1) print("Entering mock") with mock_datetime_now(target_dt, datetime): print("- now ->", datetime.datetime.now()) print("- isinstance(now, dt) ->", isinstance(datetime.datetime.now(), datetime.datetime)) print("- isinstance(target, dt) ->", isinstance(target_dt, datetime.datetime)) with mock_date_today(target_date, datetime): print("- today ->", datetime.date.today()) print("- isinstance(now, date) ->", isinstance(datetime.date.today(), datetime.date)) print("- isinstance(target, date) ->", isinstance(target_date, datetime.date)) print("Outside mock") print("- now ->", datetime.datetime.now()) print("- isinstance(now, dt) ->", isinstance(datetime.datetime.now(), datetime.datetime)) print("- isinstance(target, dt) ->", isinstance(target_dt, datetime.datetime)) print("- today ->", datetime.date.today()) print("- isinstance(now, date) ->", isinstance(datetime.date.today(), datetime.date)) print("- isinstance(target, date) ->", isinstance(target_date, datetime.date)) if __name__ == '__main__': # pragma: no cover main() factory_boy-2.8.1/tests/compat.py000066400000000000000000000030721302510513700170400ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (c) 2011-2015 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. """Compatibility tools for tests""" import sys is_python2 = (sys.version_info[0] == 2) if sys.version_info[0:2] < (2, 7): # pragma: no cover import unittest2 as unittest else: # pragma: no cover import unittest if sys.version_info[0] == 2: # pragma: no cover import StringIO as io else: # pragma: no cover import io if sys.version_info[0:2] < (3, 3): # pragma: no cover import mock else: # pragma: no cover from unittest import mock factory_boy-2.8.1/tests/cyclic/000077500000000000000000000000001302510513700164475ustar00rootroot00000000000000factory_boy-2.8.1/tests/cyclic/__init__.py000066400000000000000000000000001302510513700205460ustar00rootroot00000000000000factory_boy-2.8.1/tests/cyclic/bar.py000066400000000000000000000026231302510513700175700ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (c) 2011-2015 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): class Meta: model = Bar y = 13 foo = factory.SubFactory('cyclic.foo.FooFactory') factory_boy-2.8.1/tests/cyclic/foo.py000066400000000000000000000026531302510513700176120ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (c) 2011-2015 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 . import bar as bar_mod class Foo(object): def __init__(self, bar, x): self.bar = bar self.x = x class FooFactory(factory.Factory): class Meta: model = Foo x = 42 bar = factory.SubFactory(bar_mod.BarFactory) factory_boy-2.8.1/tests/cyclic/self_ref.py000066400000000000000000000027731302510513700206170ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (c) 2011-2015 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 TreeElement(object): def __init__(self, name, parent): self.parent = parent self.name = name class TreeElementFactory(factory.Factory): class Meta: model = TreeElement name = factory.Sequence(lambda n: "tree%s" % n) parent = factory.SubFactory('tests.cyclic.self_ref.TreeElementFactory') factory_boy-2.8.1/tests/djapp/000077500000000000000000000000001302510513700162775ustar00rootroot00000000000000factory_boy-2.8.1/tests/djapp/__init__.py000066400000000000000000000000001302510513700203760ustar00rootroot00000000000000factory_boy-2.8.1/tests/djapp/models.py000066400000000000000000000063311302510513700201370ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (c) 2011-2015 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. """Helpers for testing django apps.""" import os.path try: from PIL import Image except ImportError: try: import Image except ImportError: Image = None from django.conf import settings from django.db import models class StandardModel(models.Model): foo = models.CharField(max_length=20) class NonIntegerPk(models.Model): foo = models.CharField(max_length=20, primary_key=True) bar = models.CharField(max_length=20, blank=True) class MultifieldModel(models.Model): slug = models.SlugField(max_length=20, unique=True) text = models.CharField(max_length=20) class AbstractBase(models.Model): foo = models.CharField(max_length=20) class Meta: abstract = True class ConcreteSon(AbstractBase): pass class AbstractSon(AbstractBase): class Meta: abstract = True class ConcreteGrandSon(AbstractSon): pass class StandardSon(StandardModel): pass class PointedModel(models.Model): foo = models.CharField(max_length=20) class PointingModel(models.Model): foo = models.CharField(max_length=20) pointed = models.OneToOneField(PointedModel, related_name='pointer', null=True) WITHFILE_UPLOAD_TO = 'django' WITHFILE_UPLOAD_DIR = os.path.join(settings.MEDIA_ROOT, WITHFILE_UPLOAD_TO) class WithFile(models.Model): afile = models.FileField(upload_to=WITHFILE_UPLOAD_TO) if Image is not None: # PIL is available class WithImage(models.Model): animage = models.ImageField(upload_to=WITHFILE_UPLOAD_TO) size = models.IntegerField(default=0) else: class WithImage(models.Model): pass class WithSignals(models.Model): foo = models.CharField(max_length=20) class CustomManager(models.Manager): def create(self, arg=None, **kwargs): return super(CustomManager, self).create(**kwargs) class WithCustomManager(models.Model): foo = models.CharField(max_length=20) objects = CustomManager() class AbstractWithCustomManager(models.Model): custom_objects = CustomManager() class Meta: abstract = True class FromAbstractWithCustomManager(AbstractWithCustomManager): pass factory_boy-2.8.1/tests/djapp/settings.py000066400000000000000000000033151302510513700205130ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (c) 2011-2015 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. """Settings for factory_boy/Django tests.""" import os FACTORY_ROOT = os.path.join( os.path.abspath(os.path.dirname(__file__)), # /path/to/fboy/tests/djapp/ os.pardir, # /path/to/fboy/tests/ os.pardir, # /path/to/fboy ) MEDIA_ROOT = os.path.join(FACTORY_ROOT, 'tmp_test') DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', }, 'replica': { 'ENGINE': 'django.db.backends.sqlite3', }, } INSTALLED_APPS = [ 'tests.djapp' ] MIDDLEWARE_CLASSES = () SECRET_KEY = 'testing.' factory_boy-2.8.1/tests/test_alchemy.py000066400000000000000000000171201302510513700202350ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (c) 2015 Romain Command& # # 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. """Tests for factory_boy/SQLAlchemy interactions.""" import factory from .compat import unittest from .compat import mock import warnings try: import sqlalchemy except ImportError: sqlalchemy = None if sqlalchemy: from factory.alchemy import SQLAlchemyModelFactory from .alchemyapp import models else: class Fake(object): class Meta: sqlalchemy_session = None models = Fake() models.StandardModel = Fake() models.NonIntegerPk = Fake() models.session = Fake() SQLAlchemyModelFactory = Fake class StandardFactory(SQLAlchemyModelFactory): class Meta: model = models.StandardModel sqlalchemy_session = models.session id = factory.Sequence(lambda n: n) foo = factory.Sequence(lambda n: 'foo%d' % n) class NonIntegerPkFactory(SQLAlchemyModelFactory): class Meta: model = models.NonIntegerPk sqlalchemy_session = models.session id = factory.Sequence(lambda n: 'foo%d' % n) @unittest.skipIf(sqlalchemy is None, "SQLalchemy not installed.") class SQLAlchemyPkSequenceTestCase(unittest.TestCase): def setUp(self): super(SQLAlchemyPkSequenceTestCase, self).setUp() StandardFactory.reset_sequence(1) NonIntegerPkFactory._meta.sqlalchemy_session.rollback() def test_pk_first(self): std = StandardFactory.build() self.assertEqual('foo1', std.foo) def test_pk_many(self): std1 = StandardFactory.build() std2 = StandardFactory.build() self.assertEqual('foo1', std1.foo) self.assertEqual('foo2', std2.foo) def test_pk_creation(self): std1 = StandardFactory.create() self.assertEqual('foo1', std1.foo) self.assertEqual(1, std1.id) StandardFactory.reset_sequence() std2 = StandardFactory.create() self.assertEqual('foo0', std2.foo) self.assertEqual(0, std2.id) def test_pk_force_value(self): std1 = StandardFactory.create(id=10) self.assertEqual('foo1', std1.foo) # sequence and pk are unrelated self.assertEqual(10, std1.id) StandardFactory.reset_sequence() std2 = StandardFactory.create() self.assertEqual('foo0', std2.foo) # Sequence doesn't care about pk self.assertEqual(0, std2.id) @unittest.skipIf(sqlalchemy is None, "SQLalchemy not installed.") class SQLAlchemySessionPersistenceTestCase(unittest.TestCase): def setUp(self): super(SQLAlchemySessionPersistenceTestCase, self).setUp() self.mock_session = mock.NonCallableMagicMock(spec=models.session) def test_flushing(self): class FlushingPersistenceFactory(StandardFactory): class Meta: sqlalchemy_session = self.mock_session sqlalchemy_session_persistence = 'flush' self.mock_session.commit.assert_not_called() self.mock_session.flush.assert_not_called() FlushingPersistenceFactory.create() self.mock_session.commit.assert_not_called() self.mock_session.flush.assert_called_once_with() def test_committing(self): class CommittingPersistenceFactory(StandardFactory): class Meta: sqlalchemy_session = self.mock_session sqlalchemy_session_persistence = 'commit' self.mock_session.commit.assert_not_called() self.mock_session.flush.assert_not_called() CommittingPersistenceFactory.create() self.mock_session.commit.assert_called_once_with() self.mock_session.flush.assert_not_called() def test_noflush_nocommit(self): class InactivePersistenceFactory(StandardFactory): class Meta: sqlalchemy_session = self.mock_session sqlalchemy_session_persistence = None self.mock_session.commit.assert_not_called() self.mock_session.flush.assert_not_called() InactivePersistenceFactory.create() self.mock_session.commit.assert_not_called() self.mock_session.flush.assert_not_called() def test_type_error(self): with self.assertRaises(TypeError): class BadPersistenceFactory(StandardFactory): class Meta: sqlalchemy_session_persistence = 'invalid_persistence_option' model = models.StandardModel def test_force_flush_deprecation(self): with warnings.catch_warnings(record=True) as warning_list: class OutdatedPersistenceFactory(StandardFactory): class Meta: force_flush = True sqlalchemy_session = self.mock_session # There should be *1* DeprecationWarning self.assertEqual(len(warning_list), 1) warning = warning_list[0] self.assertTrue(issubclass(warning.category, DeprecationWarning)) # The warning text should point to the class declaration. text = warnings.formatwarning(warning.message, warning.category, warning.filename, warning.lineno) self.assertIn('test_alchemy.py', text) self.assertIn('class OutdatedPersistenceFactory', text) # However, we shall keep the old-style behavior. self.mock_session.commit.assert_not_called() self.mock_session.flush.assert_not_called() OutdatedPersistenceFactory.create() self.mock_session.commit.assert_not_called() self.mock_session.flush.assert_called_once_with() @unittest.skipIf(sqlalchemy is None, "SQLalchemy not installed.") class SQLAlchemyNonIntegerPkTestCase(unittest.TestCase): def setUp(self): super(SQLAlchemyNonIntegerPkTestCase, self).setUp() NonIntegerPkFactory.reset_sequence() NonIntegerPkFactory._meta.sqlalchemy_session.rollback() def test_first(self): nonint = NonIntegerPkFactory.build() self.assertEqual('foo0', nonint.id) def test_many(self): nonint1 = NonIntegerPkFactory.build() nonint2 = NonIntegerPkFactory.build() self.assertEqual('foo0', nonint1.id) self.assertEqual('foo1', nonint2.id) def test_creation(self): nonint1 = NonIntegerPkFactory.create() self.assertEqual('foo0', nonint1.id) NonIntegerPkFactory.reset_sequence() nonint2 = NonIntegerPkFactory.build() self.assertEqual('foo0', nonint2.id) def test_force_pk(self): nonint1 = NonIntegerPkFactory.create(id='foo10') self.assertEqual('foo10', nonint1.id) NonIntegerPkFactory.reset_sequence() nonint2 = NonIntegerPkFactory.create() self.assertEqual('foo0', nonint2.id) factory_boy-2.8.1/tests/test_base.py000066400000000000000000000415361302510513700175350ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (c) 2010 Mark Sandstrom # Copyright (c) 2011-2015 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. import warnings from factory import base from factory import declarations from factory import errors from .compat import unittest class TestObject(object): def __init__(self, one=None, two=None, three=None, four=None): self.one = one self.two = two self.three = three self.four = four class FakeDjangoModel(object): @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): class Meta: abstract = True @classmethod def _create(cls, model_class, *args, **kwargs): return model_class.create(**kwargs) class TestModel(FakeDjangoModel): pass class SafetyTestCase(unittest.TestCase): def test_base_factory(self): self.assertRaises(errors.FactoryError, base.BaseFactory) class AbstractFactoryTestCase(unittest.TestCase): def test_factory_for_optional(self): """Ensure that model= is optional for abstract=True.""" class TestObjectFactory(base.Factory): class Meta: abstract = True self.assertTrue(TestObjectFactory._meta.abstract) self.assertIsNone(TestObjectFactory._meta.model) def test_factory_for_and_abstract_factory_optional(self): """Ensure that Meta.abstract is optional.""" class TestObjectFactory(base.Factory): pass self.assertTrue(TestObjectFactory._meta.abstract) self.assertIsNone(TestObjectFactory._meta.model) def test_abstract_factory_cannot_be_called(self): class TestObjectFactory(base.Factory): pass self.assertRaises(errors.FactoryError, TestObjectFactory.build) self.assertRaises(errors.FactoryError, TestObjectFactory.create) def test_abstract_factory_not_inherited(self): """abstract=True isn't propagated to child classes.""" class TestObjectFactory(base.Factory): class Meta: abstract = True model = TestObject class TestObjectChildFactory(TestObjectFactory): pass self.assertFalse(TestObjectChildFactory._meta.abstract) def test_abstract_or_model_is_required(self): class TestObjectFactory(base.Factory): class Meta: abstract = False model = None self.assertRaises(errors.FactoryError, TestObjectFactory.build) self.assertRaises(errors.FactoryError, TestObjectFactory.create) class OptionsTests(unittest.TestCase): def test_base_attrs(self): class AbstractFactory(base.Factory): pass # Declarative attributes self.assertTrue(AbstractFactory._meta.abstract) self.assertIsNone(AbstractFactory._meta.model) self.assertEqual((), AbstractFactory._meta.inline_args) self.assertEqual((), AbstractFactory._meta.exclude) self.assertEqual(base.CREATE_STRATEGY, AbstractFactory._meta.strategy) # Non-declarative attributes self.assertEqual({}, AbstractFactory._meta.declarations) self.assertEqual({}, AbstractFactory._meta.postgen_declarations) self.assertEqual(AbstractFactory, AbstractFactory._meta.factory) self.assertEqual(base.Factory, AbstractFactory._meta.base_factory) self.assertEqual(AbstractFactory, AbstractFactory._meta.counter_reference) def test_declaration_collecting(self): 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, 'y2': lazy2}, AbstractFactory._meta.declarations) self.assertEqual({'z': postgen}, AbstractFactory._meta.postgen_declarations) def test_inherited_declaration_collecting(self): lazy = declarations.LazyFunction(int) lazy2 = declarations.LazyAttribute(lambda _o: 2) postgen = declarations.PostGenerationDeclaration() postgen2 = declarations.PostGenerationDeclaration() class AbstractFactory(base.Factory): x = 1 y = lazy z = postgen class OtherFactory(AbstractFactory): a = lazy2 b = postgen2 # Declarations aren't removed self.assertEqual(lazy2, OtherFactory.a) self.assertEqual(postgen2, OtherFactory.b) self.assertEqual(1, OtherFactory.x) self.assertEqual(lazy, OtherFactory.y) self.assertEqual(postgen, OtherFactory.z) # And are available in class Meta self.assertEqual({'x': 1, 'y': lazy, 'a': lazy2}, OtherFactory._meta.declarations) self.assertEqual({'z': postgen, 'b': postgen2}, OtherFactory._meta.postgen_declarations) def test_inherited_declaration_shadowing(self): lazy = declarations.LazyFunction(int) lazy2 = declarations.LazyAttribute(lambda _o: 2) postgen = declarations.PostGenerationDeclaration() postgen2 = declarations.PostGenerationDeclaration() class AbstractFactory(base.Factory): x = 1 y = lazy z = postgen class OtherFactory(AbstractFactory): y = lazy2 z = postgen2 # Declarations aren't removed self.assertEqual(1, OtherFactory.x) self.assertEqual(lazy2, OtherFactory.y) self.assertEqual(postgen2, OtherFactory.z) # And are available in class Meta self.assertEqual({'x': 1, 'y': lazy2}, OtherFactory._meta.declarations) self.assertEqual({'z': postgen2}, OtherFactory._meta.postgen_declarations) class DeclarationParsingTests(unittest.TestCase): def test_classmethod(self): class TestObjectFactory(base.Factory): class Meta: model = TestObject @classmethod def some_classmethod(cls): return cls.create() self.assertTrue(hasattr(TestObjectFactory, 'some_classmethod')) obj = TestObjectFactory.some_classmethod() self.assertEqual(TestObject, obj.__class__) class FactoryTestCase(unittest.TestCase): def test_magic_happens(self): """Calling a FooFactory doesn't yield a FooFactory instance.""" class TestObjectFactory(base.Factory): class Meta: model = TestObject self.assertEqual(TestObject, TestObjectFactory._meta.model) obj = TestObjectFactory.build() self.assertFalse(hasattr(obj, '_meta')) def test_display(self): class TestObjectFactory(base.Factory): class Meta: model = FakeDjangoModel self.assertIn('TestObjectFactory', str(TestObjectFactory)) self.assertIn('FakeDjangoModel', str(TestObjectFactory)) def test_lazy_attribute_non_existent_param(self): class TestObjectFactory(base.Factory): class Meta: model = TestObject one = declarations.LazyAttribute(lambda a: a.does_not_exist ) self.assertRaises(AttributeError, TestObjectFactory) def test_inheritance_with_sequence(self): """Tests that sequence IDs are shared between parent and son.""" class TestObjectFactory(base.Factory): class Meta: model = TestObject one = declarations.Sequence(lambda a: a) class TestSubFactory(TestObjectFactory): class Meta: model = TestObject pass parent = TestObjectFactory.build() sub = TestSubFactory.build() alt_parent = TestObjectFactory.build() alt_sub = TestSubFactory.build() ones = set([x.one for x in (parent, alt_parent, sub, alt_sub)]) self.assertEqual(4, len(ones)) class FactorySequenceTestCase(unittest.TestCase): def setUp(self): super(FactorySequenceTestCase, self).setUp() class TestObjectFactory(base.Factory): class Meta: model = TestObject one = declarations.Sequence(lambda n: n) self.TestObjectFactory = TestObjectFactory def test_reset_sequence(self): o1 = self.TestObjectFactory() self.assertEqual(0, o1.one) o2 = self.TestObjectFactory() self.assertEqual(1, o2.one) self.TestObjectFactory.reset_sequence() o3 = self.TestObjectFactory() self.assertEqual(0, o3.one) def test_reset_sequence_with_value(self): o1 = self.TestObjectFactory() self.assertEqual(0, o1.one) o2 = self.TestObjectFactory() self.assertEqual(1, o2.one) self.TestObjectFactory.reset_sequence(42) o3 = self.TestObjectFactory() self.assertEqual(42, o3.one) def test_reset_sequence_subclass_fails(self): """Tests that the sequence of a 'slave' factory cannot be reseted.""" class SubTestObjectFactory(self.TestObjectFactory): pass self.assertRaises(ValueError, SubTestObjectFactory.reset_sequence) def test_reset_sequence_subclass_force(self): """Tests that reset_sequence(force=True) works.""" class SubTestObjectFactory(self.TestObjectFactory): pass o1 = SubTestObjectFactory() self.assertEqual(0, o1.one) o2 = SubTestObjectFactory() self.assertEqual(1, o2.one) SubTestObjectFactory.reset_sequence(force=True) o3 = SubTestObjectFactory() self.assertEqual(0, o3.one) # The master sequence counter has been reset o4 = self.TestObjectFactory() self.assertEqual(1, o4.one) def test_reset_sequence_subclass_parent(self): """Tests that the sequence of a 'slave' factory cannot be reseted.""" class SubTestObjectFactory(self.TestObjectFactory): pass o1 = SubTestObjectFactory() self.assertEqual(0, o1.one) o2 = SubTestObjectFactory() self.assertEqual(1, o2.one) self.TestObjectFactory.reset_sequence() o3 = SubTestObjectFactory() self.assertEqual(0, o3.one) o4 = self.TestObjectFactory() self.assertEqual(1, o4.one) class FactoryDefaultStrategyTestCase(unittest.TestCase): def setUp(self): self.default_strategy = base.Factory._meta.strategy def tearDown(self): base.Factory._meta.strategy = self.default_strategy def test_build_strategy(self): base.Factory._meta.strategy = base.BUILD_STRATEGY class TestModelFactory(base.Factory): class Meta: model = TestModel one = 'one' test_model = TestModelFactory() self.assertEqual(test_model.one, 'one') self.assertFalse(test_model.id) def test_create_strategy(self): # Default Meta.strategy class TestModelFactory(FakeModelFactory): class Meta: model = TestModel one = 'one' test_model = TestModelFactory() self.assertEqual(test_model.one, 'one') self.assertTrue(test_model.id) def test_stub_strategy(self): base.Factory._meta.strategy = base.STUB_STRATEGY class TestModelFactory(base.Factory): class Meta: model = TestModel one = 'one' test_model = TestModelFactory() self.assertEqual(test_model.one, 'one') self.assertFalse(hasattr(test_model, 'id')) # We should have a plain old object def test_unknown_strategy(self): base.Factory._meta.strategy = 'unknown' class TestModelFactory(base.Factory): class Meta: model = TestModel one = 'one' self.assertRaises(base.Factory.UnknownStrategy, TestModelFactory) def test_stub_with_create_strategy(self): class TestModelFactory(base.StubFactory): class Meta: model = TestModel one = 'one' TestModelFactory._meta.strategy = base.CREATE_STRATEGY self.assertRaises(base.StubFactory.UnsupportedStrategy, TestModelFactory) def test_stub_with_build_strategy(self): class TestModelFactory(base.StubFactory): class Meta: model = TestModel one = 'one' TestModelFactory._meta.strategy = base.BUILD_STRATEGY obj = TestModelFactory() # For stubs, build() is an alias of stub(). self.assertFalse(isinstance(obj, TestModel)) def test_change_strategy(self): @base.use_strategy(base.CREATE_STRATEGY) class TestModelFactory(base.StubFactory): class Meta: model = TestModel one = 'one' self.assertEqual(base.CREATE_STRATEGY, TestModelFactory._meta.strategy) class FactoryCreationTestCase(unittest.TestCase): def test_factory_for(self): class TestFactory(base.Factory): class Meta: model = TestObject self.assertTrue(isinstance(TestFactory.build(), TestObject)) def test_stub(self): class TestFactory(base.StubFactory): pass self.assertEqual(TestFactory._meta.strategy, base.STUB_STRATEGY) def test_inheritance_with_stub(self): class TestObjectFactory(base.StubFactory): class Meta: model = TestObject pass class TestFactory(TestObjectFactory): pass self.assertEqual(TestFactory._meta.strategy, base.STUB_STRATEGY) def test_stub_and_subfactory(self): class StubA(base.StubFactory): class Meta: model = TestObject one = 'blah' class StubB(base.StubFactory): class Meta: model = TestObject stubbed = declarations.SubFactory(StubA, two='two') b = StubB() self.assertEqual('blah', b.stubbed.one) self.assertEqual('two', b.stubbed.two) def test_custom_creation(self): class TestModelFactory(FakeModelFactory): class Meta: model = TestModel @classmethod def _prepare(cls, create, **kwargs): kwargs['four'] = 4 return super(TestModelFactory, cls)._prepare(create, **kwargs) b = TestModelFactory.build(one=1) self.assertEqual(1, b.one) self.assertEqual(4, b.four) self.assertEqual(None, b.id) c = TestModelFactory(one=1) self.assertEqual(1, c.one) self.assertEqual(4, c.four) self.assertEqual(1, c.id) # Errors def test_no_associated_class(self): class Test(base.Factory): pass self.assertTrue(Test._meta.abstract) class PostGenerationParsingTestCase(unittest.TestCase): def test_extraction(self): class TestObjectFactory(base.Factory): class Meta: model = TestObject foo = declarations.PostGenerationDeclaration() self.assertIn('foo', TestObjectFactory._meta.postgen_declarations) def test_classlevel_extraction(self): class TestObjectFactory(base.Factory): class Meta: model = TestObject foo = declarations.PostGenerationDeclaration() foo__bar = 42 self.assertIn('foo', TestObjectFactory._meta.postgen_declarations) self.assertIn('foo__bar', TestObjectFactory._meta.declarations) if __name__ == '__main__': # pragma: no cover unittest.main() factory_boy-2.8.1/tests/test_containers.py000066400000000000000000000206751302510513700207710ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (c) 2010 Mark Sandstrom # Copyright (c) 2011-2015 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. from factory import base from factory import containers from factory import declarations from factory import errors from .compat import unittest class LazyStubTestCase(unittest.TestCase): def test_basic(self): stub = containers.LazyStub({'one': 1, 'two': 2}) self.assertEqual({'one': 1, 'two': 2}, stub.__fill__()) def test_setting_values(self): stub = containers.LazyStub({'one': 1, 'two': 2}) self.assertRaises(AttributeError, setattr, stub, 'one', 1) def test_reading_value(self): stub = containers.LazyStub({'one': 1, 'two': 2}) self.assertEqual(1, stub.one) self.assertRaises(AttributeError, getattr, stub, 'three') def test_accessing_container(self): class LazyAttr(containers.LazyValue): def __init__(self, obj_attr, container_attr): self.obj_attr = obj_attr self.container_attr = container_attr def evaluate(self, obj, containers=()): if containers: add = getattr(containers[0], self.container_attr) else: add = 0 return getattr(obj, self.obj_attr) + add class DummyContainer(object): three = 3 stub = containers.LazyStub({'one': LazyAttr('two', 'three'), 'two': 2, 'three': 42}, containers=(DummyContainer(),)) self.assertEqual(5, stub.one) stub = containers.LazyStub({'one': LazyAttr('two', 'three'), 'two': 2, 'three': 42}, containers=()) self.assertEqual(2, stub.one) def test_access_parent(self): """Test simple access to a stub' parent.""" o1 = containers.LazyStub({'rank': 1}) o2 = containers.LazyStub({'rank': 2}, (o1,)) stub = containers.LazyStub({'rank': 3}, (o2, o1)) self.assertEqual(3, stub.rank) self.assertEqual(2, stub.factory_parent.rank) self.assertEqual(1, stub.factory_parent.factory_parent.rank) class LazyAttr(containers.LazyValue): def __init__(self, attrname): self.attrname = attrname def evaluate(self, obj, container=None): return 1 + getattr(obj, self.attrname) def test_cyclic_definition(self): stub = containers.LazyStub({ 'one': self.LazyAttr('two'), 'two': self.LazyAttr('one'), }) self.assertRaises(errors.CyclicDefinitionError, getattr, stub, 'one') def test_cyclic_definition_rescue(self): class LazyAttrDefault(self.LazyAttr): def __init__(self, attname, defvalue): super(LazyAttrDefault, self).__init__(attname) self.defvalue = defvalue def evaluate(self, obj, container=None): try: return super(LazyAttrDefault, self).evaluate(obj, container) except errors.CyclicDefinitionError: return self.defvalue stub = containers.LazyStub({ 'one': LazyAttrDefault('two', 10), 'two': self.LazyAttr('one'), }) self.assertEqual(10, stub.one) self.assertEqual(11, stub.two) def test_representation(self): class RandomObj(object): pass stub = containers.LazyStub({'one': 1, 'two': 2}, model_class=RandomObj) self.assertIn('RandomObj', repr(stub)) self.assertIn('RandomObj', str(stub)) self.assertIn('one', str(stub)) class AttributeBuilderTestCase(unittest.TestCase): def make_fake_factory(self, decls): class Meta: declarations = decls parameters = {} parameters_dependencies = {} class FakeFactory(object): _meta = Meta @classmethod def _generate_next_sequence(cls): return 1 return FakeFactory def test_empty(self): """Tests building attributes from an empty definition.""" FakeFactory = self.make_fake_factory({}) ab = containers.AttributeBuilder(FakeFactory) self.assertEqual({}, ab.build(create=False)) def test_factory_defined(self): FakeFactory = self.make_fake_factory({'one': 1}) ab = containers.AttributeBuilder(FakeFactory) self.assertEqual({'one': 1}, ab.build(create=False)) def test_extended(self): FakeFactory = self.make_fake_factory({'one': 1}) ab = containers.AttributeBuilder(FakeFactory, {'two': 2}) self.assertEqual({'one': 1, 'two': 2}, ab.build(create=False)) def test_overridden(self): FakeFactory = self.make_fake_factory({'one': 1}) ab = containers.AttributeBuilder(FakeFactory, {'one': 2}) self.assertEqual({'one': 2}, ab.build(create=False)) def test_factory_defined_sequence(self): seq = declarations.Sequence(lambda n: 'xx%d' % n) FakeFactory = self.make_fake_factory({'one': seq}) ab = containers.AttributeBuilder(FakeFactory) self.assertEqual({'one': 'xx1'}, ab.build(create=False)) def test_additionnal_sequence(self): seq = declarations.Sequence(lambda n: 'xx%d' % n) FakeFactory = self.make_fake_factory({'one': 1}) ab = containers.AttributeBuilder(FakeFactory, extra={'two': seq}) self.assertEqual({'one': 1, 'two': 'xx1'}, ab.build(create=False)) def test_replaced_sequence(self): seq = declarations.Sequence(lambda n: 'xx%d' % n) seq2 = declarations.Sequence(lambda n: 'yy%d' % n) FakeFactory = self.make_fake_factory({'one': seq}) ab = containers.AttributeBuilder(FakeFactory, extra={'one': seq2}) self.assertEqual({'one': 'yy1'}, ab.build(create=False)) def test_lazy_function(self): lf = declarations.LazyFunction(int) FakeFactory = self.make_fake_factory({'one': 1, 'two': lf}) 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) FakeFactory = self.make_fake_factory({'one': 1, 'two': la}) ab = containers.AttributeBuilder(FakeFactory) self.assertEqual({'one': 1, 'two': 2}, ab.build(create=False)) ab = containers.AttributeBuilder(FakeFactory, {'one': 4}) self.assertEqual({'one': 4, 'two': 8}, ab.build(create=False)) ab = containers.AttributeBuilder(FakeFactory, {'one': 4, 'three': la}) self.assertEqual({'one': 4, 'two': 8, 'three': 8}, ab.build(create=False)) def test_subfields(self): class FakeInnerFactory(object): pass sf = declarations.SubFactory(FakeInnerFactory) FakeFactory = self.make_fake_factory({'one': sf, 'two': 2}) ab = containers.AttributeBuilder(FakeFactory, {'one__blah': 1, 'two__bar': 2}) self.assertTrue(ab.has_subfields(sf)) self.assertEqual(['one'], list(ab._subfields.keys())) self.assertEqual(2, ab._declarations['two__bar']) def test_sub_factory(self): pass if __name__ == '__main__': # pragma: no cover unittest.main() factory_boy-2.8.1/tests/test_declarations.py000066400000000000000000000302101302510513700212560ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (c) 2010 Mark Sandstrom # Copyright (c) 2011-2015 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. import datetime import itertools from factory import base from factory import declarations from factory import helpers from .compat import mock, unittest from . import tools class OrderedDeclarationTestCase(unittest.TestCase): def test_errors(self): decl = declarations.OrderedDeclaration() self.assertRaises(NotImplementedError, decl.evaluate, None, {}, False) class DigTestCase(unittest.TestCase): class MyObj(object): def __init__(self, n): self.n = n def test_chaining(self): obj = self.MyObj(1) obj.a = self.MyObj(2) obj.a.b = self.MyObj(3) obj.a.b.c = self.MyObj(4) self.assertEqual(2, declarations.deepgetattr(obj, 'a').n) self.assertRaises(AttributeError, declarations.deepgetattr, obj, 'b') self.assertEqual(2, declarations.deepgetattr(obj, 'a.n')) self.assertEqual(3, declarations.deepgetattr(obj, 'a.c', 3)) self.assertRaises(AttributeError, declarations.deepgetattr, obj, 'a.c.n') self.assertRaises(AttributeError, declarations.deepgetattr, obj, 'a.d') self.assertEqual(3, declarations.deepgetattr(obj, 'a.b').n) self.assertEqual(3, declarations.deepgetattr(obj, 'a.b.n')) self.assertEqual(4, declarations.deepgetattr(obj, 'a.b.c').n) self.assertEqual(4, declarations.deepgetattr(obj, 'a.b.c.n')) self.assertEqual(42, declarations.deepgetattr(obj, 'a.b.c.n.x', 42)) class SelfAttributeTestCase(unittest.TestCase): def test_standard(self): a = declarations.SelfAttribute('foo.bar.baz') self.assertEqual(0, a.depth) self.assertEqual('foo.bar.baz', a.attribute_name) self.assertEqual(declarations._UNSPECIFIED, a.default) def test_dot(self): a = declarations.SelfAttribute('.bar.baz') self.assertEqual(1, a.depth) self.assertEqual('bar.baz', a.attribute_name) self.assertEqual(declarations._UNSPECIFIED, a.default) def test_default(self): a = declarations.SelfAttribute('bar.baz', 42) self.assertEqual(0, a.depth) self.assertEqual('bar.baz', a.attribute_name) self.assertEqual(42, a.default) def test_parent(self): a = declarations.SelfAttribute('..bar.baz') self.assertEqual(2, a.depth) self.assertEqual('bar.baz', a.attribute_name) self.assertEqual(declarations._UNSPECIFIED, a.default) def test_grandparent(self): a = declarations.SelfAttribute('...bar.baz') self.assertEqual(3, a.depth) self.assertEqual('bar.baz', a.attribute_name) self.assertEqual(declarations._UNSPECIFIED, a.default) class IteratorTestCase(unittest.TestCase): def test_cycle(self): it = declarations.Iterator([1, 2]) self.assertEqual(1, it.evaluate(0, None, False)) self.assertEqual(2, it.evaluate(1, None, False)) self.assertEqual(1, it.evaluate(2, None, False)) self.assertEqual(2, it.evaluate(3, None, False)) def test_no_cycling(self): it = declarations.Iterator([1, 2], cycle=False) self.assertEqual(1, it.evaluate(0, None, False)) self.assertEqual(2, it.evaluate(1, None, False)) self.assertRaises(StopIteration, it.evaluate, 2, None, False) def test_reset_cycle(self): it = declarations.Iterator([1, 2]) self.assertEqual(1, it.evaluate(0, None, False)) self.assertEqual(2, it.evaluate(1, None, False)) self.assertEqual(1, it.evaluate(2, None, False)) self.assertEqual(2, it.evaluate(3, None, False)) self.assertEqual(1, it.evaluate(4, None, False)) it.reset() self.assertEqual(1, it.evaluate(5, None, False)) self.assertEqual(2, it.evaluate(6, None, False)) def test_reset_no_cycling(self): it = declarations.Iterator([1, 2], cycle=False) self.assertEqual(1, it.evaluate(0, None, False)) self.assertEqual(2, it.evaluate(1, None, False)) self.assertRaises(StopIteration, it.evaluate, 2, None, False) it.reset() self.assertEqual(1, it.evaluate(0, None, False)) self.assertEqual(2, it.evaluate(1, None, False)) self.assertRaises(StopIteration, it.evaluate, 2, None, False) def test_getter(self): it = declarations.Iterator([(1, 2), (1, 3)], getter=lambda p: p[1]) self.assertEqual(2, it.evaluate(0, None, False)) self.assertEqual(3, it.evaluate(1, None, False)) self.assertEqual(2, it.evaluate(2, None, False)) self.assertEqual(3, it.evaluate(3, None, False)) class PostGenerationDeclarationTestCase(unittest.TestCase): def test_extract_no_prefix(self): decl = declarations.PostGenerationDeclaration() context = decl.extract('foo', {'foo': 13, 'foo__bar': 42}) self.assertTrue(context.did_extract) self.assertEqual(context.value, 13) self.assertEqual(context.extra, {'bar': 42}) def test_decorator_simple(self): call_params = [] @helpers.post_generation def foo(*args, **kwargs): call_params.append(args) call_params.append(kwargs) context = foo.extract('foo', {'foo': 13, 'foo__bar': 42, 'blah': 42, 'blah__baz': 1}) self.assertTrue(context.did_extract) self.assertEqual(13, context.value) self.assertEqual({'bar': 42}, context.extra) # No value returned. foo.call(None, False, context) self.assertEqual(2, len(call_params)) self.assertEqual((None, False, 13), call_params[0]) self.assertEqual({'bar': 42}, call_params[1]) class FactoryWrapperTestCase(unittest.TestCase): def test_invalid_path(self): self.assertRaises(ValueError, declarations._FactoryWrapper, 'UnqualifiedSymbol') self.assertRaises(ValueError, declarations._FactoryWrapper, 42) def test_class(self): w = declarations._FactoryWrapper(datetime.date) self.assertEqual(datetime.date, w.get()) def test_path(self): w = declarations._FactoryWrapper('datetime.date') self.assertEqual(datetime.date, w.get()) def test_lazyness(self): f = declarations._FactoryWrapper('factory.declarations.Sequence') self.assertEqual(None, f.factory) factory_class = f.get() self.assertEqual(declarations.Sequence, factory_class) def test_cache(self): """Ensure that _FactoryWrapper tries to import only once.""" orig_date = datetime.date w = declarations._FactoryWrapper('datetime.date') self.assertEqual(None, w.factory) factory_class = w.get() self.assertEqual(orig_date, factory_class) try: # Modify original value datetime.date = None # Repeat import factory_class = w.get() self.assertEqual(orig_date, factory_class) finally: # IMPORTANT: restore attribute. datetime.date = orig_date class PostGenerationMethodCallTestCase(unittest.TestCase): def setUp(self): self.obj = mock.MagicMock() def ctx(self, value=None, force_value=False, extra=None): return declarations.ExtractionContext( value, bool(value) or force_value, extra, ) def test_simplest_setup_and_call(self): decl = declarations.PostGenerationMethodCall('method') decl.call(self.obj, False, self.ctx()) self.obj.method.assert_called_once_with() def test_call_with_method_args(self): decl = declarations.PostGenerationMethodCall( 'method', 'data') decl.call(self.obj, False, self.ctx()) self.obj.method.assert_called_once_with('data') def test_call_with_passed_extracted_string(self): decl = declarations.PostGenerationMethodCall( 'method') decl.call(self.obj, False, self.ctx('data')) self.obj.method.assert_called_once_with('data') def test_call_with_passed_extracted_int(self): decl = declarations.PostGenerationMethodCall('method') decl.call(self.obj, False, self.ctx(1)) self.obj.method.assert_called_once_with(1) def test_call_with_passed_extracted_iterable(self): decl = declarations.PostGenerationMethodCall('method') decl.call(self.obj, False, self.ctx((1, 2, 3))) self.obj.method.assert_called_once_with((1, 2, 3)) def test_call_with_method_kwargs(self): decl = declarations.PostGenerationMethodCall( 'method', data='data') decl.call(self.obj, False, self.ctx()) self.obj.method.assert_called_once_with(data='data') def test_call_with_passed_kwargs(self): decl = declarations.PostGenerationMethodCall('method') decl.call(self.obj, False, self.ctx(extra={'data': 'other'})) self.obj.method.assert_called_once_with(data='other') def test_multi_call_with_multi_method_args(self): decl = declarations.PostGenerationMethodCall( 'method', 'arg1', 'arg2') decl.call(self.obj, False, self.ctx()) self.obj.method.assert_called_once_with('arg1', 'arg2') def test_multi_call_with_passed_multiple_args(self): decl = declarations.PostGenerationMethodCall( 'method', 'arg1', 'arg2') decl.call(self.obj, False, self.ctx(('param1', 'param2', 'param3'))) self.obj.method.assert_called_once_with('param1', 'param2', 'param3') def test_multi_call_with_passed_tuple(self): decl = declarations.PostGenerationMethodCall( 'method', 'arg1', 'arg2') decl.call(self.obj, False, self.ctx((('param1', 'param2'),))) self.obj.method.assert_called_once_with(('param1', 'param2')) def test_multi_call_with_kwargs(self): decl = declarations.PostGenerationMethodCall( 'method', 'arg1', 'arg2') decl.call(self.obj, False, self.ctx(extra={'x': 2})) self.obj.method.assert_called_once_with('arg1', 'arg2', x=2) class PostGenerationOrdering(unittest.TestCase): def test_post_generation_declaration_order(self): postgen_results = [] class Related(base.Factory): class Meta: model = mock.MagicMock() class Ordered(base.Factory): class Meta: model = mock.MagicMock() a = declarations.RelatedFactory(Related) z = declarations.RelatedFactory(Related) @helpers.post_generation def a1(*args, **kwargs): postgen_results.append('a1') @helpers.post_generation def zz(*args, **kwargs): postgen_results.append('zz') @helpers.post_generation def aa(*args, **kwargs): postgen_results.append('aa') postgen_names = [ k for k, v in Ordered._meta.sorted_postgen_declarations ] self.assertEqual(postgen_names, ['a', 'z', 'a1', 'zz', 'aa']) # Test generation happens in desired order Ordered() self.assertEqual(postgen_results, ['a1', 'zz', 'aa']) if __name__ == '__main__': # pragma: no cover unittest.main() factory_boy-2.8.1/tests/test_django.py000066400000000000000000000702321302510513700200600ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (c) 2011-2015 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. """Tests for factory_boy/Django interactions.""" import os from .compat import is_python2, unittest, mock try: import django except ImportError: # pragma: no cover django = None # Setup Django as soon as possible if django is not None: os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tests.djapp.settings') if django.VERSION >= (1, 7, 0): django.setup() from django import test as django_test from django.conf import settings from django.db import models as django_models if django.VERSION <= (1, 8, 0): from django.test.simple import DjangoTestSuiteRunner else: from django.test.runner import DiscoverRunner as DjangoTestSuiteRunner from django.test import utils as django_test_utils from django.db.models import signals from .djapp import models else: django_test = unittest try: from PIL import Image except ImportError: # pragma: no cover # Try PIL alternate name try: import Image except ImportError: # OK, not installed Image = None import factory import factory.django from factory.compat import BytesIO from . import testdata from . import tools test_state = {} def setUpModule(): if django is None: # pragma: no cover raise unittest.SkipTest("Django not installed") django_test_utils.setup_test_environment() runner = DjangoTestSuiteRunner() runner_state = runner.setup_databases() test_state.update({ 'runner': runner, 'runner_state': runner_state, }) def tearDownModule(): if django is None: # pragma: no cover return runner = test_state['runner'] runner_state = test_state['runner_state'] runner.teardown_databases(runner_state) django_test_utils.teardown_test_environment() if django is not None: class StandardFactory(factory.django.DjangoModelFactory): class Meta: model = models.StandardModel foo = factory.Sequence(lambda n: "foo%d" % n) class StandardFactoryWithPKField(factory.django.DjangoModelFactory): class Meta: model = models.StandardModel django_get_or_create = ('pk',) foo = factory.Sequence(lambda n: "foo%d" % n) pk = None class NonIntegerPkFactory(factory.django.DjangoModelFactory): class Meta: model = models.NonIntegerPk foo = factory.Sequence(lambda n: "foo%d" % n) bar = '' class MultifieldModelFactory(factory.django.DjangoModelFactory): class Meta: model = models.MultifieldModel django_get_or_create = ['slug'] text = factory.Faker('text') class AbstractBaseFactory(factory.django.DjangoModelFactory): class Meta: model = models.AbstractBase abstract = True foo = factory.Sequence(lambda n: "foo%d" % n) class ConcreteSonFactory(AbstractBaseFactory): class Meta: model = models.ConcreteSon class AbstractSonFactory(AbstractBaseFactory): class Meta: model = models.AbstractSon class ConcreteGrandSonFactory(AbstractBaseFactory): class Meta: model = models.ConcreteGrandSon class WithFileFactory(factory.django.DjangoModelFactory): class Meta: model = models.WithFile if django is not None: afile = factory.django.FileField() class WithImageFactory(factory.django.DjangoModelFactory): class Meta: model = models.WithImage if django is not None: animage = factory.django.ImageField() class WithSignalsFactory(factory.django.DjangoModelFactory): class Meta: model = models.WithSignals class WithCustomManagerFactory(factory.django.DjangoModelFactory): class Meta: model = models.WithCustomManager foo = factory.Sequence(lambda n: "foo%d" % n) @unittest.skipIf(django is None, "Django not installed.") class ModelTests(django_test.TestCase): def test_unset_model(self): class UnsetModelFactory(factory.django.DjangoModelFactory): pass self.assertRaises(factory.FactoryError, UnsetModelFactory.create) def test_cross_database(self): class OtherDBFactory(factory.django.DjangoModelFactory): class Meta: model = models.StandardModel database = 'replica' obj = OtherDBFactory() self.assertFalse(models.StandardModel.objects.exists()) self.assertEqual(obj, models.StandardModel.objects.using('replica').get()) @unittest.skipIf(django is None, "Django not installed.") class DjangoPkSequenceTestCase(django_test.TestCase): def setUp(self): super(DjangoPkSequenceTestCase, self).setUp() StandardFactory.reset_sequence() def test_pk_first(self): std = StandardFactory.build() self.assertEqual('foo0', std.foo) def test_pk_many(self): std1 = StandardFactory.build() std2 = StandardFactory.build() self.assertEqual('foo0', std1.foo) self.assertEqual('foo1', std2.foo) def test_pk_creation(self): std1 = StandardFactory.create() self.assertEqual('foo0', std1.foo) self.assertEqual(1, std1.pk) StandardFactory.reset_sequence() std2 = StandardFactory.create() self.assertEqual('foo0', std2.foo) self.assertEqual(2, std2.pk) def test_pk_force_value(self): std1 = StandardFactory.create(pk=10) self.assertEqual('foo0', std1.foo) # sequence is unrelated to pk self.assertEqual(10, std1.pk) StandardFactory.reset_sequence() std2 = StandardFactory.create() self.assertEqual('foo0', std2.foo) self.assertEqual(11, std2.pk) @unittest.skipIf(django is None, "Django not installed.") class DjangoGetOrCreateTests(django_test.TestCase): def test_simple_call(self): obj1 = MultifieldModelFactory(slug='slug1') obj2 = MultifieldModelFactory(slug='slug1') obj3 = MultifieldModelFactory(slug='alt') self.assertEqual(obj1, obj2) self.assertEqual(2, models.MultifieldModel.objects.count()) def test_missing_arg(self): with self.assertRaises(factory.FactoryError): MultifieldModelFactory() def test_multicall(self): objs = MultifieldModelFactory.create_batch(6, slug=factory.Iterator(['main', 'alt']), ) self.assertEqual(6, len(objs)) self.assertEqual(2, len(set(objs))) self.assertEqual(2, models.MultifieldModel.objects.count()) @unittest.skipIf(django is None, "Django not installed.") class DjangoPkForceTestCase(django_test.TestCase): def setUp(self): super(DjangoPkForceTestCase, self).setUp() StandardFactoryWithPKField.reset_sequence() def test_no_pk(self): std = StandardFactoryWithPKField() self.assertIsNotNone(std.pk) self.assertEqual('foo0', std.foo) def test_force_pk(self): std = StandardFactoryWithPKField(pk=42) self.assertIsNotNone(std.pk) self.assertEqual('foo0', std.foo) def test_reuse_pk(self): std1 = StandardFactoryWithPKField(foo='bar') self.assertIsNotNone(std1.pk) std2 = StandardFactoryWithPKField(pk=std1.pk, foo='blah') self.assertEqual(std1.pk, std2.pk) self.assertEqual('bar', std2.foo) @unittest.skipIf(django is None, "Django not installed.") class DjangoModelLoadingTestCase(django_test.TestCase): """Tests class Meta: model = 'app.Model' pattern.""" def test_loading(self): class ExampleFactory(factory.DjangoModelFactory): class Meta: model = 'djapp.StandardModel' self.assertEqual(models.StandardModel, ExampleFactory._get_model_class()) def test_building(self): class ExampleFactory(factory.DjangoModelFactory): class Meta: model = 'djapp.StandardModel' e = ExampleFactory.build() self.assertEqual(models.StandardModel, e.__class__) def test_inherited_loading(self): """Proper loading of a model within 'child' factories. See https://github.com/FactoryBoy/factory_boy/issues/109. """ class ExampleFactory(factory.DjangoModelFactory): class Meta: model = 'djapp.StandardModel' class Example2Factory(ExampleFactory): pass e = Example2Factory.build() self.assertEqual(models.StandardModel, e.__class__) def test_inherited_loading_and_sequence(self): """Proper loading of a model within 'child' factories. See https://github.com/FactoryBoy/factory_boy/issues/109. """ class ExampleFactory(factory.DjangoModelFactory): class Meta: model = 'djapp.StandardModel' foo = factory.Sequence(lambda n: n) class Example2Factory(ExampleFactory): class Meta: model = 'djapp.StandardSon' self.assertEqual(models.StandardSon, Example2Factory._get_model_class()) e1 = ExampleFactory.build() e2 = Example2Factory.build() e3 = ExampleFactory.build() self.assertEqual(models.StandardModel, e1.__class__) self.assertEqual(models.StandardSon, e2.__class__) self.assertEqual(models.StandardModel, e3.__class__) self.assertEqual(0, e1.foo) self.assertEqual(1, e2.foo) self.assertEqual(2, e3.foo) @unittest.skipIf(django is None, "Django not installed.") class DjangoNonIntegerPkTestCase(django_test.TestCase): def setUp(self): super(DjangoNonIntegerPkTestCase, self).setUp() NonIntegerPkFactory.reset_sequence() def test_first(self): nonint = NonIntegerPkFactory.build() self.assertEqual('foo0', nonint.foo) def test_many(self): nonint1 = NonIntegerPkFactory.build() nonint2 = NonIntegerPkFactory.build() self.assertEqual('foo0', nonint1.foo) self.assertEqual('foo1', nonint2.foo) def test_creation(self): nonint1 = NonIntegerPkFactory.create() self.assertEqual('foo0', nonint1.foo) self.assertEqual('foo0', nonint1.pk) NonIntegerPkFactory.reset_sequence() nonint2 = NonIntegerPkFactory.build() self.assertEqual('foo0', nonint2.foo) def test_force_pk(self): nonint1 = NonIntegerPkFactory.create(pk='foo10') self.assertEqual('foo10', nonint1.foo) self.assertEqual('foo10', nonint1.pk) NonIntegerPkFactory.reset_sequence() nonint2 = NonIntegerPkFactory.create() self.assertEqual('foo0', nonint2.foo) self.assertEqual('foo0', nonint2.pk) @unittest.skipIf(django is None, "Django not installed.") class DjangoAbstractBaseSequenceTestCase(django_test.TestCase): def test_auto_sequence(self): """The sequence of the concrete son of an abstract model should be autonomous.""" obj = ConcreteSonFactory() self.assertEqual(1, obj.pk) def test_auto_sequence(self): """The sequence of the concrete grandson of an abstract model should be autonomous.""" obj = ConcreteGrandSonFactory() self.assertEqual(1, obj.pk) def test_optional_abstract(self): """Users need not describe the factory for an abstract model as abstract.""" class AbstractBaseFactory(factory.django.DjangoModelFactory): class Meta: model = models.AbstractBase foo = factory.Sequence(lambda n: "foo%d" % n) class ConcreteSonFactory(AbstractBaseFactory): class Meta: model = models.ConcreteSon obj = ConcreteSonFactory() self.assertEqual(1, obj.pk) self.assertEqual("foo0", obj.foo) @unittest.skipIf(django is None, "Django not installed.") class DjangoRelatedFieldTestCase(django_test.TestCase): @classmethod def setUpClass(cls): super(DjangoRelatedFieldTestCase, cls).setUpClass() class PointedFactory(factory.django.DjangoModelFactory): class Meta: model = models.PointedModel foo = 'ahah' class PointerFactory(factory.django.DjangoModelFactory): class Meta: model = models.PointingModel pointed = factory.SubFactory(PointedFactory, foo='hihi') foo = 'bar' cls.PointedFactory = PointedFactory cls.PointerFactory = PointerFactory def test_direct_related_create(self): ptr = self.PointerFactory() self.assertEqual('hihi', ptr.pointed.foo) self.assertEqual(ptr.pointed, models.PointedModel.objects.get()) @unittest.skipIf(django is None, "Django not installed.") class DjangoFileFieldTestCase(unittest.TestCase): def tearDown(self): super(DjangoFileFieldTestCase, self).tearDown() for path in os.listdir(models.WITHFILE_UPLOAD_DIR): # Remove temporary files written during tests. os.unlink(os.path.join(models.WITHFILE_UPLOAD_DIR, path)) def test_default_build(self): o = WithFileFactory.build() self.assertIsNone(o.pk) self.assertEqual(b'', o.afile.read()) self.assertEqual('example.dat', o.afile.name) o.save() self.assertEqual('django/example.dat', o.afile.name) def test_default_create(self): o = WithFileFactory.create() self.assertIsNotNone(o.pk) self.assertEqual(b'', o.afile.read()) self.assertEqual('django/example.dat', o.afile.name) def test_with_content(self): o = WithFileFactory.build(afile__data='foo') self.assertIsNone(o.pk) # Django only allocates the full path on save() o.save() self.assertEqual(b'foo', o.afile.read()) self.assertEqual('django/example.dat', o.afile.name) def test_with_file(self): with open(testdata.TESTFILE_PATH, 'rb') as f: o = WithFileFactory.build(afile__from_file=f) o.save() self.assertEqual(b'example_data\n', o.afile.read()) self.assertEqual('django/example.data', o.afile.name) def test_with_path(self): o = WithFileFactory.build(afile__from_path=testdata.TESTFILE_PATH) self.assertIsNone(o.pk) # Django only allocates the full path on save() o.save() self.assertEqual(b'example_data\n', o.afile.read()) self.assertEqual('django/example.data', o.afile.name) def test_with_file_empty_path(self): with open(testdata.TESTFILE_PATH, 'rb') as f: o = WithFileFactory.build( afile__from_file=f, afile__from_path='' ) # Django only allocates the full path on save() o.save() self.assertEqual(b'example_data\n', o.afile.read()) self.assertEqual('django/example.data', o.afile.name) def test_with_path_empty_file(self): o = WithFileFactory.build( afile__from_path=testdata.TESTFILE_PATH, afile__from_file=None, ) self.assertIsNone(o.pk) # Django only allocates the full path on save() o.save() self.assertEqual(b'example_data\n', o.afile.read()) self.assertEqual('django/example.data', o.afile.name) def test_error_both_file_and_path(self): self.assertRaises(ValueError, WithFileFactory.build, afile__from_file='fakefile', afile__from_path=testdata.TESTFILE_PATH, ) def test_override_filename_with_path(self): o = WithFileFactory.build( afile__from_path=testdata.TESTFILE_PATH, afile__filename='example.foo', ) self.assertIsNone(o.pk) # Django only allocates the full path on save() o.save() self.assertEqual(b'example_data\n', o.afile.read()) self.assertEqual('django/example.foo', o.afile.name) def test_existing_file(self): o1 = WithFileFactory.build(afile__from_path=testdata.TESTFILE_PATH) o1.save() self.assertEqual('django/example.data', o1.afile.name) o2 = WithFileFactory.build(afile__from_file=o1.afile) self.assertIsNone(o2.pk) o2.save() self.assertEqual(b'example_data\n', o2.afile.read()) self.assertNotEqual('django/example.data', o2.afile.name) self.assertRegexpMatches(o2.afile.name, r'django/example_\w+.data') def test_no_file(self): o = WithFileFactory.build(afile=None) self.assertIsNone(o.pk) self.assertFalse(o.afile) @unittest.skipIf(django is None, "Django not installed.") @unittest.skipIf(Image is None, "PIL not installed.") class DjangoImageFieldTestCase(unittest.TestCase): def tearDown(self): super(DjangoImageFieldTestCase, self).tearDown() for path in os.listdir(models.WITHFILE_UPLOAD_DIR): # Remove temporary files written during tests. os.unlink(os.path.join(models.WITHFILE_UPLOAD_DIR, path)) def test_default_build(self): o = WithImageFactory.build() self.assertIsNone(o.pk) o.save() self.assertEqual(100, o.animage.width) self.assertEqual(100, o.animage.height) self.assertEqual('django/example.jpg', o.animage.name) def test_default_create(self): o = WithImageFactory.create() self.assertIsNotNone(o.pk) o.save() self.assertEqual(100, o.animage.width) self.assertEqual(100, o.animage.height) self.assertEqual('django/example.jpg', o.animage.name) def test_complex_create(self): o = WithImageFactory.create( size=10, animage__filename=factory.Sequence(lambda n: 'img%d.jpg' % n), __sequence=42, animage__width=factory.SelfAttribute('..size'), animage__height=factory.SelfAttribute('width'), ) self.assertIsNotNone(o.pk) self.assertEqual('django/img42.jpg', o.animage.name) def test_with_content(self): o = WithImageFactory.build(animage__width=13, animage__color='red') self.assertIsNone(o.pk) o.save() self.assertEqual(13, o.animage.width) self.assertEqual(13, o.animage.height) self.assertEqual('django/example.jpg', o.animage.name) i = Image.open(os.path.join(settings.MEDIA_ROOT, o.animage.name)) colors = i.getcolors() # 169 pixels with rgb(254, 0, 0) self.assertEqual([(169, (254, 0, 0))], colors) self.assertEqual('JPEG', i.format) def test_gif(self): o = WithImageFactory.build(animage__width=13, animage__color='blue', animage__format='GIF') self.assertIsNone(o.pk) o.save() self.assertEqual(13, o.animage.width) self.assertEqual(13, o.animage.height) self.assertEqual('django/example.jpg', o.animage.name) i = Image.open(os.path.join(settings.MEDIA_ROOT, o.animage.name)) colors = i.convert('RGB').getcolors() # 169 pixels with rgb(0, 0, 255) self.assertEqual([(169, (0, 0, 255))], colors) self.assertEqual('GIF', i.format) def test_with_file(self): with open(testdata.TESTIMAGE_PATH, 'rb') as f: o = WithImageFactory.build(animage__from_file=f) o.save() # Image file for a 42x42 green jpeg: 301 bytes long. self.assertEqual(301, len(o.animage.read())) self.assertEqual('django/example.jpeg', o.animage.name) def test_with_path(self): o = WithImageFactory.build(animage__from_path=testdata.TESTIMAGE_PATH) self.assertIsNone(o.pk) o.save() # Image file for a 42x42 green jpeg: 301 bytes long. self.assertEqual(301, len(o.animage.read())) self.assertEqual('django/example.jpeg', o.animage.name) def test_with_file_empty_path(self): with open(testdata.TESTIMAGE_PATH, 'rb') as f: o = WithImageFactory.build( animage__from_file=f, animage__from_path='' ) o.save() # Image file for a 42x42 green jpeg: 301 bytes long. self.assertEqual(301, len(o.animage.read())) self.assertEqual('django/example.jpeg', o.animage.name) def test_with_path_empty_file(self): o = WithImageFactory.build( animage__from_path=testdata.TESTIMAGE_PATH, animage__from_file=None, ) self.assertIsNone(o.pk) o.save() # Image file for a 42x42 green jpeg: 301 bytes long. self.assertEqual(301, len(o.animage.read())) self.assertEqual('django/example.jpeg', o.animage.name) def test_error_both_file_and_path(self): self.assertRaises(ValueError, WithImageFactory.build, animage__from_file='fakefile', animage__from_path=testdata.TESTIMAGE_PATH, ) def test_override_filename_with_path(self): o = WithImageFactory.build( animage__from_path=testdata.TESTIMAGE_PATH, animage__filename='example.foo', ) self.assertIsNone(o.pk) o.save() # Image file for a 42x42 green jpeg: 301 bytes long. self.assertEqual(301, len(o.animage.read())) self.assertEqual('django/example.foo', o.animage.name) def test_existing_file(self): o1 = WithImageFactory.build(animage__from_path=testdata.TESTIMAGE_PATH) o1.save() o2 = WithImageFactory.build(animage__from_file=o1.animage) self.assertIsNone(o2.pk) o2.save() # Image file for a 42x42 green jpeg: 301 bytes long. self.assertEqual(301, len(o2.animage.read())) self.assertNotEqual('django/example.jpeg', o2.animage.name) self.assertRegexpMatches(o2.animage.name, r'django/example_\w+.jpeg') def test_no_file(self): o = WithImageFactory.build(animage=None) self.assertIsNone(o.pk) self.assertFalse(o.animage) def _img_test_func(self): img = Image.new('RGB', (32,32), 'blue') img_io = BytesIO() img.save(img_io, format='JPEG') img_io.seek(0) return img_io def test_with_func(self): o = WithImageFactory.build(animage__from_func=self._img_test_func) self.assertIsNone(o.pk) i = Image.open(o.animage.file) self.assertEqual('JPEG', i.format) self.assertEqual(32, i.width) self.assertEqual(32, i.height) @unittest.skipIf(django is None, "Django not installed.") class PreventSignalsTestCase(unittest.TestCase): def setUp(self): self.handlers = mock.MagicMock() signals.pre_init.connect(self.handlers.pre_init) signals.pre_save.connect(self.handlers.pre_save) signals.post_save.connect(self.handlers.post_save) def tearDown(self): signals.pre_init.disconnect(self.handlers.pre_init) signals.pre_save.disconnect(self.handlers.pre_save) signals.post_save.disconnect(self.handlers.post_save) def assertSignalsReactivated(self): WithSignalsFactory() self.assertEqual(self.handlers.pre_save.call_count, 1) self.assertEqual(self.handlers.post_save.call_count, 1) def test_context_manager(self): with factory.django.mute_signals(signals.pre_save, signals.post_save): WithSignalsFactory() self.assertEqual(self.handlers.pre_init.call_count, 1) self.assertFalse(self.handlers.pre_save.called) self.assertFalse(self.handlers.post_save.called) self.assertSignalsReactivated() def test_signal_cache(self): with factory.django.mute_signals(signals.pre_save, signals.post_save): signals.post_save.connect(self.handlers.mute_block_receiver) WithSignalsFactory() self.assertTrue(self.handlers.mute_block_receiver.call_count, 1) self.assertEqual(self.handlers.pre_init.call_count, 1) self.assertFalse(self.handlers.pre_save.called) self.assertFalse(self.handlers.post_save.called) self.assertSignalsReactivated() self.assertTrue(self.handlers.mute_block_receiver.call_count, 1) def test_class_decorator(self): @factory.django.mute_signals(signals.pre_save, signals.post_save) class WithSignalsDecoratedFactory(factory.django.DjangoModelFactory): class Meta: model = models.WithSignals WithSignalsDecoratedFactory() self.assertEqual(self.handlers.pre_init.call_count, 1) self.assertFalse(self.handlers.pre_save.called) self.assertFalse(self.handlers.post_save.called) self.assertSignalsReactivated() def test_class_decorator_with_subfactory(self): @factory.django.mute_signals(signals.pre_save, signals.post_save) class WithSignalsDecoratedFactory(factory.django.DjangoModelFactory): class Meta: model = models.WithSignals @factory.post_generation def post(obj, create, extracted, **kwargs): if not extracted: WithSignalsDecoratedFactory.create(post=42) # This will disable the signals (twice), create two objects, # and reactivate the signals. WithSignalsDecoratedFactory() self.assertEqual(self.handlers.pre_init.call_count, 2) self.assertFalse(self.handlers.pre_save.called) self.assertFalse(self.handlers.post_save.called) self.assertSignalsReactivated() def test_class_decorator_build(self): @factory.django.mute_signals(signals.pre_save, signals.post_save) class WithSignalsDecoratedFactory(factory.django.DjangoModelFactory): class Meta: model = models.WithSignals WithSignalsDecoratedFactory.build() self.assertEqual(self.handlers.pre_init.call_count, 1) self.assertFalse(self.handlers.pre_save.called) self.assertFalse(self.handlers.post_save.called) self.assertSignalsReactivated() def test_function_decorator(self): @factory.django.mute_signals(signals.pre_save, signals.post_save) def foo(): WithSignalsFactory() foo() self.assertEqual(self.handlers.pre_init.call_count, 1) self.assertFalse(self.handlers.pre_save.called) self.assertFalse(self.handlers.post_save.called) self.assertSignalsReactivated() def test_classmethod_decorator(self): class Foo(object): @classmethod @factory.django.mute_signals(signals.pre_save, signals.post_save) def generate(cls): WithSignalsFactory() Foo.generate() self.assertEqual(self.handlers.pre_init.call_count, 1) self.assertFalse(self.handlers.pre_save.called) self.assertFalse(self.handlers.post_save.called) self.assertSignalsReactivated() @unittest.skipIf(django is None, "Django not installed.") class DjangoCustomManagerTestCase(unittest.TestCase): def test_extra_args(self): # Our CustomManager will remove the 'arg=' argument. model = WithCustomManagerFactory(arg='foo') def test_with_manager_on_abstract(self): class ObjFactory(factory.django.DjangoModelFactory): class Meta: model = models.FromAbstractWithCustomManager # Our CustomManager will remove the 'arg=' argument, # invalid for the actual model. ObjFactory.create(arg='invalid') if __name__ == '__main__': # pragma: no cover unittest.main() factory_boy-2.8.1/tests/test_faker.py000066400000000000000000000117201302510513700177030ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (c) 2010 Mark Sandstrom # Copyright (c) 2011-2015 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. import unittest import faker.providers import factory class MockFaker(object): def __init__(self, expected): self.expected = expected def format(self, provider, **kwargs): return self.expected[provider] class FakerTests(unittest.TestCase): def setUp(self): self._real_fakers = factory.Faker._FAKER_REGISTRY factory.Faker._FAKER_REGISTRY = {} def tearDown(self): factory.Faker._FAKER_REGISTRY = self._real_fakers def _setup_mock_faker(self, locale=None, **definitions): if locale is None: locale = factory.Faker._DEFAULT_LOCALE factory.Faker._FAKER_REGISTRY[locale] = MockFaker(definitions) def test_simple_biased(self): self._setup_mock_faker(name="John Doe") faker_field = factory.Faker('name') self.assertEqual("John Doe", faker_field.generate({})) def test_full_factory(self): class Profile(object): def __init__(self, first_name, last_name, email): self.first_name = first_name self.last_name = last_name self.email = email class ProfileFactory(factory.Factory): class Meta: model = Profile first_name = factory.Faker('first_name') last_name = factory.Faker('last_name', locale='fr_FR') email = factory.Faker('email') self._setup_mock_faker(first_name="John", last_name="Doe", email="john.doe@example.org") self._setup_mock_faker(first_name="Jean", last_name="Valjean", email="jvaljean@exemple.fr", locale='fr_FR') profile = ProfileFactory() self.assertEqual("John", profile.first_name) self.assertEqual("Valjean", profile.last_name) self.assertEqual('john.doe@example.org', profile.email) def test_override_locale(self): class Profile(object): def __init__(self, first_name, last_name): self.first_name = first_name self.last_name = last_name class ProfileFactory(factory.Factory): class Meta: model = Profile first_name = factory.Faker('first_name') last_name = factory.Faker('last_name', locale='fr_FR') self._setup_mock_faker(first_name="John", last_name="Doe") self._setup_mock_faker(first_name="Jean", last_name="Valjean", locale='fr_FR') self._setup_mock_faker(first_name="Johannes", last_name="Brahms", locale='de_DE') profile = ProfileFactory() self.assertEqual("John", profile.first_name) self.assertEqual("Valjean", profile.last_name) with factory.Faker.override_default_locale('de_DE'): profile = ProfileFactory() self.assertEqual("Johannes", profile.first_name) self.assertEqual("Valjean", profile.last_name) profile = ProfileFactory() self.assertEqual("John", profile.first_name) self.assertEqual("Valjean", profile.last_name) def test_add_provider(self): class Face(object): def __init__(self, smiley, french_smiley): self.smiley = smiley self.french_smiley = french_smiley class FaceFactory(factory.Factory): class Meta: model = Face smiley = factory.Faker('smiley') french_smiley = factory.Faker('smiley', locale='fr_FR') class SmileyProvider(faker.providers.BaseProvider): def smiley(self): return ':)' class FrenchSmileyProvider(faker.providers.BaseProvider): def smiley(self): return '(:' factory.Faker.add_provider(SmileyProvider) factory.Faker.add_provider(FrenchSmileyProvider, 'fr_FR') face = FaceFactory() self.assertEqual(":)", face.smiley) self.assertEqual("(:", face.french_smiley) factory_boy-2.8.1/tests/test_fuzzy.py000066400000000000000000000460511302510513700200070ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (c) 2010 Mark Sandstrom # Copyright (c) 2011-2015 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. import datetime import decimal from factory import compat from factory import fuzzy from .compat import mock, unittest from . import utils class FuzzyAttributeTestCase(unittest.TestCase): def test_simple_call(self): d = fuzzy.FuzzyAttribute(lambda: 10) res = d.evaluate(2, None, False) self.assertEqual(10, res) res = d.evaluate(2, None, False) self.assertEqual(10, res) class FuzzyChoiceTestCase(unittest.TestCase): def test_unbiased(self): options = [1, 2, 3] d = fuzzy.FuzzyChoice(options) res = d.evaluate(2, None, False) self.assertIn(res, options) def test_mock(self): options = [1, 2, 3] fake_choice = lambda d: sum(d) d = fuzzy.FuzzyChoice(options) with mock.patch('factory.fuzzy._random.choice', fake_choice): res = d.evaluate(2, None, False) self.assertEqual(6, res) def test_generator(self): def options(): for i in range(3): yield i d = fuzzy.FuzzyChoice(options()) res = d.evaluate(2, None, False) self.assertIn(res, [0, 1, 2]) # And repeat res = d.evaluate(2, None, False) self.assertIn(res, [0, 1, 2]) def test_lazy_generator(self): class Gen(object): def __init__(self, options): self.options = options self.unrolled = False def __iter__(self): self.unrolled = True return iter(self.options) opts = Gen([1, 2, 3]) d = fuzzy.FuzzyChoice(opts) self.assertFalse(opts.unrolled) res = d.evaluate(2, None, False) self.assertIn(res, [1, 2, 3]) self.assertTrue(opts.unrolled) class FuzzyIntegerTestCase(unittest.TestCase): def test_definition(self): """Tests all ways of defining a FuzzyInteger.""" fuzz = fuzzy.FuzzyInteger(2, 3) for _i in range(20): res = fuzz.evaluate(2, None, False) self.assertIn(res, [2, 3]) fuzz = fuzzy.FuzzyInteger(4) for _i in range(20): res = fuzz.evaluate(2, None, False) self.assertIn(res, [0, 1, 2, 3, 4]) def test_biased(self): fake_randrange = lambda low, high, step: (low + high) * step fuzz = fuzzy.FuzzyInteger(2, 8) with mock.patch('factory.fuzzy._random.randrange', fake_randrange): res = fuzz.evaluate(2, None, False) self.assertEqual((2 + 8 + 1) * 1, res) def test_biased_high_only(self): fake_randrange = lambda low, high, step: (low + high) * step fuzz = fuzzy.FuzzyInteger(8) with mock.patch('factory.fuzzy._random.randrange', fake_randrange): res = fuzz.evaluate(2, None, False) self.assertEqual((0 + 8 + 1) * 1, res) def test_biased_with_step(self): fake_randrange = lambda low, high, step: (low + high) * step fuzz = fuzzy.FuzzyInteger(5, 8, 3) with mock.patch('factory.fuzzy._random.randrange', fake_randrange): res = fuzz.evaluate(2, None, False) self.assertEqual((5 + 8 + 1) * 3, res) class FuzzyDecimalTestCase(unittest.TestCase): def test_definition(self): """Tests all ways of defining a FuzzyDecimal.""" fuzz = fuzzy.FuzzyDecimal(2.0, 3.0) for _i in range(20): res = fuzz.evaluate(2, None, False) self.assertTrue(decimal.Decimal('2.0') <= res <= decimal.Decimal('3.0'), "value %d is not between 2.0 and 3.0" % res) fuzz = fuzzy.FuzzyDecimal(4.0) for _i in range(20): res = fuzz.evaluate(2, None, False) self.assertTrue(decimal.Decimal('0.0') <= res <= decimal.Decimal('4.0'), "value %d is not between 0.0 and 4.0" % res) fuzz = fuzzy.FuzzyDecimal(1.0, 4.0, precision=5) for _i in range(20): res = fuzz.evaluate(2, None, False) self.assertTrue(decimal.Decimal('0.54') <= res <= decimal.Decimal('4.0'), "value %d is not between 0.54 and 4.0" % res) self.assertTrue(res.as_tuple().exponent, -5) def test_biased(self): fake_uniform = lambda low, high: low + high fuzz = fuzzy.FuzzyDecimal(2.0, 8.0) with mock.patch('factory.fuzzy._random.uniform', fake_uniform): res = fuzz.evaluate(2, None, False) self.assertEqual(decimal.Decimal('10.0'), res) def test_biased_high_only(self): fake_uniform = lambda low, high: low + high fuzz = fuzzy.FuzzyDecimal(8.0) with mock.patch('factory.fuzzy._random.uniform', fake_uniform): res = fuzz.evaluate(2, None, False) self.assertEqual(decimal.Decimal('8.0'), res) def test_precision(self): fake_uniform = lambda low, high: low + high + 0.001 fuzz = fuzzy.FuzzyDecimal(8.0, precision=3) with mock.patch('factory.fuzzy._random.uniform', fake_uniform): res = fuzz.evaluate(2, None, False) 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 def setUpClass(cls): # Setup useful constants cls.jan1 = datetime.date(2013, 1, 1) cls.jan3 = datetime.date(2013, 1, 3) cls.jan31 = datetime.date(2013, 1, 31) def test_accurate_definition(self): """Tests all ways of defining a FuzzyDate.""" fuzz = fuzzy.FuzzyDate(self.jan1, self.jan31) for _i in range(20): res = fuzz.evaluate(2, None, False) self.assertLessEqual(self.jan1, res) self.assertLessEqual(res, self.jan31) def test_partial_definition(self): """Test defining a FuzzyDate without passing an end date.""" with utils.mocked_date_today(self.jan3, fuzzy): fuzz = fuzzy.FuzzyDate(self.jan1) for _i in range(20): res = fuzz.evaluate(2, None, False) self.assertLessEqual(self.jan1, res) self.assertLessEqual(res, self.jan3) def test_invalid_definition(self): self.assertRaises(ValueError, fuzzy.FuzzyDate, self.jan31, self.jan1) def test_invalid_partial_definition(self): with utils.mocked_date_today(self.jan1, fuzzy): self.assertRaises(ValueError, fuzzy.FuzzyDate, self.jan31) def test_biased(self): """Tests a FuzzyDate with a biased random.randint.""" fake_randint = lambda low, high: (low + high) // 2 fuzz = fuzzy.FuzzyDate(self.jan1, self.jan31) with mock.patch('factory.fuzzy._random.randint', fake_randint): res = fuzz.evaluate(2, None, False) self.assertEqual(datetime.date(2013, 1, 16), res) def test_biased_partial(self): """Tests a FuzzyDate with a biased random and implicit upper bound.""" with utils.mocked_date_today(self.jan3, fuzzy): fuzz = fuzzy.FuzzyDate(self.jan1) fake_randint = lambda low, high: (low + high) // 2 with mock.patch('factory.fuzzy._random.randint', fake_randint): res = fuzz.evaluate(2, None, False) self.assertEqual(datetime.date(2013, 1, 2), res) class FuzzyNaiveDateTimeTestCase(unittest.TestCase): @classmethod def setUpClass(cls): # Setup useful constants cls.jan1 = datetime.datetime(2013, 1, 1) cls.jan3 = datetime.datetime(2013, 1, 3) cls.jan31 = datetime.datetime(2013, 1, 31) def test_accurate_definition(self): """Tests explicit definition of a FuzzyNaiveDateTime.""" fuzz = fuzzy.FuzzyNaiveDateTime(self.jan1, self.jan31) for _i in range(20): res = fuzz.evaluate(2, None, False) self.assertLessEqual(self.jan1, res) self.assertLessEqual(res, self.jan31) def test_partial_definition(self): """Test defining a FuzzyNaiveDateTime without passing an end date.""" with utils.mocked_datetime_now(self.jan3, fuzzy): fuzz = fuzzy.FuzzyNaiveDateTime(self.jan1) for _i in range(20): res = fuzz.evaluate(2, None, False) self.assertLessEqual(self.jan1, res) self.assertLessEqual(res, self.jan3) def test_aware_start(self): """Tests that a timezone-aware start datetime is rejected.""" self.assertRaises(ValueError, fuzzy.FuzzyNaiveDateTime, self.jan1.replace(tzinfo=compat.UTC), self.jan31) def test_aware_end(self): """Tests that a timezone-aware end datetime is rejected.""" self.assertRaises(ValueError, fuzzy.FuzzyNaiveDateTime, self.jan1, self.jan31.replace(tzinfo=compat.UTC)) def test_force_year(self): fuzz = fuzzy.FuzzyNaiveDateTime(self.jan1, self.jan31, force_year=4) for _i in range(20): res = fuzz.evaluate(2, None, False) self.assertEqual(4, res.year) def test_force_month(self): fuzz = fuzzy.FuzzyNaiveDateTime(self.jan1, self.jan31, force_month=4) for _i in range(20): res = fuzz.evaluate(2, None, False) self.assertEqual(4, res.month) def test_force_day(self): fuzz = fuzzy.FuzzyNaiveDateTime(self.jan1, self.jan31, force_day=4) for _i in range(20): res = fuzz.evaluate(2, None, False) self.assertEqual(4, res.day) def test_force_hour(self): fuzz = fuzzy.FuzzyNaiveDateTime(self.jan1, self.jan31, force_hour=4) for _i in range(20): res = fuzz.evaluate(2, None, False) self.assertEqual(4, res.hour) def test_force_minute(self): fuzz = fuzzy.FuzzyNaiveDateTime(self.jan1, self.jan31, force_minute=4) for _i in range(20): res = fuzz.evaluate(2, None, False) self.assertEqual(4, res.minute) def test_force_second(self): fuzz = fuzzy.FuzzyNaiveDateTime(self.jan1, self.jan31, force_second=4) for _i in range(20): res = fuzz.evaluate(2, None, False) self.assertEqual(4, res.second) def test_force_microsecond(self): fuzz = fuzzy.FuzzyNaiveDateTime(self.jan1, self.jan31, force_microsecond=4) for _i in range(20): res = fuzz.evaluate(2, None, False) self.assertEqual(4, res.microsecond) def test_invalid_definition(self): self.assertRaises(ValueError, fuzzy.FuzzyNaiveDateTime, self.jan31, self.jan1) def test_invalid_partial_definition(self): with utils.mocked_datetime_now(self.jan1, fuzzy): self.assertRaises(ValueError, fuzzy.FuzzyNaiveDateTime, self.jan31) def test_biased(self): """Tests a FuzzyDate with a biased random.randint.""" fake_randint = lambda low, high: (low + high) // 2 fuzz = fuzzy.FuzzyNaiveDateTime(self.jan1, self.jan31) with mock.patch('factory.fuzzy._random.randint', fake_randint): res = fuzz.evaluate(2, None, False) self.assertEqual(datetime.datetime(2013, 1, 16), res) def test_biased_partial(self): """Tests a FuzzyDate with a biased random and implicit upper bound.""" with utils.mocked_datetime_now(self.jan3, fuzzy): fuzz = fuzzy.FuzzyNaiveDateTime(self.jan1) fake_randint = lambda low, high: (low + high) // 2 with mock.patch('factory.fuzzy._random.randint', fake_randint): res = fuzz.evaluate(2, None, False) self.assertEqual(datetime.datetime(2013, 1, 2), res) class FuzzyDateTimeTestCase(unittest.TestCase): @classmethod def setUpClass(cls): # Setup useful constants cls.jan1 = datetime.datetime(2013, 1, 1, tzinfo=compat.UTC) cls.jan3 = datetime.datetime(2013, 1, 3, tzinfo=compat.UTC) cls.jan31 = datetime.datetime(2013, 1, 31, tzinfo=compat.UTC) def test_accurate_definition(self): """Tests explicit definition of a FuzzyDateTime.""" fuzz = fuzzy.FuzzyDateTime(self.jan1, self.jan31) for _i in range(20): res = fuzz.evaluate(2, None, False) self.assertLessEqual(self.jan1, res) self.assertLessEqual(res, self.jan31) def test_partial_definition(self): """Test defining a FuzzyDateTime without passing an end date.""" with utils.mocked_datetime_now(self.jan3, fuzzy): fuzz = fuzzy.FuzzyDateTime(self.jan1) for _i in range(20): res = fuzz.evaluate(2, None, False) self.assertLessEqual(self.jan1, res) self.assertLessEqual(res, self.jan3) def test_invalid_definition(self): self.assertRaises(ValueError, fuzzy.FuzzyDateTime, self.jan31, self.jan1) def test_invalid_partial_definition(self): with utils.mocked_datetime_now(self.jan1, fuzzy): self.assertRaises(ValueError, fuzzy.FuzzyDateTime, self.jan31) def test_naive_start(self): """Tests that a timezone-naive start datetime is rejected.""" self.assertRaises(ValueError, fuzzy.FuzzyDateTime, self.jan1.replace(tzinfo=None), self.jan31) def test_naive_end(self): """Tests that a timezone-naive end datetime is rejected.""" self.assertRaises(ValueError, fuzzy.FuzzyDateTime, self.jan1, self.jan31.replace(tzinfo=None)) def test_force_year(self): fuzz = fuzzy.FuzzyDateTime(self.jan1, self.jan31, force_year=4) for _i in range(20): res = fuzz.evaluate(2, None, False) self.assertEqual(4, res.year) def test_force_month(self): fuzz = fuzzy.FuzzyDateTime(self.jan1, self.jan31, force_month=4) for _i in range(20): res = fuzz.evaluate(2, None, False) self.assertEqual(4, res.month) def test_force_day(self): fuzz = fuzzy.FuzzyDateTime(self.jan1, self.jan31, force_day=4) for _i in range(20): res = fuzz.evaluate(2, None, False) self.assertEqual(4, res.day) def test_force_hour(self): fuzz = fuzzy.FuzzyDateTime(self.jan1, self.jan31, force_hour=4) for _i in range(20): res = fuzz.evaluate(2, None, False) self.assertEqual(4, res.hour) def test_force_minute(self): fuzz = fuzzy.FuzzyDateTime(self.jan1, self.jan31, force_minute=4) for _i in range(20): res = fuzz.evaluate(2, None, False) self.assertEqual(4, res.minute) def test_force_second(self): fuzz = fuzzy.FuzzyDateTime(self.jan1, self.jan31, force_second=4) for _i in range(20): res = fuzz.evaluate(2, None, False) self.assertEqual(4, res.second) def test_force_microsecond(self): fuzz = fuzzy.FuzzyDateTime(self.jan1, self.jan31, force_microsecond=4) for _i in range(20): res = fuzz.evaluate(2, None, False) self.assertEqual(4, res.microsecond) def test_biased(self): """Tests a FuzzyDate with a biased random.randint.""" fake_randint = lambda low, high: (low + high) // 2 fuzz = fuzzy.FuzzyDateTime(self.jan1, self.jan31) with mock.patch('factory.fuzzy._random.randint', fake_randint): res = fuzz.evaluate(2, None, False) self.assertEqual(datetime.datetime(2013, 1, 16, tzinfo=compat.UTC), res) def test_biased_partial(self): """Tests a FuzzyDate with a biased random and implicit upper bound.""" with utils.mocked_datetime_now(self.jan3, fuzzy): fuzz = fuzzy.FuzzyDateTime(self.jan1) fake_randint = lambda low, high: (low + high) // 2 with mock.patch('factory.fuzzy._random.randint', fake_randint): res = fuzz.evaluate(2, None, False) self.assertEqual(datetime.datetime(2013, 1, 2, tzinfo=compat.UTC), res) class FuzzyTextTestCase(unittest.TestCase): def test_unbiased(self): chars = ['a', 'b', 'c'] fuzz = fuzzy.FuzzyText(prefix='pre', suffix='post', chars=chars, length=12) res = fuzz.evaluate(2, None, False) self.assertEqual('pre', res[:3]) self.assertEqual('post', res[-4:]) self.assertEqual(3 + 12 + 4, len(res)) for char in res[3:-4]: self.assertIn(char, chars) def test_mock(self): fake_choice = lambda chars: chars[0] chars = ['a', 'b', 'c'] fuzz = fuzzy.FuzzyText(prefix='pre', suffix='post', chars=chars, length=4) with mock.patch('factory.fuzzy._random.choice', fake_choice): res = fuzz.evaluate(2, None, False) self.assertEqual('preaaaapost', res) def test_generator(self): def options(): yield 'a' yield 'b' yield 'c' fuzz = fuzzy.FuzzyText(chars=options(), length=12) res = fuzz.evaluate(2, None, False) self.assertEqual(12, len(res)) for char in res: self.assertIn(char, ['a', 'b', 'c']) class FuzzyRandomTestCase(unittest.TestCase): def test_seeding(self): fuzz = fuzzy.FuzzyInteger(1, 1000) fuzzy.reseed_random(42) value = fuzz.evaluate(sequence=1, obj=None, create=False) fuzzy.reseed_random(42) value2 = fuzz.evaluate(sequence=1, obj=None, create=False) self.assertEqual(value, value2) def test_reset_state(self): fuzz = fuzzy.FuzzyInteger(1, 1000) state = fuzzy.get_random_state() value = fuzz.evaluate(sequence=1, obj=None, create=False) fuzzy.set_random_state(state) value2 = fuzz.evaluate(sequence=1, obj=None, create=False) self.assertEqual(value, value2) factory_boy-2.8.1/tests/test_helpers.py000066400000000000000000000051671302510513700202650ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (c) 2010 Mark Sandstrom # Copyright (c) 2011-2015 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. import logging from factory import helpers from .compat import io, unittest class DebugTest(unittest.TestCase): """Tests for the 'factory.debug()' helper.""" def test_default_logger(self): stream1 = io.StringIO() stream2 = io.StringIO() l = logging.getLogger('factory.test') h = logging.StreamHandler(stream1) h.setLevel(logging.INFO) l.addHandler(h) # Non-debug: no text gets out l.debug("Test") self.assertEqual('', stream1.getvalue()) with helpers.debug(stream=stream2): # Debug: text goes to new stream only l.debug("Test2") self.assertEqual('', stream1.getvalue()) self.assertEqual("Test2\n", stream2.getvalue()) def test_alternate_logger(self): stream1 = io.StringIO() stream2 = io.StringIO() l1 = logging.getLogger('factory.test') l2 = logging.getLogger('factory.foo') h = logging.StreamHandler(stream1) h.setLevel(logging.DEBUG) l2.addHandler(h) # Non-debug: no text gets out l1.debug("Test") self.assertEqual('', stream1.getvalue()) l2.debug("Test") self.assertEqual('', stream1.getvalue()) with helpers.debug('factory.test', stream=stream2): # Debug: text goes to new stream only l1.debug("Test2") l2.debug("Test3") self.assertEqual("", stream1.getvalue()) self.assertEqual("Test2\n", stream2.getvalue()) factory_boy-2.8.1/tests/test_mongoengine.py000066400000000000000000000065741302510513700211330ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (c) 2013 Romain Command& # # 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. """Tests for factory_boy/MongoEngine interactions.""" import factory import os from .compat import unittest try: import mongoengine except ImportError: mongoengine = None if os.environ.get('SKIP_MONGOENGINE') == '1': mongoengine = None if mongoengine: from factory.mongoengine import MongoEngineFactory class Address(mongoengine.EmbeddedDocument): street = mongoengine.StringField() class Person(mongoengine.Document): name = mongoengine.StringField() address = mongoengine.EmbeddedDocumentField(Address) class AddressFactory(MongoEngineFactory): class Meta: model = Address street = factory.Sequence(lambda n: 'street%d' % n) class PersonFactory(MongoEngineFactory): class Meta: model = Person name = factory.Sequence(lambda n: 'name%d' % n) address = factory.SubFactory(AddressFactory) @unittest.skipIf(mongoengine is None, "mongoengine not installed.") class MongoEngineTestCase(unittest.TestCase): db_name = os.environ.get('MONGO_DATABASE', 'factory_boy_test') db_host = os.environ.get('MONGO_HOST', 'localhost') db_port = int(os.environ.get('MONGO_PORT', '27017')) server_timeout_ms = int(os.environ.get('MONGO_TIMEOUT', '300')) @classmethod def setUpClass(cls): from pymongo import read_preferences as mongo_rp cls.db = mongoengine.connect( db=cls.db_name, host=cls.db_host, port=cls.db_port, # PyMongo>=2.1 requires an explicit read_preference. read_preference=mongo_rp.ReadPreference.PRIMARY, # PyMongo>=2.1 has a 20s timeout, use 100ms instead serverselectiontimeoutms=cls.server_timeout_ms, ) @classmethod def tearDownClass(cls): cls.db.drop_database(cls.db_name) def setUp(self): mongoengine.connect('factory_boy_test') def test_build(self): std = PersonFactory.build() self.assertEqual('name0', std.name) self.assertEqual('street0', std.address.street) self.assertIsNone(std.id) def test_creation(self): std1 = PersonFactory.create() self.assertEqual('name1', std1.name) self.assertEqual('street1', std1.address.street) self.assertIsNotNone(std1.id) factory_boy-2.8.1/tests/test_using.py000066400000000000000000002250521302510513700177450ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (c) 2011-2015 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. """Tests using factory.""" import functools import os import sys import warnings import factory from factory import errors from .compat import is_python2, unittest from . import tools class TestObject(object): def __init__(self, one=None, two=None, three=None, four=None, five=None): self.one = one self.two = two self.three = three self.four = four self.five = five def as_dict(self): return dict( one=self.one, two=self.two, three=self.three, four=self.four, five=self.five, ) class FakeModel(object): @classmethod def create(cls, **kwargs): instance = cls(**kwargs) instance.id = 1 return instance class FakeModelManager(object): def get_or_create(self, **kwargs): defaults = kwargs.pop('defaults', {}) kwargs.update(defaults) instance = FakeModel.create(**kwargs) instance.id = 2 instance._defaults = defaults return instance, True def create(self, **kwargs): instance = FakeModel.create(**kwargs) instance.id = 2 instance._defaults = None return instance def values_list(self, *args, **kwargs): return self def order_by(self, *args, **kwargs): return [1] def using(self, db): return self objects = FakeModelManager() def __init__(self, **kwargs): for name, value in kwargs.items(): setattr(self, name, value) self.id = None class FakeModelFactory(factory.Factory): class Meta: abstract = True @classmethod def _create(cls, model_class, *args, **kwargs): return model_class.create(**kwargs) class TestModel(FakeModel): pass class SimpleBuildTestCase(unittest.TestCase): """Tests the minimalist 'factory.build/create' functions.""" def test_build(self): obj = factory.build(TestObject, two=2) self.assertEqual(obj.one, None) self.assertEqual(obj.two, 2) self.assertEqual(obj.three, None) self.assertEqual(obj.four, None) def test_complex(self): obj = factory.build(TestObject, two=2, three=factory.LazyAttribute(lambda o: o.two + 1)) self.assertEqual(obj.one, None) self.assertEqual(obj.two, 2) self.assertEqual(obj.three, 3) self.assertEqual(obj.four, None) def test_build_batch(self): objs = factory.build_batch(TestObject, 4, two=2, three=factory.LazyAttribute(lambda o: o.two + 1)) self.assertEqual(4, len(objs)) self.assertEqual(4, len(set(objs))) for obj in objs: self.assertEqual(obj.one, None) self.assertEqual(obj.two, 2) self.assertEqual(obj.three, 3) self.assertEqual(obj.four, None) def test_create(self): obj = factory.create(FakeModel, foo='bar') self.assertEqual(obj.id, None) self.assertEqual(obj.foo, 'bar') def test_create_custom_base(self): obj = factory.create(FakeModel, foo='bar', FACTORY_CLASS=factory.django.DjangoModelFactory) self.assertEqual(obj.id, 2) self.assertEqual(obj.foo, 'bar') def test_create_batch(self): objs = factory.create_batch(FakeModel, 4, foo='bar') self.assertEqual(4, len(objs)) self.assertEqual(4, len(set(objs))) for obj in objs: self.assertEqual(obj.id, None) self.assertEqual(obj.foo, 'bar') def test_create_batch_custom_base(self): objs = factory.create_batch(FakeModel, 4, foo='bar', FACTORY_CLASS=factory.django.DjangoModelFactory) self.assertEqual(4, len(objs)) self.assertEqual(4, len(set(objs))) for obj in objs: self.assertEqual(obj.id, 2) self.assertEqual(obj.foo, 'bar') def test_stub(self): obj = factory.stub(TestObject, three=3) self.assertEqual(obj.three, 3) self.assertFalse(hasattr(obj, 'two')) def test_stub_batch(self): objs = factory.stub_batch(FakeModel, 4, foo='bar') self.assertEqual(4, len(objs)) self.assertEqual(4, len(set(objs))) for obj in objs: self.assertFalse(hasattr(obj, 'id')) self.assertEqual(obj.foo, 'bar') def test_generate_build(self): obj = factory.generate(FakeModel, factory.BUILD_STRATEGY, foo='bar') self.assertEqual(obj.id, None) self.assertEqual(obj.foo, 'bar') def test_generate_create(self): obj = factory.generate(FakeModel, factory.CREATE_STRATEGY, foo='bar') self.assertEqual(obj.id, None) self.assertEqual(obj.foo, 'bar') def test_generate_create_custom_base(self): obj = factory.generate(FakeModel, factory.CREATE_STRATEGY, foo='bar', FACTORY_CLASS=factory.django.DjangoModelFactory) self.assertEqual(obj.id, 2) self.assertEqual(obj.foo, 'bar') def test_generate_stub(self): 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(FakeModel, factory.BUILD_STRATEGY, 20, foo='bar') self.assertEqual(20, len(objs)) self.assertEqual(20, len(set(objs))) for obj in objs: self.assertEqual(obj.id, None) self.assertEqual(obj.foo, 'bar') def test_generate_batch_create(self): objs = factory.generate_batch(FakeModel, factory.CREATE_STRATEGY, 20, foo='bar') self.assertEqual(20, len(objs)) self.assertEqual(20, len(set(objs))) for obj in objs: self.assertEqual(obj.id, None) self.assertEqual(obj.foo, 'bar') def test_generate_batch_create_custom_base(self): objs = factory.generate_batch(FakeModel, factory.CREATE_STRATEGY, 20, foo='bar', FACTORY_CLASS=factory.django.DjangoModelFactory) self.assertEqual(20, len(objs)) self.assertEqual(20, len(set(objs))) for obj in objs: self.assertEqual(obj.id, 2) self.assertEqual(obj.foo, 'bar') def test_generate_batch_stub(self): objs = factory.generate_batch(FakeModel, factory.STUB_STRATEGY, 20, foo='bar') self.assertEqual(20, len(objs)) self.assertEqual(20, len(set(objs))) for obj in objs: self.assertFalse(hasattr(obj, 'id')) self.assertEqual(obj.foo, 'bar') def test_simple_generate_build(self): obj = factory.simple_generate(FakeModel, False, foo='bar') self.assertEqual(obj.id, None) self.assertEqual(obj.foo, 'bar') def test_simple_generate_create(self): obj = factory.simple_generate(FakeModel, True, foo='bar') self.assertEqual(obj.id, None) self.assertEqual(obj.foo, 'bar') def test_simple_generate_create_custom_base(self): obj = factory.simple_generate(FakeModel, True, foo='bar', FACTORY_CLASS=factory.django.DjangoModelFactory) self.assertEqual(obj.id, 2) self.assertEqual(obj.foo, 'bar') def test_simple_generate_batch_build(self): objs = factory.simple_generate_batch(FakeModel, False, 20, foo='bar') self.assertEqual(20, len(objs)) self.assertEqual(20, len(set(objs))) for obj in objs: self.assertEqual(obj.id, None) self.assertEqual(obj.foo, 'bar') def test_simple_generate_batch_create(self): objs = factory.simple_generate_batch(FakeModel, True, 20, foo='bar') self.assertEqual(20, len(objs)) self.assertEqual(20, len(set(objs))) for obj in objs: self.assertEqual(obj.id, None) self.assertEqual(obj.foo, 'bar') def test_simple_generate_batch_create_custom_base(self): objs = factory.simple_generate_batch(FakeModel, True, 20, foo='bar', FACTORY_CLASS=factory.django.DjangoModelFactory) self.assertEqual(20, len(objs)) self.assertEqual(20, len(set(objs))) for obj in objs: self.assertEqual(obj.id, 2) self.assertEqual(obj.foo, 'bar') def test_make_factory(self): fact = factory.make_factory(TestObject, two=2, three=factory.LazyAttribute(lambda o: o.two + 1)) obj = fact.build() self.assertEqual(obj.one, None) self.assertEqual(obj.two, 2) self.assertEqual(obj.three, 3) self.assertEqual(obj.four, None) obj = fact.build(two=4) self.assertEqual(obj.one, None) self.assertEqual(obj.two, 4) self.assertEqual(obj.three, 5) self.assertEqual(obj.four, None) def test_build_to_dict(self): # We have a generic factory class TestObjectFactory(factory.Factory): class Meta: model = TestObject one = 'one' two = factory.LazyAttribute(lambda o: o.one * 2) # Now, get a dict out of it obj = factory.build(dict, FACTORY_CLASS=TestObjectFactory) self.assertEqual({'one': 'one', 'two': 'oneone'}, obj) class UsingFactoryTestCase(unittest.TestCase): def test_attribute(self): class TestObjectFactory(factory.Factory): class Meta: model = TestObject one = 'one' test_object = TestObjectFactory.build() self.assertEqual(test_object.one, 'one') def test_inheriting_model_class(self): @factory.use_strategy(factory.BUILD_STRATEGY) class TestObjectFactory(factory.Factory, TestObject): class Meta: model = TestObject one = 'one' test_object = TestObjectFactory() self.assertEqual(test_object.one, 'one') def test_abstract(self): class SomeAbstractFactory(factory.Factory): class Meta: abstract = True one = 'one' class InheritedFactory(SomeAbstractFactory): class Meta: model = TestObject test_object = InheritedFactory.build() self.assertEqual(test_object.one, 'one') def test_sequence(self): class TestObjectFactory(factory.Factory): class Meta: model = TestObject one = factory.Sequence(lambda n: 'one%d' % n) two = factory.Sequence(lambda n: 'two%d' % n) test_object0 = TestObjectFactory.build() self.assertEqual(test_object0.one, 'one0') self.assertEqual(test_object0.two, 'two0') test_object1 = TestObjectFactory.build() self.assertEqual(test_object1.one, 'one1') self.assertEqual(test_object1.two, 'two1') def test_sequence_custom_begin(self): class TestObjectFactory(factory.Factory): class Meta: model = TestObject @classmethod def _setup_next_sequence(cls): return 42 one = factory.Sequence(lambda n: 'one%d' % n) two = factory.Sequence(lambda n: 'two%d' % n) test_object0 = TestObjectFactory.build() self.assertEqual('one42', test_object0.one) self.assertEqual('two42', test_object0.two) test_object1 = TestObjectFactory.build() self.assertEqual('one43', test_object1.one) self.assertEqual('two43', test_object1.two) def test_sequence_override(self): class TestObjectFactory(factory.Factory): class Meta: model = TestObject one = factory.Sequence(lambda n: 'one%d' % n) o1 = TestObjectFactory() o2 = TestObjectFactory() o3 = TestObjectFactory(__sequence=42) o4 = TestObjectFactory() self.assertEqual('one0', o1.one) self.assertEqual('one1', o2.one) self.assertEqual('one42', o3.one) self.assertEqual('one2', o4.one) def test_custom_create(self): class TestModelFactory(factory.Factory): class Meta: model = TestModel two = 2 @classmethod def _create(cls, model_class, *args, **kwargs): obj = model_class.create(**kwargs) obj.properly_created = True return obj obj = TestModelFactory.create(one=1) self.assertEqual(1, obj.one) self.assertEqual(2, obj.two) self.assertEqual(1, obj.id) self.assertTrue(obj.properly_created) def test_non_django_create(self): class NonDjango(object): def __init__(self, x, y=2): self.x = x self.y = y class NonDjangoFactory(factory.Factory): class Meta: model = NonDjango x = 3 obj = NonDjangoFactory.create() self.assertEqual(3, obj.x) self.assertEqual(2, obj.y) def test_sequence_batch(self): class TestObjectFactory(factory.Factory): class Meta: model = TestObject one = factory.Sequence(lambda n: 'one%d' % n) two = factory.Sequence(lambda n: 'two%d' % n) objs = TestObjectFactory.build_batch(20) self.assertEqual(20, len(objs)) self.assertEqual(20, len(set(objs))) for i, obj in enumerate(objs): self.assertEqual('one%d' % i, obj.one) self.assertEqual('two%d' % i, obj.two) def test_lazy_attribute(self): class TestObjectFactory(factory.Factory): class Meta: model = TestObject one = factory.LazyAttribute(lambda a: 'abc' ) two = factory.LazyAttribute(lambda a: a.one + ' xyz') test_object = TestObjectFactory.build() self.assertEqual(test_object.one, 'abc') self.assertEqual(test_object.two, 'abc xyz') def test_lazy_attribute_sequence(self): class TestObjectFactory(factory.Factory): class Meta: model = TestObject one = factory.LazyAttributeSequence(lambda a, n: 'abc%d' % n) two = factory.LazyAttributeSequence(lambda a, n: a.one + ' xyz%d' % n) test_object0 = TestObjectFactory.build() self.assertEqual(test_object0.one, 'abc0') self.assertEqual(test_object0.two, 'abc0 xyz0') test_object1 = TestObjectFactory.build() self.assertEqual(test_object1.one, 'abc1') self.assertEqual(test_object1.two, 'abc1 xyz1') def test_lazy_attribute_decorator(self): class TestObjectFactory(factory.Factory): class Meta: model = TestObject @factory.lazy_attribute def one(a): return 'one' test_object = TestObjectFactory.build() self.assertEqual(test_object.one, 'one') def test_self_attribute(self): class TmpObj(object): n = 3 class TestObjectFactory(factory.Factory): class Meta: model = TestObject one = 'xx' two = factory.SelfAttribute('one') three = TmpObj() four = factory.SelfAttribute('three.n') five = factory.SelfAttribute('three.nnn', 5) test_object = TestObjectFactory.build(one=1) self.assertEqual(1, test_object.two) self.assertEqual(3, test_object.three.n) self.assertEqual(3, test_object.four) self.assertEqual(5, test_object.five) def test_self_attribute_parent(self): class TestModel2(FakeModel): pass class TestModelFactory(FakeModelFactory): class Meta: model = TestModel one = 3 three = factory.SelfAttribute('..bar') class TestModel2Factory(FakeModelFactory): class Meta: model = TestModel2 bar = 4 two = factory.SubFactory(TestModelFactory, one=1) test_model = TestModel2Factory() self.assertEqual(4, test_model.two.three) def test_sequence_decorator(self): class TestObjectFactory(factory.Factory): class Meta: model = TestObject @factory.sequence def one(n): return 'one%d' % n test_object = TestObjectFactory.build() self.assertEqual(test_object.one, 'one0') def test_lazy_attribute_sequence_decorator(self): class TestObjectFactory(factory.Factory): class Meta: model = TestObject @factory.lazy_attribute_sequence def one(a, n): return 'one%d' % n @factory.lazy_attribute_sequence def two(a, n): return a.one + ' two%d' % n test_object = TestObjectFactory.build() self.assertEqual(test_object.one, 'one0') self.assertEqual(test_object.two, 'one0 two0') def test_build_with_parameters(self): class TestObjectFactory(factory.Factory): class Meta: model = TestObject one = factory.Sequence(lambda n: 'one%d' % n) two = factory.Sequence(lambda n: 'two%d' % n) test_object0 = TestObjectFactory.build(three='three') self.assertEqual(test_object0.one, 'one0') self.assertEqual(test_object0.two, 'two0') self.assertEqual(test_object0.three, 'three') test_object1 = TestObjectFactory.build(one='other') self.assertEqual(test_object1.one, 'other') self.assertEqual(test_object1.two, 'two1') def test_create(self): class TestModelFactory(FakeModelFactory): class Meta: model = TestModel one = 'one' test_model = TestModelFactory.create() self.assertEqual(test_model.one, 'one') self.assertTrue(test_model.id) def test_create_batch(self): class TestModelFactory(FakeModelFactory): class Meta: model = TestModel one = 'one' objs = TestModelFactory.create_batch(20, two=factory.Sequence(int)) self.assertEqual(20, len(objs)) self.assertEqual(20, len(set(objs))) for i, obj in enumerate(objs): self.assertEqual('one', obj.one) self.assertEqual(i, obj.two) self.assertTrue(obj.id) def test_generate_build(self): class TestModelFactory(FakeModelFactory): class Meta: model = TestModel one = 'one' test_model = TestModelFactory.generate(factory.BUILD_STRATEGY) self.assertEqual(test_model.one, 'one') self.assertFalse(test_model.id) def test_generate_create(self): class TestModelFactory(FakeModelFactory): class Meta: model = TestModel one = 'one' test_model = TestModelFactory.generate(factory.CREATE_STRATEGY) self.assertEqual(test_model.one, 'one') self.assertTrue(test_model.id) def test_generate_stub(self): class TestModelFactory(FakeModelFactory): class Meta: model = TestModel one = 'one' test_model = TestModelFactory.generate(factory.STUB_STRATEGY) self.assertEqual(test_model.one, 'one') self.assertFalse(hasattr(test_model, 'id')) def test_generate_batch_build(self): class TestModelFactory(FakeModelFactory): class Meta: model = TestModel one = 'one' objs = TestModelFactory.generate_batch(factory.BUILD_STRATEGY, 20, two='two') self.assertEqual(20, len(objs)) self.assertEqual(20, len(set(objs))) for i, obj in enumerate(objs): self.assertEqual('one', obj.one) self.assertEqual('two', obj.two) self.assertFalse(obj.id) def test_generate_batch_create(self): class TestModelFactory(FakeModelFactory): class Meta: model = TestModel one = 'one' objs = TestModelFactory.generate_batch(factory.CREATE_STRATEGY, 20, two='two') self.assertEqual(20, len(objs)) self.assertEqual(20, len(set(objs))) for i, obj in enumerate(objs): self.assertEqual('one', obj.one) self.assertEqual('two', obj.two) self.assertTrue(obj.id) def test_generate_batch_stub(self): class TestModelFactory(FakeModelFactory): class Meta: model = TestModel one = 'one' objs = TestModelFactory.generate_batch(factory.STUB_STRATEGY, 20, two='two') self.assertEqual(20, len(objs)) self.assertEqual(20, len(set(objs))) for i, obj in enumerate(objs): self.assertEqual('one', obj.one) self.assertEqual('two', obj.two) self.assertFalse(hasattr(obj, 'id')) def test_simple_generate_build(self): class TestModelFactory(FakeModelFactory): class Meta: model = TestModel one = 'one' test_model = TestModelFactory.simple_generate(False) self.assertEqual(test_model.one, 'one') self.assertFalse(test_model.id) def test_simple_generate_create(self): class TestModelFactory(FakeModelFactory): class Meta: model = TestModel one = 'one' test_model = TestModelFactory.simple_generate(True) self.assertEqual(test_model.one, 'one') self.assertTrue(test_model.id) def test_simple_generate_batch_build(self): class TestModelFactory(FakeModelFactory): class Meta: model = TestModel one = 'one' objs = TestModelFactory.simple_generate_batch(False, 20, two='two') self.assertEqual(20, len(objs)) self.assertEqual(20, len(set(objs))) for i, obj in enumerate(objs): self.assertEqual('one', obj.one) self.assertEqual('two', obj.two) self.assertFalse(obj.id) def test_simple_generate_batch_create(self): class TestModelFactory(FakeModelFactory): class Meta: model = TestModel one = 'one' objs = TestModelFactory.simple_generate_batch(True, 20, two='two') self.assertEqual(20, len(objs)) self.assertEqual(20, len(set(objs))) for i, obj in enumerate(objs): self.assertEqual('one', obj.one) self.assertEqual('two', obj.two) self.assertTrue(obj.id) def test_stub_batch(self): class TestObjectFactory(factory.Factory): class Meta: model = TestObject one = 'one' two = factory.LazyAttribute(lambda a: a.one + ' two') three = factory.Sequence(lambda n: int(n)) objs = TestObjectFactory.stub_batch(20, one=factory.Sequence(lambda n: str(n))) self.assertEqual(20, len(objs)) self.assertEqual(20, len(set(objs))) for i, obj in enumerate(objs): self.assertEqual(str(i), obj.one) self.assertEqual('%d two' % i, obj.two) self.assertEqual(i, obj.three) def test_inheritance(self): class TestObjectFactory(factory.Factory): class Meta: model = TestObject one = 'one' two = factory.LazyAttribute(lambda a: a.one + ' two') class TestObjectFactory2(TestObjectFactory): class Meta: model = TestObject three = 'three' four = factory.LazyAttribute(lambda a: a.three + ' four') test_object = TestObjectFactory2.build() self.assertEqual(test_object.one, 'one') self.assertEqual(test_object.two, 'one two') self.assertEqual(test_object.three, 'three') self.assertEqual(test_object.four, 'three four') test_object_alt = TestObjectFactory.build() self.assertEqual(None, test_object_alt.three) def test_override_inherited(self): """Overriding inherited declarations""" class TestObjectFactory(factory.Factory): class Meta: model = TestObject one = 'one' class TestObjectFactory2(TestObjectFactory): one = 'two' test_object = TestObjectFactory2.build() self.assertEqual('two', test_object.one) def test_override_inherited_deep(self): """Overriding inherited declarations""" class TestObjectFactory(factory.Factory): class Meta: model = TestObject one = 'one' class TestObjectFactory2(TestObjectFactory): one = 'two' class TestObjectFactory3(TestObjectFactory2): pass test_object = TestObjectFactory3.build() self.assertEqual('two', test_object.one) def test_inheritance_and_sequences(self): """Sequence counters should be kept within an inheritance chain.""" class TestObjectFactory(factory.Factory): class Meta: model = TestObject one = factory.Sequence(lambda n: n) class TestObjectFactory2(TestObjectFactory): class Meta: model = TestObject to1a = TestObjectFactory() self.assertEqual(0, to1a.one) to2a = TestObjectFactory2() self.assertEqual(1, to2a.one) to1b = TestObjectFactory() self.assertEqual(2, to1b.one) to2b = TestObjectFactory2() self.assertEqual(3, to2b.one) def test_inheritance_sequence_inheriting_objects(self): """Sequence counters are kept with inheritance, incl. misc objects.""" class TestObject2(TestObject): pass class TestObjectFactory(factory.Factory): class Meta: model = TestObject one = factory.Sequence(lambda n: n) class TestObjectFactory2(TestObjectFactory): class Meta: model = TestObject2 to1a = TestObjectFactory() self.assertEqual(0, to1a.one) to2a = TestObjectFactory2() self.assertEqual(1, to2a.one) to1b = TestObjectFactory() self.assertEqual(2, to1b.one) to2b = TestObjectFactory2() self.assertEqual(3, to2b.one) def test_inheritance_sequence_unrelated_objects(self): """Sequence counters are kept with inheritance, unrelated objects. See issue https://github.com/FactoryBoy/factory_boy/issues/93 Problem: sequence counter is somewhat shared between factories until the "slave" factory has been called. """ class TestObject2(object): def __init__(self, one): self.one = one class TestObjectFactory(factory.Factory): class Meta: model = TestObject one = factory.Sequence(lambda n: n) class TestObjectFactory2(TestObjectFactory): class Meta: model = TestObject2 to1a = TestObjectFactory() self.assertEqual(0, to1a.one) to2a = TestObjectFactory2() self.assertEqual(0, to2a.one) to1b = TestObjectFactory() self.assertEqual(1, to1b.one) to2b = TestObjectFactory2() self.assertEqual(1, to2b.one) def test_inheritance_with_inherited_class(self): class TestObjectFactory(factory.Factory): class Meta: model = TestObject one = 'one' two = factory.LazyAttribute(lambda a: a.one + ' two') class TestFactory(TestObjectFactory): three = 'three' four = factory.LazyAttribute(lambda a: a.three + ' four') test_object = TestFactory.build() self.assertEqual(test_object.one, 'one') self.assertEqual(test_object.two, 'one two') self.assertEqual(test_object.three, 'three') self.assertEqual(test_object.four, 'three four') def test_dual_inheritance(self): class TestObjectFactory(factory.Factory): class Meta: model = TestObject one = 'one' class TestOtherFactory(factory.Factory): class Meta: model = TestObject two = 'two' four = 'four' class TestFactory(TestObjectFactory, TestOtherFactory): three = 'three' obj = TestFactory.build(two=2) self.assertEqual('one', obj.one) self.assertEqual(2, obj.two) self.assertEqual('three', obj.three) self.assertEqual('four', obj.four) def test_class_method_accessible(self): class TestObjectFactory(factory.Factory): class Meta: model = TestObject @classmethod def alt_create(cls, **kwargs): return kwargs self.assertEqual(TestObjectFactory.alt_create(foo=1), {"foo": 1}) def test_static_method_accessible(self): class TestObjectFactory(factory.Factory): class Meta: model = TestObject @staticmethod def alt_create(**kwargs): return kwargs self.assertEqual(TestObjectFactory.alt_create(foo=1), {"foo": 1}) def test_inline_args(self): class TestObject(object): def __init__(self, *args, **kwargs): self.args = args self.kwargs = kwargs class TestObjectFactory(factory.Factory): class Meta: model = TestObject inline_args = ('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_exclude(self): class TestObject(object): def __init__(self, *args, **kwargs): self.args = args self.kwargs = kwargs class TestObjectFactory(factory.Factory): class Meta: model = TestObject exclude = ('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_exclude_and_inline_args(self): class TestObject(object): def __init__(self, *args, **kwargs): self.args = args self.kwargs = kwargs class TestObjectFactory(factory.Factory): class Meta: model = TestObject exclude = ('x', 'z') inline_args = ('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): class TestObject(object): def __init__(self, *args, **kwargs): self.args = args self.kwargs = kwargs class TestObjectFactory(factory.Factory): class Meta: model = TestObject inline_args = ('one', 'two',) one = 1 two = 2 three = 3 obj = TestObjectFactory.build() self.assertEqual((1, 2), obj.args) self.assertEqual({'three': 3}, obj.kwargs) def test_create(self): class TestObject(object): def __init__(self, *args, **kwargs): self.args = None self.kwargs = None @classmethod def create(cls, *args, **kwargs): inst = cls() inst.args = args inst.kwargs = kwargs return inst class TestObjectFactory(factory.Factory): class Meta: model = TestObject inline_args = ('one', 'two') one = 1 two = 2 three = 3 @classmethod def _create(cls, model_class, *args, **kwargs): return model_class.create(*args, **kwargs) obj = TestObjectFactory.create() self.assertEqual((1, 2), obj.args) self.assertEqual({'three': 3}, obj.kwargs) class KwargAdjustTestCase(unittest.TestCase): """Tests for the _adjust_kwargs method.""" def test_build(self): class TestObject(object): def __init__(self, *args, **kwargs): self.args = args self.kwargs = kwargs class TestObjectFactory(factory.Factory): class Meta: model = TestObject @classmethod def _adjust_kwargs(cls, **kwargs): kwargs['foo'] = len(kwargs) return kwargs obj = TestObjectFactory.build(x=1, y=2, z=3) self.assertEqual({'x': 1, 'y': 2, 'z': 3, 'foo': 3}, obj.kwargs) self.assertEqual((), obj.args) def test_rename(self): class TestObject(object): def __init__(self, attributes=None): self.attributes = attributes class TestObjectFactory(factory.Factory): class Meta: model = TestObject rename = {'attributes_': 'attributes'} attributes_ = 42 obj = TestObjectFactory.build() self.assertEqual(42, obj.attributes) class TraitTestCase(unittest.TestCase): def test_traits(self): class TestObjectFactory(factory.Factory): class Meta: model = TestObject class Params: even = factory.Trait(two=True, four=True) odd = factory.Trait(one=True, three=True, five=True) obj1 = TestObjectFactory() self.assertEqual(obj1.as_dict(), dict(one=None, two=None, three=None, four=None, five=None)) obj2 = TestObjectFactory(even=True) self.assertEqual(obj2.as_dict(), dict(one=None, two=True, three=None, four=True, five=None)) obj3 = TestObjectFactory(odd=True) self.assertEqual(obj3.as_dict(), dict(one=True, two=None, three=True, four=None, five=True)) obj4 = TestObjectFactory(even=True, odd=True) self.assertEqual(obj4.as_dict(), dict(one=True, two=True, three=True, four=True, five=True)) obj5 = TestObjectFactory(odd=True, two=True) self.assertEqual(obj5.as_dict(), dict(one=True, two=True, three=True, four=None, five=True)) def test_traits_inheritance(self): """A trait can be set in an inherited class.""" class TestObjectFactory(factory.Factory): class Meta: model = TestObject class Params: even = factory.Trait(two=True, four=True) odd = factory.Trait(one=True, three=True, five=True) class EvenObjectFactory(TestObjectFactory): even = True # Simple call obj1 = EvenObjectFactory() self.assertEqual(obj1.as_dict(), dict(one=None, two=True, three=None, four=True, five=None)) # Force-disable it obj2 = EvenObjectFactory(even=False) self.assertEqual(obj2.as_dict(), dict(one=None, two=None, three=None, four=None, five=None)) def test_traits_override(self): """Override a trait in a subclass.""" class TestObjectFactory(factory.Factory): class Meta: model = TestObject class Params: even = factory.Trait(two=True, four=True) odd = factory.Trait(one=True, three=True, five=True) class WeirdMathFactory(TestObjectFactory): class Params: # Here, one is even. even = factory.Trait(two=True, four=True, one=True) obj = WeirdMathFactory(even=True) self.assertEqual(obj.as_dict(), dict(one=True, two=True, three=None, four=True, five=None)) def test_traits_chaining(self): """Use a trait to enable other traits.""" class TestObjectFactory(factory.Factory): class Meta: model = TestObject class Params: even = factory.Trait(two=True, four=True) odd = factory.Trait(one=True, three=True, five=True) full = factory.Trait(even=True, odd=True) # Setting "full" should enable all fields. obj = TestObjectFactory(full=True) self.assertEqual(obj.as_dict(), dict(one=True, two=True, three=True, four=True, five=True)) # Does it break usual patterns? obj1 = TestObjectFactory() self.assertEqual(obj1.as_dict(), dict(one=None, two=None, three=None, four=None, five=None)) obj2 = TestObjectFactory(even=True) self.assertEqual(obj2.as_dict(), dict(one=None, two=True, three=None, four=True, five=None)) obj3 = TestObjectFactory(odd=True) self.assertEqual(obj3.as_dict(), dict(one=True, two=None, three=True, four=None, five=True)) def test_prevent_cyclic_traits(self): with self.assertRaises(errors.CyclicDefinitionError): class TestObjectFactory(factory.Factory): class Meta: model = TestObject class Params: a = factory.Trait(b=True, one=True) b = factory.Trait(a=True, two=True) class SubFactoryTestCase(unittest.TestCase): def test_sub_factory(self): class TestModel2(FakeModel): pass class TestModelFactory(FakeModelFactory): class Meta: model = TestModel one = 3 class TestModel2Factory(FakeModelFactory): class Meta: model = TestModel2 two = factory.SubFactory(TestModelFactory, one=1) test_model = TestModel2Factory(two__one=4) self.assertEqual(4, test_model.two.one) self.assertEqual(1, test_model.id) self.assertEqual(1, test_model.two.id) def test_sub_factory_with_lazy_fields(self): class TestModel2(FakeModel): pass class TestModelFactory(FakeModelFactory): class Meta: model = TestModel class TestModel2Factory(FakeModelFactory): class Meta: model = TestModel2 two = factory.SubFactory(TestModelFactory, one=factory.Sequence(lambda n: 'x%dx' % n), two=factory.LazyAttribute(lambda o: '%s%s' % (o.one, o.one)), ) test_model = TestModel2Factory(one=42) self.assertEqual('x0x', test_model.two.one) self.assertEqual('x0xx0x', test_model.two.two) def test_sub_factory_with_lazy_fields_access_factory_parent(self): class TestModel2(FakeModel): pass class TestModelFactory(FakeModelFactory): class Meta: model = TestModel one = 3 class TestModel2Factory(FakeModelFactory): class Meta: model = TestModel2 one = 'parent' child = factory.SubFactory(TestModelFactory, one=factory.LazyAttribute(lambda o: '%s child' % o.factory_parent.one), ) test_model = TestModel2Factory() self.assertEqual('parent child', test_model.child.one) def test_sub_factory_and_sequence(self): class TestObject(object): def __init__(self, **kwargs): for k, v in kwargs.items(): setattr(self, k, v) class TestObjectFactory(factory.Factory): class Meta: model = TestObject one = factory.Sequence(lambda n: int(n)) class WrappingTestObjectFactory(factory.Factory): class Meta: model = TestObject wrapped = factory.SubFactory(TestObjectFactory) wrapping = WrappingTestObjectFactory.build() self.assertEqual(0, wrapping.wrapped.one) wrapping = WrappingTestObjectFactory.build() self.assertEqual(1, wrapping.wrapped.one) def test_sub_factory_overriding(self): class TestObject(object): def __init__(self, **kwargs): for k, v in kwargs.items(): setattr(self, k, v) class TestObjectFactory(factory.Factory): class Meta: model = TestObject class OtherTestObject(object): def __init__(self, **kwargs): for k, v in kwargs.items(): setattr(self, k, v) class WrappingTestObjectFactory(factory.Factory): class Meta: model = OtherTestObject wrapped = factory.SubFactory(TestObjectFactory, two=2, four=4) wrapped__two = 4 wrapped__three = 3 wrapping = WrappingTestObjectFactory.build() self.assertEqual(wrapping.wrapped.two, 4) self.assertEqual(wrapping.wrapped.three, 3) self.assertEqual(wrapping.wrapped.four, 4) def test_nested_sub_factory(self): """Test nested sub-factories.""" class TestObject(object): def __init__(self, **kwargs): for k, v in kwargs.items(): setattr(self, k, v) class TestObjectFactory(factory.Factory): class Meta: model = TestObject class WrappingTestObjectFactory(factory.Factory): class Meta: model = TestObject wrapped = factory.SubFactory(TestObjectFactory) wrapped_bis = factory.SubFactory(TestObjectFactory, one=1) class OuterWrappingTestObjectFactory(factory.Factory): class Meta: model = TestObject wrap = factory.SubFactory(WrappingTestObjectFactory, wrapped__two=2) outer = OuterWrappingTestObjectFactory.build() self.assertEqual(outer.wrap.wrapped.two, 2) self.assertEqual(outer.wrap.wrapped_bis.one, 1) def test_nested_sub_factory_with_overridden_sub_factories(self): """Test nested sub-factories, with attributes overridden with subfactories.""" class TestObject(object): def __init__(self, **kwargs): for k, v in kwargs.items(): setattr(self, k, v) class TestObjectFactory(factory.Factory): class Meta: model = TestObject two = 'two' class WrappingTestObjectFactory(factory.Factory): class Meta: model = TestObject wrapped = factory.SubFactory(TestObjectFactory) friend = factory.LazyAttribute(lambda o: o.wrapped.two.four + 1) class OuterWrappingTestObjectFactory(factory.Factory): class Meta: model = TestObject wrap = factory.SubFactory(WrappingTestObjectFactory, wrapped__two=factory.SubFactory(TestObjectFactory, four=4)) outer = OuterWrappingTestObjectFactory.build() self.assertEqual(outer.wrap.wrapped.two.four, 4) self.assertEqual(outer.wrap.friend, 5) def test_nested_subfactory_with_override(self): """Tests replacing a SubFactory field with an actual value.""" # The test class class TestObject(object): def __init__(self, two='one', wrapped=None): self.two = two self.wrapped = wrapped # Innermost factory class TestObjectFactory(factory.Factory): class Meta: model = TestObject two = 'two' # Intermediary factory class WrappingTestObjectFactory(factory.Factory): class Meta: model = TestObject wrapped = factory.SubFactory(TestObjectFactory) wrapped__two = 'three' obj = TestObject(two='four') outer = WrappingTestObjectFactory(wrapped=obj) self.assertEqual(obj, outer.wrapped) self.assertEqual('four', outer.wrapped.two) def test_sub_factory_and_inheritance(self): """Test inheriting from a factory with subfactories, overriding.""" class TestObject(object): def __init__(self, **kwargs): for k, v in kwargs.items(): setattr(self, k, v) class TestObjectFactory(factory.Factory): class Meta: model = TestObject two = 'two' class WrappingTestObjectFactory(factory.Factory): class Meta: model = TestObject wrapped = factory.SubFactory(TestObjectFactory) friend = factory.LazyAttribute(lambda o: o.wrapped.two + 1) class ExtendedWrappingTestObjectFactory(WrappingTestObjectFactory): wrapped__two = 4 wrapping = ExtendedWrappingTestObjectFactory.build() self.assertEqual(wrapping.wrapped.two, 4) self.assertEqual(wrapping.friend, 5) def test_diamond_sub_factory(self): """Tests the case where an object has two fields with a common field.""" class InnerMost(object): def __init__(self, a, b): self.a = a self.b = b class SideA(object): def __init__(self, inner_from_a): self.inner_from_a = inner_from_a class SideB(object): def __init__(self, inner_from_b): self.inner_from_b = inner_from_b class OuterMost(object): def __init__(self, foo, side_a, side_b): self.foo = foo self.side_a = side_a self.side_b = side_b class InnerMostFactory(factory.Factory): class Meta: model = InnerMost a = 15 b = 20 class SideAFactory(factory.Factory): class Meta: model = SideA inner_from_a = factory.SubFactory(InnerMostFactory, a=20) class SideBFactory(factory.Factory): class Meta: model = SideB inner_from_b = factory.SubFactory(InnerMostFactory, b=15) class OuterMostFactory(factory.Factory): class Meta: model = OuterMost foo = 30 side_a = factory.SubFactory(SideAFactory, inner_from_a__a=factory.ContainerAttribute(lambda obj, containers: containers[1].foo * 2)) side_b = factory.SubFactory(SideBFactory, inner_from_b=factory.ContainerAttribute(lambda obj, containers: containers[0].side_a.inner_from_a)) outer = OuterMostFactory.build() self.assertEqual(outer.foo, 30) self.assertEqual(outer.side_a.inner_from_a, outer.side_b.inner_from_b) self.assertEqual(outer.side_a.inner_from_a.a, outer.foo * 2) self.assertEqual(outer.side_a.inner_from_a.b, 20) outer = OuterMostFactory.build(side_a__inner_from_a__b = 4) self.assertEqual(outer.foo, 30) self.assertEqual(outer.side_a.inner_from_a, outer.side_b.inner_from_b) 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): class Meta: model = TestModel one = 3 two = factory.ContainerAttribute(lambda obj, containers: len(containers or []), strict=False) class TestModel2Factory(FakeModelFactory): class Meta: model = 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): class Meta: model = TestModel one = 3 two = factory.ContainerAttribute(lambda obj, containers: len(containers or []), strict=True) class TestModel2Factory(FakeModelFactory): class Meta: model = 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): class Meta: model = TestModel one = 3 @factory.container_attribute def two(self, containers): if containers: return len(containers) return 42 class TestModel2Factory(FakeModelFactory): class Meta: model = 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): def test_iterator(self): class TestObjectFactory(factory.Factory): class Meta: model = TestObject one = factory.Iterator(range(10, 30)) objs = TestObjectFactory.build_batch(20) for i, obj in enumerate(objs): self.assertEqual(i + 10, obj.one) @unittest.skipUnless(is_python2, "Scope bleeding fixed in Python3+") @tools.disable_warnings def test_iterator_list_comprehension_scope_bleeding(self): class TestObjectFactory(factory.Factory): class Meta: model = TestObject one = factory.Iterator([j * 3 for j in range(5)]) # Scope bleeding: j will end up in TestObjectFactory's scope. self.assertRaises(TypeError, TestObjectFactory.build) @tools.disable_warnings def test_iterator_list_comprehension_protected(self): class TestObjectFactory(factory.Factory): class Meta: model = TestObject one = factory.Iterator([_j * 3 for _j in range(5)]) # Scope bleeding : _j will end up in TestObjectFactory's scope. # But factory_boy ignores it, as a protected variable. objs = TestObjectFactory.build_batch(20) for i, obj in enumerate(objs): self.assertEqual(3 * (i % 5), obj.one) def test_iterator_decorator(self): class TestObjectFactory(factory.Factory): class Meta: model = TestObject @factory.iterator def one(): for i in range(10, 50): # pragma: no cover yield i objs = TestObjectFactory.build_batch(20) for i, obj in enumerate(objs): self.assertEqual(i + 10, obj.one) def test_iterator_late_loading(self): """Ensure that Iterator doesn't unroll on class creation. This allows, for Django objects, to call: foo = factory.Iterator(models.MyThingy.objects.all()) """ class DBRequest(object): def __init__(self): self.ready = False def __iter__(self): if not self.ready: raise ValueError("Not ready!!") return iter([1, 2, 3]) # calling __iter__() should crash req1 = DBRequest() with self.assertRaises(ValueError): iter(req1) req2 = DBRequest() class TestObjectFactory(factory.Factory): class Meta: model = TestObject one = factory.Iterator(req2) req2.ready = True obj = TestObjectFactory() self.assertEqual(1, obj.one) class BetterFakeModelManager(object): def __init__(self, keys, instance): self.keys = keys self.instance = instance def get_or_create(self, **kwargs): defaults = kwargs.pop('defaults', {}) if kwargs == self.keys: return self.instance, False kwargs.update(defaults) instance = FakeModel.create(**kwargs) instance.id = 2 return instance, True def using(self, db): return self class BetterFakeModel(object): @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 DjangoModelFactoryTestCase(unittest.TestCase): def test_simple(self): class FakeModelFactory(factory.django.DjangoModelFactory): class Meta: model = FakeModel obj = FakeModelFactory(one=1) self.assertEqual(1, obj.one) self.assertEqual(2, obj.id) def test_existing_instance(self): prev = BetterFakeModel.create(x=1, y=2, z=3) prev.id = 42 class MyFakeModel(BetterFakeModel): objects = BetterFakeModelManager({'x': 1}, prev) class MyFakeModelFactory(factory.django.DjangoModelFactory): class Meta: model = MyFakeModel django_get_or_create = ('x',) x = 1 y = 4 z = 6 obj = MyFakeModelFactory() self.assertEqual(prev, obj) self.assertEqual(1, obj.x) self.assertEqual(2, obj.y) self.assertEqual(3, obj.z) self.assertEqual(42, obj.id) def test_existing_instance_complex_key(self): prev = BetterFakeModel.create(x=1, y=2, z=3) prev.id = 42 class MyFakeModel(BetterFakeModel): objects = BetterFakeModelManager({'x': 1, 'y': 2, 'z': 3}, prev) class MyFakeModelFactory(factory.django.DjangoModelFactory): class Meta: model = MyFakeModel django_get_or_create = ('x', 'y', 'z') x = 1 y = 4 z = 6 obj = MyFakeModelFactory(y=2, z=3) self.assertEqual(prev, obj) self.assertEqual(1, obj.x) self.assertEqual(2, obj.y) self.assertEqual(3, obj.z) self.assertEqual(42, obj.id) def test_new_instance(self): prev = BetterFakeModel.create(x=1, y=2, z=3) prev.id = 42 class MyFakeModel(BetterFakeModel): objects = BetterFakeModelManager({'x': 1}, prev) class MyFakeModelFactory(factory.django.DjangoModelFactory): class Meta: model = MyFakeModel django_get_or_create = ('x',) x = 1 y = 4 z = 6 obj = MyFakeModelFactory(x=2) self.assertNotEqual(prev, obj) self.assertEqual(2, obj.x) self.assertEqual(4, obj.y) self.assertEqual(6, obj.z) self.assertEqual(2, obj.id) def test_new_instance_complex_key(self): prev = BetterFakeModel.create(x=1, y=2, z=3) prev.id = 42 class MyFakeModel(BetterFakeModel): objects = BetterFakeModelManager({'x': 1, 'y': 2, 'z': 3}, prev) class MyFakeModelFactory(factory.django.DjangoModelFactory): class Meta: model = MyFakeModel django_get_or_create = ('x', 'y', 'z') x = 1 y = 4 z = 6 obj = MyFakeModelFactory(y=2, z=4) self.assertNotEqual(prev, obj) self.assertEqual(1, obj.x) self.assertEqual(2, obj.y) self.assertEqual(4, obj.z) self.assertEqual(2, obj.id) def test_sequence(self): class TestModelFactory(factory.django.DjangoModelFactory): class Meta: model = TestModel a = factory.Sequence(lambda n: 'foo_%s' % n) o1 = TestModelFactory() o2 = TestModelFactory() self.assertEqual('foo_0', o1.a) self.assertEqual('foo_1', o2.a) o3 = TestModelFactory.build() o4 = TestModelFactory.build() self.assertEqual('foo_2', o3.a) self.assertEqual('foo_3', o4.a) def test_no_get_or_create(self): class TestModelFactory(factory.django.DjangoModelFactory): class Meta: model = TestModel a = factory.Sequence(lambda n: 'foo_%s' % n) o = TestModelFactory() self.assertEqual(None, o._defaults) self.assertEqual('foo_0', o.a) self.assertEqual(2, o.id) def test_get_or_create(self): class TestModelFactory(factory.django.DjangoModelFactory): class Meta: model = TestModel django_get_or_create = ('a', 'b') a = factory.Sequence(lambda n: 'foo_%s' % n) b = 2 c = 3 d = 4 o = TestModelFactory() self.assertEqual({'c': 3, 'd': 4}, o._defaults) self.assertEqual('foo_0', o.a) self.assertEqual(2, o.b) self.assertEqual(3, o.c) self.assertEqual(4, o.d) self.assertEqual(2, o.id) def test_full_get_or_create(self): """Test a DjangoModelFactory with all fields in get_or_create.""" class TestModelFactory(factory.django.DjangoModelFactory): class Meta: model = TestModel django_get_or_create = ('a', 'b', 'c', 'd') a = factory.Sequence(lambda n: 'foo_%s' % n) b = 2 c = 3 d = 4 o = TestModelFactory() self.assertEqual({}, o._defaults) self.assertEqual('foo_0', o.a) self.assertEqual(2, o.b) self.assertEqual(3, o.c) self.assertEqual(4, o.d) self.assertEqual(2, o.id) class PostGenerationTestCase(unittest.TestCase): def test_post_generation(self): class TestObjectFactory(factory.Factory): class Meta: model = TestObject one = 1 @factory.post_generation def incr_one(self, _create, _increment): self.one += 1 obj = TestObjectFactory.build() self.assertEqual(2, obj.one) self.assertFalse(hasattr(obj, 'incr_one')) obj = TestObjectFactory.build(one=2) self.assertEqual(3, obj.one) self.assertFalse(hasattr(obj, 'incr_one')) def test_post_generation_hook(self): class TestObjectFactory(factory.Factory): class Meta: model = TestObject one = 1 @factory.post_generation def incr_one(self, _create, _increment): self.one += 1 return 42 @classmethod def _after_postgeneration(cls, obj, create, results): obj.create = create obj.results = results obj = TestObjectFactory.build() self.assertEqual(2, obj.one) self.assertFalse(obj.create) self.assertEqual({'incr_one': 42}, obj.results) def test_post_generation_extraction(self): class TestObjectFactory(factory.Factory): class Meta: model = TestObject one = 1 @factory.post_generation def incr_one(self, _create, increment=1): self.one += increment obj = TestObjectFactory.build(incr_one=2) self.assertEqual(3, obj.one) self.assertFalse(hasattr(obj, 'incr_one')) obj = TestObjectFactory.build(one=2, incr_one=2) self.assertEqual(4, obj.one) self.assertFalse(hasattr(obj, 'incr_one')) def test_post_generation_extraction_lambda(self): def my_lambda(obj, create, extracted, **kwargs): self.assertTrue(isinstance(obj, TestObject)) self.assertFalse(create) self.assertEqual(extracted, 42) self.assertEqual(kwargs, {'foo': 13}) class TestObjectFactory(factory.Factory): class Meta: model = 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): class Meta: model = 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): 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') obj = TestObjectFactory.build() # Normal fields self.assertEqual(3, obj.one) self.assertEqual(2, obj.two) # RelatedFactory was built self.assertIsNone(obj.three) self.assertIsNotNone(obj.related) self.assertEqual(1, obj.related.one) self.assertEqual(2, obj.related.two) # RelatedFactory was passed "parent" object self.assertEqual(obj, obj.related.three) obj = TestObjectFactory.build(three__one=3) # Normal fields self.assertEqual(3, obj.one) self.assertEqual(2, obj.two) # RelatedFactory was build self.assertIsNone(obj.three) self.assertIsNotNone(obj.related) # three__one was correctly parse self.assertEqual(3, obj.related.one) self.assertEqual(4, obj.related.two) # RelatedFactory received "parent" object self.assertEqual(obj, obj.related.three) def test_related_factory_no_name(self): relateds = [] class TestRelatedObject(object): def __init__(self, obj=None, one=None, two=None): relateds.append(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 = TestObjectFactory.build() # Normal fields self.assertEqual(3, obj.one) self.assertEqual(2, obj.two) # RelatedFactory was built self.assertIsNone(obj.three) self.assertEqual(1, len(relateds)) related = relateds[0] self.assertEqual(1, related.one) self.assertEqual(2, related.two) self.assertIsNone(related.three) obj = TestObjectFactory.build(three__one=3) # Normal fields self.assertEqual(3, obj.one) self.assertEqual(2, obj.two) # RelatedFactory was build self.assertIsNone(obj.three) self.assertEqual(2, len(relateds)) related = relateds[1] 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) def test_parameterized_related_factory(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 class Params: blah = 1 one = 3 two = 2 three = factory.RelatedFactory(TestRelatedObjectFactory, 'obj') three__two = factory.SelfAttribute('blah') obj = TestObjectFactory.build() self.assertEqual(3, obj.one) self.assertEqual(2, obj.two) self.assertEqual(1, obj.related.one) self.assertEqual(1, obj.related.two) obj2 = TestObjectFactory.build(blah='blah') self.assertEqual('blah', obj2.related.two) class RelatedFactoryExtractionTestCase(unittest.TestCase): def setUp(self): self.relateds = [] class TestRelatedObject(object): def __init__(subself, obj): self.relateds.append(subself) subself.obj = obj obj.related = subself class TestRelatedObjectFactory(factory.Factory): class Meta: model = TestRelatedObject class TestObjectFactory(factory.Factory): class Meta: model = TestObject one = factory.RelatedFactory(TestRelatedObjectFactory, 'obj') self.TestRelatedObject = TestRelatedObject self.TestRelatedObjectFactory = TestRelatedObjectFactory self.TestObjectFactory = TestObjectFactory def test_no_extraction(self): o = self.TestObjectFactory() self.assertEqual(1, len(self.relateds)) rel = self.relateds[0] self.assertEqual(o, rel.obj) self.assertEqual(rel, o.related) def test_passed_value(self): o = self.TestObjectFactory(one=42) self.assertEqual([], self.relateds) self.assertFalse(hasattr(o, 'related')) def test_passed_none(self): o = self.TestObjectFactory(one=None) self.assertEqual([], self.relateds) self.assertFalse(hasattr(o, 'related')) class CircularTestCase(unittest.TestCase): def test_example(self): sys.path.insert(0, os.path.abspath(os.path.dirname(__file__))) from .cyclic import foo f = foo.FooFactory.build(bar__foo=None) self.assertEqual(42, f.x) self.assertEqual(13, f.bar.y) self.assertIsNone(f.bar.foo) from .cyclic import bar b = bar.BarFactory.build(foo__bar__foo__bar=None) self.assertEqual(13, b.y) self.assertEqual(42, b.foo.x) self.assertEqual(13, b.foo.bar.y) self.assertEqual(42, b.foo.bar.foo.x) self.assertIsNone(b.foo.bar.foo.bar) class SelfReferentialTests(unittest.TestCase): def test_no_parent(self): from .cyclic import self_ref obj = self_ref.TreeElementFactory(parent=None) self.assertIsNone(obj.parent) def test_deep(self): from .cyclic import self_ref obj = self_ref.TreeElementFactory(parent__parent__parent__parent=None) self.assertIsNotNone(obj.parent) self.assertIsNotNone(obj.parent.parent) self.assertIsNotNone(obj.parent.parent.parent) self.assertIsNone(obj.parent.parent.parent.parent) class DictTestCase(unittest.TestCase): def test_empty_dict(self): class TestObjectFactory(factory.Factory): class Meta: model = TestObject one = factory.Dict({}) o = TestObjectFactory() self.assertEqual({}, o.one) def test_naive_dict(self): class TestObjectFactory(factory.Factory): class Meta: model = TestObject one = factory.Dict({'a': 1}) o = TestObjectFactory() self.assertEqual({'a': 1}, o.one) def test_sequence_dict(self): class TestObjectFactory(factory.Factory): class Meta: model = TestObject one = factory.Dict({'a': factory.Sequence(lambda n: n + 2)}) o1 = TestObjectFactory() o2 = TestObjectFactory() self.assertEqual({'a': 2}, o1.one) self.assertEqual({'a': 3}, o2.one) def test_dict_override(self): class TestObjectFactory(factory.Factory): class Meta: model = TestObject one = factory.Dict({'a': 1}) o = TestObjectFactory(one__a=2) self.assertEqual({'a': 2}, o.one) def test_dict_extra_key(self): class TestObjectFactory(factory.Factory): class Meta: model = TestObject one = factory.Dict({'a': 1}) o = TestObjectFactory(one__b=2) self.assertEqual({'a': 1, 'b': 2}, o.one) def test_dict_merged_fields(self): class TestObjectFactory(factory.Factory): class Meta: model = TestObject two = 13 one = factory.Dict({ 'one': 1, 'two': 2, 'three': factory.SelfAttribute('two'), }) o = TestObjectFactory(one__one=42) self.assertEqual({'one': 42, 'two': 2, 'three': 2}, o.one) def test_nested_dicts(self): class TestObjectFactory(factory.Factory): class Meta: model = TestObject one = 1 two = factory.Dict({ 'one': 3, 'two': factory.SelfAttribute('one'), 'three': factory.Dict({ 'one': 5, 'two': factory.SelfAttribute('..one'), 'three': factory.SelfAttribute('...one'), }), }) o = TestObjectFactory() self.assertEqual(1, o.one) self.assertEqual({ 'one': 3, 'two': 3, 'three': { 'one': 5, 'two': 3, 'three': 1, }, }, o.two) class ListTestCase(unittest.TestCase): def test_empty_list(self): class TestObjectFactory(factory.Factory): class Meta: model = TestObject one = factory.List([]) o = TestObjectFactory() self.assertEqual([], o.one) def test_naive_list(self): class TestObjectFactory(factory.Factory): class Meta: model = TestObject one = factory.List([1]) o = TestObjectFactory() self.assertEqual([1], o.one) def test_sequence_list(self): class TestObjectFactory(factory.Factory): class Meta: model = TestObject one = factory.List([factory.Sequence(lambda n: n + 2)]) o1 = TestObjectFactory() o2 = TestObjectFactory() self.assertEqual([2], o1.one) self.assertEqual([3], o2.one) def test_list_override(self): class TestObjectFactory(factory.Factory): class Meta: model = TestObject one = factory.List([1]) o = TestObjectFactory(one__0=2) self.assertEqual([2], o.one) def test_list_extra_key(self): class TestObjectFactory(factory.Factory): class Meta: model = TestObject one = factory.List([1]) o = TestObjectFactory(one__1=2) self.assertEqual([1, 2], o.one) def test_list_merged_fields(self): class TestObjectFactory(factory.Factory): class Meta: model = TestObject two = 13 one = factory.List([ 1, 2, factory.SelfAttribute('1'), ]) o = TestObjectFactory(one__0=42) self.assertEqual([42, 2, 2], o.one) def test_nested_lists(self): class TestObjectFactory(factory.Factory): class Meta: model = TestObject one = 1 two = factory.List([ 3, factory.SelfAttribute('0'), factory.List([ 5, factory.SelfAttribute('..0'), factory.SelfAttribute('...one'), ]), ]) o = TestObjectFactory() self.assertEqual(1, o.one) self.assertEqual([ 3, 3, [ 5, 3, 1, ], ], o.two) if __name__ == '__main__': # pragma: no cover unittest.main() factory_boy-2.8.1/tests/test_utils.py000066400000000000000000000337611302510513700177640ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (c) 2010 Mark Sandstrom # Copyright (c) 2011-2015 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. from __future__ import unicode_literals import itertools from factory import utils from .compat import is_python2, unittest class ExtractDictTestCase(unittest.TestCase): def test_empty_dict(self): self.assertEqual({}, utils.extract_dict('foo', {})) def test_unused_key(self): self.assertEqual({}, utils.extract_dict('foo', {'bar__baz': 42})) def test_empty_key(self): self.assertEqual({}, utils.extract_dict('', {'foo': 13, 'bar__baz': 42})) d = {'foo': 13, 'bar__baz': 42, '__foo': 1} self.assertEqual({'foo': 1}, utils.extract_dict('', d)) self.assertNotIn('__foo', d) def test_one_key(self): d = {'foo': 13, 'foo__baz': 42, '__foo': 1} self.assertEqual({'baz': 42}, utils.extract_dict('foo', d, pop=False)) self.assertEqual(42, d['foo__baz']) self.assertEqual({'baz': 42}, utils.extract_dict('foo', d, pop=True)) self.assertNotIn('foo__baz', d) def test_one_key_excluded(self): d = {'foo': 13, 'foo__baz': 42, '__foo': 1} self.assertEqual({}, utils.extract_dict('foo', d, pop=False, exclude=('foo__baz',))) self.assertEqual(42, d['foo__baz']) self.assertEqual({}, utils.extract_dict('foo', d, pop=True, exclude=('foo__baz',))) self.assertIn('foo__baz', d) def test_many_keys(self): d = {'foo': 13, 'foo__baz': 42, 'foo__foo__bar': 2, 'foo__bar': 3, '__foo': 1} self.assertEqual({'foo__bar': 2, 'bar': 3, 'baz': 42}, utils.extract_dict('foo', d, pop=False)) self.assertEqual(42, d['foo__baz']) self.assertEqual(3, d['foo__bar']) self.assertEqual(2, d['foo__foo__bar']) self.assertEqual({'foo__bar': 2, 'bar': 3, 'baz': 42}, utils.extract_dict('foo', d, pop=True)) self.assertNotIn('foo__baz', d) self.assertNotIn('foo__bar', d) self.assertNotIn('foo__foo__bar', d) def test_many_keys_excluded(self): d = {'foo': 13, 'foo__baz': 42, 'foo__foo__bar': 2, 'foo__bar': 3, '__foo': 1} self.assertEqual({'foo__bar': 2, 'baz': 42}, utils.extract_dict('foo', d, pop=False, exclude=('foo__bar', 'bar'))) self.assertEqual(42, d['foo__baz']) self.assertEqual(3, d['foo__bar']) self.assertEqual(2, d['foo__foo__bar']) self.assertEqual({'foo__bar': 2, 'baz': 42}, utils.extract_dict('foo', d, pop=True, exclude=('foo__bar', 'bar'))) self.assertNotIn('foo__baz', d) 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'], {})) def test_unused_key(self): self.assertEqual({'foo': {}}, utils.multi_extract_dict(['foo'], {'bar__baz': 42})) self.assertEqual({'foo': {}, 'baz': {}}, utils.multi_extract_dict(['foo', 'baz'], {'bar__baz': 42})) def test_no_key(self): self.assertEqual({}, utils.multi_extract_dict([], {'bar__baz': 42})) def test_empty_key(self): self.assertEqual({'': {}}, utils.multi_extract_dict([''], {'foo': 13, 'bar__baz': 42})) d = {'foo': 13, 'bar__baz': 42, '__foo': 1} self.assertEqual({'': {'foo': 1}}, utils.multi_extract_dict([''], d)) self.assertNotIn('__foo', d) def test_one_extracted(self): d = {'foo': 13, 'foo__baz': 42, '__foo': 1} self.assertEqual({'foo': {'baz': 42}}, utils.multi_extract_dict(['foo'], d, pop=False)) self.assertEqual(42, d['foo__baz']) self.assertEqual({'foo': {'baz': 42}}, utils.multi_extract_dict(['foo'], d, pop=True)) self.assertNotIn('foo__baz', d) def test_many_extracted(self): d = {'foo': 13, 'foo__baz': 42, 'foo__foo__bar': 2, 'foo__bar': 3, '__foo': 1} self.assertEqual({'foo': {'foo__bar': 2, 'bar': 3, 'baz': 42}}, utils.multi_extract_dict(['foo'], d, pop=False)) self.assertEqual(42, d['foo__baz']) self.assertEqual(3, d['foo__bar']) self.assertEqual(2, d['foo__foo__bar']) self.assertEqual({'foo': {'foo__bar': 2, 'bar': 3, 'baz': 42}}, utils.multi_extract_dict(['foo'], d, pop=True)) self.assertNotIn('foo__baz', d) self.assertNotIn('foo__bar', d) self.assertNotIn('foo__foo__bar', d) def test_many_keys_one_extracted(self): d = {'foo': 13, 'foo__baz': 42, '__foo': 1} self.assertEqual({'foo': {'baz': 42}, 'baz': {}}, utils.multi_extract_dict(['foo', 'baz'], d, pop=False)) self.assertEqual(42, d['foo__baz']) self.assertEqual({'foo': {'baz': 42}, 'baz': {}}, utils.multi_extract_dict(['foo', 'baz'], d, pop=True)) self.assertNotIn('foo__baz', d) def test_many_keys_many_extracted(self): d = { 'foo': 13, 'foo__baz': 42, 'foo__foo__bar': 2, 'foo__bar': 3, 'bar__foo': 1, 'bar__bar__baz': 4, } self.assertEqual( { 'foo': {'foo__bar': 2, 'bar': 3, 'baz': 42}, 'bar': {'foo': 1, 'bar__baz': 4}, 'baz': {} }, utils.multi_extract_dict(['foo', 'bar', 'baz'], d, pop=False)) self.assertEqual(42, d['foo__baz']) self.assertEqual(3, d['foo__bar']) self.assertEqual(2, d['foo__foo__bar']) self.assertEqual(1, d['bar__foo']) self.assertEqual(4, d['bar__bar__baz']) self.assertEqual( { 'foo': {'foo__bar': 2, 'bar': 3, 'baz': 42}, 'bar': {'foo': 1, 'bar__baz': 4}, 'baz': {} }, utils.multi_extract_dict(['foo', 'bar', 'baz'], d, pop=True)) self.assertNotIn('foo__baz', d) self.assertNotIn('foo__bar', d) self.assertNotIn('foo__foo__bar', d) self.assertNotIn('bar__foo', d) self.assertNotIn('bar__bar__baz', d) def test_son_in_list(self): """Make sure that prefixes are used in decreasing match length order.""" d = { 'foo': 13, 'foo__baz': 42, 'foo__foo__bar': 2, 'foo__bar': 3, 'bar__foo': 1, 'bar__bar__baz': 4, } self.assertEqual( { 'foo__foo': {'bar': 2}, 'foo': {'bar': 3, 'baz': 42}, 'bar__bar': {'baz': 4}, 'bar': {'foo': 1}, 'baz': {} }, utils.multi_extract_dict( ['foo', 'bar', 'baz', 'foo__foo', 'bar__bar'], d, pop=False)) self.assertEqual(42, d['foo__baz']) self.assertEqual(3, d['foo__bar']) self.assertEqual(2, d['foo__foo__bar']) self.assertEqual(1, d['bar__foo']) self.assertEqual(4, d['bar__bar__baz']) self.assertEqual( { 'foo__foo': {'bar': 2}, 'foo': {'bar': 3, 'baz': 42}, 'bar__bar': {'baz': 4}, 'bar': {'foo': 1}, 'baz': {} }, utils.multi_extract_dict( ['foo', 'bar', 'baz', 'foo__foo', 'bar__bar'], d, pop=True)) self.assertNotIn('foo__baz', d) self.assertNotIn('foo__bar', d) 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__') class LogPPrintTestCase(unittest.TestCase): def test_nothing(self): txt = str(utils.log_pprint()) self.assertEqual('', txt) def test_only_args(self): txt = str(utils.log_pprint((1, 2, 3))) self.assertEqual('1, 2, 3', txt) def test_only_kwargs(self): txt = str(utils.log_pprint(kwargs={'a': 1, 'b': 2})) self.assertIn(txt, ['a=1, b=2', 'b=2, a=1']) def test_bytes_args(self): txt = str(utils.log_pprint((b'\xe1\xe2',))) expected = "b'\\xe1\\xe2'" if is_python2: expected = expected.lstrip('b') self.assertEqual(expected, txt) def test_text_args(self): txt = str(utils.log_pprint(('ŧêßŧ',))) expected = "'ŧêßŧ'" if is_python2: expected = "u'\\u0167\\xea\\xdf\\u0167'" self.assertEqual(expected, txt) def test_bytes_kwargs(self): txt = str(utils.log_pprint(kwargs={'x': b'\xe1\xe2', 'y': b'\xe2\xe1'})) expected1 = "x=b'\\xe1\\xe2', y=b'\\xe2\\xe1'" expected2 = "y=b'\\xe2\\xe1', x=b'\\xe1\\xe2'" if is_python2: expected1 = expected1.replace('b', '') expected2 = expected2.replace('b', '') self.assertIn(txt, (expected1, expected2)) def test_text_kwargs(self): txt = str(utils.log_pprint(kwargs={'x': 'ŧêßŧ', 'y': 'ŧßêŧ'})) expected1 = "x='ŧêßŧ', y='ŧßêŧ'" expected2 = "y='ŧßêŧ', x='ŧêßŧ'" if is_python2: expected1 = "x=u'\\u0167\\xea\\xdf\\u0167', y=u'\\u0167\\xdf\\xea\\u0167'" expected2 = "y=u'\\u0167\\xdf\\xea\\u0167', x=u'\\u0167\\xea\\xdf\\u0167'" self.assertIn(txt, (expected1, expected2)) class ResetableIteratorTestCase(unittest.TestCase): def test_no_reset(self): i = utils.ResetableIterator([1, 2, 3]) self.assertEqual([1, 2, 3], list(i)) def test_no_reset_new_iterator(self): i = utils.ResetableIterator([1, 2, 3]) iterator = iter(i) self.assertEqual(1, next(iterator)) self.assertEqual(2, next(iterator)) iterator2 = iter(i) self.assertEqual(3, next(iterator2)) def test_infinite(self): i = utils.ResetableIterator(itertools.cycle([1, 2, 3])) iterator = iter(i) values = [next(iterator) for _i in range(10)] self.assertEqual([1, 2, 3, 1, 2, 3, 1, 2, 3, 1], values) def test_reset_simple(self): i = utils.ResetableIterator([1, 2, 3]) iterator = iter(i) self.assertEqual(1, next(iterator)) self.assertEqual(2, next(iterator)) i.reset() self.assertEqual(1, next(iterator)) self.assertEqual(2, next(iterator)) self.assertEqual(3, next(iterator)) def test_reset_at_begin(self): i = utils.ResetableIterator([1, 2, 3]) iterator = iter(i) i.reset() i.reset() self.assertEqual(1, next(iterator)) self.assertEqual(2, next(iterator)) self.assertEqual(3, next(iterator)) def test_reset_at_end(self): i = utils.ResetableIterator([1, 2, 3]) iterator = iter(i) self.assertEqual(1, next(iterator)) self.assertEqual(2, next(iterator)) self.assertEqual(3, next(iterator)) i.reset() self.assertEqual(1, next(iterator)) self.assertEqual(2, next(iterator)) self.assertEqual(3, next(iterator)) def test_reset_after_end(self): i = utils.ResetableIterator([1, 2, 3]) iterator = iter(i) self.assertEqual(1, next(iterator)) self.assertEqual(2, next(iterator)) self.assertEqual(3, next(iterator)) self.assertRaises(StopIteration, next, iterator) i.reset() # Previous iter() has stopped iterator = iter(i) self.assertEqual(1, next(iterator)) self.assertEqual(2, next(iterator)) self.assertEqual(3, next(iterator)) def test_reset_twice(self): i = utils.ResetableIterator([1, 2, 3, 4, 5]) iterator = iter(i) self.assertEqual(1, next(iterator)) self.assertEqual(2, next(iterator)) i.reset() self.assertEqual(1, next(iterator)) self.assertEqual(2, next(iterator)) self.assertEqual(3, next(iterator)) self.assertEqual(4, next(iterator)) i.reset() self.assertEqual(1, next(iterator)) self.assertEqual(2, next(iterator)) self.assertEqual(3, next(iterator)) self.assertEqual(4, next(iterator)) def test_reset_shorter(self): i = utils.ResetableIterator([1, 2, 3, 4, 5]) iterator = iter(i) self.assertEqual(1, next(iterator)) self.assertEqual(2, next(iterator)) self.assertEqual(3, next(iterator)) self.assertEqual(4, next(iterator)) i.reset() self.assertEqual(1, next(iterator)) self.assertEqual(2, next(iterator)) i.reset() self.assertEqual(1, next(iterator)) self.assertEqual(2, next(iterator)) self.assertEqual(3, next(iterator)) self.assertEqual(4, next(iterator)) factory_boy-2.8.1/tests/testdata/000077500000000000000000000000001302510513700170125ustar00rootroot00000000000000factory_boy-2.8.1/tests/testdata/__init__.py000066400000000000000000000024521302510513700211260ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (c) 2011-2015 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. import os.path TESTDATA_ROOT = os.path.abspath(os.path.dirname(__file__)) TESTFILE_PATH = os.path.join(TESTDATA_ROOT, 'example.data') TESTIMAGE_PATH = os.path.join(TESTDATA_ROOT, 'example.jpeg') factory_boy-2.8.1/tests/testdata/example.data000066400000000000000000000000151302510513700212740ustar00rootroot00000000000000example_data factory_boy-2.8.1/tests/testdata/example.jpeg000066400000000000000000000004551302510513700213200ustar00rootroot00000000000000JFIFHHC       C **" ?Cټfactory_boy-2.8.1/tests/tools.py000066400000000000000000000026311302510513700167150ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (c) 2010 Mark Sandstrom # Copyright (c) 2011-2015 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. import functools import warnings def disable_warnings(fun): @functools.wraps(fun) def decorated(*args, **kwargs): with warnings.catch_warnings(): warnings.simplefilter('ignore') return fun(*args, **kwargs) return decorated factory_boy-2.8.1/tests/utils.py000066400000000000000000000053661302510513700167250ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (c) 2010 Mark Sandstrom # Copyright (c) 2011-2015 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. import datetime from .compat import mock from . import alter_time class MultiModulePatcher(object): """An abstract context processor for patching multiple modules.""" def __init__(self, *target_modules, **kwargs): super(MultiModulePatcher, self).__init__(**kwargs) self.patchers = [self._build_patcher(mod) for mod in target_modules] def _build_patcher(self, target_module): # pragma: no cover """Build a mock patcher for the target module.""" raise NotImplementedError() def __enter__(self): for patcher in self.patchers: mocked_symbol = patcher.start() def __exit__(self, exc_type=None, exc_val=None, exc_tb=None): for patcher in self.patchers: patcher.stop() class mocked_date_today(MultiModulePatcher): """A context processor changing the value of date.today().""" def __init__(self, target_date, *target_modules, **kwargs): self.target_date = target_date super(mocked_date_today, self).__init__(*target_modules, **kwargs) def _build_patcher(self, target_module): module_datetime = getattr(target_module, 'datetime') return alter_time.mock_date_today(self.target_date, module_datetime) class mocked_datetime_now(MultiModulePatcher): def __init__(self, target_dt, *target_modules, **kwargs): self.target_dt = target_dt super(mocked_datetime_now, self).__init__(*target_modules, **kwargs) def _build_patcher(self, target_module): module_datetime = getattr(target_module, 'datetime') return alter_time.mock_datetime_now(self.target_dt, module_datetime) factory_boy-2.8.1/tox.ini000066400000000000000000000013471302510513700153570ustar00rootroot00000000000000[tox] envlist = py{27,34,35}-django{17,18,19,110}-alchemy10-mongoengine010 examples lint toxworkdir = {env:TOX_WORKDIR:.tox} [testenv] deps = -rrequirements_test.txt django17: Django>=1.7,<1.8 django18: Django>=1.8,<1.9 django19: Django>=1.9,<1.10 django110: Django>=1.10,<1.11 django{17,18,19,110}: Pillow alchemy10: SQLAlchemy>=1.0,<1.1 mongoengine010: mongoengine>=0.10,<0.11 whitelist_externals = make commands = make test [testenv:examples] basepython = python3.4 deps = -rrequirements_test.txt -rexamples/requirements.txt whitelist_externals = make commands = make example-test [testenv:lint] deps = flake8 check_manifest whitelist_externals = make commands = make lint