pax_global_header00006660000000000000000000000064147501104040014506gustar00rootroot0000000000000052 comment=f82a7bfdce7ad96893e2b21f89917995e399f24b factory-boy-3.3.3/000077500000000000000000000000001475011040400137525ustar00rootroot00000000000000factory-boy-3.3.3/.github/000077500000000000000000000000001475011040400153125ustar00rootroot00000000000000factory-boy-3.3.3/.github/ISSUE_TEMPLATE/000077500000000000000000000000001475011040400174755ustar00rootroot00000000000000factory-boy-3.3.3/.github/ISSUE_TEMPLATE/bug_report.md000066400000000000000000000007531475011040400221740ustar00rootroot00000000000000--- name: Bug report about: Create a report to help us improve --- #### Description *A clear and concise description of what the bug is.* #### To Reproduce *Share how the bug happened:* ##### Model / Factory code ```python # Include your factories and models here ``` ##### The issue *Add a short description along with your code* ```python # Include the code that provoked the bug, including as full a stack-trace as possible ``` #### Notes *Add any notes you feel relevant here :)* factory-boy-3.3.3/.github/ISSUE_TEMPLATE/improvement-suggestion.md000066400000000000000000000007221475011040400245520ustar00rootroot00000000000000--- name: Improvement suggestion about: Suggest an idea for this project --- #### The problem *Please describe the problem you're encountering (e.g "It's very complex to do [...]")* #### Proposed solution *Please provide some wild idea you think could solve this issue. It's much easier to work from an existing suggestion :)* #### Extra notes *Any notes you feel interesting to include: alternatives you've considered, reasons to include the change, anything!* factory-boy-3.3.3/.github/SUPPORT.md000066400000000000000000000005461475011040400170150ustar00rootroot00000000000000# Getting support Most questions should be asked with the `factory-boy` tag on [StackOverflow](https://stackoverflow.com/questions/tagged/factory-boy). Alternatively, a discussion group exists at https://groups.google.com/d/forum/factoryboy. Please **do not open issues for support requests**. Issues are meant for bug reports and improvement suggestions. factory-boy-3.3.3/.github/dependabot.yml000066400000000000000000000003721475011040400201440ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "monthly" groups: GitHub_Actions: patterns: - "*" # Group all Actions updates into a single larger pull request factory-boy-3.3.3/.github/workflows/000077500000000000000000000000001475011040400173475ustar00rootroot00000000000000factory-boy-3.3.3/.github/workflows/check.yml000066400000000000000000000013651475011040400211540ustar00rootroot00000000000000name: Check on: push: branches: - "master" pull_request: concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true jobs: build: name: ${{ matrix.tox-environment }} runs-on: ubuntu-latest strategy: fail-fast: false matrix: tox-environment: - docs - examples - lint env: TOXENV: ${{ matrix.tox-environment }} steps: - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: '3' cache: pip - name: Install dependencies run: python -m pip install tox - name: Run run: tox factory-boy-3.3.3/.github/workflows/linkcheck.yml000066400000000000000000000006711475011040400220310ustar00rootroot00000000000000name: Linkcheck on: schedule: - cron: '11 11 * * 1' jobs: build: name: Linkcheck runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: python-version: '3' - name: Install dependencies run: python -m pip install tox - name: Run linkcheck run: tox -e linkcheck factory-boy-3.3.3/.github/workflows/test.yml000066400000000000000000000016131475011040400210520ustar00rootroot00000000000000name: Test on: push: branches: - "master" pull_request: concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true jobs: tests: name: Python ${{ matrix.python-version }} runs-on: ubuntu-latest strategy: fail-fast: false matrix: python-version: - "3.9" - "3.10" - "3.11" - "3.12" - "3.13" - "pypy-3.10" steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} cache: pip - name: Install dependencies run: python -m pip install tox-gh-actions - name: Run tests run: tox env: DATABASE_TYPE: ${{ matrix.database-type }} factory-boy-3.3.3/.gitignore000066400000000000000000000002571475011040400157460ustar00rootroot00000000000000# Temporary files .*.swp *.pyc *.pyo .idea/ # Build-related files docs/_build/ auto_dev_requirements*.txt .coverage .tox *.egg-info *.egg build/ dist/ htmlcov/ MANIFEST tags factory-boy-3.3.3/.readthedocs.yaml000066400000000000000000000003451475011040400172030ustar00rootroot00000000000000version: 2 build: os: "ubuntu-lts-latest" tools: python: "latest" python: install: - method: pip path: . extra_requirements: - doc sphinx: configuration: docs/conf.py fail_on_warning: true factory-boy-3.3.3/CODE_OF_CONDUCT.md000066400000000000000000000062431475011040400165560ustar00rootroot00000000000000# Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at raphael DOT barrois AT xelmail DOT com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/4/ factory-boy-3.3.3/CONTRIBUTING.rst000066400000000000000000000053251475011040400164200ustar00rootroot00000000000000Contributing ============ Thanks for taking the time to contribute to factory_boy! Code of Conduct --------------- This project and everyone participating in it is governed by the `Code of Conduct`_. By participating, you are expected to uphold this code. Please report inappropriate behavior to raphael DOT barrois AT xelmail DOT com. .. _Code of Conduct: https://github.com/FactoryBoy/factory_boy/blob/master/CODE_OF_CONDUCT.md *(If I'm the person with the inappropriate behavior, please accept my apologies. I know I can mess up. I can't expect you to tell me, but if you chose to do so, I'll do my best to handle criticism constructively. -- Raphaël)* *(As the community around this project grows, we hope to have more core developers available to handle that kind of issues)* Contributions ------------- Bug reports, patches, documentation improvements and suggestions are welcome! Please open an issue_ or send a `pull request`_. Feedback about the documentation is especially valuable — the authors of ``factory_boy`` feel more confident about writing code than writing docs :-) .. _issue: https://github.com/FactoryBoy/factory_boy/issues/new .. _pull request: https://github.com/FactoryBoy/factory_boy/compare/ Where to start? --------------- If you're new to the project and want to help, a great first step would be: * Fixing an issue in the docs (outdated setup instructions, missing information, unclear feature, etc.); * Working on an existing issue (some should be marked ``BeginnerFriendly``); * Reviewing an existing pull request; * Or any other way you'd like to help. Code contributions ------------------ In order to merge some code, you'll need to open a `pull request`_. There are a few rules to keep in mind regarding pull requests: * A pull request should only solve a single issue / add a single feature; * If the code change is significant, please also create an issue_ for easier discussion; * We have automated testing; please make sure that the updated code passes automated checks; * We're striving to improve the quality of the library, with higher test and docs coverage. If you don't know how/where to add docs or tests, we'll be very happy to point you in the right direction! Questions --------- GitHub issues aren't a good medium for handling questions. There are better places to ask questions, for example Stack Overflow; please use the ``factory-boy`` tag to make those questions easy to find by the maintainers. If you want to ask a question anyway, please make sure that: - it's a question about ``factory_boy`` and not about ``Django`` or ``Faker``; - it isn't answered by the documentation; - it wasn't asked already. A good question can be written as a suggestion to improve the documentation. factory-boy-3.3.3/CREDITS000066400000000000000000000105341475011040400147750ustar00rootroot00000000000000Credits ======= Maintainers ----------- The ``factory_boy`` project is operated and maintained by: * Jeff Widman (https://github.com/jeffwidman) * Raphaël Barrois (https://github.com/rbarrois) .. _contributors: Contributors ------------ The project was initially created by Mark Sandstrom . The project has received contributions from (in alphabetical order): * Adam Chainz * Alejandro * Alexey Kotlyarov * Amit Shah * Anas Zahim (https://github.com/kamotos) * Andrey Voronov * Branko Majic * Carl Meyer * Chris Lasher * Chris Seto * Christoph Sieghart * David Baumgold * Demur Nodia (https://github.com/demonno) * Eduard Iskandarov * Federico Bond (https://github.com/federicobond) * Flavio Curella * François Freitag * George Hickman * Grégoire Deveaux * Hervé Cauwelier * Hugo Osvaldo Barrera * Ilya Baryshev * Ilya Pirogov * Ionuț Arțăriși * Issa Jubril * Ivan Miric * Janusz Skonieczny * Javier Buzzi (https://github.com/kingbuzzman) * Jeff Widman (https://github.com/jeffwidman) * Jon Dufresne * Jonathan Tushman * Joshua Carp * Leonardo Lazzaro * Luke GB * Marc Abramowitz * Mark Sandstrom * Martin Bächtold (https://github.com/mbaechtold) * Michael Joseph * Mikhail Korobov * Oleg Pidsadnyi * Omer * Pauly Fenwar * Peter Marsh * Puneeth Chaganti * QuantumGhost * Raphaël Barrois (https://github.com/rbarrois) * Rich Rauenzahn * Richard Moch * Rob Zyskowski * Robrecht De Rouck * Samuel Paccoud * Sarah Boyce * Saul Shanabrook * Sean Löfgren * Shahriar Tajbakhsh * Tom * alex-netquity * anentropic * minimumserious * mluszczyk * nkryptic * obiwanus * tsouvarev * yamaneko Contributor license agreement ----------------------------- .. note:: This agreement is required to allow redistribution of submitted contributions. See http://oss-watch.ac.uk/resources/cla for an explanation. Any contributor proposing updates to the code or documentation of this project *MUST* add its name to the list in the :ref:`contributors` section, thereby "signing" the following contributor license agreement: They accept and agree to the following terms for their present end future contributions submitted to the ``factory_boy`` project: * They represent that they are legally entitled to grant this license, and that their contributions are their original creation * They grant the ``factory_boy`` project a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare derivative works of, publicly display, sublicense and distribute their contributions and such derivative works. * They are not expected to provide support for their contributions, except to the extent they desire to provide support. .. note:: The above agreement is inspired by the Apache Contributor License Agreement. .. vim:set ft=rst: factory-boy-3.3.3/ChangeLog000077700000000000000000000000001475011040400211302docs/changelog.rstustar00rootroot00000000000000factory-boy-3.3.3/LICENSE000066400000000000000000000021601475011040400147560ustar00rootroot00000000000000Copyright (c) 2010 Mark Sandstrom Copyright (c) 2011-2015 Raphaël Barrois Copyright (c) The FactoryBoy project 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-3.3.3/MANIFEST.in000066400000000000000000000004121475011040400155050ustar00rootroot00000000000000include ChangeLog CODE_OF_CONDUCT.md CONTRIBUTING.rst CREDITS LICENSE README.rst include Makefile tox.ini graft factory graft docs graft examples graft tests exclude .readthedocs.yaml global-exclude *.py[cod] __pycache__ .*.sw[po] prune .github prune docs/_build factory-boy-3.3.3/Makefile000066400000000000000000000052771475011040400154250ustar00rootroot00000000000000PACKAGE=factory TESTS_DIR=tests DOC_DIR=docs EXAMPLES_DIR=examples SETUP_PY=setup.py # Use current python binary instead of system default. COVERAGE = python $(shell which coverage) FLAKE8 = flake8 ISORT = isort CTAGS = ctags all: default default: # Package management # ================== # DOC: Remove temporary or compiled files clean: find . -type f -name '*.pyc' -delete find . -type f -path '*/__pycache__/*' -delete find . -type d -empty -delete @rm -rf tmp_test/ # DOC: Install and/or upgrade dependencies update: pip install --upgrade pip setuptools pip install --upgrade --editable .[dev,doc] pip freeze release: fullrelease .PHONY: clean update release # Tests and quality # ================= # DOC: Run tests for all supported versions (creates a set of virtualenvs) testall: tox # DOC: Run tests for the currently installed version # Remove cgi warning when dropping support for Django 3.2. test: mypy --ignore-missing-imports tests/test_typing.py python \ -b \ -X dev \ -Werror \ -Wignore:::mongomock: \ -Wignore:::mongomock.__version__: \ -Wignore:::pkg_resources: \ -m unittest # DOC: Test the examples 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 # DOC: Perform code quality tasks lint: $(FLAKE8) --exclude $(PACKAGE)/__init__.py $(EXAMPLES_DIR) $(PACKAGE) $(SETUP_PY) $(TESTS_DIR) $(FLAKE8) --ignore F401 $(PACKAGE)/__init__.py $(ISORT) --check-only --diff $(EXAMPLES_DIR) $(PACKAGE) $(SETUP_PY) $(TESTS_DIR) check-manifest coverage: $(COVERAGE) erase $(COVERAGE) run --branch -m unittest $(COVERAGE) report $(COVERAGE) html .PHONY: test testall example-test lint coverage # Development # =========== # DOC: Generate a "tags" file TAGS: $(CTAGS) --recurse $(PACKAGE) $(TESTS_DIR) .PHONY: TAGS # Documentation # ============= # DOC: Compile the documentation doc: $(MAKE) -C $(DOC_DIR) SPHINXOPTS="-n -W" html linkcheck: $(MAKE) -C $(DOC_DIR) linkcheck spelling: $(MAKE) -C $(DOC_DIR) SPHINXOPTS=-W spelling # DOC: Show this help message help: @grep -A1 '^# DOC:' Makefile \ | awk ' \ BEGIN { FS="\n"; RS="--\n"; opt_len=0; } \ { \ doc=$$1; name=$$2; \ sub("# DOC: ", "", doc); \ sub(":", "", name); \ if (length(name) > opt_len) { \ opt_len = length(name) \ } \ opts[NR] = name; \ docs[name] = doc; \ } \ END { \ pat="%-" (opt_len + 4) "s %s\n"; \ asort(opts); \ for (i in opts) { \ opt=opts[i]; \ printf pat, opt, docs[opt] \ } \ }' .PHONY: doc linkcheck help factory-boy-3.3.3/README.rst000066400000000000000000000316231475011040400154460ustar00rootroot00000000000000factory_boy =========== .. image:: https://github.com/FactoryBoy/factory_boy/workflows/Test/badge.svg :target: https://github.com/FactoryBoy/factory_boy/actions?query=workflow%3ATest .. image:: https://github.com/FactoryBoy/factory_boy/workflows/Check/badge.svg :target: https://github.com/FactoryBoy/factory_boy/actions?query=workflow%3ACheck .. 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.org/project/factory-boy/ :alt: Supported Python versions .. image:: https://img.shields.io/pypi/wheel/factory_boy.svg :target: https://pypi.org/project/factory-boy/ :alt: Wheel status .. image:: https://img.shields.io/pypi/l/factory_boy.svg :target: https://github.com/FactoryBoy/factory_boy/blob/master/LICENSE :alt: License factory_boy is a fixtures replacement based on thoughtbot's `factory_bot `_. As a fixtures replacement tool, it aims to replace static, hard to maintain fixtures with easy-to-use factories for complex objects. 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, MongoDB, 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.org/project/factory-boy/ * Mailing-list: `factoryboy@googlegroups.com `_ | https://groups.google.com/forum/#!forum/factoryboy Download -------- PyPI: https://pypi.org/project/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 a Python 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 ORM integration """"""""""""""" factory_boy integration with Object Relational Mapping (ORM) tools is provided 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`` More details can be found in the ORM section. Using factories """"""""""""""" factory_boy supports several different instantiation strategies: build, create, and stub: .. code-block:: python # Returns a User instance that's not saved user = UserFactory.build() # Returns a saved User instance. # UserFactory must subclass an ORM base class, such as DjangoModelFactory. 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 instantiation 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 `faker `_ 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 >>> RandomUserFactory() Reproducible random values """""""""""""""""""""""""" The 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, located in the ``factory.random`` module: .. code-block:: python import factory.random def setup_test_environment(): factory.random.reseed_random('my_awesome_project') # Other setup here 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: '{}.{}@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{}@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 Support Policy -------------- ``factory_boy`` supports active Python versions as well as PyPy3. - **Python**'s `supported versions `__. - **Django**'s `supported versions `__. - **SQLAlchemy**: `latest version on PyPI `__. - **MongoEngine**: `latest version on PyPI `__. 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 `_. Development dependencies can be installed in a `virtualenv `_ with: .. code-block:: sh $ pip install --editable '.[dev]' All pull requests should pass the test suite, which can be launched simply with: .. code-block:: sh $ make testall In order to test coverage, please use: .. code-block:: sh $ make coverage To test with a specific framework version, you may use a ``tox`` target: .. code-block:: sh # list all tox environments $ tox --listenvs # run tests inside a specific environment (django/mongoengine/SQLAlchemy are not installed) $ tox -e py310 # run tests inside a specific environment (django) $ tox -e py310-djangomain # run tests inside a specific environment (alchemy) $ tox -e py310-alchemy # run tests inside a specific environment (mongoengine) $ tox -e py310-mongo Packaging --------- For users interesting in packaging FactoryBoy into downstream distribution channels (e.g. ``.deb``, ``.rpm``, ``.ebuild``), the following tips might be helpful: Dependencies """""""""""" The package's run-time dependencies are listed in ``setup.cfg``. The dependencies useful for building and testing the library are covered by the ``dev`` and ``doc`` extras. Moreover, all development / testing tasks are driven through ``make(1)``. Building """""""" In order to run the build steps (currently only for docs), run: .. code-block:: sh python setup.py egg_info make doc Testing """"""" When testing for the active Python environment, run the following: .. code-block:: sh make test .. note:: You must make sure that the ``factory`` module is importable, as it is imported from the testing code. factory-boy-3.3.3/docs/000077500000000000000000000000001475011040400147025ustar00rootroot00000000000000factory-boy-3.3.3/docs/Makefile000066400000000000000000000011721475011040400163430ustar00rootroot00000000000000# Minimal makefile for Sphinx documentation # # You can set these variables from the command line, and also # from the environment for the first two. SPHINXOPTS ?= SPHINXBUILD ?= sphinx-build SOURCEDIR = . BUILDDIR = _build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) factory-boy-3.3.3/docs/_static/000077500000000000000000000000001475011040400163305ustar00rootroot00000000000000factory-boy-3.3.3/docs/_static/.keep_dir000066400000000000000000000000001475011040400201010ustar00rootroot00000000000000factory-boy-3.3.3/docs/changelog.rst000066400000000000000000000754131475011040400173750ustar00rootroot00000000000000ChangeLog ========= .. Note for v4.x: don't forget to check "Deprecated" sections for removal. 3.3.3 (2025-02-03) ------------------ *New:* - Publish type annotations 3.3.2 (2025-02-03) ------------------ *Bugfix:* - Fix docs generation *New:* - Add support for Python 3.13 3.3.1 (2024-08-18) ------------------ *New:* - Add support for Django 4.2 - Add support for Django 5.1 - Add support for Python 3.12 - :issue:`903`: Add basic typing annotations - Run the test suite against ``mongomock`` instead of an actual MongoDB server *Bugfix:* - :issue:`1031`: Do not require :attr:`~factory.alchemy.SQLAlchemyOptions.sqlalchemy_session` when :attr:`~factory.alchemy.SQLAlchemyOptions.sqlalchemy_session_factory` is provided. *Removed:* - Stop advertising and verifying support for Django 3.2, 4.0, 4.1 3.3.0 (2023-07-19) ------------------ *New:* - :issue:`366`: Add :class:`factory.django.Password` to generate Django :class:`~django.contrib.auth.models.User` passwords. - :issue:`304`: Add :attr:`~factory.alchemy.SQLAlchemyOptions.sqlalchemy_session_factory` to dynamically create sessions for use by the :class:`~factory.alchemy.SQLAlchemyModelFactory`. - Add support for Django 4.0 - Add support for Django 4.1 - Add support for Python 3.10 - Add support for Python 3.11 *Bugfix:* - Make :meth:`~factory.django.mute_signals` mute signals during post-generation. - :issue:`775`: Change the signature for :class:`~factory.alchemy.SQLAlchemyModelFactory`'s ``_save`` and ``_get_or_create`` methods to avoid argument names clashes with a field named ``session``. *Deprecated:* - :class:`~factory.django.DjangoModelFactory` will stop issuing a second call to :meth:`~django.db.models.Model.save` on the created instance when :ref:`post-generation-hooks` return a value. To help with the transition, :class:`factory.django.DjangoModelFactory`'s ``_after_postgeneration`` raises a :class:`DeprecationWarning` when calling :meth:`~django.db.models.Model.save`. Inspect your :class:`~factory.django.DjangoModelFactory` subclasses: - If the :meth:`~django.db.models.Model.save` call is not needed after :class:`~factory.PostGeneration`, set :attr:`factory.django.DjangoOptions.skip_postgeneration_save` to ``True`` in the factory meta. - Otherwise, the instance has been modified by :class:`~factory.PostGeneration` hooks and needs to be :meth:`~django.db.models.Model.save`\ d. Either: - call :meth:`django.db.models.Model.save` in the :class:`~factory.PostGeneration` hook that modifies the instance, or - override the :class:`~factory.Factory._after_postgeneration` method to :meth:`~django.db.models.Model.save` the instance. *Removed:* - Drop support for Django 2.2 - Drop support for Django 3.0 - Drop support for Django 3.1 - Drop support for Python 3.6 - Drop support for Python 3.7 3.2.1 (2021-10-26) ------------------ *New:* - Add support for Django 3.2 *Bugfix:* - Do not override signals receivers registered in a :meth:`~factory.django.mute_signals` context. - :issue:`775`: Change the signature for :class:`~factory.alchemy.SQLAlchemyModelFactory`'s ``_save`` and ``_get_or_create`` methods to avoid argument names clashes with a field named ``session``. 3.2.0 (2020-12-28) ------------------ *New:* - Add support for Django 3.1 - Add support for Python 3.9 *Removed:* - Drop support for Django 1.11. This version `is not maintained anymore `__. - Drop support for Python 3.5. This version `is not maintained anymore `__. *Deprecated:* - :func:`factory.use_strategy`. Use :attr:`factory.FactoryOptions.strategy` instead. The purpose of :func:`~factory.use_strategy` duplicates the factory option. Follow :pep:`20`: *There should be one-- and preferably only one --obvious way to do it.* :func:`~factory.use_strategy()` will be removed in the next major version. *Bug fix:* - :issue:`785` :issue:`786` :issue:`787` :issue:`788` :issue:`790` :issue:`796`: Calls to :class:`factory.Faker` and :class:`factory.django.FileField` within a :class:`~factory.Trait` or :class:`~factory.Maybe` no longer lead to a ``KeyError`` crash. 3.1.0 (2020-10-02) ------------------ *New:* - Allow all types of declarations in :class:`factory.Faker` calls - enables references to other faker-defined attributes. 3.0.1 (2020-08-13) ------------------ *Bug fix:* - :issue:`769`: Fix ``import factory; factory.django.DjangoModelFactory`` and similar calls. 3.0.0 (2020-08-12) ------------------ Breaking changes """""""""""""""" The following aliases were removed: +================================================+===================================================+ | Broken alias | New import | +================================================+===================================================+ | ``from factory import DjangoModelFactory`` | ``from factory.django import DjangoModelFactory`` | +------------------------------------------------+---------------------------------------------------+ | ``from factory import MogoFactory`` | ``from factory.mogo import MogoFactory`` | +------------------------------------------------+---------------------------------------------------+ | ``from factory.fuzzy import get_random_state`` | ``from factory.random import get_random_state`` | +------------------------------------------------+---------------------------------------------------+ | ``from factory.fuzzy import set_random_state`` | ``from factory.random import set_random_state`` | +------------------------------------------------+---------------------------------------------------+ | ``from factory.fuzzy import reseed_random`` | ``from factory.random import reseed_random`` | +================================================+===================================================+ *Removed:* - Drop support for Python 2 and 3.4. These versions `are not maintained anymore `__. - Drop support for Django 2.0 and 2.1. These versions `are not maintained anymore `__. - Remove deprecated ``force_flush`` from ``SQLAlchemyModelFactory`` options. Use ``sqlalchemy_session_persistence = "flush"`` instead. - Drop deprecated ``attributes()`` from :class:`~factory.Factory` subclasses; use ``factory.make_factory(dict, FactoryClass._meta.pre_declarations)`` instead. - Drop deprecated ``declarations()`` from :class:`~factory.Factory` subclasses; use ``FactoryClass._meta.pre_declarations`` instead. - Drop ``factory.compat`` module. *New:* - Add support for Python 3.8 - Add support for Django 2.2 and 3.0 - Report misconfiguration when a :py:class:`~factory.Factory` is used as the :py:attr:`~factory.FactoryOptions.model` for another :py:class:`~factory.Factory`. - Allow configuring the color palette of :py:class:`~factory.django.ImageField`. - :py:meth:`~factory.random.get_random_state()` now represents the state of Faker and ``factory_boy`` fuzzy attributes. - Add SQLAlchemy ``get_or_create`` support *Improvements:* - :issue:`561`: Display a developer-friendly error message when providing a model instead of a factory in a :class:`~factory.SubFactory` class. *Bug fix:* - Fix issue with SubFactory not preserving signal muting behavior of the used factory, thanks `Patrick Stein `_. - Fix issue with overriding parameters in a Trait, thanks `Grégoire Rocher `_. - :issue:`598`: Limit ``get_or_create`` behavior to fields specified in ``django_get_or_create``. - :issue:`606`: Re-raise :class:`~django.db.IntegrityError` when ``django_get_or_create`` with multiple fields fails to lookup model using user provided keyword arguments. - :issue:`630`: TypeError masked by __repr__ AttributeError when initializing ``Maybe`` with inconsistent phases. 2.12.0 (2019-05-11) ------------------- *New:* - Add support for Python 3.7 - Add support for Django 2.1 - Add ``getter`` to :class:`~factory.fuzzy.FuzzyChoice` that mimics the behavior of ``getter`` in :class:`~factory.Iterator` - Make the ``extra_kwargs`` parameter of :class:`~factory.Faker`'s ``generate`` method optional - Add :class:`~factory.RelatedFactoryList` class for one-to-many support, thanks `Sean Harrington `_. - Make the `locale` argument for :class:`~factory.Faker` keyword-only *Bug fix:* - Allow renamed arguments to be optional, thanks to `Justin Crown `_. - Fix `django_get_or_create` behavior when using multiple fields with `unique=True`, thanks to `@YPCrumble ` 2.11.1 (2018-05-05) ------------------- *Bug fix:* - Fix passing deep context to a :class:`~factory.SubFactory` (``Foo(x__y__z=factory.Faker('name')``) 2.11.0 (2018-05-05) ------------------- *Bug fix:* - Fix :class:`~factory.fuzzy.FuzzyFloat` to return a 15 decimal digits precision float by default - :issue:`451`: Restore :class:`~factory.django.FileField` to a ``factory.declarations.ParameteredAttribute``, relying on composition to parse the provided parameters. - :issue:`389`: Fix random state management with ``faker``. - :issue:`466`: Restore mixing :class:`~factory.Trait` and :meth:`~factory.post_generation`. 2.10.0 (2018-01-28) ------------------- *Bug fix:* - :issue:`443`: Don't crash when calling :meth:`factory.Iterator.reset()` on a brand new iterator. *New:* - :issue:`397`: Allow a :class:`factory.Maybe` to contain a :class:`~factory.PostGeneration` declaration. This also applies to :class:`factory.Trait`, since they use a :class:`factory.Maybe` declaration internally. .. _v2.9.2: 2.9.2 (2017-08-03) ------------------ *Bug fix:* - Fix declaration corruption bug when a factory defined `foo__bar__baz=1` and a caller provided a `foo__bar=x` parameter at call time: this got merged into the factory's base declarations. .. _v2.9.1: 2.9.1 (2017-08-02) ------------------ *Bug fix:* - Fix packaging issues (see https://github.com/zestsoftware/zest.releaser/issues/212) - Don't crash when debugging PostGenerationDeclaration .. _v2.9.0: 2.9.0 (2017-07-30) ------------------ This version brings massive changes to the core engine, thus reducing the number of corner cases and weird behaviors. *New:* - :issue:`275`: `factory.fuzzy` and `factory.faker` now use the same random seed. - Add :class:`factory.Maybe`, which chooses among two possible declarations based on another field's value (powers the :class:`~factory.Trait` feature). - :class:`~factory.PostGenerationMethodCall` only allows to pass one positional argument; use keyword arguments for extra parameters. *Deprecation:* - `factory.fuzzy.get_random_state` is deprecated, `factory.random.get_random_state` should be used instead. - `factory.fuzzy.set_random_state` is deprecated, `factory.random.set_random_state` should be used instead. - `factory.fuzzy.reseed_random` is deprecated, `factory.random.reseed_random` should be used instead. .. _v2.8.1: 2.8.1 (2016-12-17) ------------------ *Bug fix:* - 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 *Bug fix:* - :issue:`334`: Adjust for the package change in ``faker`` .. _v2.7.0: 2.7.0 (2016-04-19) ------------------ *New:* - :pr:`267`: Add :class:`factory.LazyFunction` to remove unneeded lambda parameters, thanks to `Hervé Cauwelier `_. - :issue:`251`: Add :ref:`parameterized factories ` and :class:`traits ` - :pr:`256`, :pr:`292`: Improve error messages in corner cases *Removed:* - :pr:`278`: Formally drop support for Python2.6 .. warning:: Version 2.7.0 moves all error classes to `factory.errors`. This breaks existing import statements for any error classes except those importing `FactoryError` directly from the `factory` module. .. _v2.6.1: 2.6.1 (2016-02-10) ------------------ *New:* - :pr:`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``. *Bug fix:* - :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 ``django.apps.apps.get_model()`` until required - :pr:`219`: Stop using ``mogo.model.Model.new()``, deprecated 4 years ago. .. _v2.5.2: 2.5.2 (2015-04-21) ------------------ *Bug fix:* - 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) ------------------ *Bug fix:* - 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`). *Bug fix:* - 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`` occurrences; the files containing other attributes to rename can be found with ``grep -R FACTORY .`` .. _v2.4.1: 2.4.1 (2014-06-23) ------------------ *Bug fix:* - 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 `_ (:pr:`120`) - Add :meth:`~factory.django.mute_signals` decorator to temporarily disable some signals, thanks to `ilya-pirogov `_ (:pr:`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 ``factory.Factory.FACTORY_FOR`` to :attr:`~factory.FactoryOptions.model` * Rename ``factory.Factory.ABSTRACT_FACTORY`` to :attr:`~factory.FactoryOptions.abstract` * Rename ``factory.Factory.FACTORY_STRATEGY`` to :attr:`~factory.FactoryOptions.strategy` * Rename ``factory.Factory.FACTORY_ARG_PARAMETERS`` to :attr:`~factory.FactoryOptions.inline_args` * Rename ``factory.Factory.FACTORY_HIDDEN_ARGS`` to :attr:`~factory.FactoryOptions.exclude` For :class:`factory.django.DjangoModelFactory`: * Rename ``factory.django.DjangoModelFactory.FACTORY_DJANGO_GET_OR_CREATE`` to :attr:`~factory.django.DjangoOptions.django_get_or_create` For :class:`factory.alchemy.SQLAlchemyModelFactory`: * Rename ``factory.alchemy.SQLAlchemyModelFactory.FACTORY_SESSION`` to :attr:`~factory.alchemy.SQLAlchemyOptions.sqlalchemy_session` .. _v2.3.1: 2.3.1 (2014-01-22) ------------------ *Bug fix:* - Fix badly written assert containing state-changing code, spotted by ``chsigi`` (:pr:`126`) - Don't crash when handling objects whose ``__repr__`` is non-pure-ASCII bytes on Python 2, discovered by `mbertheau `_ (:issue:`123`) and `strycore `_ (:pr:`127`) .. _v2.3.0: 2.3.0 (2013-12-25) ------------------ *New:* - Add :class:`~factory.fuzzy.FuzzyText`, thanks to `jdufresne `_ (:pr:`97`) - Add :class:`~factory.fuzzy.FuzzyDecimal`, thanks to `thedrow `_ (:pr:`94`) - Add support for :class:`~mongoengine.EmbeddedDocument`, thanks to `imiric `_ (:pr:`100`) .. _v2.2.1: 2.2.1 (2013-09-24) ------------------ *Bug fix:* - 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) ------------------ *Bug fix:* - Removed duplicated :class:`~factory.alchemy.SQLAlchemyModelFactory` lurking in :mod:`factory` (:pr:`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 ``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 ``factory.Factory.FACTORY_FOR`` attribute (:issue:`74`) .. _v2.1.1: 2.1.1 (2013-07-02) ------------------ *Bug fix:* - 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 ``factory_parent`` attribute to the ``factory.builder.Resolver`` 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` (:pr:`64`, thanks to `Romain Commandé `_) - Add :class:`factory.django.FileField` and :class:`factory.django.ImageField` hooks for related Django model fields (:issue:`52`) *Bug fix* - Properly handle non-integer primary keys 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 ``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 ``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 type for :class:`~factory.Sequence` is now :obj:`int` - Fields listed in ``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 ``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 ``factory.InfiniteIterator`` and ``factory.infinite_iterator`` - Remove ``factory.CircularSubFactory`` - Remove ``extract_prefix`` kwarg to post-generation hooks. - Stop defaulting to Django's ``Foo.objects.create()`` when "creating" instances - Remove STRATEGY_* - Remove ``factory.Factory.set_building_function`` / ``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 ``FACTORY_ARG_PARAMETERS``. - Keep the ``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:** - ``factory.InfiniteIterator`` is deprecated in favor of :class:`~factory.Iterator` - ``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 ``factory.Factory.set_creation_function`` and ``factory.Factory.set_building_function`` are now deprecated - Implicit associated class discovery is no longer supported, you must set the ``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 automatic 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 ``factory.CircularSubFactory`` to solve circular dependencies between factories .. _v1.1.5: 1.1.5 (2012-07-09) ------------------ *Bug fix:* - Fix ``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) ------------------ *Bug fix:* - Fix packaging rules .. _v1.1.2: 1.1.2 (2012-02-25) ------------------ *New:* - Add :class:`~factory.Iterator` and ``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 ``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 *Bug fix:* - Allow ``classmethod``/``staticmethod`` on factories *Deprecation:* - Auto-discovery of ``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 ``factory.MOGO_BUILD`` build function - Add support for inheriting from multiple :class:`~factory.Factory` - Base :class:`~factory.Factory` classes can now be declared abstract through ``factory.Factory.ABSTRACT_FACTORY``. - 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``. *Bug fix:* - Handle nested :class:`~factory.SubFactory` - Share sequence counter between parent and subclasses - Fix :class:`~factory.SubFactory` / :class:`~factory.Sequence` interference .. _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 *Bug fix:* - 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 ------- See :doc:`credits`. .. vim:et:ts=4:sw=4:tw=119:ft=rst: factory-boy-3.3.3/docs/conf.py000066400000000000000000000065301475011040400162050ustar00rootroot00000000000000# Configuration file for the Sphinx documentation builder. # # This file only contains a selection of the most common options. For a full # list see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html # -- Path setup -------------------------------------------------------------- import os import sys # 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.dirname(os.path.abspath('.'))) # Must be imported after the parent directory was added to sys.path for global sphinx installation. import factory # noqa # -- Project information ----------------------------------------------------- project = 'Factory Boy' copyright = '2011-2015, Raphaël Barrois, Mark Sandstrom' author = 'Raphaël Barrois, Mark Sandstrom' # The full version, including alpha/beta/rc tags release = factory.__version__ # The short X.Y version. version = '.'.join(release.split('.')[:2]) # -- General configuration --------------------------------------------------- # 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 %s'), 'pr': ('https://github.com/FactoryBoy/factory_boy/pull/%s', 'pull request %s'), } # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The master toctree document. master_doc = 'index' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['_build'] # -- 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' if 'READTHEDOCS_VERSION' in os.environ: # Use the readthedocs version string in preference to our known version. html_title = "{} {} documentation".format( project, os.environ['READTHEDOCS_VERSION']) # 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'] # -- linkcheck --------------------------------------------------------------- linkcheck_retries = 3 # -- intersphinx ------------------------------------------------------------- intersphinx_mapping = { 'python': ('https://docs.python.org/3', None), 'django': ( 'https://docs.djangoproject.com/en/dev/', 'https://docs.djangoproject.com/en/dev/_objects/', ), 'mongoengine': ( 'https://mongoengine-odm.readthedocs.io/', None, ), 'sqlalchemy': ( 'https://docs.sqlalchemy.org/en/latest/', 'https://docs.sqlalchemy.org/en/latest/objects.inv', ), } # -- spelling --------------------------------------------------------------- spelling_exclude_patterns = [ 'credits.rst', ] factory-boy-3.3.3/docs/credits.rst000077700000000000000000000000001475011040400203172../CREDITSustar00rootroot00000000000000factory-boy-3.3.3/docs/examples.rst000066400000000000000000000072411475011040400172560ustar00rootroot00000000000000Examples ======== Here are some real-world examples of using FactoryBoy. Objects ------- First, let's define a couple of objects: .. code-block:: python class Account: def __init__(self, username, email, date_joined): self.username = username self.email = email self.date_joined = date_joined def __str__(self): return '%s (%s)' % (self.username, self.email) class Profile: 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 __str__(self): return '%s %s (%s)' % ( self.firstname, self.lastname, self.account.username, ) Factories --------- And now, we'll define the related factories: .. code-block:: python import datetime import factory 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 = 'John' lastname = 'Doe' We have now defined basic factories for our ``Account`` and ``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 = 'Jane' account__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-3.3.3/docs/fuzzy.rst000066400000000000000000000237061475011040400166330ustar00rootroot00000000000000Fuzzy 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 in :issue:`271`. 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` characters 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. .. warning:: Custom :class:`BaseFuzzyAttribute` subclasses **MUST** use :obj:`factory.random.randgen` as a randomness source; this ensures that data they generate can be regenerated using the simple state from :meth:`factory.random.get_random_state`. factory-boy-3.3.3/docs/ideas.rst000066400000000000000000000006511475011040400165230ustar00rootroot00000000000000Ideas ===== This is a list of future features that may be incorporated into factory_boy: * When a :class:`~factory.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-3.3.3/docs/index.rst000066400000000000000000000004521475011040400165440ustar00rootroot00000000000000.. include:: ../README.rst Contents, indices and tables ---------------------------- .. toctree:: :maxdepth: 2 introduction reference orms recipes fuzzy examples internals changelog credits ideas * :ref:`genindex` * :ref:`modindex` * :ref:`search` factory-boy-3.3.3/docs/internals.rst000066400000000000000000000066061475011040400174430ustar00rootroot00000000000000Internals ========= .. currentmodule:: factory Behind the scenes: steps performed when parsing a factory declaration, and when calling it. This section will be based on the following factory declaration: .. literalinclude:: ../tests/test_docs_internals.py :pyobject: UserFactory Parsing, Step 1: Metaclass and type declaration ----------------------------------------------- 1. Python parses the declaration and calls (thanks to the metaclass declaration): .. code-block:: python factory.base.BaseFactory.__new__( 'UserFactory', (factory.Factory,), attributes, ) 2. That metaclass removes :attr:`~Factory.Meta` and :attr:`~Factory.Params` from the class attributes, then generate the actual factory class (according to standard Python rules) 3. It initializes a :class:`FactoryOptions` object, and links it to the class Parsing, Step 2: adapting the class definition ----------------------------------------------- 1. The :class:`FactoryOptions` reads the options from the :attr:`class Meta ` declaration 2. It finds a few specific pointer (loading the model class, finding the reference factory for the sequence counter, etc.) 3. It copies declarations and parameters from parent classes 4. It scans current class attributes (from ``vars()``) to detect pre/post declarations 5. Declarations are split among pre-declarations and post-declarations (a raw value shadowing a post-declaration is seen as a post-declaration) .. note:: A declaration for ``foo__bar`` will be converted into parameter ``bar`` for declaration ``foo``. Instantiating, Step 1: Converging entry points ---------------------------------------------- First, decide the strategy: - If the entry point is specific to a strategy (:meth:`~Factory.build`, :meth:`~Factory.create_batch`, ...), use it - If it is generic (:meth:`~Factory.generate`, :meth:`Factory.__call__`), use the strategy defined at the :attr:`class Meta ` level Then, we'll pass the strategy and passed-in overrides to the ``Factory._generate`` method. .. note:: According to the project road map, a future version will use a ``Factory._generate_batch`` at its core instead. A factory's ``Factory._generate`` function actually delegates to a ``StepBuilder()`` object. This object will carry the overall "build an object" context (strategy, depth, and possibly other). Instantiating, Step 2: Preparing values --------------------------------------- 1. The ``StepBuilder`` merges overrides with the class-level declarations 2. The sequence counter for this instance is initialized 3. A ``Resolver`` is set up with all those declarations, and parses them in order; it will call each value's ``evaluate()`` method, including extra parameters. 4. If needed, the ``Resolver`` might recurse (through the ``StepBuilder``, e.g when encountering a :class:`SubFactory`. Instantiating, Step 3: Building the object ------------------------------------------ 1. The ``StepBuilder`` fetches the attributes computed by the ``Resolver``. 2. It applies renaming/adjustment rules 3. It passes them to the ``FactoryOptions.instantiate`` method, which forwards to the proper methods. 4. Post-declaration are applied (in declaration order) .. note:: This document discusses implementation details; there is no guarantee that the described methods names and signatures will be kept as is. factory-boy-3.3.3/docs/introduction.rst000066400000000000000000000231621475011040400201610ustar00rootroot00000000000000Introduction ============ 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 >>> # The sequence counter starts at 0 by default >>> UserFactory() >>> UserFactory() >>> # A value can be provided for a sequence-driven field >>> # but this still increments the sequence counter >>> UserFactory(username="ada.lovelace") >>> 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 To set or reset the sequence counter see :ref:`Forcing a sequence counter `. 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 overridden >>> 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 behavior: 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-3.3.3/docs/logo.png000066400000000000000000000522061475011040400163550ustar00rootroot00000000000000PNG  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-3.3.3/docs/make.bat000066400000000000000000000014331475011040400163100ustar00rootroot00000000000000@ECHO OFF pushd %~dp0 REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set SOURCEDIR=. set BUILDDIR=_build if "%1" == "" goto help %SPHINXBUILD% >NUL 2>NUL if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.http://sphinx-doc.org/ exit /b 1 ) %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% goto end :help %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% :end popd factory-boy-3.3.3/docs/orms.rst000066400000000000000000000465451475011040400164320ustar00rootroot00000000000000.. _orm: Using factory_boy with ORMs =========================== .. currentmodule:: factory factory_boy provides custom :class:`Factory` subclasses for various ORMs, adding dedicated features. Django ------ .. module:: 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. .. 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() [, ] .. warning:: When ``django_get_or_create`` is used, be aware that any new values passed to the Factory are **not** used to update an existing model. .. code-block:: pycon >>> john = UserFactory(username="john") # Fetches the existing user >>> john.email "john@example.com" >>> john = UserFactory( # Fetches the existing user >>> username="john", # and provides a new email value >>> email="a_new_email@example.com" >>> ) >>> john.email # The email value was not updated "john@example.com" .. attribute:: skip_postgeneration_save Transitional option to prevent :class:`~factory.django.DjangoModelFactory`'s ``_after_postgeneration`` from issuing a duplicate call to :meth:`~django.db.models.Model.save` on the created instance when :class:`factory.PostGeneration` hooks return a value. Extra fields """""""""""" .. class:: Password Applies :func:`~django.contrib.auth.hashers.make_password` to the clear-text argument before to generate the object. .. method:: __init__(self, password) :param str or None password: Default password. .. note:: When the ``password`` argument is ``None``, the resulting password is unusable as if ``set_unusable_password()`` were used. This is distinct from setting the password to an empty string. .. code-block:: python class UserFactory(factory.django.DjangoModelFactory): class Meta: model = models.User password = factory.django.Password('pw') .. code-block:: pycon >>> from django.contrib.auth.hashers import check_password >>> # Create user with the default password from the factory. >>> user = UserFactory.create() >>> check_password('pw', user.password) True >>> # Override user password at call time. >>> other_user = UserFactory.create(password='other_pw') >>> check_password('other_pw', other_user.password) True >>> # Set unusable password >>> no_password_user = UserFactory.create(password=None) >>> no_password_user.has_usable_password() False .. class:: FileField Custom declarations for :class:`django.db.models.FileField` .. method:: __init__(self, from_path='', from_file='', from_func='', data=b'', filename='example.dat') :param str from_path: Use data from the file located at ``from_path``, and keep its filename :param io.BytesIO from_file: Use the contents of the provided file object; use its filename if available, unless ``filename`` is also provided. :param Callable 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='', from_func='', 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 io.BytesIO from_file: Use the contents of the provided file object; use its filename if available :param Callable 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'``) :param str palette: The image palette (as supported by PIL) (default: ``'RGB'``) .. note:: If the value ``None`` was passed for the :class:`ImageField` 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 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 ---- .. module:: 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 ----------- .. module:: 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 ` is a :class:`mongoengine.EmbeddedDocument`, the :class:`~MongoEngineFactory`'s ``create`` function won't "save" it, since this wouldn't make sense. This feature makes it possible to use :class:`~factory.SubFactory` to create embedded document. A minimalist example: .. code-block:: python import mongoengine class Address(mongoengine.EmbeddedDocument): street = mongoengine.StringField() class Person(mongoengine.Document): name = mongoengine.StringField() address = mongoengine.EmbeddedDocumentField(Address) import factory class AddressFactory(factory.mongoengine.MongoEngineFactory): class Meta: model = Address street = factory.Sequence(lambda n: 'street%d' % n) class PersonFactory(factory.mongoengine.MongoEngineFactory): class Meta: model = Person name = factory.Sequence(lambda n: 'name%d' % n) address = factory.SubFactory(AddressFactory) SQLAlchemy ---------- .. module:: factory.alchemy Factory_boy also supports `SQLAlchemy`_ models through the :class:`SQLAlchemyModelFactory` class. To work, this class needs an `SQLAlchemy`_ session object affected to the :attr:`Meta.sqlalchemy_session ` attribute. .. _SQLAlchemy: https://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.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:: sqlalchemy_session_factory .. versionadded:: 3.3.0 :class:`~collections.abc.Callable` returning a :class:`~sqlalchemy.orm.Session` instance to use to communicate with the database. You can either provide the session through this attribute, or through :attr:`~factory.alchemy.SQLAlchemyOptions.sqlalchemy_session`, but not both at the same time. .. code-block:: python from . import common class UserFactory(factory.alchemy.SQLAlchemyModelFactory): class Meta: model = User sqlalchemy_session_factory = lambda: common.Session() username = 'john' .. attribute:: sqlalchemy_session_persistence Control the action taken by ``sqlalchemy_session`` at the end of a create call. Valid values are: * ``None``: do nothing * ``'flush'``: perform a session :meth:`~sqlalchemy.orm.Session.flush` * ``'commit'``: perform a session :meth:`~sqlalchemy.orm.Session.commit` The default value is ``None``. .. attribute:: sqlalchemy_get_or_create .. versionadded:: 3.0.0 Fields whose name are passed in this list will be used to perform a :meth:`Model.query.one_or_none() ` or the usual :meth:`Session.add() `: .. code-block:: python class UserFactory(factory.alchemy.SQLAlchemyModelFactory): class Meta: model = User sqlalchemy_session = session sqlalchemy_get_or_create = ('username',) username = 'john' .. code-block:: pycon >>> User.query.all() [] >>> UserFactory() # Creates a new user >>> User.query.all() [] >>> UserFactory() # Fetches the existing user >>> User.query.all() # No new user! [] >>> UserFactory(username='jack') # Creates another user >>> User.query.all() [, ] .. warning:: When ``sqlalchemy_get_or_create`` is used, be aware that any new values passed to the Factory are **not** used to update an existing model. .. code-block:: pycon >>> john = UserFactory(username="john") # Fetches the existing user >>> john.email "john@example.com" >>> john = UserFactory( # Fetches the existing user >>> username="john", # and provides a new email value >>> email="a_new_email@example.com" >>> ) >>> john.email # The email value was not updated "john@example.com" A (very) simple example: .. code-block:: python from sqlalchemy import Column, Integer, Unicode, create_engine from sqlalchemy.orm import declarative_base, 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: '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.scoped_session` * Each :class:`~SQLAlchemyModelFactory` subclass uses this :class:`~sqlalchemy.orm.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.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.scoped_session`: .. code-block:: python # myproject/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 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.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-3.3.3/docs/recipes.rst000066400000000000000000000444071475011040400170770ustar00rootroot00000000000000Common 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()`` is a :class:`~django.db.models.query.QuerySet` and will only hit the database when ``factory_boy`` starts iterating on it, i.e on 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, factory_related_name='user', action=models.UserLog.ACTION_CREATE, ) When a ``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. Prior to version 2.9, the solution to this was to override the ``factory.Factory._generate`` method on the factory. Since version 2.9, the :meth:`~factory.django.mute_signals` decorator should be used: .. code-block:: python from django.db.models.signals import post_save @factory.django.mute_signals(post_save) 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) @factory.django.mute_signals(post_save) 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, factory_related_name='user') .. OHAI_VIM:* .. code-block:: pycon >>> u = UserFactory(profile__title="Lord") >>> u.get_profile().title "Lord" Such behavior can be extended to other situations where a signal interferes with factory_boy related factories. Any factories that call these classes with :class:`~factory.SubFactory` will also need to be decorated in the same manner. .. _DEPRECATED: Release 4.0: post_generation and RelatedFactory will stop issuing calls to save(). Refs issues 316 and 366. .. 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 use the :meth:`~factory.django.mute_signals` decorator 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 or not extracted: # Simple build, or nothing to add, do nothing. return # Add the iterable of groups using bulk addition self.groups.add(*extracted) .. 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. For SQLAlchemy, change ``self.groups.add(group)`` in the above example to ``self.groups.append(group)``. Many-to-many relation with a 'through' -------------------------------------- If only one link is required, this can be simply performed with a :class:`~factory.RelatedFactory`. If more links are needed, simply add more :class:`~factory.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, factory_related_name='user', ) class UserWith2GroupsFactory(UserFactory): membership1 = factory.RelatedFactory( GroupLevelFactory, factory_related_name='user', group__name='Group1', ) membership2 = factory.RelatedFactory( GroupLevelFactory, factory_related_name='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 than the default :meth:`Model.objects.create() ` method: .. code-block:: python class UserFactory(factory.django.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 reproducible. 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' attributes mustn't conflict with preexisting 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 .. _recipe-random-management: Using reproducible randomness ----------------------------- Although using random values is great, it can provoke test flakiness. factory_boy provides a few helpers for this. .. note:: Those methods will seed the random engine used in both :class:`factory.Faker` and :mod:`factory.fuzzy` objects. Seeding the random engine The simplest way to manage randomness is to push a selected seed when starting tests: .. code-block:: python import factory.random # Pass in any value factory.random.reseed_random('my awesome project') Reproducing unseeded tests A project might choose not to use an explicit random seed (for better fuzzing), but still wishes to have reproducible tests. For such cases, use a combination of :meth:`factory.random.get_random_state()` and :meth:`factory.random.set_random_state()`. Since the random state structure is implementation-specific, we recommend passing it around as a base64-encoded pickle dump. .. code-block:: python class MyTestRunner: def setup_test_environment(self): state = os.environ.get('TEST_RANDOM_STATE') if state: try: decoded_state = pickle.loads(base64.b64decode(state.encode('ascii'))) except ValueError: decoded_state = None if decoded_state: factory.random.set_random_state(decoded_state) else: encoded_state = base64.b64encode(pickle.dumps(factory.random.get_random_state())) print("Current random state: %s" % encoded_state.decode('ascii')) super().setup_test_environment() 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) # Agent 000, Agent 001, Agent 002 username = factory.Faker('user_name') .. code-block:: pycon >>> factory.build(dict, FACTORY_CLASS=UserFactory) {'first_name': "Agent 000", 'username': 'john_doe'} Fuzzying Django model field choices ----------------------------------- When defining a :class:`~factory.fuzzy.FuzzyChoice` you can reuse the same choice list from the model field descriptor. Use the ``getter`` kwarg to select the first element from each choice tuple. .. code-block:: python class UserFactory(factory.Factory): class Meta: model = User # CATEGORY_CHOICES is a list of (key, title) tuples category = factory.fuzzy.FuzzyChoice(User.CATEGORY_CHOICES, getter=lambda c: c[0]) Django models with `GenericForeignKeys` --------------------------------------- For model which uses `GenericForeignKey `_ .. literalinclude:: ../examples/django_demo/generic_foreignkey/models.py We can create factories like this: .. literalinclude:: ../examples/django_demo/generic_foreignkey/factories.py factory-boy-3.3.3/docs/reference.rst000066400000000000000000001754271475011040400174120ustar00rootroot00000000000000Reference ========= .. module:: 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 behavior 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 .. method:: get_model_class() Returns the actual model class (:attr:`FactoryOptions.model` might be the path to the class; this function will always return a proper class). .. 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:** .. classmethod:: __call__(**kwargs) 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 ``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 ``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 ``size`` stubs from the :class:`Factory`. .. classmethod:: generate(cls, strategy, **kwargs) Provide a new instance, with the provided ``strategy``. .. classmethod:: generate_batch(cls, strategy, size, **kwargs) Provides a list of ``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 ``size`` instances, either built or created according to ``create``. **Extension points:** A :class:`Factory` subclass may override a couple of class methods to adapt its behavior: .. 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 @classmethod 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.build().sequenced_attribute 0 >>> SomeFactory.reset_sequence(4) >>> SomeFactory.build().sequenced_attribute 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 >>> ConferenceFactory(duration='short') >>> ConferenceFactory(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 .. function:: use_strategy(strategy) .. deprecated:: 3.2 Use :py:attr:`factory.FactoryOptions.strategy` instead. *Decorator* Change the default strategy of the decorated :class:`Factory` to the chosen ``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 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 io.StringIO 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 ``stream``; 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 `faker `_; its argument is the name of a ``faker`` 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' Some providers accept parameters; they should be passed after the provider name: .. code-block:: python class UserFactory(factory.Factory): class Meta: model = User arrival = factory.Faker( 'date_between_dates', date_start=datetime.date(2020, 1, 1), date_end=datetime.date(2020, 5, 31), ) As with :class:`~factory.SubFactory`, the parameters can be any valid declaration. This does not apply to the provider name or the locale. .. code-block:: python class TripFactory(factory.Factory): class Meta: model = Trip departure = factory.Faker( 'date', end_datetime=datetime.date.today(), ) arrival = factory.Faker( 'date_between_dates', date_start=factory.SelfAttribute('..departure'), ) .. note:: When using :class:`~factory.SelfAttribute` or :class:`~factory.LazyAttribute` in a :class:`factory.Faker` parameter, the current object is the declarations provided to the :class:`~factory.Faker` declaration; go :ref:`up a level ` to reach fields of the surrounding :class:`~factory.Factory`, as shown in the ``SelfAttribute('..xxx')`` example above. .. 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 ``faker``; 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 an argument a function to call; that should not take any arguments 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 overridden >>> LogFactory(timestamp=now - timedelta(days=1)) :class:`LazyFunction` is also useful for assigning copies of mutable objects (like lists) to an object's property. Example: .. code-block:: python DEFAULT_TEAM = ['Player1', 'Player2'] class TeamFactory(factory.Factory): class Meta: model = models.Team teammates = factory.LazyFunction(lambda: list(DEFAULT_TEAM)) Decorator ~~~~~~~~~ The class :class:`LazyFunction` does not provide a decorator. For complex cases, use :meth:`~factory.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 ``builder.Resolver``: 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 = "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 '%s@example.com' % clean_name .. code-block:: pycon >>> joel = UserFactory(name="Joël") >>> joel.email 'joel@example.com' Transformer """"""""""" .. class:: Transformer(default_value, *, transform) .. versionadded:: 3.3.0 A :class:`Transformer` applies a ``transform`` function to the provided value before to set the transformed value on the generated object. It expects one positional argument and one keyword argument: - ``default_value``: the default value, which passes through the ``transform`` function. - ``transform``: a function taking the value as parameter and returning the transformed value, .. code-block:: python class UpperFactory(factory.Factory): name = factory.Transformer("Joe", transform=str.upper) class Meta: model = Upper .. code-block:: pycon >>> UpperFactory().name 'JOE' >>> UpperFactory(name="John").name 'JOHN' Disabling ~~~~~~~~~ To disable a :class:`Transformer`, wrap the value in ``Transformer.Force``: .. code-block:: pycon >>> UpperFactory(name=factory.Transformer.Force("John")).name 'John' Sequence """""""" .. class:: Sequence(lambda) 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. .. 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-0000' >>> UserFactory().phone '123-555-0001' .. note:: The sequence counter starts at 0 and can be set or reset, see :ref:`Forcing a sequence counter `. 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 # current sequence counter at 9999 '000-555-9999' >>> UserFactory().phone # current sequence counter at 10000 '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 '0040', 'A23-B040' >>> u2 = UserFactory() >>> u2.phone, u2.office '0041', 'A23-B041' 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-0000' >>> e = EmployeeFactory() >>> e.phone, e.office_phone '123-555-0001', '0001' >>> u2 = UserFactory() >>> u2.phone '123-555-0002' .. _forcing-a-sequence-counter: 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@s0.example.com' >>> UserFactory(login='jack').email 'jack@s1.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 :class:`~factory.SubFactory`'s ``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.fuzzy.FuzzyDate() birthmonth = factory.SelfAttribute('birthdate.month') .. code-block:: pycon >>> u = UserFactory() >>> u.birthdate date(2000, 3, 15) >>> u.birthmonth 3 .. _factory-parent: 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 ``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 successive 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 behavior 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 .. warning:: Values from the underlying iterator are *kept* in memory; once the initial iterator has been emptied, saved values are used instead of executing the function instead. Use ``factory.Iterator(my_func, cycle=False)`` to disable value recycling. 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` behavior 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` behavior 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. Maybe """"" .. class:: Maybe(decider, yes_declaration, no_declaration) Sometimes, the way to build a given field depends on the value of another, for instance of a parameter. In those cases, use the :class:`~factory.Maybe` declaration: it takes the name of a "decider" boolean field, and two declarations; depending on the value of the field whose name is held in the 'decider' parameter, it will apply the effects of one or the other declaration: .. code-block:: python class UserFactory(factory.Factory): class Meta: model = User is_active = True deactivation_date = factory.Maybe( 'is_active', yes_declaration=None, no_declaration=factory.fuzzy.FuzzyDateTime(timezone.now() - datetime.timedelta(days=10)), ) .. code-block:: pycon >>> u = UserFactory(is_active=True) >>> u.deactivation_date None >>> u = UserFactory(is_active=False) >>> u.deactivation_date datetime.datetime(2017, 4, 1, 23, 21, 23, tzinfo=UTC) .. note:: If the condition for the decider is complex, use a :class:`LazyAttribute` defined in the :attr:`~Factory.Params` section of your factory to handle the computation. .. _post-generation-hooks: 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. - :class:`RelatedFactoryList`: this builds or creates a *list* of the 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:: factory_related_name If set, the object generated by the factory declaring the ``RelatedFactory`` is passed as keyword argument to the related factory. .. 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, # Not CityFactory() factory_related_name='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 is 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. However, the build context is passed down to that factory; this means that calls to :class:`factory.SelfAttribute` *can* go back to the calling factory's context: .. code-block:: python class CountryFactory(factory.Factory): class Meta: model = Country lang = 'fr' capital_city = factory.RelatedFactory( CityFactory, factory_related_name='capital_of', # Would also work with SelfAttribute('capital_of.lang') main_lang=factory.SelfAttribute('..lang'), ) RelatedFactoryList """""""""""""""""" .. class:: RelatedFactoryList(factory, factory_related_name='', size=2, **kwargs) .. OHAI_VIM** A :class:`RelatedFactoryList` behaves like a :class:`RelatedFactory`, only it returns a list of factories. This is useful for simulating one-to-many relations, rather than the one-to-one relation generated by :class:`RelatedFactory`. .. 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:: factory_related_name If set, the object generated by the factory declaring the ``RelatedFactory`` is passed as keyword argument to the related factory. .. attribute:: size Either an ``int``, or a ``lambda`` that returns an ``int``, which will define the number of related Factories to be generated for each parent object. .. versionadded:: 2.12 Note that the API for :class:`RelatedFactoryList` is considered experimental, and might change in a future version for increased consistency with other declarations. .. note:: Note that using a ``lambda`` for ``size`` allows the number of related objects per parents object to vary. This is useful for testing, when you likely don't want your mock data to have parent objects with the exact same, static number of related objects. .. code-block:: python class FooFactory(factory.Factory): class Meta: model = Foo # Generate a list of `factory` objects of random size, ranging from 1 -> 5 bar = factory.RelatedFactoryList(BarFactory, size=lambda: random.randint(1, 5)) # Each Foo object will have exactly 3 Bar objects generated for its foobar attribute. foobar = factory.RelatedFactoryList(BarFactory, size=3) 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``, ``create``, ``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(obj, create, extracted, **kwargs): if not create: return path = extracted or os.path.join('/tmp/mbox/', obj.login) os.path.makedirs(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, *arg, **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:: arg The default, optional, positional argument 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, we could use ``PostGenerationMethodCall`` to register created users in an external system. .. code-block:: python class User(models.Model): name = models.CharField(max_length=191) def register(self, system, auth_token="ABC"): self.registration_id = system.register(auth_token) class UserFactory(factory.django.DjangoModelFactory): class Meta: model = User name = 'user' register = factory.PostGenerationMethodCall("register", DefaultRegistry()) 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. .. code-block:: pycon >>> # DefaultRegistry uses UUID for identifiers. >>> UserFactory().registration_id 'edf42c11-0065-43ad-ad3d-78ab7497aaae' >>> # OtherRegistry uses int for identifiers. >>> UserFactory(register=OtherRegistry()).registration_id 123456 .. warning:: In order to keep a consistent and simple API, a :class:`PostGenerationMethodCall` allows *at most one* positional argument; all other parameters should be passed as keyword arguments. Keywords extracted from the factory arguments are merged into the defaults present in the :class:`PostGenerationMethodCall` declaration. .. code-block:: pycon >>> # Calls user.register(DefaultRegistry(), auth_token="DEF") >>> UserFactory(register__auth_token="DEF") 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. Helper methods can be used to create factories in a dynamic way based on parameters. Internally, helper methods use :func:`make_factory` to create a new :class:`Factory` and perform additional calls on the newly created :class:`Factory` according to the method name. Please note, that all Factories created with this methods inherit from the :class:`factory.Factory` class. For full support of your ``ORM``, specify a base class with the ``FACTORY_CLASS`` parameter as shown in :func:`make_factory` examples. .. function:: build(klass, FACTORY_CLASS=None, **kwargs) .. function:: build_batch(klass, size, FACTORY_CLASS=None, **kwargs) Create a factory for ``klass`` using declarations passed in kwargs; return an instance built from that factory with :data:`BUILD_STRATEGY`, or a list of ``size`` instances (for :func:`build_batch`). :param type 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 ``klass`` using declarations passed in kwargs; return an instance created from that factory with :data:`CREATE_STRATEGY`, or a list of ``size`` instances (for :func:`create_batch`). :param type 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 ``klass`` using declarations passed in kwargs; return an instance stubbed from that factory with :data:`STUB_STRATEGY`, or a list of ``size`` instances (for :func:`stub_batch`). :param type 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 ``klass`` using declarations passed in kwargs; return an instance generated from that factory with the ``strategy`` strategy, or a list of ``size`` instances (for :func:`generate_batch`). :param type 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 ``klass`` using declarations passed in kwargs; return an instance generated from that factory according to the ``create`` flag, or a list of ``size`` instances (for :func:`simple_generate_batch`). :param type 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`) Randomness management --------------------- .. module:: factory.random Using :mod:`random` in factories allows to "fuzz" a program efficiently. However, it's sometimes required to *reproduce* a failing test. :mod:`factory.fuzzy` and :class:`factory.Faker` share a dedicated instance of :class:`random.Random`, which can be managed through the :mod:`factory.random` module: .. method:: get_random_state() Call :meth:`get_random_state` to retrieve the random generator's current state. This method synchronizes both Faker’s and factory_boy’s random state. The returned object is implementation-specific. .. 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. That seed can be anything accepted by :func:`random.seed`. .. data:: randgen The :class:`random.Random` global instance used by :mod:`factory.fuzzy` and :class:`factory.Faker`. See :ref:`recipe-random-management` for help in using those methods in a test setup. factory-boy-3.3.3/docs/spelling_wordlist.txt000066400000000000000000000005111475011040400212040ustar00rootroot00000000000000args backends backtrace boolean datastore datetimes dicts Django filename fuzzer fuzzers fuzzying getter instantiation iterable iterables kwarg kwargs metaclass misconfiguration Mogo MongoDB mongoengine pre prepend pymongo queryset recurse subclassed subclasses subclassing subfactories thoughtbot tox unexplicit username lookup factory-boy-3.3.3/examples/000077500000000000000000000000001475011040400155705ustar00rootroot00000000000000factory-boy-3.3.3/examples/Makefile000066400000000000000000000002441475011040400172300ustar00rootroot00000000000000EXAMPLES = django_demo flask_alchemy TEST_TARGETS = $(addprefix runtest-,$(EXAMPLES)) test: $(TEST_TARGETS) $(TEST_TARGETS): runtest-%: cd $* && ./runtests.sh factory-boy-3.3.3/examples/django_demo/000077500000000000000000000000001475011040400200365ustar00rootroot00000000000000factory-boy-3.3.3/examples/django_demo/django_demo/000077500000000000000000000000001475011040400223045ustar00rootroot00000000000000factory-boy-3.3.3/examples/django_demo/django_demo/__init__.py000066400000000000000000000000001475011040400244030ustar00rootroot00000000000000factory-boy-3.3.3/examples/django_demo/django_demo/settings.py000066400000000000000000000060761475011040400245270ustar00rootroot00000000000000""" Django settings for django_demo project. Generated by 'django-admin startproject' using Django 1.10. For more information on this file, see https://docs.djangoproject.com/en/1.10/topics/settings/ For the full list of settings and their values, see https://docs.djangoproject.com/en/1.10/ref/settings/ """ import os # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = 'kh)1s3@93ju6f6$qx!758f6h^(_3d0brqzoxubo@xsn3*%2wgu' # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True ALLOWED_HOSTS = [] # Application definition INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'generic_foreignkey' ] MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] ROOT_URLCONF = 'django_demo.urls' TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ] WSGI_APPLICATION = 'django_demo.wsgi.application' # Database # https://docs.djangoproject.com/en/1.10/ref/settings/#databases DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), } } # Password validation # https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators AUTH_PASSWORD_VALIDATORS = [ { 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', }, { 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', }, { 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', }, { 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', }, ] # Internationalization # https://docs.djangoproject.com/en/1.10/topics/i18n/ LANGUAGE_CODE = 'en-us' TIME_ZONE = 'UTC' USE_I18N = True USE_L10N = True USE_TZ = True # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/1.10/howto/static-files/ STATIC_URL = '/static/' factory-boy-3.3.3/examples/django_demo/django_demo/urls.py000066400000000000000000000013731475011040400236470ustar00rootroot00000000000000"""django_demo URL Configuration The `urlpatterns` list routes URLs to views. For more information please see: https://docs.djangoproject.com/en/1.10/topics/http/urls/ Examples: Function views 1. Add an import: from my_app import views 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') Class-based views 1. Add an import: from other_app.views import Home 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') Including another URLconf 1. Import the include() function: from django.conf.urls import url, include 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) """ from django.contrib import admin from django.urls import path urlpatterns = [ path('admin/', admin.site.urls), ] factory-boy-3.3.3/examples/django_demo/django_demo/wsgi.py000066400000000000000000000006201475011040400236250ustar00rootroot00000000000000""" WSGI config for django_demo project. It exposes the WSGI callable as a module-level variable named ``application``. For more information on this file, see https://docs.djangoproject.com/en/1.10/howto/deployment/wsgi/ """ import os from django.core.wsgi import get_wsgi_application os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_demo.settings") application = get_wsgi_application() factory-boy-3.3.3/examples/django_demo/generic_foreignkey/000077500000000000000000000000001475011040400236745ustar00rootroot00000000000000factory-boy-3.3.3/examples/django_demo/generic_foreignkey/__init__.py000066400000000000000000000000001475011040400257730ustar00rootroot00000000000000factory-boy-3.3.3/examples/django_demo/generic_foreignkey/apps.py000066400000000000000000000001561475011040400252130ustar00rootroot00000000000000from django.apps import AppConfig class GenericForeignKeyConfig(AppConfig): name = 'generic_foreignkey' factory-boy-3.3.3/examples/django_demo/generic_foreignkey/factories.py000066400000000000000000000017461475011040400262350ustar00rootroot00000000000000from django.contrib.auth.models import Group, User from django.contrib.contenttypes.models import ContentType import factory.django from .models import TaggedItem class UserFactory(factory.django.DjangoModelFactory): first_name = 'Adam' class Meta: model = User class GroupFactory(factory.django.DjangoModelFactory): name = 'group' class Meta: model = Group class TaggedItemFactory(factory.django.DjangoModelFactory): object_id = factory.SelfAttribute('content_object.id') content_type = factory.LazyAttribute( lambda o: ContentType.objects.get_for_model(o.content_object)) class Meta: exclude = ['content_object'] abstract = True class TaggedUserFactory(TaggedItemFactory): content_object = factory.SubFactory(UserFactory) class Meta: model = TaggedItem class TaggedGroupFactory(TaggedItemFactory): content_object = factory.SubFactory(GroupFactory) class Meta: model = TaggedItem factory-boy-3.3.3/examples/django_demo/generic_foreignkey/migrations/000077500000000000000000000000001475011040400260505ustar00rootroot00000000000000factory-boy-3.3.3/examples/django_demo/generic_foreignkey/migrations/0001_initial.py000066400000000000000000000015051475011040400305140ustar00rootroot00000000000000import django.db.models.deletion from django.db import migrations, models class Migration(migrations.Migration): initial = True dependencies = [ ('contenttypes', '0002_remove_content_type_name'), ] operations = [ migrations.CreateModel( name='TaggedItem', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('tag', models.SlugField()), ('object_id', models.PositiveIntegerField()), ( 'content_type', models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType' ) ), ], ), ] factory-boy-3.3.3/examples/django_demo/generic_foreignkey/migrations/__init__.py000066400000000000000000000000001475011040400301470ustar00rootroot00000000000000factory-boy-3.3.3/examples/django_demo/generic_foreignkey/models.py000066400000000000000000000010011475011040400255210ustar00rootroot00000000000000from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType from django.db import models class TaggedItem(models.Model): """Example GenericForeignKey model from django docs""" tag = models.SlugField() content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) object_id = models.PositiveIntegerField() content_object = GenericForeignKey('content_type', 'object_id') def __str__(self): return self.tag factory-boy-3.3.3/examples/django_demo/generic_foreignkey/tests.py000066400000000000000000000020701475011040400254070ustar00rootroot00000000000000from django.contrib.auth.models import Group, User from django.contrib.contenttypes.models import ContentType from django.test import TestCase from .factories import GroupFactory, TaggedGroupFactory, TaggedUserFactory, UserFactory class GenericFactoryTest(TestCase): def test_user_factory(self): user = UserFactory() self.assertEqual(user.first_name, 'Adam') def test_group_factory(self): group = GroupFactory() self.assertEqual(group.name, 'group') def test_generic_user(self): model = TaggedUserFactory(tag='user') self.assertEqual(model.tag, 'user') self.assertTrue(isinstance(model.content_object, User)) self.assertEqual(model.content_type, ContentType.objects.get_for_model(model.content_object)) def test_generic_group(self): model = TaggedGroupFactory(tag='group') self.assertEqual(model.tag, 'group') self.assertTrue(isinstance(model.content_object, Group)) self.assertEqual(model.content_type, ContentType.objects.get_for_model(model.content_object)) factory-boy-3.3.3/examples/django_demo/manage.py000077500000000000000000000003741475011040400216470ustar00rootroot00000000000000#!/usr/bin/env python import os import sys if __name__ == "__main__": os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_demo.settings") from django.core.management import execute_from_command_line execute_from_command_line(sys.argv) factory-boy-3.3.3/examples/django_demo/requirements.txt000066400000000000000000000000071475011040400233170ustar00rootroot00000000000000Django factory-boy-3.3.3/examples/django_demo/runtests.sh000077500000000000000000000000631475011040400222630ustar00rootroot00000000000000#!/bin/sh cd $(dirname $0) python manage.py test; factory-boy-3.3.3/examples/flask_alchemy/000077500000000000000000000000001475011040400203725ustar00rootroot00000000000000factory-boy-3.3.3/examples/flask_alchemy/demoapp.py000066400000000000000000000017421475011040400223750ustar00rootroot00000000000000# Copyright: See the LICENSE file. from flask import Flask from flask_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 f'' factory-boy-3.3.3/examples/flask_alchemy/demoapp_factories.py000066400000000000000000000010311475011040400244230ustar00rootroot00000000000000import demoapp import factory.alchemy import factory.fuzzy 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-3.3.3/examples/flask_alchemy/requirements.txt000066400000000000000000000000271475011040400236550ustar00rootroot00000000000000Flask Flask-SQLAlchemy factory-boy-3.3.3/examples/flask_alchemy/runtests.sh000077500000000000000000000001301475011040400226120ustar00rootroot00000000000000#!/bin/sh cd $(dirname $0) for f in test_*.py; do python -m unittest discover done factory-boy-3.3.3/examples/flask_alchemy/test_demoapp.py000066400000000000000000000020331475011040400234260ustar00rootroot00000000000000import unittest 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_context = demoapp.app.app_context() self.app_context.push() self.app = demoapp.app.test_client() self.db = demoapp.db self.db.create_all() def tearDown(self): self.db.drop_all() self.app_context.pop() 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())) factory-boy-3.3.3/examples/requirements.txt000066400000000000000000000001021475011040400210450ustar00rootroot00000000000000-r flask_alchemy/requirements.txt -r django_demo/requirements.txt factory-boy-3.3.3/factory/000077500000000000000000000000001475011040400154215ustar00rootroot00000000000000factory-boy-3.3.3/factory/__init__.py000066400000000000000000000025451475011040400175400ustar00rootroot00000000000000# Copyright: See the LICENSE file. import importlib.metadata from .base import ( BaseDictFactory, BaseListFactory, DictFactory, Factory, ListFactory, StubFactory, use_strategy, ) from .declarations import ( ContainerAttribute, Dict, Iterator, LazyAttribute, LazyAttributeSequence, LazyFunction, List, Maybe, PostGeneration, PostGenerationMethodCall, RelatedFactory, RelatedFactoryList, SelfAttribute, Sequence, SubFactory, Trait, Transformer, ) from .enums import BUILD_STRATEGY, CREATE_STRATEGY, STUB_STRATEGY from .errors import FactoryError from .faker import Faker from .helpers import ( build, build_batch, container_attribute, create, create_batch, debug, generate, generate_batch, iterator, lazy_attribute, lazy_attribute_sequence, make_factory, post_generation, sequence, simple_generate, simple_generate_batch, stub, stub_batch, ) try: from . import alchemy except ImportError: pass try: from . import django except ImportError: pass try: from . import mogo except ImportError: pass try: from . import mongoengine except ImportError: pass __author__ = 'Raphaël Barrois ' __version__ = importlib.metadata.version("factory_boy") factory-boy-3.3.3/factory/alchemy.py000066400000000000000000000111071475011040400174150ustar00rootroot00000000000000# Copyright: See the LICENSE file. from sqlalchemy.exc import IntegrityError from sqlalchemy.orm.exc import NoResultFound from . import base, errors 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) ) @staticmethod def _check_has_sqlalchemy_session_set(meta, value): if value is not None and getattr(meta, "sqlalchemy_session", None) is not None: raise RuntimeError("Provide either a sqlalchemy_session or a sqlalchemy_session_factory, not both") def _build_default_options(self): return super()._build_default_options() + [ base.OptionDefault('sqlalchemy_get_or_create', (), inherit=True), base.OptionDefault('sqlalchemy_session', None, inherit=True), base.OptionDefault( 'sqlalchemy_session_factory', None, inherit=True, checker=self._check_has_sqlalchemy_session_set ), base.OptionDefault( 'sqlalchemy_session_persistence', None, inherit=True, checker=self._check_sqlalchemy_session_persistence, ), ] class SQLAlchemyModelFactory(base.Factory): """Factory for SQLAlchemy models. """ _options_class = SQLAlchemyOptions _original_params = None class Meta: abstract = True @classmethod def _generate(cls, strategy, params): # Original params are used in _get_or_create if it cannot build an # object initially due to an IntegrityError being raised cls._original_params = params return super()._generate(strategy, params) @classmethod def _get_or_create(cls, model_class, session, args, kwargs): key_fields = {} for field in cls._meta.sqlalchemy_get_or_create: if field not in kwargs: raise errors.FactoryError( "sqlalchemy_get_or_create - " "Unable to find initialization value for '%s' in factory %s" % (field, cls.__name__)) key_fields[field] = kwargs.pop(field) obj = session.query(model_class).filter_by( *args, **key_fields).one_or_none() if not obj: try: obj = cls._save(model_class, session, args, {**key_fields, **kwargs}) except IntegrityError as e: session.rollback() if cls._original_params is None: raise e get_or_create_params = { lookup: value for lookup, value in cls._original_params.items() if lookup in cls._meta.sqlalchemy_get_or_create } if get_or_create_params: try: obj = session.query(model_class).filter_by( **get_or_create_params).one() except NoResultFound: # Original params are not a valid lookup and triggered a create(), # that resulted in an IntegrityError. raise e else: raise e return obj @classmethod def _create(cls, model_class, *args, **kwargs): """Create an instance of the model, and save it to the database.""" session_factory = cls._meta.sqlalchemy_session_factory if session_factory: cls._meta.sqlalchemy_session = session_factory() session = cls._meta.sqlalchemy_session if session is None: raise RuntimeError("No session provided.") if cls._meta.sqlalchemy_get_or_create: return cls._get_or_create(model_class, session, args, kwargs) return cls._save(model_class, session, args, kwargs) @classmethod def _save(cls, model_class, session, args, kwargs): session_persistence = cls._meta.sqlalchemy_session_persistence 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-3.3.3/factory/base.py000066400000000000000000000576011475011040400167160ustar00rootroot00000000000000# Copyright: See the LICENSE file. import collections import logging import warnings from typing import Generic, List, Type, TypeVar from . import builder, declarations, enums, errors, utils logger = logging.getLogger('factory.generate') T = TypeVar('T') # 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 == enums.BUILD_STRATEGY: return cls.build(**kwargs) elif cls._meta.strategy == enums.CREATE_STRATEGY: return cls.create(**kwargs) elif cls._meta.strategy == enums.STUB_STRATEGY: return cls.stub(**kwargs) else: raise errors.UnknownStrategy('Unknown Meta.strategy: {}'.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().__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 f'<{cls.__name__} for {cls._meta.model}>' class BaseMeta: abstract = True strategy = enums.CREATE_STRATEGY class OptionDefault: """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: def __init__(self): self.factory = None self.base_factory = None self.base_declarations = {} self.parameters = {} self.parameters_dependencies = {} self.pre_declarations = builder.DeclarationSet() self.post_declarations = builder.DeclarationSet() self._counter = None self.counter_reference = None @property def declarations(self): base_declarations = dict(self.base_declarations) for name, param in utils.sort_ordered_objects(self.parameters.items(), getter=lambda item: item[1]): base_declarations.update(param.as_declarations(name, base_declarations)) return base_declarations 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. """ def is_model(meta, value): if isinstance(value, FactoryMetaClass): raise TypeError( "%s is already a %s" % (repr(value), Factory.__name__) ) return [ OptionDefault('model', None, inherit=True, checker=is_model), OptionDefault('abstract', False, inherit=False), OptionDefault('strategy', enums.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 = { 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.get_model_class() if self.model is None: self.abstract = True self.counter_reference = self._get_counter_reference() # Scan the inheritance chain, starting from the furthest point, # excluding the current class, to retrieve all declarations. for parent in reversed(self.factory.__mro__[1:]): if not hasattr(parent, '_meta'): continue self.base_declarations.update(parent._meta.base_declarations) self.parameters.update(parent._meta.parameters) for k, v in vars(self.factory).items(): if self._is_declaration(k, v): self.base_declarations[k] = v if params is not None: for k, v in utils.sort_ordered_objects(vars(params).items(), getter=lambda item: item[1]): if not k.startswith('_'): self.parameters[k] = declarations.SimpleParameter.wrap(v) self._check_parameter_dependencies(self.parameters) self.pre_declarations, self.post_declarations = builder.parse_declarations(self.declarations) 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._meta.counter_reference else: return self def _initialize_counter(self): """Initialize our counter pointer. If we're the top-level factory, instantiate a new counter Otherwise, point to the top-level factory's counter. """ if self._counter is not None: return if self.counter_reference is self: self._counter = _Counter(seq=self.factory._setup_next_sequence()) else: self.counter_reference._initialize_counter() self._counter = self.counter_reference._counter def next_sequence(self): """Retrieve a new sequence ID. This will call, in order: - 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. """ self._initialize_counter() return self._counter.next() def reset_sequence(self, value=None, force=False): self._initialize_counter() if self.counter_reference is not self and not force: raise ValueError( "Can't reset a sequence on descendant factory %r; reset sequence on %r or use `force=True`." % (self.factory, self.counter_reference.factory)) if value is None: value = self.counter_reference.factory._setup_next_sequence() self._counter.reset(value) def prepare_arguments(self, attributes): """Convert an attributes dict to a (args, kwargs) tuple.""" kwargs = dict(attributes) # 1. Extension points kwargs = self.factory._adjust_kwargs(**kwargs) # 2. Remove hidden objects kwargs = { k: v for k, v in kwargs.items() if k not in self.exclude and k not in self.parameters and v is not declarations.SKIP } # 3. Rename fields for old_name, new_name in self.rename.items(): if old_name in kwargs: kwargs[new_name] = kwargs.pop(old_name) # 4. Extract inline args args = tuple( kwargs.pop(arg_name) for arg_name in self.inline_args ) return args, kwargs def instantiate(self, step, args, kwargs): model = self.get_model_class() if step.builder.strategy == enums.BUILD_STRATEGY: return self.factory._build(model, *args, **kwargs) elif step.builder.strategy == enums.CREATE_STRATEGY: return self.factory._create(model, *args, **kwargs) else: assert step.builder.strategy == enums.STUB_STRATEGY return StubObject(**kwargs) def use_postgeneration_results(self, step, instance, results): self.factory._after_postgeneration( instance, create=step.builder.strategy == enums.CREATE_STRATEGY, results=results, ) 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 enums.get_builder_phase(value): # All objects with a defined 'builder phase' are declarations. return True return not name.startswith("_") def _check_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.Parameter): 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 %r; Params around %s" % (self.factory, ', '.join(cyclic))) return deps def get_model_class(self): """Extension point for loading model classes. This can be overridden in framework-specific subclasses to hook into existing model repositories, for instance. """ return self.model def __str__(self): return "<%s for %s>" % (self.__class__.__name__, self.factory.__name__) def __repr__(self): return str(self) # Factory base classes class _Counter: """Simple, naive counter. Attributes: for_class (obj): the class this counter related to seq (int): the next value """ def __init__(self, seq): self.seq = seq def next(self): value = self.seq self.seq += 1 return value def reset(self, next_value=0): self.seq = next_value class BaseFactory(Generic[T]): """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. """ cls._meta.reset_sequence(value, force=force) @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 _adjust_kwargs(cls, **kwargs): """Extension point for custom kwargs adjustment.""" return kwargs @classmethod def _generate(cls, strategy, params): """generate the object. Args: params (dict): attributes to use for generating the object strategy: the strategy to use """ 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__)) step = builder.StepBuilder(cls._meta, params, strategy) return step.build() @classmethod def _after_postgeneration(cls, instance, create, results=None): """Hook called after post-generation declarations have been handled. Args: instance (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) -> T: """Build an instance of the associated class, with overridden attrs. The instance will not be saved and persisted to any datastore. """ return cls._generate(enums.BUILD_STRATEGY, kwargs) @classmethod def build_batch(cls, size: int, **kwargs) -> List[T]: """Build a batch of instances of the given class, with overridden attrs. The instances will not be saved and persisted to any datastore. 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) -> T: """Create an instance of the associated class, with overridden attrs. The instance will be saved and persisted in the appropriate datastore. """ return cls._generate(enums.CREATE_STRATEGY, kwargs) @classmethod def create_batch(cls, size: int, **kwargs) -> List[T]: """Create a batch of instances of the given class, with overridden attrs. The instances will be saved and persisted in the appropriate datastore. 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 overridden attrs. This will return an object whose attributes are those defined in this factory's declarations or in the extra kwargs. """ return cls._generate(enums.STUB_STRATEGY, kwargs) @classmethod def stub_batch(cls, size, **kwargs): """Stub a batch of instances of the given class, with overridden 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 (enums.STUB_STRATEGY, enums.BUILD_STRATEGY, enums.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 (enums.STUB_STRATEGY, enums.BUILD_STRATEGY, enums.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 = enums.CREATE_STRATEGY if create else enums.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 = enums.CREATE_STRATEGY if create else enums.BUILD_STRATEGY return cls.generate_batch(strategy, size, **kwargs) class Factory(BaseFactory[T], metaclass=FactoryMetaClass): """Factory base with build and create support. This class has the ability to support multiple ORMs by using custom creation functions. """ # Backwards compatibility AssociatedClassError: Type[Exception] class Meta(BaseMeta): pass # Add the association after metaclass execution. # Otherwise, AssociatedClassError would be detected as a declaration. Factory.AssociatedClassError = errors.AssociatedClassError class StubObject: """A generic container.""" def __init__(self, **kwargs): for field, value in kwargs.items(): setattr(self, field, value) class StubFactory(Factory): class Meta: strategy = enums.STUB_STRATEGY model = 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) # kwargs are constructed from a list, their insertion order matches the list # order, no additional sorting is required. values = kwargs.values() 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. """ warnings.warn( "use_strategy() is deprecated and will be removed in the future.", DeprecationWarning, stacklevel=2, ) def wrapped_class(klass): klass._meta.strategy = new_strategy return klass return wrapped_class factory-boy-3.3.3/factory/builder.py000066400000000000000000000311731475011040400174260ustar00rootroot00000000000000"""Build factory instances.""" import collections from . import enums, errors, utils DeclarationWithContext = collections.namedtuple( 'DeclarationWithContext', ['name', 'declaration', 'context'], ) class DeclarationSet: """A set of declarations, including the recursive parameters. Attributes: declarations (dict(name => declaration)): the top-level declarations contexts (dict(name => dict(subfield => value))): the nested parameters related to a given top-level declaration This object behaves similarly to a dict mapping a top-level declaration name to a DeclarationWithContext, containing field name, declaration object and extra context. """ def __init__(self, initial=None): self.declarations = {} self.contexts = collections.defaultdict(dict) self.update(initial or {}) @classmethod def split(cls, entry): """Split a declaration name into a (declaration, subpath) tuple. Examples: >>> DeclarationSet.split('foo__bar') ('foo', 'bar') >>> DeclarationSet.split('foo') ('foo', None) >>> DeclarationSet.split('foo__bar__baz') ('foo', 'bar__baz') """ if enums.SPLITTER in entry: return entry.split(enums.SPLITTER, 1) else: return (entry, None) @classmethod def join(cls, root, subkey): """Rebuild a full declaration name from its components. for every string x, we have `join(split(x)) == x`. """ if subkey is None: return root return enums.SPLITTER.join((root, subkey)) def copy(self): return self.__class__(self.as_dict()) def update(self, values): """Add new declarations to this set/ Args: values (dict(name, declaration)): the declarations to ingest. """ for k, v in values.items(): root, sub = self.split(k) if sub is None: self.declarations[root] = v else: self.contexts[root][sub] = v extra_context_keys = set(self.contexts) - set(self.declarations) if extra_context_keys: raise errors.InvalidDeclarationError( "Received deep context for unknown fields: %r (known=%r)" % ( { self.join(root, sub): v for root in extra_context_keys for sub, v in self.contexts[root].items() }, sorted(self.declarations), ) ) def filter(self, entries): """Filter a set of declarations: keep only those related to this object. This will keep: - Declarations that 'override' the current ones - Declarations that are parameters to current ones """ return [ entry for entry in entries if self.split(entry)[0] in self.declarations ] def sorted(self): return utils.sort_ordered_objects( self.declarations, getter=lambda entry: self.declarations[entry], ) def __contains__(self, key): return key in self.declarations def __getitem__(self, key): return DeclarationWithContext( name=key, declaration=self.declarations[key], context=self.contexts[key], ) def __iter__(self): return iter(self.declarations) def values(self): """Retrieve the list of declarations, with their context.""" for name in self: yield self[name] def _items(self): """Extract a list of (key, value) pairs, suitable for our __init__.""" for name in self.declarations: yield name, self.declarations[name] for subkey, value in self.contexts[name].items(): yield self.join(name, subkey), value def as_dict(self): """Return a dict() suitable for our __init__.""" return dict(self._items()) def __repr__(self): return '' % self.as_dict() def _captures_overrides(declaration_with_context): declaration = declaration_with_context.declaration if enums.get_builder_phase(declaration) == enums.BuilderPhase.ATTRIBUTE_RESOLUTION: return declaration.CAPTURE_OVERRIDES else: return False def parse_declarations(decls, base_pre=None, base_post=None): pre_declarations = base_pre.copy() if base_pre else DeclarationSet() post_declarations = base_post.copy() if base_post else DeclarationSet() # Inject extra declarations, splitting between known-to-be-post and undetermined extra_post = {} extra_maybenonpost = {} for k, v in decls.items(): if enums.get_builder_phase(v) == enums.BuilderPhase.POST_INSTANTIATION: if k in pre_declarations: # Conflict: PostGenerationDeclaration with the same # name as a BaseDeclaration raise errors.InvalidDeclarationError( "PostGenerationDeclaration %s=%r shadows declaration %r" % (k, v, pre_declarations[k]) ) extra_post[k] = v elif k in post_declarations: # Passing in a scalar value to a PostGenerationDeclaration # Set it as `key__` magic_key = post_declarations.join(k, '') extra_post[magic_key] = v else: extra_maybenonpost[k] = v # Start with adding new post-declarations post_declarations.update(extra_post) # Fill in extra post-declaration context extra_pre_declarations = {} extra_post_declarations = {} post_overrides = post_declarations.filter(extra_maybenonpost) for k, v in extra_maybenonpost.items(): if k in post_overrides: extra_post_declarations[k] = v elif k in pre_declarations and _captures_overrides(pre_declarations[k]): # Send the overriding value to the existing declaration. # By symmetry with the behaviour of PostGenerationDeclaration, # we send it as `key__` -- i.e under the '' key. magic_key = pre_declarations.join(k, '') extra_pre_declarations[magic_key] = v else: # Anything else is pre_declarations extra_pre_declarations[k] = v pre_declarations.update(extra_pre_declarations) post_declarations.update(extra_post_declarations) return pre_declarations, post_declarations class BuildStep: def __init__(self, builder, sequence, parent_step=None): self.builder = builder self.sequence = sequence self.attributes = {} self.parent_step = parent_step self.stub = None def resolve(self, declarations): self.stub = Resolver( declarations=declarations, step=self, sequence=self.sequence, ) for field_name in declarations: self.attributes[field_name] = getattr(self.stub, field_name) @property def chain(self): if self.parent_step: parent_chain = self.parent_step.chain else: parent_chain = () return (self.stub,) + parent_chain def recurse(self, factory, declarations, force_sequence=None): from . import base if not issubclass(factory, base.BaseFactory): raise errors.AssociatedClassError( "%r: Attempting to recursing into a non-factory object %r" % (self, factory)) builder = self.builder.recurse(factory._meta, declarations) return builder.build(parent_step=self, force_sequence=force_sequence) def __repr__(self): return f"" class StepBuilder: """A factory instantiation step. Attributes: - parent: the parent StepBuilder, or None for the root step - extras: the passed-in kwargs for this branch - factory: the factory class being built - strategy: the strategy to use """ def __init__(self, factory_meta, extras, strategy): self.factory_meta = factory_meta self.strategy = strategy self.extras = extras self.force_init_sequence = extras.pop('__sequence', None) def build(self, parent_step=None, force_sequence=None): """Build a factory instance.""" # TODO: Handle "batch build" natively pre, post = parse_declarations( self.extras, base_pre=self.factory_meta.pre_declarations, base_post=self.factory_meta.post_declarations, ) if force_sequence is not None: sequence = force_sequence elif self.force_init_sequence is not None: sequence = self.force_init_sequence else: sequence = self.factory_meta.next_sequence() step = BuildStep( builder=self, sequence=sequence, parent_step=parent_step, ) step.resolve(pre) args, kwargs = self.factory_meta.prepare_arguments(step.attributes) instance = self.factory_meta.instantiate( step=step, args=args, kwargs=kwargs, ) postgen_results = {} for declaration_name in post.sorted(): declaration = post[declaration_name] postgen_results[declaration_name] = declaration.declaration.evaluate_post( instance=instance, step=step, overrides=declaration.context, ) self.factory_meta.use_postgeneration_results( instance=instance, step=step, results=postgen_results, ) return instance def recurse(self, factory_meta, extras): """Recurse into a sub-factory call.""" return self.__class__(factory_meta, extras, strategy=self.strategy) def __repr__(self): return f"" class Resolver: """Resolve a set of declarations. 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. __declarations (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. __step (BuildStep): the BuildStep related to this resolver. This allows to have the value of a field depend on the value of another field """ __initialized = False def __init__(self, declarations, step, sequence): self.__declarations = declarations self.__step = step self.__values = {} self.__pending = [] self.__initialized = True @property def factory_parent(self): return self.__step.parent_step.stub if self.__step.parent_step else None def __repr__(self): return '' % self.__step 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 %r; cycle found in %r." % (name, self.__pending)) elif name in self.__values: return self.__values[name] elif name in self.__declarations: declaration = self.__declarations[name] value = declaration.declaration if enums.get_builder_phase(value) == enums.BuilderPhase.ATTRIBUTE_RESOLUTION: self.__pending.append(name) try: value = value.evaluate_pre( instance=self, step=self.__step, overrides=declaration.context, ) finally: last = self.__pending.pop() assert name == last self.__values[name] = value return value else: raise AttributeError( "The parameter %r is unknown. Evaluated attributes are %r, " "definitions are %r." % (name, self.__values, self.__declarations)) def __setattr__(self, name, value): """Prevent setting attributes once __init__ is done.""" if not self.__initialized: return super().__setattr__(name, value) else: raise AttributeError('Setting of object attributes is not allowed') factory-boy-3.3.3/factory/declarations.py000066400000000000000000000640511475011040400204510ustar00rootroot00000000000000# Copyright: See the LICENSE file. import itertools import logging import typing as T from . import enums, errors, utils logger = logging.getLogger('factory.generate') class BaseDeclaration(utils.OrderedBase): """A factory declaration. Declarations mark an attribute as needing lazy evaluation. This allows them to refer to attributes defined by other BaseDeclarations in the same factory. """ FACTORY_BUILDER_PHASE = enums.BuilderPhase.ATTRIBUTE_RESOLUTION #: Whether this declaration has a special handling for call-time overrides #: (e.g. Tranformer). #: Overridden values will be passed in the `extra` args. CAPTURE_OVERRIDES = False #: Whether to unroll the context before evaluating the declaration. #: Set to False on declarations that perform their own unrolling. UNROLL_CONTEXT_BEFORE_EVALUATION = True def __init__(self, **defaults): super().__init__() self._defaults = defaults or {} def unroll_context(self, instance, step, context): full_context = dict() full_context.update(self._defaults) full_context.update(context) if not self.UNROLL_CONTEXT_BEFORE_EVALUATION: return full_context if not any(enums.get_builder_phase(v) for v in full_context.values()): # Optimization for simple contexts - don't do anything. return full_context import factory.base subfactory = factory.base.DictFactory return step.recurse(subfactory, full_context, force_sequence=step.sequence) def _unwrap_evaluate_pre(self, wrapped, *, instance, step, overrides): """Evaluate a wrapped pre-declaration. This is especially useful for declarations wrapping another one, e.g. Maybe or Transformer. """ if isinstance(wrapped, BaseDeclaration): return wrapped.evaluate_pre( instance=instance, step=step, overrides=overrides, ) return wrapped def evaluate_pre(self, instance, step, overrides): context = self.unroll_context(instance, step, overrides) return self.evaluate(instance, step, context) def evaluate(self, instance, step, extra): """Evaluate this declaration. Args: instance (builder.Resolver): The object holding currently computed attributes step: a factory.builder.BuildStep extra (dict): additional, call-time added kwargs for the step. """ raise NotImplementedError('This is an abstract method') class OrderedDeclaration(BaseDeclaration): """Compatibility""" # FIXME(rbarrois) class LazyFunction(BaseDeclaration): """Simplest BaseDeclaration computed by calling the given function. Attributes: function (function): a function without arguments and returning the computed value. """ def __init__(self, function): super().__init__() self.function = function def evaluate(self, instance, step, extra): logger.debug("LazyFunction: Evaluating %r on %r", self.function, step) return self.function() class LazyAttribute(BaseDeclaration): """Specific BaseDeclaration computed using a lambda. Attributes: function (function): a function, expecting the current LazyStub and returning the computed value. """ def __init__(self, function): super().__init__() self.function = function def evaluate(self, instance, step, extra): logger.debug("LazyAttribute: Evaluating %r on %r", self.function, instance) return self.function(instance) class Transformer(BaseDeclaration): CAPTURE_OVERRIDES = True UNROLL_CONTEXT_BEFORE_EVALUATION = False class Force: """ Bypass a transformer's transformation. The forced value can be any declaration, and will be evaluated as if it had been passed instead of the Transformer declaration. """ def __init__(self, forced_value): self.forced_value = forced_value def __repr__(self): return f'Transformer.Force({repr(self.forced_value)})' def __init__(self, default, *, transform): super().__init__() self.default = default self.transform = transform def evaluate_pre(self, instance, step, overrides): # The call-time value, if present, is set under the "" key. value_or_declaration = overrides.pop("", self.default) if isinstance(value_or_declaration, self.Force): bypass_transform = True value_or_declaration = value_or_declaration.forced_value else: bypass_transform = False value = self._unwrap_evaluate_pre( value_or_declaration, instance=instance, step=step, overrides=overrides, ) if bypass_transform: return value return self.transform(value) class _UNSPECIFIED: 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(BaseDeclaration): """Specific BaseDeclaration 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): super().__init__() 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, instance, step, extra): if self.depth > 1: # Fetching from a parent target = step.chain[self.depth - 1] else: target = instance logger.debug("SelfAttribute: Picking attribute %r on %r", self.attribute_name, 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(BaseDeclaration): """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().__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, instance, step, extra): # 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 %r", 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.""" if self.iterator is not None: self.iterator.reset() class Sequence(BaseDeclaration): """Specific BaseDeclaration 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. """ def __init__(self, function): super().__init__() self.function = function def evaluate(self, instance, step, extra): logger.debug("Sequence: Computing next value of %r for seq=%s", self.function, step.sequence) return self.function(int(step.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, instance, step, extra): logger.debug( "LazyAttributeSequence: Computing next value of %r for seq=%s, obj=%r", self.function, step.sequence, instance) return self.function(instance, int(step.sequence)) class ContainerAttribute(BaseDeclaration): """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): super().__init__() self.function = function self.strict = strict def evaluate(self, instance, step, extra): """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. """ # Strip the current instance from the chain chain = step.chain[1:] if self.strict and not chain: raise TypeError( "A ContainerAttribute in 'strict' mode can only be used " "within a SubFactory.") return self.function(instance, chain) class ParameteredAttribute(BaseDeclaration): """Base class for attributes expecting parameters. Attributes: defaults (dict): Default values for the parameters. May be overridden by call-time parameters. """ def evaluate(self, instance, step, extra): """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: instance (builder.Resolver): The object holding currently computed attributes step: a factory.builder.BuildStep extra (dict): additional, call-time added kwargs for the step. """ return self.generate(step, extra) def generate(self, step, params): """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: """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 (isinstance(factory_or_path, str) 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 f'<_FactoryImport: {self.module}.{self.name}>' else: return f'<_FactoryImport: {self.factory.__class__}>' class SubFactory(BaseDeclaration): """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 """ # Whether to align the attribute's sequence counter to the holding # factory's sequence counter FORCE_SEQUENCE = False UNROLL_CONTEXT_BEFORE_EVALUATION = False def __init__(self, factory, **kwargs): super().__init__(**kwargs) self.factory_wrapper = _FactoryWrapper(factory) def get_factory(self): """Retrieve the wrapped factory.Factory subclass.""" return self.factory_wrapper.get() def evaluate(self, instance, step, extra): """Evaluate the current definition and fill its attributes. Args: step: a factory.builder.BuildStep params (dict): additional, call-time added kwargs for the step. """ subfactory = self.get_factory() logger.debug( "SubFactory: Instantiating %s.%s(%s), create=%r", subfactory.__module__, subfactory.__name__, utils.log_pprint(kwargs=extra), step, ) force_sequence = step.sequence if self.FORCE_SEQUENCE else None return step.recurse(subfactory, extra, force_sequence=force_sequence) class Dict(SubFactory): """Fill a dict with usual declarations.""" FORCE_SEQUENCE = True def __init__(self, params, dict_factory='factory.DictFactory'): super().__init__(dict_factory, **dict(params)) class List(SubFactory): """Fill a list with standard declarations.""" FORCE_SEQUENCE = True def __init__(self, params, list_factory='factory.ListFactory'): params = {str(i): v for i, v in enumerate(params)} super().__init__(list_factory, **params) # Parameters # ========== class Skip: def __bool__(self): return False SKIP = Skip() class Maybe(BaseDeclaration): def __init__(self, decider, yes_declaration=SKIP, no_declaration=SKIP): super().__init__() if enums.get_builder_phase(decider) is None: # No builder phase => flat value decider = SelfAttribute(decider, default=None) self.decider = decider self.yes = yes_declaration self.no = no_declaration phases = { 'yes_declaration': enums.get_builder_phase(yes_declaration), 'no_declaration': enums.get_builder_phase(no_declaration), } used_phases = {phase for phase in phases.values() if phase is not None} if len(used_phases) > 1: raise TypeError(f"Inconsistent phases for {self!r}: {phases!r}") self.FACTORY_BUILDER_PHASE = used_phases.pop() if used_phases else enums.BuilderPhase.ATTRIBUTE_RESOLUTION def evaluate_post(self, instance, step, overrides): """Handle post-generation declarations""" decider_phase = enums.get_builder_phase(self.decider) if decider_phase == enums.BuilderPhase.ATTRIBUTE_RESOLUTION: # Note: we work on the *builder stub*, not on the actual instance. # This gives us access to all Params-level definitions. choice = self.decider.evaluate_pre( instance=step.stub, step=step, overrides=overrides) else: assert decider_phase == enums.BuilderPhase.POST_INSTANTIATION choice = self.decider.evaluate_post( instance=instance, step=step, overrides={}) target = self.yes if choice else self.no if enums.get_builder_phase(target) == enums.BuilderPhase.POST_INSTANTIATION: return target.evaluate_post( instance=instance, step=step, overrides=overrides, ) else: # Flat value (can't be ATTRIBUTE_RESOLUTION, checked in __init__) return target def evaluate_pre(self, instance, step, overrides): choice = self.decider.evaluate_pre(instance=instance, step=step, overrides={}) target = self.yes if choice else self.no # The value can't be POST_INSTANTIATION, checked in __init__; # evaluate it as `evaluate_pre` return self._unwrap_evaluate_pre( target, instance=instance, step=step, overrides=overrides, ) def __repr__(self): return f'Maybe({self.decider!r}, yes={self.yes!r}, no={self.no!r})' class Parameter(utils.OrderedBase): """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 as_declarations(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 SimpleParameter(Parameter): def __init__(self, value): super().__init__() self.value = value def as_declarations(self, field_name, declarations): return { field_name: self.value, } @classmethod def wrap(cls, value): if not isinstance(value, Parameter): return cls(value) value.touch_creation_counter() return value class Trait(Parameter): """The simplest complex parameter, it enables a bunch of new declarations based on a boolean flag.""" def __init__(self, **overrides): super().__init__() self.overrides = overrides def as_declarations(self, field_name, declarations): overrides = {} for maybe_field, new_value in self.overrides.items(): overrides[maybe_field] = Maybe( decider=SelfAttribute( '%s.%s' % ( '.' * maybe_field.count(enums.SPLITTER), field_name, ), default=False, ), yes_declaration=new_value, no_declaration=declarations.get(maybe_field, SKIP), ) return overrides def get_revdeps(self, parameters): """This might alter fields it's injecting.""" return [param for param in parameters if param in self.overrides] def __repr__(self): return '%s(%s)' % ( self.__class__.__name__, ', '.join('%s=%r' % t for t in self.overrides.items()) ) # Post-generation # =============== class PostGenerationContext(T.NamedTuple): value_provided: bool value: T.Any extra: T.Dict[str, T.Any] class PostGenerationDeclaration(BaseDeclaration): """Declarations to be called once the model object has been generated.""" FACTORY_BUILDER_PHASE = enums.BuilderPhase.POST_INSTANTIATION def evaluate_post(self, instance, step, overrides): context = self.unroll_context(instance, step, overrides) postgen_context = PostGenerationContext( value_provided=bool('' in context), value=context.get(''), extra={k: v for k, v in context.items() if k != ''}, ) return self.call(instance, step, postgen_context) def call(self, instance, step, context): # pragma: no cover """Call this hook; no return value is expected. Args: instance (object): the newly generated object step (bool): whether the object was 'built' or 'created' context: a declarations.PostGenerationContext 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().__init__() self.function = function def call(self, instance, step, context): logger.debug( "PostGeneration: Calling %s.%s(%s)", self.function.__module__, self.function.__name__, utils.log_pprint( (instance, step), context._asdict(), ), ) create = step.builder.strategy == enums.CREATE_STRATEGY return self.function( instance, create, context.value, **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 """ UNROLL_CONTEXT_BEFORE_EVALUATION = False def __init__(self, factory, factory_related_name='', **defaults): super().__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, instance, step, context): factory = self.get_factory() if context.value_provided: # The user passed in a custom value logger.debug( "RelatedFactory: Using provided %r instead of generating %s.%s.", context.value, factory.__module__, factory.__name__, ) return context.value passed_kwargs = dict(self.defaults) passed_kwargs.update(context.extra) if self.name: passed_kwargs[self.name] = instance logger.debug( "RelatedFactory: Generating %s.%s(%s)", factory.__module__, factory.__name__, utils.log_pprint((step,), passed_kwargs), ) return step.recurse(factory, passed_kwargs) class RelatedFactoryList(RelatedFactory): """Calls a factory 'size' times once the object has been generated. Attributes: factory (Factory): the factory to call "size-times" defaults (dict): extra declarations for calling the related factory factory_related_name (str): the name to use to refer to the generated object when calling the related factory size (int|lambda): the number of times 'factory' is called, ultimately returning a list of 'factory' objects w/ size 'size'. """ def __init__(self, factory, factory_related_name='', size=2, **defaults): self.size = size super().__init__(factory, factory_related_name, **defaults) def call(self, instance, step, context): parent = super() return [ parent.call(instance, step, context) for i in range(self.size if isinstance(self.size, int) else self.size()) ] class NotProvided: pass 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().__init__() if len(args) > 1: raise errors.InvalidDeclarationError( "A PostGenerationMethodCall can only handle 1 positional argument; " "please provide other parameters through keyword arguments." ) self.method_name = method_name self.method_arg = args[0] if args else NotProvided self.method_kwargs = kwargs def call(self, instance, step, context): if not context.value_provided: if self.method_arg is NotProvided: args = () else: args = (self.method_arg,) else: args = (context.value,) kwargs = dict(self.method_kwargs) kwargs.update(context.extra) method = getattr(instance, self.method_name) logger.debug( "PostGenerationMethodCall: Calling %r.%s(%s)", instance, self.method_name, utils.log_pprint(args, kwargs), ) return method(*args, **kwargs) factory-boy-3.3.3/factory/django.py000066400000000000000000000276151475011040400172500ustar00rootroot00000000000000# Copyright: See the LICENSE file. """factory_boy extensions for use with the Django framework.""" import functools import io import logging import os import warnings from typing import Dict, TypeVar from django.contrib.auth.hashers import make_password from django.core import files as django_files from django.db import IntegrityError from . import base, declarations, errors logger = logging.getLogger('factory.generate') DEFAULT_DB_ALIAS = 'default' # Same as django.db.DEFAULT_DB_ALIAS T = TypeVar("T") _LAZY_LOADS: Dict[str, object] = {} 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. """ from django import apps as django_apps _LAZY_LOADS['get_model'] = django_apps.apps.get_model class DjangoOptions(base.FactoryOptions): def _build_default_options(self): return super()._build_default_options() + [ base.OptionDefault('django_get_or_create', (), inherit=True), base.OptionDefault('database', DEFAULT_DB_ALIAS, inherit=True), base.OptionDefault('skip_postgeneration_save', False, inherit=True), ] def _get_counter_reference(self): counter_reference = super()._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 def get_model_class(self): if isinstance(self.model, str) and '.' in self.model: app, model_name = self.model.split('.', 1) self.model = get_model(app, model_name) return self.model class DjangoModelFactory(base.Factory[T]): """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 _original_params = None class Meta: abstract = True # Optional, but explicit. @classmethod def _load_model_class(cls, definition): if isinstance(definition, str) 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( f"No model set on {cls.__module__}.{cls.__name__}.Meta") 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 _generate(cls, strategy, params): # Original params are used in _get_or_create if it cannot build an # object initially due to an IntegrityError being raised cls._original_params = params return super()._generate(strategy, params) @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 try: instance, _created = manager.get_or_create(*args, **key_fields) except IntegrityError as e: if cls._original_params is None: raise e get_or_create_params = { lookup: value for lookup, value in cls._original_params.items() if lookup in cls._meta.django_get_or_create } if get_or_create_params: try: instance = manager.get(**get_or_create_params) except manager.model.DoesNotExist: # Original params are not a valid lookup and triggered a create(), # that resulted in an IntegrityError. Follow Django’s behavior. raise e else: raise e return instance @classmethod def _create(cls, model_class, *args, **kwargs): """Create an instance of the model, and save it to the database.""" if cls._meta.django_get_or_create: return cls._get_or_create(model_class, *args, **kwargs) manager = cls._get_manager(model_class) return manager.create(*args, **kwargs) # DEPRECATED. Remove this override with the next major release. @classmethod def _after_postgeneration(cls, instance, create, results=None): """Save again the instance if creating and at least one hook ran.""" if create and results and not cls._meta.skip_postgeneration_save: warnings.warn( f"{cls.__name__}._after_postgeneration will stop saving the instance " "after postgeneration hooks in the next major release.\n" "If the save call is extraneous, set skip_postgeneration_save=True " f"in the {cls.__name__}.Meta.\n" "To keep saving the instance, move the save call to your " "postgeneration hooks or override _after_postgeneration.", DeprecationWarning, ) # Some post-generation hooks ran, and may have modified us. instance.save() class Password(declarations.Transformer): def __init__(self, password, transform=make_password, **kwargs): super().__init__(password, transform=transform, **kwargs) class FileField(declarations.BaseDeclaration): """Helper to fill in django.db.models.FileField from a Factory.""" DEFAULT_FILENAME = 'example.dat' def _make_data(self, params): """Create data for the field.""" return params.get('data', b'') def _make_content(self, params): path = '' from_path = params.get('from_path') from_file = params.get('from_file') from_func = params.get('from_func') if len([p for p in (from_path, from_file, from_func) 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 from_path: path = from_path with open(path, 'rb') as f: content = django_files.base.ContentFile(f.read()) elif from_file: f = from_file content = django_files.File(f) path = content.name elif from_func: func = 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 evaluate(self, instance, step, extra): """Fill in the field.""" filename, content = self._make_content(extra) 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. from PIL import Image width = params.get('width', 100) height = params.get('height', width) color = params.get('color', 'blue') image_format = params.get('format', 'JPEG') image_palette = params.get('palette', 'RGB') thumb_io = io.BytesIO() with Image.new(image_palette, (width, height), color) as thumb: thumb.save(thumb_io, format=image_format) return thumb_io.getvalue() class mute_signals: """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 + signal.receivers 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. callable_obj._create = self.wrap_method(callable_obj._create.__func__) callable_obj._generate = self.wrap_method(callable_obj._generate.__func__) callable_obj._after_postgeneration = self.wrap_method( callable_obj._after_postgeneration.__func__ ) return callable_obj else: @functools.wraps(callable_obj) def wrapper(*args, **kwargs): # A mute_signals() object is not reentrant; use a copy every time. with self.copy(): return callable_obj(*args, **kwargs) return wrapper def wrap_method(self, method): @classmethod @functools.wraps(method) def wrapped_method(*args, **kwargs): # A mute_signals() object is not reentrant; use a copy every time. with self.copy(): return method(*args, **kwargs) return wrapped_method factory-boy-3.3.3/factory/enums.py000066400000000000000000000010551475011040400171230ustar00rootroot00000000000000# Copyright: See the LICENSE file. # Strategies BUILD_STRATEGY = 'build' CREATE_STRATEGY = 'create' STUB_STRATEGY = 'stub' #: String for splitting an attribute name into a #: (subfactory_name, subfactory_field) tuple. SPLITTER = '__' # Target build phase, for declarations class BuilderPhase: #: During attribute resolution/computation ATTRIBUTE_RESOLUTION = 'attributes' #: Once the target object has been built POST_INSTANTIATION = 'post_instance' def get_builder_phase(obj): return getattr(obj, 'FACTORY_BUILDER_PHASE', None) factory-boy-3.3.3/factory/errors.py000066400000000000000000000013541475011040400173120ustar00rootroot00000000000000# Copyright: See the LICENSE file. 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.""" class InvalidDeclarationError(FactoryError): """Raised when a sub-declaration has no related declaration. This means that the user declared 'foo__bar' without adding a declaration at 'foo'. """ factory-boy-3.3.3/factory/faker.py000066400000000000000000000037261475011040400170730ustar00rootroot00000000000000# Copyright: See the LICENSE file. """Additional declarations for "faker" attributes. Usage: class MyFactory(factory.Factory): class Meta: model = MyProfile first_name = factory.Faker('name') """ import contextlib from typing import Dict import faker import faker.config from . import declarations class Faker(declarations.BaseDeclaration): """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, **kwargs): locale = kwargs.pop('locale', None) self.provider = provider super().__init__( locale=locale, **kwargs) def evaluate(self, instance, step, extra): locale = extra.pop('locale') subfaker = self._get_faker(locale) return subfaker.format(self.provider, **extra) _FAKER_REGISTRY: Dict[str, faker.Faker] = {} _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: subfaker = faker.Faker(locale=locale) cls._FAKER_REGISTRY[locale] = subfaker 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-3.3.3/factory/fuzzy.py000066400000000000000000000212671475011040400171720ustar00rootroot00000000000000# Copyright: See the LICENSE file. """Additional declarations for "fuzzy" attribute definitions.""" import datetime import decimal import string import warnings from . import declarations, random random_seed_warning = ( "Setting a specific random seed for {} can still have varying results " "unless you also set a specific end date. For details and potential solutions " "see https://github.com/FactoryBoy/factory_boy/issues/331" ) class BaseFuzzyAttribute(declarations.BaseDeclaration): """Base class for fuzzy attributes. Custom fuzzers should override the `fuzz()` method. """ def fuzz(self): # pragma: no cover raise NotImplementedError() def evaluate(self, instance, step, extra): 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): super().__init__() 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): super().__init__() self.prefix = prefix self.suffix = suffix self.length = length self.chars = tuple(chars) # Unroll iterators def fuzz(self): chars = [random.randgen.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. getter (callable or None): a function to parse returned values """ def __init__(self, choices, getter=None): self.choices = None self.choices_generator = choices self.getter = getter super().__init__() def fuzz(self): if self.choices is None: self.choices = list(self.choices_generator) value = random.randgen.choice(self.choices) if self.getter is None: return value return self.getter(value) class FuzzyInteger(BaseFuzzyAttribute): """Random integer within a given range.""" def __init__(self, low, high=None, step=1): if high is None: high = low low = 0 self.low = low self.high = high self.step = step super().__init__() def fuzz(self): return random.randgen.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): if high is None: high = low low = 0.0 self.low = low self.high = high self.precision = precision super().__init__() def fuzz(self): base = decimal.Decimal(str(random.randgen.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, precision=15): if high is None: high = low low = 0 self.low = low self.high = high self.precision = precision super().__init__() def fuzz(self): base = random.randgen.uniform(self.low, self.high) return float(format(base, '.%dg' % self.precision)) class FuzzyDate(BaseFuzzyAttribute): """Random date within a given date range.""" def __init__(self, start_date, end_date=None): super().__init__() if end_date is None: if random.randgen.state_set: cls_name = self.__class__.__name__ warnings.warn(random_seed_warning.format(cls_name), stacklevel=2) 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.randgen.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): super().__init__() if end_dt is None: if random.randgen.state_set: cls_name = self.__class__.__name__ warnings.warn(random_seed_warning.format(cls_name), stacklevel=2) 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.randgen.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.now(). """ 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()._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=datetime.timezone.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()._check_bounds(start_dt, end_dt) factory-boy-3.3.3/factory/helpers.py000066400000000000000000000065201475011040400174400ustar00rootroot00000000000000# Copyright: See the LICENSE file. """Simple wrappers around Factory class definition.""" import contextlib import logging from . import base, 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) try: yield finally: 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-3.3.3/factory/mogo.py000066400000000000000000000010161475011040400167320ustar00rootroot00000000000000# Copyright: See the LICENSE file. """factory_boy extensions for use with the mogo library (pymongo wrapper).""" 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-3.3.3/factory/mongoengine.py000066400000000000000000000011121475011040400202730ustar00rootroot00000000000000# Copyright: See the LICENSE file. """factory_boy extensions for use with the mongoengine library (pymongo wrapper).""" 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-3.3.3/factory/py.typed000066400000000000000000000000001475011040400171060ustar00rootroot00000000000000factory-boy-3.3.3/factory/random.py000066400000000000000000000013311475011040400172510ustar00rootroot00000000000000import random import faker.generator randgen = random.Random() randgen.state_set = False def get_random_state(): """Retrieve the state of factory.fuzzy's random generator.""" state = randgen.getstate() # Returned state must represent both Faker and factory_boy. faker.generator.random.setstate(state) return state def set_random_state(state): """Force-set the state of factory.fuzzy's random generator.""" randgen.state_set = True randgen.setstate(state) faker.generator.random.setstate(state) def reseed_random(seed): """Reseed factory.fuzzy's random generator.""" r = random.Random(seed) random_internal_state = r.getstate() set_random_state(random_internal_state) factory-boy-3.3.3/factory/utils.py000066400000000000000000000060421475011040400171350ustar00rootroot00000000000000# Copyright: See the LICENSE file. import collections import importlib def import_object(module_name, attribute_name): """Import an object from its absolute path. Example: >>> import_object('datetime', 'datetime') """ module = importlib.import_module(module_name) return getattr(module, attribute_name) class log_pprint: """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( [ repr(arg) for arg in self.args ] + [ '%s=%s' % (key, repr(value)) for key, value in self.kwargs.items() ] ) class ResetableIterator: """An iterator wrapper that can be 'reset()' to its start.""" def __init__(self, iterator, **kwargs): super().__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: try: value = next(self.iterator) except StopIteration: break else: self.past_elements.append(value) yield value def reset(self): self.next_elements.clear() self.next_elements.extend(self.past_elements) class OrderedBase: """Marks a class as being ordered. Each instance (even from subclasses) will share a global creation counter. """ CREATION_COUNTER_FIELD = '_creation_counter' def __init__(self, **kwargs): super().__init__(**kwargs) if type(self) is not OrderedBase: self.touch_creation_counter() def touch_creation_counter(self): bases = type(self).__mro__ root = bases[bases.index(OrderedBase) - 1] if not hasattr(root, self.CREATION_COUNTER_FIELD): setattr(root, self.CREATION_COUNTER_FIELD, 0) next_counter = getattr(root, self.CREATION_COUNTER_FIELD) setattr(self, self.CREATION_COUNTER_FIELD, next_counter) setattr(root, self.CREATION_COUNTER_FIELD, next_counter + 1) def sort_ordered_objects(items, getter=lambda x: x): """Sort an iterable of OrderedBase instances. Args: items (iterable): the objects to sort getter (callable or None): a function to extract the OrderedBase instance from an object. Examples: >>> sort_ordered_objects([x, y, z]) >>> sort_ordered_objects(v.items(), getter=lambda e: e[1]) """ return sorted(items, key=lambda x: getattr(getter(x), OrderedBase.CREATION_COUNTER_FIELD, -1)) factory-boy-3.3.3/setup.cfg000066400000000000000000000045321475011040400155770ustar00rootroot00000000000000[metadata] name = factory_boy version = 3.3.3 description = A versatile test fixtures replacement based on thoughtbot's factory_bot for Ruby. long_description = file: README.rst # https://docutils.sourceforge.io/FAQ.html#what-s-the-official-mime-type-for-restructuredtext-data long_description_content_type = text/x-rst 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 license = MIT classifiers = Development Status :: 5 - Production/Stable Framework :: Django Framework :: Django :: 4.2 Framework :: Django :: 5.0 Framework :: Django :: 5.1 Intended Audience :: Developers License :: OSI Approved :: MIT License Operating System :: OS Independent Programming Language :: Python Programming Language :: Python :: 3 Programming Language :: Python :: 3 :: Only Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 Programming Language :: Python :: 3.12 Programming Language :: Python :: 3.13 Programming Language :: Python :: Implementation :: CPython Programming Language :: Python :: Implementation :: PyPy Topic :: Software Development :: Testing Topic :: Software Development :: Libraries :: Python Modules [options] packages = factory python_requires = >=3.8 install_requires = Faker>=0.7.0 [options.extras_require] dev = coverage Django flake8 isort mypy Pillow SQLAlchemy mongoengine mongomock wheel>=0.32.0 tox zest.releaser[recommended] doc = Sphinx sphinx_rtd_theme sphinxcontrib-spelling [options.package_data] factory = py.typed [bdist_wheel] universal = 1 [zest.releaser] ; semver-style versions version-levels = 3 [distutils] index-servers = pypi [flake8] ignore = # Ignore "and" at start of line. W503 # Ignore "do not assign a lambda expression, use a def". E731 max-line-length = 120 [isort] multi_line_output = 3 include_trailing_comma = True force_grid_wrap = 0 use_parentheses = True line_length = 88 [coverage:run] dynamic_context = test_function [coverage:report] include= factory/*.py tests/*.py [coverage:html] show_contexts = True factory-boy-3.3.3/setup.py000077500000000000000000000000751475011040400154710ustar00rootroot00000000000000#!/usr/bin/env python from setuptools import setup setup() factory-boy-3.3.3/tests/000077500000000000000000000000001475011040400151145ustar00rootroot00000000000000factory-boy-3.3.3/tests/__init__.py000066400000000000000000000000001475011040400172130ustar00rootroot00000000000000factory-boy-3.3.3/tests/alchemyapp/000077500000000000000000000000001475011040400172375ustar00rootroot00000000000000factory-boy-3.3.3/tests/alchemyapp/__init__.py000066400000000000000000000000001475011040400213360ustar00rootroot00000000000000factory-boy-3.3.3/tests/alchemyapp/models.py000066400000000000000000000023461475011040400211010ustar00rootroot00000000000000# Copyright: See the LICENSE file. """Helpers for testing SQLAlchemy apps.""" from sqlalchemy import Column, Integer, Unicode, create_engine from sqlalchemy.orm import declarative_base, scoped_session, sessionmaker engine_name = 'sqlite://' session = scoped_session(sessionmaker()) engine = create_engine(engine_name) session.configure(bind=engine) Base = declarative_base() class StandardModel(Base): __tablename__ = 'StandardModelTable' id = Column(Integer(), primary_key=True) foo = Column(Unicode(20)) class MultiFieldModel(Base): __tablename__ = 'MultiFieldModelTable' id = Column(Integer(), primary_key=True) foo = Column(Unicode(20)) slug = Column(Unicode(20), unique=True) class MultifieldUniqueModel(Base): __tablename__ = 'MultiFieldUniqueModelTable' id = Column(Integer(), primary_key=True) slug = Column(Unicode(20), unique=True) text = Column(Unicode(20), unique=True) title = Column(Unicode(20), unique=True) class NonIntegerPk(Base): __tablename__ = 'NonIntegerPk' id = Column(Unicode(20), primary_key=True) class SpecialFieldModel(Base): __tablename__ = 'SpecialFieldModelTable' id = Column(Integer(), primary_key=True) session = Column(Unicode(20)) factory-boy-3.3.3/tests/alter_time.py000066400000000000000000000067441475011040400176260ustar00rootroot00000000000000#!/usr/bin/env python # This code is in the public domain # Author: Raphaël Barrois import datetime from unittest 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 https://bugs.python.org/msg68532 # And https://docs.python.org/3/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 MockedDatetime(real_datetime_class, metaclass=DatetimeSubclassMeta): @classmethod def now(cls, tz=None): return target.replace(tzinfo=tz) @classmethod def utcnow(cls): return target 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 https://bugs.python.org/msg68532 # And https://docs.python.org/3/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 MockedDate(real_date_class, metaclass=DateSubclassMeta): @classmethod def today(cls): return target 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)) factory-boy-3.3.3/tests/cyclic/000077500000000000000000000000001475011040400163625ustar00rootroot00000000000000factory-boy-3.3.3/tests/cyclic/__init__.py000066400000000000000000000000001475011040400204610ustar00rootroot00000000000000factory-boy-3.3.3/tests/cyclic/bar.py000066400000000000000000000005121475011040400174760ustar00rootroot00000000000000# Copyright: See the LICENSE file. """Helper to test circular factory dependencies.""" import factory class Bar: 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-3.3.3/tests/cyclic/foo.py000066400000000000000000000005431475011040400175210ustar00rootroot00000000000000# Copyright: See the LICENSE file. """Helper to test circular factory dependencies.""" import factory from . import bar as bar_mod class Foo: 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-3.3.3/tests/cyclic/self_ref.py000066400000000000000000000006631475011040400205260ustar00rootroot00000000000000# Copyright: See the LICENSE file. """Helper to test circular factory dependencies.""" import factory class TreeElement: 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-3.3.3/tests/djapp/000077500000000000000000000000001475011040400162125ustar00rootroot00000000000000factory-boy-3.3.3/tests/djapp/__init__.py000066400000000000000000000000001475011040400203110ustar00rootroot00000000000000factory-boy-3.3.3/tests/djapp/models.py000066400000000000000000000056411475011040400200550ustar00rootroot00000000000000# Copyright: See the LICENSE file. """Helpers for testing django apps.""" import os.path from django.conf import settings from django.db import models from django.db.models import signals try: from PIL import Image except ImportError: Image = None 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.TextField() class MultifieldUniqueModel(models.Model): slug = models.SlugField(max_length=20, unique=True) text = models.CharField(max_length=20, unique=True) title = models.CharField(max_length=20, unique=True) 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 PointerModel(models.Model): bar = models.CharField(max_length=20) pointed = models.OneToOneField( PointedModel, related_name='pointer', null=True, on_delete=models.CASCADE ) class WithDefaultValue(models.Model): foo = models.CharField(max_length=20, default='') class WithPassword(models.Model): pw = models.CharField(max_length=128) 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) def __init__(self, post_save_signal_receiver=None): super().__init__() if post_save_signal_receiver: signals.post_save.connect( post_save_signal_receiver, sender=self.__class__, ) class CustomManager(models.Manager): def create(self, arg=None, **kwargs): return super().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 class HasMultifieldModel(models.Model): multifield = models.ForeignKey(to=MultifieldModel, on_delete=models.CASCADE) factory-boy-3.3.3/tests/djapp/settings.py000066400000000000000000000014171475011040400204270ustar00rootroot00000000000000# Copyright: See the LICENSE file. """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.' # TODO: Will be the default after Django 5.0. Remove this setting when # Django 5.0 is the last supported version. USE_TZ = True factory-boy-3.3.3/tests/test_alchemy.py000066400000000000000000000253751475011040400201630ustar00rootroot00000000000000# Copyright: See the LICENSE file. """Tests for factory_boy/SQLAlchemy interactions.""" import unittest from unittest import mock try: import sqlalchemy except ImportError: raise unittest.SkipTest("sqlalchemy tests disabled.") import factory from factory.alchemy import SQLAlchemyModelFactory from .alchemyapp import models 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) class NoSessionFactory(SQLAlchemyModelFactory): class Meta: model = models.StandardModel sqlalchemy_session = None id = factory.Sequence(lambda n: n) class MultifieldModelFactory(SQLAlchemyModelFactory): class Meta: model = models.MultiFieldModel sqlalchemy_get_or_create = ('slug',) sqlalchemy_session = models.session sqlalchemy_session_persistence = 'commit' id = factory.Sequence(lambda n: n) foo = factory.Sequence(lambda n: 'foo%d' % n) class WithGetOrCreateFieldFactory(SQLAlchemyModelFactory): class Meta: model = models.StandardModel sqlalchemy_get_or_create = ('foo',) sqlalchemy_session = models.session sqlalchemy_session_persistence = 'commit' id = factory.Sequence(lambda n: n) foo = factory.Sequence(lambda n: 'foo%d' % n) class WithMultipleGetOrCreateFieldsFactory(SQLAlchemyModelFactory): class Meta: model = models.MultifieldUniqueModel sqlalchemy_get_or_create = ("slug", "text",) sqlalchemy_session = models.session sqlalchemy_session_persistence = 'commit' id = factory.Sequence(lambda n: n) slug = factory.Sequence(lambda n: "slug%s" % n) text = factory.Sequence(lambda n: "text%s" % n) class TransactionTestCase(unittest.TestCase): def setUp(self): models.Base.metadata.create_all(models.engine) def tearDown(self): models.session.remove() models.Base.metadata.drop_all(models.engine) class SQLAlchemyPkSequenceTestCase(TransactionTestCase): def setUp(self): super().setUp() StandardFactory.reset_sequence(1) 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) class SQLAlchemyGetOrCreateTests(TransactionTestCase): def test_simple_call(self): obj1 = WithGetOrCreateFieldFactory(foo='foo1') obj2 = WithGetOrCreateFieldFactory(foo='foo1') self.assertEqual(obj1, obj2) def test_missing_arg(self): with self.assertRaises(factory.FactoryError): MultifieldModelFactory() def test_raises_exception_when_existing_objs(self): StandardFactory.create_batch(2, foo='foo') with self.assertRaises(sqlalchemy.orm.exc.MultipleResultsFound): WithGetOrCreateFieldFactory(foo='foo') 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( list( obj.slug for obj in models.session.query( models.MultiFieldModel.slug ).order_by(models.MultiFieldModel.slug) ), ["alt", "main"], ) class MultipleGetOrCreateFieldsTest(TransactionTestCase): def test_one_defined(self): obj1 = WithMultipleGetOrCreateFieldsFactory() obj2 = WithMultipleGetOrCreateFieldsFactory(slug=obj1.slug) self.assertEqual(obj1, obj2) def test_both_defined(self): obj1 = WithMultipleGetOrCreateFieldsFactory() with self.assertRaises(sqlalchemy.exc.IntegrityError): WithMultipleGetOrCreateFieldsFactory(slug=obj1.slug, text="alt") def test_unique_field_not_in_get_or_create(self): WithMultipleGetOrCreateFieldsFactory(title='Title') with self.assertRaises(sqlalchemy.exc.IntegrityError): WithMultipleGetOrCreateFieldsFactory(title='Title') class SQLAlchemySessionPersistenceTestCase(unittest.TestCase): def setUp(self): super().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 class SQLAlchemyNonIntegerPkTestCase(TransactionTestCase): def tearDown(self): super().tearDown() NonIntegerPkFactory.reset_sequence() 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) class SQLAlchemyNoSessionTestCase(TransactionTestCase): def test_create_raises_exception_when_no_session_was_set(self): with self.assertRaises(RuntimeError): NoSessionFactory.create() def test_build_does_not_raises_exception_when_no_session_was_set(self): NoSessionFactory.reset_sequence() # Make sure we start at test ID 0 inst0 = NoSessionFactory.build() inst1 = NoSessionFactory.build() self.assertEqual(inst0.id, 0) self.assertEqual(inst1.id, 1) class SQLAlchemySessionFactoryTestCase(TransactionTestCase): def test_create_get_session_from_sqlalchemy_session_factory(self): class SessionGetterFactory(SQLAlchemyModelFactory): class Meta: model = models.StandardModel sqlalchemy_session_factory = lambda: models.session id = factory.Sequence(lambda n: n) SessionGetterFactory.create() self.assertEqual(SessionGetterFactory._meta.sqlalchemy_session, models.session) # Reuse the session obtained from sqlalchemy_session_factory. SessionGetterFactory.create() def test_create_raise_exception_sqlalchemy_session_factory_not_callable(self): message = "^Provide either a sqlalchemy_session or a sqlalchemy_session_factory, not both$" with self.assertRaisesRegex(RuntimeError, message): class SessionAndGetterFactory(SQLAlchemyModelFactory): class Meta: model = models.StandardModel sqlalchemy_session = models.session sqlalchemy_session_factory = lambda: models.session id = factory.Sequence(lambda n: n) class NameConflictTests(TransactionTestCase): """Regression test for `TypeError: _save() got multiple values for argument 'session'` See #775. """ def test_no_name_conflict_on_save(self): class SpecialFieldWithSaveFactory(SQLAlchemyModelFactory): class Meta: model = models.SpecialFieldModel sqlalchemy_session = models.session id = factory.Sequence(lambda n: n) session = '' saved_child = SpecialFieldWithSaveFactory() self.assertEqual(saved_child.session, "") def test_no_name_conflict_on_get_or_create(self): class SpecialFieldWithGetOrCreateFactory(SQLAlchemyModelFactory): class Meta: model = models.SpecialFieldModel sqlalchemy_get_or_create = ('session',) sqlalchemy_session = models.session id = factory.Sequence(lambda n: n) session = '' get_or_created_child = SpecialFieldWithGetOrCreateFactory() self.assertEqual(get_or_created_child.session, "") factory-boy-3.3.3/tests/test_base.py000066400000000000000000000401231475011040400174370ustar00rootroot00000000000000# Copyright: See the LICENSE file. import unittest from factory import base, declarations, enums, errors class TestObject: 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: @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): with 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 with self.assertRaises(errors.FactoryError): TestObjectFactory.build() with 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 with self.assertRaises(errors.FactoryError): TestObjectFactory.build() with 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(enums.CREATE_STRATEGY, AbstractFactory._meta.strategy) # Non-declarative attributes self.assertEqual({}, AbstractFactory._meta.pre_declarations.as_dict()) self.assertEqual({}, AbstractFactory._meta.post_declarations.as_dict()) self.assertEqual(AbstractFactory, AbstractFactory._meta.factory) self.assertEqual(base.Factory, AbstractFactory._meta.base_factory) self.assertEqual(AbstractFactory._meta, 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.pre_declarations.as_dict(), ) self.assertEqual( {'z': postgen}, AbstractFactory._meta.post_declarations.as_dict(), ) 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.pre_declarations.as_dict(), ) self.assertEqual( {'z': postgen, 'b': postgen2}, OtherFactory._meta.post_declarations.as_dict(), ) 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.pre_declarations.as_dict(), ) self.assertEqual( {'z': postgen2}, OtherFactory._meta.post_declarations.as_dict(), ) def test_factory_as_meta_model_raises_exception(self): class FirstFactory(base.Factory): pass class Meta: model = FirstFactory with self.assertRaises(TypeError): type("SecondFactory", (base.Factory,), {"Meta": Meta}) 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) with 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 = {x.one for x in (parent, alt_parent, sub, alt_sub)} self.assertEqual(4, len(ones)) class FactorySequenceTestCase(unittest.TestCase): def setUp(self): super().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 reset.""" class SubTestObjectFactory(self.TestObjectFactory): pass with 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 reset.""" 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 test_build_strategy(self): class TestModelFactory(base.Factory): class Meta: model = TestModel strategy = enums.BUILD_STRATEGY 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): class TestModelFactory(base.Factory): class Meta: model = TestModel strategy = enums.STUB_STRATEGY 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): class TestModelFactory(base.Factory): class Meta: model = TestModel strategy = 'unknown' one = 'one' with self.assertRaises(base.Factory.UnknownStrategy): TestModelFactory() def test_stub_with_create_strategy(self): class TestModelFactory(base.StubFactory): class Meta: model = TestModel strategy = enums.CREATE_STRATEGY one = 'one' with self.assertRaises(base.StubFactory.UnsupportedStrategy): TestModelFactory() def test_stub_with_build_strategy(self): class TestModelFactory(base.StubFactory): class Meta: model = TestModel strategy = enums.BUILD_STRATEGY one = 'one' obj = TestModelFactory() # For stubs, build() is an alias of stub(). self.assertFalse(isinstance(obj, TestModel)) def test_change_strategy(self): class TestModelFactory(base.StubFactory): class Meta: model = TestModel strategy = enums.CREATE_STRATEGY one = 'one' self.assertEqual(enums.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, enums.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, enums.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 _generate(cls, create, attrs): attrs['four'] = 4 return super()._generate(create, attrs) 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.post_declarations.as_dict()) def test_classlevel_extraction(self): class TestObjectFactory(base.Factory): class Meta: model = TestObject foo = declarations.PostGenerationDeclaration() foo__bar = 42 self.assertIn('foo', TestObjectFactory._meta.post_declarations.as_dict()) self.assertIn('foo__bar', TestObjectFactory._meta.post_declarations.as_dict()) factory-boy-3.3.3/tests/test_declarations.py000066400000000000000000000260671475011040400212100ustar00rootroot00000000000000# Copyright: See the LICENSE file. import datetime import unittest from unittest import mock from factory import base, declarations, errors, helpers from . import utils class OrderedDeclarationTestCase(unittest.TestCase): def test_errors(self): with self.assertRaises(NotImplementedError): utils.evaluate_declaration(declarations.OrderedDeclaration()) class DigTestCase(unittest.TestCase): class MyObj: 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) with self.assertRaises(AttributeError): declarations.deepgetattr(obj, 'b') self.assertEqual(2, declarations.deepgetattr(obj, 'a.n')) self.assertEqual(3, declarations.deepgetattr(obj, 'a.c', 3)) with self.assertRaises(AttributeError): declarations.deepgetattr(obj, 'a.c.n') with 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 MaybeTestCase(unittest.TestCase): def test_init(self): declarations.Maybe('foo', 1, 2) with self.assertRaisesRegex(TypeError, 'Inconsistent phases'): declarations.Maybe('foo', declarations.LazyAttribute(None), declarations.PostGenerationDeclaration()) 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, utils.evaluate_declaration(it, force_sequence=0)) self.assertEqual(2, utils.evaluate_declaration(it, force_sequence=1)) self.assertEqual(1, utils.evaluate_declaration(it, force_sequence=2)) self.assertEqual(2, utils.evaluate_declaration(it, force_sequence=3)) def test_no_cycling(self): it = declarations.Iterator([1, 2], cycle=False) self.assertEqual(1, utils.evaluate_declaration(it, force_sequence=0)) self.assertEqual(2, utils.evaluate_declaration(it, force_sequence=1)) with self.assertRaises(StopIteration): utils.evaluate_declaration(it, force_sequence=2) def test_initial_reset(self): it = declarations.Iterator([1, 2]) it.reset() def test_reset_cycle(self): it = declarations.Iterator([1, 2]) self.assertEqual(1, utils.evaluate_declaration(it, force_sequence=0)) self.assertEqual(2, utils.evaluate_declaration(it, force_sequence=1)) self.assertEqual(1, utils.evaluate_declaration(it, force_sequence=2)) self.assertEqual(2, utils.evaluate_declaration(it, force_sequence=3)) self.assertEqual(1, utils.evaluate_declaration(it, force_sequence=4)) it.reset() self.assertEqual(1, utils.evaluate_declaration(it, force_sequence=5)) self.assertEqual(2, utils.evaluate_declaration(it, force_sequence=6)) def test_reset_no_cycling(self): it = declarations.Iterator([1, 2], cycle=False) self.assertEqual(1, utils.evaluate_declaration(it, force_sequence=0)) self.assertEqual(2, utils.evaluate_declaration(it, force_sequence=1)) with self.assertRaises(StopIteration): utils.evaluate_declaration(it, force_sequence=2) it.reset() self.assertEqual(1, utils.evaluate_declaration(it, force_sequence=0)) self.assertEqual(2, utils.evaluate_declaration(it, force_sequence=1)) with self.assertRaises(StopIteration): utils.evaluate_declaration(it, force_sequence=2) def test_getter(self): it = declarations.Iterator([(1, 2), (1, 3)], getter=lambda p: p[1]) self.assertEqual(2, utils.evaluate_declaration(it, force_sequence=0)) self.assertEqual(3, utils.evaluate_declaration(it, force_sequence=1)) self.assertEqual(2, utils.evaluate_declaration(it, force_sequence=2)) self.assertEqual(3, utils.evaluate_declaration(it, force_sequence=3)) class TransformerTestCase(unittest.TestCase): def test_transform(self): t = declarations.Transformer('foo', transform=str.upper) self.assertEqual("FOO", utils.evaluate_declaration(t)) class PostGenerationDeclarationTestCase(unittest.TestCase): def test_post_generation(self): call_params = [] def foo(*args, **kwargs): call_params.append(args) call_params.append(kwargs) helpers.build( dict, foo=declarations.PostGeneration(foo), foo__bar=42, blah=42, blah__baz=1, ) self.assertEqual(2, len(call_params)) self.assertEqual(3, len(call_params[0])) # instance, step, context.value self.assertEqual({'bar': 42}, call_params[1]) def test_decorator_simple(self): call_params = [] @helpers.post_generation def foo(*args, **kwargs): call_params.append(args) call_params.append(kwargs) helpers.build( dict, foo=foo, foo__bar=42, blah=42, blah__baz=1, ) self.assertEqual(2, len(call_params)) self.assertEqual(3, len(call_params[0])) # instance, step, context.value self.assertEqual({'bar': 42}, call_params[1]) class FactoryWrapperTestCase(unittest.TestCase): def test_invalid_path(self): with self.assertRaises(ValueError): declarations._FactoryWrapper('UnqualifiedSymbol') with 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 build(self, declaration, **params): f = helpers.make_factory(mock.MagicMock, post=declaration) return f(**params) def test_simplest_setup_and_call(self): obj = self.build( declarations.PostGenerationMethodCall('method'), ) obj.method.assert_called_once_with() def test_call_with_method_args(self): obj = self.build( declarations.PostGenerationMethodCall('method', 'data'), ) obj.method.assert_called_once_with('data') def test_call_with_passed_extracted_string(self): obj = self.build( declarations.PostGenerationMethodCall('method'), post='data', ) obj.method.assert_called_once_with('data') def test_call_with_passed_extracted_int(self): obj = self.build( declarations.PostGenerationMethodCall('method'), post=1, ) obj.method.assert_called_once_with(1) def test_call_with_passed_extracted_iterable(self): obj = self.build( declarations.PostGenerationMethodCall('method'), post=(1, 2, 3), ) obj.method.assert_called_once_with((1, 2, 3)) def test_call_with_method_kwargs(self): obj = self.build( declarations.PostGenerationMethodCall('method', data='data'), ) obj.method.assert_called_once_with(data='data') def test_call_with_passed_kwargs(self): obj = self.build( declarations.PostGenerationMethodCall('method'), post__data='other', ) obj.method.assert_called_once_with(data='other') def test_multi_call_with_multi_method_args(self): with self.assertRaises(errors.InvalidDeclarationError): self.build( declarations.PostGenerationMethodCall('method', 'arg1', 'arg2'), ) 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 = Ordered._meta.post_declarations.sorted() self.assertEqual(postgen_names, ['a', 'z', 'a1', 'zz', 'aa']) # Test generation happens in desired order Ordered() self.assertEqual(postgen_results, ['a1', 'zz', 'aa']) factory-boy-3.3.3/tests/test_dev_experience.py000066400000000000000000000035561475011040400215230ustar00rootroot00000000000000# Copyright: See the LICENSE file. """Tests about developer experience: help messages, errors, etc.""" import collections import unittest import factory import factory.errors Country = collections.namedtuple('Country', ['name', 'continent', 'capital_city']) City = collections.namedtuple('City', ['name', 'population']) class DeclarationTests(unittest.TestCase): def test_subfactory_to_model(self): """A helpful error message occurs when pointing a subfactory to a model.""" class CountryFactory(factory.Factory): class Meta: model = Country name = factory.Faker('country') continent = "Antarctica" # Error here: pointing the SubFactory to a model, not a factory. capital_city = factory.SubFactory(City) with self.assertRaises(factory.errors.AssociatedClassError) as raised: CountryFactory() self.assertIn('City', str(raised.exception)) self.assertIn('Country', str(raised.exception)) def test_subfactory_to_factorylike_model(self): """A helpful error message occurs when pointing a subfactory to a model. This time with a model that looks more like a factory (ie has a `._meta`).""" class CityModel: _meta = None name = "Coruscant" population = 0 class CountryFactory(factory.Factory): class Meta: model = Country name = factory.Faker('country') continent = "Antarctica" # Error here: pointing the SubFactory to a model, not a factory. capital_city = factory.SubFactory(CityModel) with self.assertRaises(factory.errors.AssociatedClassError) as raised: CountryFactory() self.assertIn('CityModel', str(raised.exception)) self.assertIn('Country', str(raised.exception)) factory-boy-3.3.3/tests/test_django.py000066400000000000000000001164221475011040400177750ustar00rootroot00000000000000# Copyright: See the LICENSE file. """Tests for factory_boy/Django interactions.""" import io import os import unittest from unittest import mock try: import django except ImportError: raise unittest.SkipTest("django tests disabled.") from django import test as django_test from django.conf import settings from django.contrib.auth.hashers import check_password from django.core.management import color from django.db import IntegrityError, connections from django.db.models import signals from django.test import utils as django_test_utils import factory import factory.django from . import testdata try: from PIL import Image except ImportError: Image = None # Setup Django before importing Django models. os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tests.djapp.settings') django.setup() from .djapp import models # noqa:E402 isort:skip test_state = {} def setUpModule(): django_test_utils.setup_test_environment() runner_state = django_test_utils.setup_databases(verbosity=0, interactive=False) test_state['runner_state'] = runner_state def tearDownModule(): django_test_utils.teardown_databases(test_state['runner_state'], verbosity=0) django_test_utils.teardown_test_environment() 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 PASSWORD = 's0_s3cr3t' class WithPasswordFactory(factory.django.DjangoModelFactory): class Meta: model = models.WithPassword pw = factory.django.Password(password=PASSWORD) class WithFileFactory(factory.django.DjangoModelFactory): class Meta: model = models.WithFile afile = factory.django.FileField() class WithImageFactory(factory.django.DjangoModelFactory): class Meta: model = models.WithImage 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) class WithMultipleGetOrCreateFieldsFactory(factory.django.DjangoModelFactory): class Meta: model = models.MultifieldUniqueModel django_get_or_create = ("slug", "text",) slug = factory.Sequence(lambda n: "slug%s" % n) text = factory.Sequence(lambda n: "text%s" % n) class ModelTests(django_test.TestCase): databases = {'default', 'replica'} def test_unset_model(self): class UnsetModelFactory(factory.django.DjangoModelFactory): pass with 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()) class DjangoResetTestCase(django_test.TestCase): def reset_database_sequences(self, *models): using = factory.django.DEFAULT_DB_ALIAS with connections[using].cursor() as cursor: sequence_sql = connections[using].ops.sequence_reset_sql(color.no_style(), models) for command in sequence_sql: cursor.execute(command) class DjangoPkSequenceTestCase(DjangoResetTestCase): def setUp(self): super().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): self.reset_database_sequences(StandardFactory._meta.model) 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) self.reset_database_sequences(StandardFactory._meta.model) StandardFactory.reset_sequence() std2 = StandardFactory.create() self.assertEqual('foo0', std2.foo) self.assertEqual(11, std2.pk) class DjangoGetOrCreateTests(django_test.TestCase): def test_simple_call(self): obj1 = MultifieldModelFactory(slug='slug1') obj2 = MultifieldModelFactory(slug='slug1') MultifieldModelFactory(slug='alt') self.assertEqual(obj1, obj2) self.assertEqual( list( models.MultifieldModel.objects.order_by("slug").values_list( "slug", flat=True ) ), ["alt", "slug1"], ) 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( list( models.MultifieldModel.objects.order_by("slug").values_list( "slug", flat=True ) ), ["alt", "main"], ) class MultipleGetOrCreateFieldsTest(django_test.TestCase): def test_one_defined(self): obj1 = WithMultipleGetOrCreateFieldsFactory() obj2 = WithMultipleGetOrCreateFieldsFactory(slug=obj1.slug) self.assertEqual(obj1, obj2) def test_both_defined(self): obj1 = WithMultipleGetOrCreateFieldsFactory() with self.assertRaises(django.db.IntegrityError): WithMultipleGetOrCreateFieldsFactory(slug=obj1.slug, text="alt") def test_unique_field_not_in_get_or_create(self): WithMultipleGetOrCreateFieldsFactory(title="Title") with self.assertRaises(django.db.IntegrityError): WithMultipleGetOrCreateFieldsFactory(title="Title") class DjangoPkForceTestCase(django_test.TestCase): def setUp(self): super().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) class DjangoModelLoadingTestCase(django_test.TestCase): """Tests class Meta: model = 'app.Model' pattern.""" def test_loading(self): class ExampleFactory(factory.django.DjangoModelFactory): class Meta: model = 'djapp.StandardModel' self.assertEqual(models.StandardModel, ExampleFactory._meta.get_model_class()) def test_building(self): class ExampleFactory(factory.django.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.django.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.django.DjangoModelFactory): class Meta: model = 'djapp.StandardModel' foo = factory.Sequence(lambda n: n) class Example2Factory(ExampleFactory): class Meta: model = 'djapp.StandardSon' 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) class DjangoNonIntegerPkTestCase(django_test.TestCase): def setUp(self): super().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) class DjangoAbstractBaseSequenceTestCase(DjangoResetTestCase): def test_auto_sequence_son(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_grandson(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 self.reset_database_sequences(models.ConcreteSon) obj = ConcreteSonFactory() self.assertEqual(1, obj.pk) self.assertEqual("foo0", obj.foo) class DjangoRelatedFieldTestCase(django_test.TestCase): @classmethod def setUpClass(cls): super().setUpClass() class PointedFactory(factory.django.DjangoModelFactory): class Meta: model = models.PointedModel foo = 'foo' class PointerFactory(factory.django.DjangoModelFactory): class Meta: model = models.PointerModel bar = 'bar' pointed = factory.SubFactory(PointedFactory, foo='new_foo') class PointedRelatedFactory(PointedFactory): pointer = factory.RelatedFactory( PointerFactory, factory_related_name='pointed', ) class Meta: skip_postgeneration_save = True class PointerExtraFactory(PointerFactory): pointed__foo = 'extra_new_foo' class PointedRelatedExtraFactory(PointedRelatedFactory): pointer__bar = 'extra_new_bar' class PointedRelatedWithTraitFactory(PointedFactory): class Params: with_pointer = factory.Trait( pointer=factory.RelatedFactory( PointerFactory, factory_related_name='pointed', bar='with_trait', ) ) class Meta: skip_postgeneration_save = True cls.PointedFactory = PointedFactory cls.PointerFactory = PointerFactory cls.PointedRelatedFactory = PointedRelatedFactory cls.PointerExtraFactory = PointerExtraFactory cls.PointedRelatedExtraFactory = PointedRelatedExtraFactory cls.PointedRelatedWithTraitFactory = PointedRelatedWithTraitFactory def test_create_pointed(self): pointed = self.PointedFactory() self.assertEqual(pointed, models.PointedModel.objects.get()) self.assertEqual(pointed.foo, 'foo') def test_create_pointer(self): pointer = self.PointerFactory() self.assertEqual(pointer.pointed, models.PointedModel.objects.get()) self.assertEqual(pointer.pointed.foo, 'new_foo') def test_create_pointer_with_deep_context(self): pointer = self.PointerFactory(pointed__foo='new_new_foo') self.assertEqual(pointer, models.PointerModel.objects.get()) self.assertEqual(pointer.bar, 'bar') self.assertEqual(pointer.pointed, models.PointedModel.objects.get()) self.assertEqual(pointer.pointed.foo, 'new_new_foo') def test_create_pointed_related(self): pointed = self.PointedRelatedFactory() self.assertEqual(pointed, models.PointedModel.objects.get()) self.assertEqual(pointed.foo, 'foo') self.assertEqual(pointed.pointer, models.PointerModel.objects.get()) self.assertEqual(pointed.pointer.bar, 'bar') def test_create_pointed_related_with_deep_context(self): pointed = self.PointedRelatedFactory(pointer__bar='new_new_bar') self.assertEqual(pointed, models.PointedModel.objects.get()) self.assertEqual(pointed.foo, 'foo') self.assertEqual(pointed.pointer, models.PointerModel.objects.get()) self.assertEqual(pointed.pointer.bar, 'new_new_bar') def test_create_pointer_extra(self): pointer = self.PointerExtraFactory() self.assertEqual(pointer, models.PointerModel.objects.get()) self.assertEqual(pointer.bar, 'bar') self.assertEqual(pointer.pointed, models.PointedModel.objects.get()) self.assertEqual(pointer.pointed.foo, 'extra_new_foo') def test_create_pointed_related_extra(self): pointed = self.PointedRelatedExtraFactory() self.assertEqual(pointed, models.PointedModel.objects.get()) self.assertEqual(pointed.foo, 'foo') self.assertEqual(pointed.pointer, models.PointerModel.objects.get()) self.assertEqual(pointed.pointer.bar, 'extra_new_bar') def test_create_pointed_related_with_trait(self): pointed = self.PointedRelatedWithTraitFactory( with_pointer=True ) self.assertEqual(pointed, models.PointedModel.objects.get()) self.assertEqual(pointed.foo, 'foo') self.assertEqual(pointed.pointer, models.PointerModel.objects.get()) self.assertEqual(pointed.pointer.bar, 'with_trait') class DjangoPasswordTestCase(django_test.TestCase): def test_build(self): u = WithPasswordFactory.build() self.assertTrue(check_password(PASSWORD, u.pw)) def test_build_with_kwargs(self): password = 'V3R¥.S€C®€T' u = WithPasswordFactory.build(pw=password) self.assertTrue(check_password(password, u.pw)) def test_create(self): u = WithPasswordFactory.create() self.assertTrue(check_password(PASSWORD, u.pw)) class DjangoFileFieldTestCase(django_test.TestCase): def tearDown(self): super().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) with o.afile as f: self.assertEqual(b'', f.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() with o.afile as f: self.assertEqual(b'foo', f.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() with o.afile as f: self.assertEqual(b'example_data\n', f.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) with o.afile as f: # Django only allocates the full path on save() o.save() f.seek(0) self.assertEqual(b'example_data\n', f.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() with o.afile as f: self.assertEqual(b'example_data\n', f.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) with o.afile as f: # Django only allocates the full path on save() o.save() f.seek(0) self.assertEqual(b'example_data\n', f.read()) self.assertEqual('django/example.data', o.afile.name) def test_error_both_file_and_path(self): with 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) with o.afile as f: # Django only allocates the full path on save() o.save() f.seek(0) self.assertEqual(b'example_data\n', f.read()) self.assertEqual('django/example.foo', o.afile.name) def test_existing_file(self): o1 = WithFileFactory.build(afile__from_path=testdata.TESTFILE_PATH) with o1.afile: o1.save() self.assertEqual('django/example.data', o1.afile.name) o2 = WithFileFactory.build(afile__from_file=o1.afile) self.assertIsNone(o2.pk) with o2.afile as f: o2.save() f.seek(0) self.assertEqual(b'example_data\n', f.read()) self.assertNotEqual('django/example.data', o2.afile.name) self.assertRegex(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) class DjangoParamsTestCase(django_test.TestCase): def test_undeclared_fields(self): class WithDefaultValueFactory(factory.django.DjangoModelFactory): class Meta: model = models.WithDefaultValue class Params: with_bar = factory.Trait( foo='bar', ) o = WithDefaultValueFactory() self.assertEqual('', o.foo) def test_pointing_with_traits_using_same_name(self): class PointedFactory(factory.django.DjangoModelFactory): class Meta: model = models.PointedModel class Params: with_bar = factory.Trait( foo='bar', ) class PointerFactory(factory.django.DjangoModelFactory): class Meta: model = models.PointerModel pointed = factory.SubFactory(PointedFactory) class Params: with_bar = factory.Trait( bar='bar', pointed__with_bar=True, ) o = PointerFactory(with_bar=True) self.assertEqual('bar', o.bar) self.assertEqual('bar', o.pointed.foo) class DjangoFakerTestCase(django_test.TestCase): def test_random(self): class StandardModelFactory(factory.django.DjangoModelFactory): class Meta: model = models.StandardModel foo = factory.Faker('pystr') o1 = StandardModelFactory() o2 = StandardModelFactory() self.assertNotEqual(o1.foo, o2.foo) @unittest.skipIf(Image is None, "PIL not installed.") class DjangoImageFieldTestCase(django_test.TestCase): def tearDown(self): super().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) with Image.open(os.path.join(settings.MEDIA_ROOT, o.animage.name)) as i: colors = i.getcolors() # 169 pixels with rgb(254, 0, 0) self.assertEqual([(169, (254, 0, 0))], colors) self.assertEqual('JPEG', i.format) def test_rgba_image(self): o = WithImageFactory.create( animage__palette='RGBA', animage__format='PNG', ) self.assertIsNotNone(o.pk) with Image.open(os.path.join(settings.MEDIA_ROOT, o.animage.name)) as i: self.assertEqual('RGBA', i.mode) 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) with Image.open(os.path.join(settings.MEDIA_ROOT, o.animage.name)) as i: 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() with o.animage as f: # Image file for a 42x42 green jpeg: 301 bytes long. self.assertEqual(301, len(f.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) with o.animage as f: o.save() f.seek(0) # Image file for a 42x42 green jpeg: 301 bytes long. self.assertEqual(301, len(f.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() with o.animage as f: # Image file for a 42x42 green jpeg: 301 bytes long. self.assertEqual(301, len(f.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) with o.animage as f: o.save() f.seek(0) # Image file for a 42x42 green jpeg: 301 bytes long. self.assertEqual(301, len(f.read())) self.assertEqual('django/example.jpeg', o.animage.name) def test_error_both_file_and_path(self): with 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) with o.animage as f: o.save() f.seek(0) # Image file for a 42x42 green jpeg: 301 bytes long. self.assertEqual(301, len(f.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() with o1.animage as f: o2 = WithImageFactory.build(animage__from_file=f) self.assertIsNone(o2.pk) o2.save() with o2.animage as f: # Image file for a 42x42 green jpeg: 301 bytes long. self.assertEqual(301, len(f.read())) self.assertNotEqual('django/example.jpeg', o2.animage.name) self.assertRegex(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 = 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) class PreventSignalsTestCase(django_test.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_receiver_created_during_model_instantiation_is_not_lost(self): with factory.django.mute_signals(signals.post_save): instance = WithSignalsFactory(post_save_signal_receiver=self.handlers.created_during_instantiation) self.assertTrue(self.handlers.created_during_instantiation.called) self.handlers.created_during_instantiation.reset_mock() instance.save() self.assertTrue(self.handlers.created_during_instantiation.called) def test_signal_receiver_order_restored_after_mute_signals(self): def must_be_first(*args, **kwargs): self.handlers.do_stuff(1) def must_be_second(*args, **kwargs): self.handlers.do_stuff(2) signals.post_save.connect(must_be_first) with factory.django.mute_signals(signals.post_save): WithSignalsFactory(post_save_signal_receiver=must_be_second) self.assertEqual(self.handlers.do_stuff.call_args_list, [mock.call(2)]) self.handlers.reset_mock() WithSignalsFactory(post_save_signal_receiver=must_be_second) self.assertEqual(self.handlers.do_stuff.call_args_list, [mock.call(1), mock.call(2)]) 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 skip_postgeneration_save = True @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_related_model_with_post_hook(self): """ Related factory with post_generation hook should not call disabled signals. Refs https://github.com/FactoryBoy/factory_boy/issues/424 """ @factory.django.mute_signals(signals.post_save) class PointedFactory(factory.django.DjangoModelFactory): class Meta: model = models.PointedModel skip_postgeneration_save = True @factory.post_generation def post_action(obj, create, extracted, **kwargs): pass class PointerFactory(factory.django.DjangoModelFactory): pointed = factory.SubFactory(PointedFactory) class Meta: model = models.PointerModel PointerFactory.create() self.handlers.post_save.assert_called_once_with( signal=mock.ANY, sender=models.PointerModel, instance=mock.ANY, created=True, update_fields=None, raw=False, using="default", ) 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: @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() class PreventChainedSignalsTestCase(django_test.TestCase): def setUp(self): self.post_save_mock = mock.Mock(side_effect=Exception('BOOM!')) signals.post_save.connect(self.post_save_mock, models.PointedModel) def tearDown(self): signals.post_save.disconnect(self.post_save_mock, models.PointedModel) @factory.django.mute_signals(signals.post_save) class WithSignalsDecoratedFactory(factory.django.DjangoModelFactory): class Meta: model = models.PointedModel def test_class_decorator_with_muted_subfactory(self): class UndecoratedFactory(factory.django.DjangoModelFactory): class Meta: model = models.PointerModel pointed = factory.SubFactory(self.WithSignalsDecoratedFactory) UndecoratedFactory() self.post_save_mock.assert_not_called() def test_class_decorator_with_muted_related_factory(self): class UndecoratedFactory(factory.django.DjangoModelFactory): class Meta: model = models.PointerModel skip_postgeneration_save = True pointed = factory.RelatedFactory(self.WithSignalsDecoratedFactory) UndecoratedFactory() self.post_save_mock.assert_not_called() class DjangoCustomManagerTestCase(django_test.TestCase): def test_extra_args(self): # Our CustomManager will remove the 'arg=' argument. 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') class DjangoModelFactoryDuplicateSaveDeprecationTest(django_test.TestCase): class StandardFactoryWithPost(StandardFactory): @factory.post_generation def post_action(obj, create, extracted, **kwargs): return 3 def test_create_warning(self): with self.assertWarns(DeprecationWarning) as cm: self.StandardFactoryWithPost.create() [msg] = cm.warning.args self.assertEqual( msg, "StandardFactoryWithPost._after_postgeneration will stop saving the " "instance after postgeneration hooks in the next major release.\n" "If the save call is extraneous, set skip_postgeneration_save=True in the " "StandardFactoryWithPost.Meta.\n" "To keep saving the instance, move the save call to your postgeneration " "hooks or override _after_postgeneration.", ) def test_build_no_warning(self): self.StandardFactoryWithPost.build() class IntegrityErrorForMissingOriginalParamsTest(django_test.TestCase): def test_raises_integrity_error(self): """ In factory.django.DjangoModelFactory._get_or_create _original_params can give some trouble when None This test case verifies if the IntegrityError is correctly re-raised """ class MultifieldModelFactory2(MultifieldModelFactory): class Meta: model = models.MultifieldModel django_get_or_create = ['text'] class HasMultifieldModelFactory(factory.django.DjangoModelFactory): multifield = factory.SubFactory(MultifieldModelFactory2) class Meta: model = models.HasMultifieldModel HasMultifieldModelFactory(multifield__slug="test") with self.assertRaises(IntegrityError): HasMultifieldModelFactory(multifield__slug="test") factory-boy-3.3.3/tests/test_docs_internals.py000066400000000000000000000102571475011040400215410ustar00rootroot00000000000000# 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 the docs/internals module.""" import datetime import unittest import factory import factory.fuzzy class User: def __init__( self, username, full_name, is_active=True, is_superuser=False, is_staff=False, creation_date=None, deactivation_date=None, ): self.username = username self.full_name = full_name self.is_active = is_active self.is_superuser = is_superuser self.is_staff = is_staff self.creation_date = creation_date self.deactivation_date = deactivation_date self.logs = [] def log(self, action, timestamp): UserLog(user=self, action=action, timestamp=timestamp) class UserLog: ACTIONS = ['create', 'update', 'disable'] def __init__(self, user, action, timestamp): self.user = user self.action = action self.timestamp = timestamp user.logs.append(self) class UserLogFactory(factory.Factory): class Meta: model = UserLog user = factory.SubFactory('test_docs_internals.UserFactory') timestamp = factory.fuzzy.FuzzyDateTime( datetime.datetime(2000, 1, 1, tzinfo=datetime.timezone.utc), ) action = factory.Iterator(UserLog.ACTIONS) class UserFactory(factory.Factory): class Meta: model = User class Params: # Allow us to quickly enable staff/superuser flags superuser = factory.Trait( is_superuser=True, is_staff=True, ) # Meta parameter handling all 'enabled'-related fields enabled = True # Classic fields username = factory.Faker('user_name') full_name = factory.Faker('name') creation_date = factory.fuzzy.FuzzyDateTime( datetime.datetime(2000, 1, 1, tzinfo=datetime.timezone.utc), datetime.datetime(2015, 12, 31, 20, tzinfo=datetime.timezone.utc) ) # Conditional flags is_active = factory.SelfAttribute('enabled') deactivation_date = factory.Maybe( 'enabled', None, factory.fuzzy.FuzzyDateTime( datetime.datetime.now().replace(tzinfo=datetime.timezone.utc) - datetime.timedelta(days=10), datetime.datetime.now().replace(tzinfo=datetime.timezone.utc) - datetime.timedelta(days=1), ), ) # Related logs creation_log = factory.RelatedFactory( UserLogFactory, factory_related_name='user', action='create', timestamp=factory.SelfAttribute('user.creation_date'), ) class DocsInternalsTests(unittest.TestCase): def test_simple_usage(self): user = UserFactory() # Default user should be active, not super self.assertTrue(user.is_active) self.assertFalse(user.is_superuser) self.assertFalse(user.is_staff) # We should have one log self.assertEqual(1, len(user.logs)) # And it should be a 'create' action linked to the user's creation_date self.assertEqual('create', user.logs[0].action) self.assertEqual(user, user.logs[0].user) self.assertEqual(user.creation_date, user.logs[0].timestamp) factory-boy-3.3.3/tests/test_faker.py000066400000000000000000000133141475011040400176170ustar00rootroot00000000000000# Copyright: See the LICENSE file. import collections import datetime import random import unittest import faker.providers import factory class MockFaker: def __init__(self, expected): self.expected = expected self.random = random.Random() def format(self, provider, **kwargs): return self.expected[provider] class AdvancedMockFaker: def __init__(self, handlers): self.handlers = handlers self.random = random.Random() def format(self, provider, **kwargs): handler = self.handlers[provider] return handler(**kwargs) 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 _setup_advanced_mock_faker(self, locale=None, **handlers): if locale is None: locale = factory.Faker._DEFAULT_LOCALE factory.Faker._FAKER_REGISTRY[locale] = AdvancedMockFaker(handlers) def test_simple_biased(self): self._setup_mock_faker(name="John Doe") faker_field = factory.Faker('name') self.assertEqual("John Doe", faker_field.evaluate(None, None, {'locale': None})) def test_full_factory(self): class Profile: 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: 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: 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) def test_faker_customization(self): """Factory declarations in Faker parameters should be accepted.""" Trip = collections.namedtuple('Trip', ['departure', 'transfer', 'arrival']) may_4th = datetime.date(1977, 5, 4) may_25th = datetime.date(1977, 5, 25) october_19th = datetime.date(1977, 10, 19) class TripFactory(factory.Factory): class Meta: model = Trip departure = may_4th arrival = may_25th transfer = factory.Faker( 'date_between_dates', start_date=factory.SelfAttribute('..departure'), end_date=factory.SelfAttribute('..arrival'), ) def fake_select_date(start_date, end_date): """Fake date_between_dates.""" # Ensure that dates have been transferred from the factory # to Faker parameters. self.assertEqual(start_date, may_4th) self.assertEqual(end_date, may_25th) return october_19th self._setup_advanced_mock_faker( date_between_dates=fake_select_date, ) trip = TripFactory() self.assertEqual(may_4th, trip.departure) self.assertEqual(october_19th, trip.transfer) self.assertEqual(may_25th, trip.arrival) factory-boy-3.3.3/tests/test_fuzzy.py000066400000000000000000000514711475011040400177240ustar00rootroot00000000000000# Copyright: See the LICENSE file. import datetime import decimal import unittest import warnings from unittest import mock from factory import fuzzy, random from . import utils class FuzzyAttributeTestCase(unittest.TestCase): def test_simple_call(self): d = fuzzy.FuzzyAttribute(lambda: 10) res = utils.evaluate_declaration(d) self.assertEqual(10, res) res = utils.evaluate_declaration(d) self.assertEqual(10, res) class FuzzyChoiceTestCase(unittest.TestCase): def test_unbiased(self): options = [1, 2, 3] d = fuzzy.FuzzyChoice(options) res = utils.evaluate_declaration(d) 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.random.randgen.choice', fake_choice): res = utils.evaluate_declaration(d) self.assertEqual(6, res) def test_generator(self): def options(): yield from range(3) d = fuzzy.FuzzyChoice(options()) res = utils.evaluate_declaration(d) self.assertIn(res, [0, 1, 2]) # And repeat res = utils.evaluate_declaration(d) self.assertIn(res, [0, 1, 2]) def test_lazy_generator(self): class Gen: 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 = utils.evaluate_declaration(d) self.assertIn(res, [1, 2, 3]) self.assertTrue(opts.unrolled) def test_getter(self): options = [('a', 1), ('b', 2), ('c', 3)] d = fuzzy.FuzzyChoice(options, getter=lambda x: x[1]) res = utils.evaluate_declaration(d) self.assertIn(res, [1, 2, 3]) 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 = utils.evaluate_declaration(fuzz) self.assertIn(res, [2, 3]) fuzz = fuzzy.FuzzyInteger(4) for _i in range(20): res = utils.evaluate_declaration(fuzz) 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.random.randgen.randrange', fake_randrange): res = utils.evaluate_declaration(fuzz) 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.random.randgen.randrange', fake_randrange): res = utils.evaluate_declaration(fuzz) 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.random.randgen.randrange', fake_randrange): res = utils.evaluate_declaration(fuzz) 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 = utils.evaluate_declaration(fuzz) 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 = utils.evaluate_declaration(fuzz) 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 = utils.evaluate_declaration(fuzz) self.assertTrue( decimal.Decimal('1.0') <= res <= decimal.Decimal('4.0'), "value %d is not between 1.0 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.random.randgen.uniform', fake_uniform): res = utils.evaluate_declaration(fuzz) 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.random.randgen.uniform', fake_uniform): res = utils.evaluate_declaration(fuzz) 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.random.randgen.uniform', fake_uniform): res = utils.evaluate_declaration(fuzz) self.assertEqual(decimal.Decimal('8.001').quantize(decimal.Decimal(10) ** -3), res) 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 utils.evaluate_declaration(fuzz) finally: decimal_context.traps[decimal.FloatOperation] = old_traps class FuzzyFloatTestCase(unittest.TestCase): def test_definition(self): """Tests all ways of defining a FuzzyFloat.""" fuzz = fuzzy.FuzzyFloat(2.0, 3.0) for _i in range(20): res = utils.evaluate_declaration(fuzz) self.assertTrue(2.0 <= res <= 3.0, "value %d is not between 2.0 and 3.0" % res) fuzz = fuzzy.FuzzyFloat(4.0) for _i in range(20): res = utils.evaluate_declaration(fuzz) self.assertTrue(0.0 <= res <= 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 = utils.evaluate_declaration(fuzz) self.assertTrue(1.0 <= res <= 4.0, "value %d is not between 1.0 and 4.0" % res) self.assertTrue(res.as_tuple().exponent, -5) def test_biased(self): fake_uniform = lambda low, high: low + high fuzz = fuzzy.FuzzyFloat(2.0, 8.0) with mock.patch('factory.random.randgen.uniform', fake_uniform): res = utils.evaluate_declaration(fuzz) self.assertEqual(10.0, res) def test_biased_high_only(self): fake_uniform = lambda low, high: low + high fuzz = fuzzy.FuzzyFloat(8.0) with mock.patch('factory.random.randgen.uniform', fake_uniform): res = utils.evaluate_declaration(fuzz) self.assertEqual(8.0, res) def test_default_precision(self): fake_uniform = lambda low, high: low + high + 0.000000000000011 fuzz = fuzzy.FuzzyFloat(8.0) with mock.patch('factory.random.randgen.uniform', fake_uniform): res = utils.evaluate_declaration(fuzz) self.assertEqual(8.00000000000001, res) def test_precision(self): fake_uniform = lambda low, high: low + high + 0.001 fuzz = fuzzy.FuzzyFloat(8.0, precision=4) with mock.patch('factory.random.randgen.uniform', fake_uniform): res = utils.evaluate_declaration(fuzz) self.assertEqual(8.001, res) 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 = utils.evaluate_declaration(fuzz) 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 = utils.evaluate_declaration(fuzz) self.assertLessEqual(self.jan1, res) self.assertLessEqual(res, self.jan3) def test_invalid_definition(self): with self.assertRaises(ValueError): fuzzy.FuzzyDate(self.jan31, self.jan1) def test_invalid_partial_definition(self): with utils.mocked_date_today(self.jan1, fuzzy): with 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.random.randgen.randint', fake_randint): res = utils.evaluate_declaration(fuzz) 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.random.randgen.randint', fake_randint): res = utils.evaluate_declaration(fuzz) 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 = utils.evaluate_declaration(fuzz) 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 = utils.evaluate_declaration(fuzz) self.assertLessEqual(self.jan1, res) self.assertLessEqual(res, self.jan3) def test_aware_start(self): """Tests that a timezone-aware start datetime is rejected.""" with self.assertRaises(ValueError): fuzzy.FuzzyNaiveDateTime(self.jan1.replace(tzinfo=datetime.timezone.utc), self.jan31) def test_aware_end(self): """Tests that a timezone-aware end datetime is rejected.""" with self.assertRaises(ValueError): fuzzy.FuzzyNaiveDateTime(self.jan1, self.jan31.replace(tzinfo=datetime.timezone.utc)) def test_force_year(self): fuzz = fuzzy.FuzzyNaiveDateTime(self.jan1, self.jan31, force_year=4) for _i in range(20): res = utils.evaluate_declaration(fuzz) 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 = utils.evaluate_declaration(fuzz) 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 = utils.evaluate_declaration(fuzz) 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 = utils.evaluate_declaration(fuzz) 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 = utils.evaluate_declaration(fuzz) 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 = utils.evaluate_declaration(fuzz) 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 = utils.evaluate_declaration(fuzz) self.assertEqual(4, res.microsecond) def test_invalid_definition(self): with self.assertRaises(ValueError): fuzzy.FuzzyNaiveDateTime(self.jan31, self.jan1) def test_invalid_partial_definition(self): with utils.mocked_datetime_now(self.jan1, fuzzy): with 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.random.randgen.randint', fake_randint): res = utils.evaluate_declaration(fuzz) 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.random.randgen.randint', fake_randint): res = utils.evaluate_declaration(fuzz) 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=datetime.timezone.utc) cls.jan3 = datetime.datetime(2013, 1, 3, tzinfo=datetime.timezone.utc) cls.jan31 = datetime.datetime(2013, 1, 31, tzinfo=datetime.timezone.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 = utils.evaluate_declaration(fuzz) 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 = utils.evaluate_declaration(fuzz) self.assertLessEqual(self.jan1, res) self.assertLessEqual(res, self.jan3) def test_invalid_definition(self): with self.assertRaises(ValueError): fuzzy.FuzzyDateTime(self.jan31, self.jan1) def test_invalid_partial_definition(self): with utils.mocked_datetime_now(self.jan1, fuzzy): with self.assertRaises(ValueError): fuzzy.FuzzyDateTime(self.jan31) def test_naive_start(self): """Tests that a timezone-naive start datetime is rejected.""" with 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.""" with 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 = utils.evaluate_declaration(fuzz) 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 = utils.evaluate_declaration(fuzz) 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 = utils.evaluate_declaration(fuzz) 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 = utils.evaluate_declaration(fuzz) 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 = utils.evaluate_declaration(fuzz) 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 = utils.evaluate_declaration(fuzz) 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 = utils.evaluate_declaration(fuzz) 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.random.randgen.randint', fake_randint): res = utils.evaluate_declaration(fuzz) self.assertEqual(datetime.datetime(2013, 1, 16, tzinfo=datetime.timezone.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.random.randgen.randint', fake_randint): res = utils.evaluate_declaration(fuzz) self.assertEqual(datetime.datetime(2013, 1, 2, tzinfo=datetime.timezone.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 = utils.evaluate_declaration(fuzz) 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.random.randgen.choice', fake_choice): res = utils.evaluate_declaration(fuzz) self.assertEqual('preaaaapost', res) def test_generator(self): def options(): yield 'a' yield 'b' yield 'c' fuzz = fuzzy.FuzzyText(chars=options(), length=12) res = utils.evaluate_declaration(fuzz) 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) random.reseed_random(42) value = utils.evaluate_declaration(fuzz) random.reseed_random(42) value2 = utils.evaluate_declaration(fuzz) self.assertEqual(value, value2) def test_seeding_warning(self): with warnings.catch_warnings(record=True) as w: # Do not turn expected warning into an error. warnings.filterwarnings("default", category=UserWarning, module=r"tests\.test_fuzzy") fuzz = fuzzy.FuzzyDate(datetime.date(2013, 1, 1)) utils.evaluate_declaration(fuzz) self.assertEqual(1, len(w)) self.assertIn('factory_boy/issues/331', str(w[-1].message)) def test_reset_state(self): fuzz = fuzzy.FuzzyInteger(1, 1000) state = random.get_random_state() value = utils.evaluate_declaration(fuzz) random.set_random_state(state) value2 = utils.evaluate_declaration(fuzz) self.assertEqual(value, value2) factory-boy-3.3.3/tests/test_helpers.py000066400000000000000000000036741475011040400202010ustar00rootroot00000000000000# Copyright: See the LICENSE file. import io import logging import unittest from factory import helpers class DebugTest(unittest.TestCase): """Tests for the 'factory.debug()' helper.""" def test_default_logger(self): stream1 = io.StringIO() stream2 = io.StringIO() logger = logging.getLogger('factory.test') h = logging.StreamHandler(stream1) h.setLevel(logging.INFO) logger.addHandler(h) # Non-debug: no text gets out logger.debug("Test") self.assertEqual('', stream1.getvalue()) with helpers.debug(stream=stream2): # Debug: text goes to new stream only logger.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()) def test_restores_logging_on_error(self): class MyException(Exception): pass stream = io.StringIO() try: with helpers.debug(stream=stream): raise MyException except MyException: logger = logging.getLogger('factory') self.assertEqual(logger.level, logging.NOTSET) self.assertEqual(logger.handlers, []) factory-boy-3.3.3/tests/test_mongoengine.py000066400000000000000000000042561475011040400210410ustar00rootroot00000000000000# Copyright: See the LICENSE file. """Tests for factory_boy/MongoEngine interactions.""" import os import unittest try: import mongoengine except ImportError: raise unittest.SkipTest("mongodb tests disabled.") import mongomock import factory 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) 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, mongo_client_class=mongomock.MongoClient, # 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, uuidRepresentation='standard', ) @classmethod def tearDownClass(cls): cls.db.drop_database(cls.db_name) 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-3.3.3/tests/test_regression.py000066400000000000000000000032441475011040400207100ustar00rootroot00000000000000# Copyright: See the LICENSE file. """Regression tests related to issues found with the project""" import datetime import typing as T import unittest import factory # Example objects # =============== class Author(T.NamedTuple): fullname: str pseudonym: T.Optional[str] = None class Book(T.NamedTuple): title: str author: Author class PublishedBook(T.NamedTuple): book: Book published_on: datetime.date countries: T.List[str] class FakerRegressionTests(unittest.TestCase): def test_locale_issue(self): """Regression test for `KeyError: 'locale'` See #785 #786 #787 #788 #790 #796. """ class AuthorFactory(factory.Factory): class Meta: model = Author class Params: unknown = factory.Trait( fullname="", ) fullname = factory.Faker("name") public_author = AuthorFactory(unknown=False) self.assertIsNone(public_author.pseudonym) unknown_author = AuthorFactory(unknown=True) self.assertEqual("", unknown_author.fullname) def test_evaluated_without_locale(self): """Regression test for `KeyError: 'locale'` raised in `evaluate`. See #965 """ class AuthorFactory(factory.Factory): fullname = factory.Faker("name") pseudonym = factory.Maybe( decider=factory.Faker("pybool"), yes_declaration="yes", no_declaration="no", ) class Meta: model = Author author = AuthorFactory() self.assertIn(author.pseudonym, ["yes", "no"]) factory-boy-3.3.3/tests/test_transformer.py000066400000000000000000000153561475011040400211010ustar00rootroot00000000000000# Copyright: See the LICENSE file. from unittest import TestCase import factory class TransformCounter: calls_count = 0 @classmethod def __call__(cls, x): cls.calls_count += 1 return x.upper() @classmethod def reset(cls): cls.calls_count = 0 transform = TransformCounter() class Upper: def __init__(self, name, **extra): self.name = name self.extra = extra class UpperFactory(factory.Factory): name = factory.Transformer("value", transform=transform) class Meta: model = Upper class TransformerTest(TestCase): def setUp(self): transform.reset() def test_transform_count(self): self.assertEqual("VALUE", UpperFactory().name) self.assertEqual(transform.calls_count, 1) def test_transform_kwarg(self): self.assertEqual("TEST", UpperFactory(name="test").name) self.assertEqual(transform.calls_count, 1) self.assertEqual("VALUE", UpperFactory().name) self.assertEqual(transform.calls_count, 2) def test_transform_faker(self): value = UpperFactory(name=factory.Faker("first_name_female", locale="fr")).name self.assertIs(value.isupper(), True) def test_transform_linked(self): value = UpperFactory( name=factory.LazyAttribute(lambda o: o.username.replace(".", " ")), username="john.doe", ).name self.assertEqual(value, "JOHN DOE") def test_force_value(self): value = UpperFactory(name=factory.Transformer.Force("Mia")).name self.assertEqual(value, "Mia") def test_force_value_declaration(self): """Pretty unlikely use case, but easy enough to cover.""" value = UpperFactory( name=factory.Transformer.Force( factory.LazyFunction(lambda: "infinity") ) ).name self.assertEqual(value, "infinity") def test_force_value_declaration_context(self): """Ensure "forced" values run at the right level.""" value = UpperFactory( name=factory.Transformer.Force( factory.LazyAttribute(lambda o: o.username.replace(".", " ")), ), username="john.doe", ).name self.assertEqual(value, "john doe") class TestObject: def __init__(self, one=None, two=None, three=None): self.one = one self.two = two self.three = three class TransformDeclarationFactory(factory.Factory): class Meta: model = TestObject one = factory.Transformer("", transform=str.upper) two = factory.Transformer(factory.Sequence(int), transform=lambda n: n ** 2) class TransformerSequenceTest(TestCase): def test_on_sequence(self): instance = TransformDeclarationFactory(__sequence=2) self.assertEqual(instance.one, "") self.assertEqual(instance.two, 4) self.assertIsNone(instance.three) def test_on_user_supplied(self): """A transformer can wrap a call-time declaration""" instance = TransformDeclarationFactory( one=factory.Sequence(str), two=2, __sequence=2, ) self.assertEqual(instance.one, "2") self.assertEqual(instance.two, 4) self.assertIsNone(instance.three) class WithMaybeFactory(factory.Factory): class Meta: model = TestObject one = True two = factory.Maybe( 'one', yes_declaration=factory.Transformer("yes", transform=str.upper), no_declaration=factory.Transformer("no", transform=str.upper), ) three = factory.Maybe('one', no_declaration=factory.Transformer("three", transform=str.upper)) class TransformerMaybeTest(TestCase): def test_default_transform(self): instance = WithMaybeFactory() self.assertIs(instance.one, True) self.assertEqual(instance.two, "YES") self.assertIsNone(instance.three) def test_yes_transform(self): instance = WithMaybeFactory(one=True) self.assertIs(instance.one, True) self.assertEqual(instance.two, "YES") self.assertIsNone(instance.three) def test_no_transform(self): instance = WithMaybeFactory(one=False) self.assertIs(instance.one, False) self.assertEqual(instance.two, "NO") self.assertEqual(instance.three, "THREE") def test_override(self): instance = WithMaybeFactory(one=True, two="NI") self.assertIs(instance.one, True) self.assertEqual(instance.two, "NI") self.assertIsNone(instance.three) class RelatedTest(TestCase): def test_default_transform(self): cities = [] class City: def __init__(self, capital_of, name): self.capital_of = capital_of self.name = name cities.append(self) class Country: def __init__(self, name): self.name = name class CityFactory(factory.Factory): class Meta: model = City name = "Rennes" class CountryFactory(factory.Factory): class Meta: model = Country name = "France" capital_city = factory.RelatedFactory( CityFactory, factory_related_name="capital_of", name=factory.Transformer("Paris", transform=str.upper), ) instance = CountryFactory() self.assertEqual(instance.name, "France") [city] = cities self.assertEqual(city.capital_of, instance) self.assertEqual(city.name, "PARIS") class WithTraitFactory(factory.Factory): class Meta: model = TestObject class Params: upper_two = factory.Trait( two=factory.Transformer("two", transform=str.upper) ) odds = factory.Trait( one="one", three="three", ) one = factory.Transformer("one", transform=str.upper) class TransformerTraitTest(TestCase): def test_traits_off(self): instance = WithTraitFactory() self.assertEqual(instance.one, "ONE") self.assertIsNone(instance.two) self.assertIsNone(instance.three) def test_trait_transform_applies(self): """A trait-provided transformer should apply to existing values""" instance = WithTraitFactory(upper_two=True) self.assertEqual(instance.one, "ONE") self.assertEqual(instance.two, "TWO") self.assertIsNone(instance.three) def test_trait_transform_applies_supplied(self): """A trait-provided transformer should be overridden by caller-provided values""" instance = WithTraitFactory(upper_two=True, two="two") self.assertEqual(instance.one, "ONE") self.assertEqual(instance.two, "two") self.assertIsNone(instance.three) factory-boy-3.3.3/tests/test_typing.py000066400000000000000000000011221475011040400200330ustar00rootroot00000000000000# Copyright: See the LICENSE file. import dataclasses import unittest import factory @dataclasses.dataclass class User: name: str email: str id: int class TypingTests(unittest.TestCase): def test_simple_factory(self) -> None: class UserFactory(factory.Factory[User]): name = "John Doe" email = "john.doe@example.org" id = 42 class Meta: model = User result: User result = UserFactory.build() result = UserFactory.create() self.assertEqual(result.name, "John Doe") factory-boy-3.3.3/tests/test_using.py000066400000000000000000002714041475011040400176620ustar00rootroot00000000000000# Copyright: See the LICENSE file. """Tests using factory.""" import collections import datetime import os import sys import unittest import factory from factory import errors from . import utils try: import django # noqa: F401 SKIP_DJANGO = False except ImportError: SKIP_DJANGO = True class TestObject: 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 Dummy: def __init__(self, **kwargs): self._fields = set(kwargs) for k, v in kwargs.items(): setattr(self, k, v) @property def as_dict(self): return {field: getattr(self, field) for field in self._fields} def __repr__(self): return '%s(%s)' % ( self.__class__.__name__, ', '.join('%s=%r' % pair for pair in sorted(self.as_dict.items())) ) class FakeModel: @classmethod def create(cls, **kwargs): instance = cls(**kwargs) instance.id = 1 return instance class FakeModelManager: 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') @unittest.skipIf(SKIP_DJANGO, "django tests disabled.") 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') @unittest.skipIf(SKIP_DJANGO, "django tests disabled.") 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') @unittest.skipIf(SKIP_DJANGO, "django tests disabled.") 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') @unittest.skipIf(SKIP_DJANGO, "django tests disabled.") 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') @unittest.skipIf(SKIP_DJANGO, "django tests disabled.") 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') @unittest.skipIf(SKIP_DJANGO, "django tests disabled.") 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): class TestObjectFactory(factory.Factory, TestObject): class Meta: model = TestObject one = 'one' test_object = TestObjectFactory.build() 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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) def test_rename_non_existent_kwarg(self): # see https://github.com/FactoryBoy/factory_boy/issues/504 class TestObject: def __init__(self, attributes=None): self.attributes = attributes class TestObjectFactory(factory.Factory): class Meta: model = TestObject rename = {'form_attributes': 'attributes'} try: TestObjectFactory() except KeyError: self.fail('should not raise KeyError for missing renamed attributes') class MaybeTestCase(unittest.TestCase): def test_simple_maybe(self): class DummyFactory(factory.Factory): class Meta: model = Dummy # Undeclared: key = None both = factory.Maybe('key', 1, 2) yes = factory.Maybe('key', 1) no = factory.Maybe('key', no_declaration=2) none = factory.Maybe('key') obj_default = DummyFactory.build() obj_true = DummyFactory.build(key=True) obj_false = DummyFactory.build(key=False) self.assertEqual(dict(both=2, no=2), obj_default.as_dict) self.assertEqual(dict(key=True, both=1, yes=1), obj_true.as_dict) self.assertEqual(dict(key=False, both=2, no=2), obj_false.as_dict) def test_declarations(self): class DummyFactory(factory.Factory): class Meta: model = Dummy a = 0 b = 1 # biggest = 'b' if .b > .a else 'a' biggest = factory.Maybe(factory.LazyAttribute(lambda o: o.a < o.b), 'b', 'a') # max_value = .b if .b > .a else .a = max(.a, .b) max_value = factory.Maybe( factory.LazyAttribute(lambda o: o.a < o.b), factory.SelfAttribute('b'), factory.SelfAttribute('a'), ) obj_ordered = DummyFactory.build(a=1, b=2) obj_equal = DummyFactory.build(a=3, b=3) obj_reverse = DummyFactory.build(a=5, b=4) self.assertEqual(dict(a=1, b=2, biggest='b', max_value=2, ), obj_ordered.as_dict) self.assertEqual(dict(a=3, b=3, biggest='a', max_value=3, ), obj_equal.as_dict) self.assertEqual(dict(a=5, b=4, biggest='a', max_value=5, ), obj_reverse.as_dict) def test_post_generation(self): # Helpers @factory.post_generation def square(obj, *args, **kwargs): obj.value *= obj.value @factory.post_generation def quintuple(obj, *args, **kwargs): obj.value *= 5 @factory.post_generation def double(obj, *args, **kwargs): obj.value *= 2 @factory.post_generation def decrement(obj, *args, **kwargs): obj.value -= 1 class DummyFactory(factory.Factory): class Meta: model = Dummy value = 0 square_it = factory.Maybe('square', square) quintuple_it = factory.Maybe('quintuple', quintuple) adjust_nums = factory.Maybe( factory.LazyAttribute(lambda o: o.value % 2 == 0), double, decrement, ) obj_untouched = DummyFactory.build(value=4) obj_squared = DummyFactory.build(value=5, square=True) obj_combined = DummyFactory.build(value=6, square=True, quintuple=True) self.assertEqual(4 * 2, obj_untouched.value) self.assertEqual(5 ** 2 - 1, obj_squared.value) self.assertEqual(6 ** 2 * 5 * 2, obj_combined.value) 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_post_generation_traits(self): @factory.post_generation def compute(obj, _create, _value, power=2, **kwargs): obj.value = obj.value ** power class DummyFactory(factory.Factory): class Meta: model = Dummy value = 3 class Params: exponentiate = factory.Trait(apply_exponent=compute) base = DummyFactory.build() self.assertEqual(dict(value=3), base.as_dict) exp = DummyFactory.build(exponentiate=True) self.assertEqual(dict(value=9), exp.as_dict) higher = DummyFactory.build(exponentiate=True, apply_exponent__power=4) self.assertEqual(dict(value=81), higher.as_dict) 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_params(self): """Override a Params value in a trait""" class TestObjectFactory(factory.Factory): class Meta: model = TestObject one = factory.LazyAttribute(lambda o: o.zero + 1) class Params: zero = 0 plus_one = factory.Trait(zero=1) obj = TestObjectFactory(plus_one=True) self.assertEqual(obj.one, 2) 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) override = factory.Trait(even=True, two=False) # 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), ) # Setting override should override two and set it to False obj = TestObjectFactory(override=True) self.assertEqual( obj.as_dict(), dict(one=None, two=False, three=None, four=True, five=None), ) 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) def test_deep_traits(self): class TestObjectFactory(factory.Factory): class Meta: model = TestObject class WrapperFactory(factory.Factory): class Meta: model = TestObject class Params: deep_one = factory.Trait( one=1, two__one=2, ) two = factory.SubFactory(TestObjectFactory) wrapper = WrapperFactory(deep_one=True) self.assertEqual(1, wrapper.one) self.assertEqual(2, wrapper.two.one) def test_traits_and_postgeneration(self): """A trait parameter should be resolved before post_generation hooks. See https://github.com/FactoryBoy/factory_boy/issues/466. """ PRICES = {} Pizza = collections.namedtuple('Pizza', ['style', 'toppings']) class PizzaFactory(factory.Factory): class Meta: model = Pizza class Params: fancy = factory.Trait( toppings=['eggs', 'ham', 'extra_cheese'], pricing__extra=10, ) pricing__extra = 0 toppings = ['tomato', 'cheese'] style = 'margharita' @factory.post_generation def pricing(self, create, extracted, base_price=5, extra=0, **kwargs): PRICES[base_price + extra] = self p = PizzaFactory.build() self.assertEqual({5: p}, PRICES) 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: f'{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: 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: def __init__(self, **kwargs): for k, v in kwargs.items(): setattr(self, k, v) class TestObjectFactory(factory.Factory): class Meta: model = TestObject class OtherTestObject: 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_sub_factory_deep_overrides(self): Author = collections.namedtuple('Author', ['name', 'country']) Book = collections.namedtuple('Book', ['title', 'author']) Chapter = collections.namedtuple('Chapter', ['book', 'number']) class AuthorFactory(factory.Factory): class Meta: model = Author name = "John" country = 'XX' class BookFactory(factory.Factory): class Meta: model = Book title = "The mighty adventures of nobody." author = factory.SubFactory(AuthorFactory) class ChapterFactory(factory.Factory): class Meta: model = Chapter book = factory.SubFactory(BookFactory) number = factory.Sequence(lambda n: n) book__author__country = factory.LazyAttribute(lambda o: 'FR') chapter = ChapterFactory() self.assertEqual('FR', chapter.book.author.country) def test_nested_sub_factory(self): """Test nested sub-factories.""" class TestObject: 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: 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: 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_deep_nested_subfactory(self): counter = iter(range(100)) class Node: def __init__(self, label, child=None): self.id = next(counter) self.label = label self.child = child class LeafFactory(factory.Factory): class Meta: model = Node label = 'leaf' class BranchFactory(factory.Factory): class Meta: model = Node label = 'branch' child = factory.SubFactory(LeafFactory) class TreeFactory(factory.Factory): class Meta: model = Node label = 'tree' child = factory.SubFactory(BranchFactory) child__child__label = 'magic-leaf' leaf = LeafFactory() # Magic corruption did happen here once: # forcing child__child=X while another part already set another value # on child__child__label meant that the value passed for child__child # was merged into the factory's inner declaration dict. mtree_1 = TreeFactory(child__child=leaf) mtree_2 = TreeFactory() self.assertEqual(0, mtree_1.child.child.id) self.assertEqual('leaf', mtree_1.child.child.label) self.assertEqual(1, mtree_1.child.id) self.assertEqual('branch', mtree_1.child.label) self.assertEqual(2, mtree_1.id) self.assertEqual('tree', mtree_1.label) self.assertEqual(3, mtree_2.child.child.id) self.assertEqual('magic-leaf', mtree_2.child.child.label) self.assertEqual(4, mtree_2.child.id) self.assertEqual('branch', mtree_2.child.label) self.assertEqual(5, mtree_2.id) self.assertEqual('tree', mtree_2.label) def test_sub_factory_and_inheritance(self): """Test inheriting from a factory with subfactories, overriding.""" class TestObject: 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: def __init__(self, a, b): self.a = a self.b = b class SideA: def __init__(self, inner_from_a): self.inner_from_a = inner_from_a class SideB: def __init__(self, inner_from_b): self.inner_from_b = inner_from_b class OuterMost: 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 sample_int = 3 container_len = factory.ContainerAttribute(lambda obj, containers: len(containers or []), strict=True) class TestModel2Factory(FakeModelFactory): class Meta: model = TestModel2 sample_int = 1 descendant = factory.SubFactory(TestModelFactory, sample_int=1) obj = TestModel2Factory.build() self.assertEqual(1, obj.sample_int) self.assertEqual(1, obj.descendant.sample_int) self.assertEqual(1, obj.descendant.container_len) with self.assertRaises(TypeError): TestModelFactory.build() def test_function_container_attribute(self): class TestModel2(FakeModel): pass class TestModelFactory(FakeModelFactory): class Meta: model = TestModel sample_int = 3 @factory.container_attribute def container_len(self, containers): if containers: return len(containers) return 42 class TestModel2Factory(FakeModelFactory): class Meta: model = TestModel2 sample_int = 1 descendant = factory.SubFactory(TestModelFactory, sample_int=1) obj = TestModel2Factory.build() self.assertEqual(1, obj.sample_int) self.assertEqual(1, obj.descendant.sample_int) self.assertEqual(1, obj.descendant.container_len) obj = TestModelFactory() self.assertEqual(3, obj.sample_int) self.assertEqual(42, obj.container_len) 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) @utils.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(): yield from range(10, 50) 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: 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) def test_iterator_time_manipulation(self): class TestObjectFactory(factory.Factory): class Meta: model = TestObject @factory.iterator def one(): now = datetime.datetime.now() yield now + datetime.timedelta(hours=1) yield now + datetime.timedelta(hours=2) obj1, obj2, obj3 = TestObjectFactory.create_batch(3) # Timers should be t+1H, t+2H, t+1H, t+2H, etc. self.assertEqual(datetime.timedelta(hours=1), obj2.one - obj1.one) self.assertEqual(obj1.one, obj3.one) class BetterFakeModelManager: 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: @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 @unittest.skipIf(SKIP_DJANGO, "django tests disabled.") 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) TestObjectFactory.build(bar=42, bar__foo=13) def test_post_generation_override_with_extra(self): class TestObjectFactory(factory.Factory): class Meta: model = TestObject one = 1 @factory.post_generation def incr_one(self, _create, override, **extra): multiplier = extra.get('multiplier', 1) if override is None: override = 1 self.one += override * multiplier obj = TestObjectFactory.build() self.assertEqual(1 + 1 * 1, obj.one) obj = TestObjectFactory.build(incr_one=2) self.assertEqual(1 + 2 * 1, obj.one) obj = TestObjectFactory.build(incr_one__multiplier=4) self.assertEqual(1 + 1 * 4, obj.one) obj = TestObjectFactory.build(incr_one=2, incr_one__multiplier=5) self.assertEqual(1 + 2 * 5, obj.one) # Passing extras through inherited params class OtherTestObjectFactory(TestObjectFactory): class Params: incr_one__multiplier = 4 obj = OtherTestObjectFactory.build() self.assertEqual(1 + 1 * 4, obj.one) def test_post_generation_method_call(self): class TestObject: 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_post_generation_extraction_declaration(self): LIBRARY = {} Book = collections.namedtuple('Book', ['author']) class BookFactory(factory.Factory): class Meta: model = Book author = factory.Faker('name') register__reference = factory.Sequence(lambda n: n) @factory.post_generation def register(self, create, extracted, reference=0, **kwargs): LIBRARY[reference] = self book = BookFactory.build() self.assertEqual({0: book}, LIBRARY) class RelatedFactoryTestCase(unittest.TestCase): def test_related_factory(self): class TestRelatedObject: 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, factory_related_name='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: 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: 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, factory_related_name='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: def __init__(self, obj=None, one=None, two=None): obj.related = self self.one = one self.two = two self.related = 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, factory_related_name='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 RelatedListFactoryTestCase(unittest.TestCase): def test_related_factory_list_of_varying_size(self): # Create our list of expected "related object counts" related_list_sizes = [5, 5, 4, 4, 3, 3, 2, 2, 1, 1] RELATED_LIST_SIZE = lambda: related_list_sizes.pop() class TestRelatedObject: def __init__(self, obj=None, one=None, two=None): # Mock out the 'List of Related Objects' generated by RelatedFactoryList if hasattr(obj, 'related_list'): obj.related_list.append(self) else: obj.related_list = [self] self.one = one self.two = two self.three = obj class TestRelatedObjectFactoryList(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 # RELATED_LIST_SIZE is a lambda, this allows flexibility, as opposed # to creating "n" related objects for every parent object... three = factory.RelatedFactoryList(TestRelatedObjectFactoryList, 'obj', size=RELATED_LIST_SIZE) # Create 5 TestObjectFactories: Each with 1, 2, ... 5 related objs for related_list_size in reversed(related_list_sizes[1::2]): 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_list) for related_obj in obj.related_list: self.assertEqual(1, related_obj.one) self.assertEqual(2, related_obj.two) # Each RelatedFactory in the RelatedFactoryList was passed the "parent" object self.assertEqual(related_list_size, len(obj.related_list)) # obj.related is the list of TestRelatedObject(s) for related_obj in obj.related_list: self.assertEqual(obj, related_obj.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_list) # three__one was correctly parse for related_obj in obj.related_list: self.assertEqual(3, related_obj.one) self.assertEqual(4, related_obj.two) # Each RelatedFactory in RelatedFactoryList received "parent" object self.assertEqual(related_list_size, len(obj.related_list)) for related_obj in obj.related_list: self.assertEqual(obj, related_obj.three) def test_related_factory_list_of_static_size(self): RELATED_LIST_SIZE = 4 class TestRelatedObject: def __init__(self, obj=None, one=None, two=None): # Mock out the 'List of Related Objects' generated by RelatedFactoryList if hasattr(obj, 'related_list'): obj.related_list.append(self) else: obj.related_list = [self] self.one = one self.two = two self.three = obj class TestRelatedObjectFactoryList(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.RelatedFactoryList(TestRelatedObjectFactoryList, 'obj', size=RELATED_LIST_SIZE) 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_list) for related_obj in obj.related_list: self.assertEqual(1, related_obj.one) self.assertEqual(2, related_obj.two) # Each RelatedFactory in the RelatedFactoryList was passed the "parent" object self.assertEqual(RELATED_LIST_SIZE, len(obj.related_list)) # obj.related is the list of TestRelatedObject(s) for related_obj in obj.related_list: self.assertEqual(obj, related_obj.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_list) # three__one was correctly parse for related_obj in obj.related_list: self.assertEqual(3, related_obj.one) self.assertEqual(4, related_obj.two) # Each RelatedFactory in RelatedFactoryList received "parent" object self.assertEqual(RELATED_LIST_SIZE, len(obj.related_list)) for related_obj in obj.related_list: self.assertEqual(obj, related_obj.three) class RelatedFactoryExtractionTestCase(unittest.TestCase): def setUp(self): self.relateds = [] class TestRelatedObject: 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, factory_related_name='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 RepeatableRandomSeedFakerTests(unittest.TestCase): def test_same_seed_is_used_between_fuzzy_and_faker_generators(self): class StudentFactory(factory.Factory): one = factory.fuzzy.FuzzyDecimal(4.0) two = factory.Faker('name') three = factory.Faker('name', locale='it') four = factory.Faker('name') class Meta: model = TestObject seed = 1000 factory.random.reseed_random(seed) students_1 = (StudentFactory(), StudentFactory()) factory.random.reseed_random(seed) students_2 = (StudentFactory(), StudentFactory()) self.assertEqual(students_1[0].one, students_2[0].one) self.assertEqual(students_1[0].two, students_2[0].two) self.assertEqual(students_1[0].three, students_2[0].three) self.assertEqual(students_1[0].four, students_2[0].four) class SelfReferentialTests(unittest.TestCase): def test_no_parent(self): from .cyclic import self_ref obj = self_ref.TreeElementFactory(parent__parent__parent=None) self.assertIsNone(obj.parent.parent.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_long_list(self): class TestObjectFactory(factory.Factory): class Meta: model = TestObject one = factory.List(list(range(100))) o = TestObjectFactory() self.assertEqual(list(range(100)), 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) factory-boy-3.3.3/tests/test_utils.py000066400000000000000000000122671475011040400176750ustar00rootroot00000000000000# Copyright: See the LICENSE file. import itertools import unittest from factory import utils 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): with self.assertRaises(AttributeError): utils.import_object('datetime', 'foo') def test_invalid_module(self): with 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'" self.assertEqual(expected, txt) def test_text_args(self): txt = str(utils.log_pprint(('ŧêßŧ',))) expected = "'ŧêßŧ'" 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'" self.assertIn(txt, (expected1, expected2)) def test_text_kwargs(self): txt = str(utils.log_pprint(kwargs={'x': 'ŧêßŧ', 'y': 'ŧßêŧ'})) expected1 = "x='ŧêßŧ', y='ŧßêŧ'" expected2 = "y='ŧßêŧ', x='ŧêßŧ'" 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)) with 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-3.3.3/tests/test_version.py000066400000000000000000000011141475011040400202070ustar00rootroot00000000000000# Copyright: See the LICENSE file. import pathlib import unittest import factory SETUP_CFG_VERSION_PREFIX = "version =" class VersionTestCase(unittest.TestCase): def get_setupcfg_version(self): setup_cfg_path = pathlib.Path(__file__).parent.parent / "setup.cfg" with setup_cfg_path.open("r") as f: for line in f: if line.startswith(SETUP_CFG_VERSION_PREFIX): return line[len(SETUP_CFG_VERSION_PREFIX):].strip() def test_version(self): self.assertEqual(factory.__version__, self.get_setupcfg_version()) factory-boy-3.3.3/tests/testdata/000077500000000000000000000000001475011040400167255ustar00rootroot00000000000000factory-boy-3.3.3/tests/testdata/__init__.py000066400000000000000000000003511475011040400210350ustar00rootroot00000000000000# Copyright: See the LICENSE file. 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-3.3.3/tests/testdata/example.data000066400000000000000000000000151475011040400212070ustar00rootroot00000000000000example_data factory-boy-3.3.3/tests/testdata/example.jpeg000066400000000000000000000004551475011040400212330ustar00rootroot00000000000000JFIFHHC       C **" ?Cټfactory-boy-3.3.3/tests/utils.py000066400000000000000000000040111475011040400166220ustar00rootroot00000000000000# Copyright: See the LICENSE file. import functools import warnings import factory from . import alter_time def disable_warnings(fun): @functools.wraps(fun) def decorated(*args, **kwargs): with warnings.catch_warnings(): warnings.simplefilter('ignore') return fun(*args, **kwargs) return decorated class MultiModulePatcher: """An abstract context processor for patching multiple modules.""" def __init__(self, *target_modules, **kwargs): super().__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: 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().__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().__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) def evaluate_declaration(declaration, force_sequence=None): kwargs = {'attr': declaration} if force_sequence is not None: kwargs['__sequence'] = force_sequence return factory.build(dict, **kwargs)['attr'] factory-boy-3.3.3/tox.ini000066400000000000000000000024631475011040400152720ustar00rootroot00000000000000[tox] minversion = 1.9 envlist = lint docs examples linkcheck py{39,310,311,312,313,py39,py310} py{39,310,311,312,313}-django42-mongo-alchemy py{py39,py310}-django42-mongo-alchemy py{310,311,312,313}-django51-mongo-alchemy pypy310-django51-mongo-alchemy py310-djangomain-mongo-alchemy [gh-actions] python = 3.9: py39 3.10: py310 3.11: py311 3.12: py312 3.13: py313 pypy-3.10: pypy310 [testenv] deps = mypy alchemy: SQLAlchemy mongo: mongoengine mongo: mongomock # mongomock imports pkg_resources, provided by setuptools. mongo: setuptools>=66.1.1 django{42,51,main}: Pillow django42: Django>=4.2,<5.0 django51: Django>=5.1,<5.2 djangomain: Django>5.1,<6.0 setenv = py: DJANGO_SETTINGS_MODULE=tests.djapp.settings pip_pre = djangomain: true allowlist_externals = make commands = make test [testenv:docs] extras = doc whitelist_externals = make commands = make doc spelling [testenv:examples] deps = -rexamples/requirements.txt whitelist_externals = make commands = make example-test [testenv:linkcheck] extras = doc whitelist_externals = make commands = make linkcheck [testenv:lint] deps = -rexamples/requirements.txt check_manifest extras = dev whitelist_externals = make commands = make lint