From 13d310fa14f4e4b9a559f8b7887f2a2492357013 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Barrois?= Date: Sun, 16 Nov 2014 22:34:29 +0100 Subject: Remove automagic pk-based sequence setup Related to issues #78, #92, #103, #111, #153, #170 The default value of all sequences is now 0; the automagic ``_setup_next_sequence`` behavior of Django/SQLAlchemy has been removed. This feature's only goal was to allow the following scenario: 1. Run a Python script that uses MyFactory.create() a couple of times (with a unique field based on the sequence counter) 2. Run the same Python script a second time Without the magical ``_setup_next_sequence``, the Sequence counter would be set to 0 at the beginning of each script run, so both runs would generate objects with the same values for the unique field ; thus conflicting and crashing. The above behavior having only a very limited use and bringing various issues (hitting the database on ``build()``, problems with non-integer or composite primary key columns, ...), it has been removed. It could still be emulated through custom ``_setup_next_sequence`` methods, or by calling ``MyFactory.reset_sequence()``. --- docs/orms.rst | 2 -- 1 file changed, 2 deletions(-) (limited to 'docs/orms.rst') diff --git a/docs/orms.rst b/docs/orms.rst index 2aa27b2..88d49e9 100644 --- a/docs/orms.rst +++ b/docs/orms.rst @@ -35,7 +35,6 @@ All factories for a Django :class:`~django.db.models.Model` should use the * The :attr:`~factory.FactoryOptions.model` attribute also supports the ``'app.Model'`` syntax * :func:`~factory.Factory.create()` uses :meth:`Model.objects.create() ` - * :func:`~factory.Factory._setup_next_sequence()` selects the next unused primary key value * When using :class:`~factory.RelatedFactory` or :class:`~factory.PostGeneration` attributes, the base object will be :meth:`saved ` once all post-generation hooks have run. @@ -284,7 +283,6 @@ To work, this class needs an `SQLAlchemy`_ session object affected to the :attr: This class provides the following features: * :func:`~factory.Factory.create()` uses :meth:`sqlalchemy.orm.session.Session.add` - * :func:`~factory.Factory._setup_next_sequence()` selects the next unused primary key value .. attribute:: FACTORY_SESSION -- cgit v1.2.3 From 336ea5ac8b2d922fb54f99edd55d4773dd126934 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Barrois?= Date: Tue, 18 Nov 2014 00:35:19 +0100 Subject: Remove deprecated features. This disables the ``FACTORY_FOR`` syntax and related parameters, that should be declared through ``class Meta``. --- docs/orms.rst | 9 --------- 1 file changed, 9 deletions(-) (limited to 'docs/orms.rst') diff --git a/docs/orms.rst b/docs/orms.rst index 88d49e9..e32eafa 100644 --- a/docs/orms.rst +++ b/docs/orms.rst @@ -39,11 +39,6 @@ All factories for a Django :class:`~django.db.models.Model` should use the attributes, the base object will be :meth:`saved ` once all post-generation hooks have run. - .. attribute:: FACTORY_DJANGO_GET_OR_CREATE - - .. deprecated:: 2.4.0 - See :attr:`DjangoOptions.django_get_or_create`. - .. class:: DjangoOptions(factory.base.FactoryOptions) @@ -284,10 +279,6 @@ To work, this class needs an `SQLAlchemy`_ session object affected to the :attr: * :func:`~factory.Factory.create()` uses :meth:`sqlalchemy.orm.session.Session.add` - .. attribute:: FACTORY_SESSION - - .. deprecated:: 2.4.0 - See :attr:`~SQLAlchemyOptions.sqlalchemy_session`. .. class:: SQLAlchemyOptions(factory.base.FactoryOptions) -- cgit v1.2.3 From f83c602874698427bdc141accd8fc14a9749d6c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Barrois?= Date: Fri, 6 Feb 2015 23:19:06 +0100 Subject: docs: Add explanations about SQLAlchemy's scoped_session. --- docs/orms.rst | 102 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 100 insertions(+), 2 deletions(-) (limited to 'docs/orms.rst') diff --git a/docs/orms.rst b/docs/orms.rst index e32eafa..ab813a2 100644 --- a/docs/orms.rst +++ b/docs/orms.rst @@ -298,9 +298,8 @@ A (very) simple exemple: from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import scoped_session, sessionmaker - session = scoped_session(sessionmaker()) engine = create_engine('sqlite://') - session.configure(bind=engine) + session = scoped_session(sessionmaker(bind=engine)) Base = declarative_base() @@ -330,3 +329,102 @@ A (very) simple exemple: >>> session.query(User).all() [] + + +Managing sessions +""""""""""""""""" + +Since `SQLAlchemy`_ is a general purpose library, +there is no "global" session management system. + +The most common pattern when working with unit tests and ``factory_boy`` +is to use `SQLAlchemy`_'s :class:`sqlalchemy.orm.scoping.scoped_session`: + +* The test runner configures some project-wide :class:`~sqlalchemy.orm.scoping.scoped_session` +* Each :class:`~SQLAlchemyModelFactory` subclass uses this + :class:`~sqlalchemy.orm.scoping.scoped_session` as its :attr:`~SQLAlchemyOptions.sqlalchemy_session` +* The :meth:`~unittest.TestCase.tearDown` method of tests calls + :meth:`Session.remove ` + to reset the session. + + +Here is an example layout: + +- A global (test-only?) file holds the :class:`~sqlalchemy.orm.scoping.scoped_session`: + +.. code-block:: python + + # myprojet/test/common.py + + from sqlalchemy import orm + Session = orm.scoped_session(orm.sessionmaker()) + + +- All factory access it: + +.. code-block:: python + + # myproject/factories.py + + import factory + import factory.alchemy + + from . import models + from .test import common + + class UserFactory(factory.alchemy.SQLAlchemyModelFactory): + class Meta: + model = models.User + + # Use the not-so-global scoped_session + # Warning: DO NOT USE common.Session()! + sqlalchemy_session = common.Session + + name = factory.Sequence(lambda n: "User %d" % n) + + +- The test runner configures the :class:`~sqlalchemy.orm.scoping.scoped_session` when it starts: + +.. code-block:: python + + # myproject/test/runtests.py + + import sqlalchemy + + from . import common + + def runtests(): + engine = sqlalchemy.create_engine('sqlite://') + + # It's a scoped_session, we can configure it later + common.Session.configure(engine=engine) + + run_the_tests + + +- :class:`test cases ` use this ``scoped_session``, + and clear it after each test: + +.. 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() -- cgit v1.2.3 From d95bc982cd8480aa44e5282ab1284a9278049066 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Barrois?= Date: Fri, 6 Feb 2015 23:29:52 +0100 Subject: docs: Improve explanation of SQLAlchemy's scoped_session. --- docs/orms.rst | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) (limited to 'docs/orms.rst') diff --git a/docs/orms.rst b/docs/orms.rst index ab813a2..9e4d106 100644 --- a/docs/orms.rst +++ b/docs/orms.rst @@ -347,6 +347,16 @@ is to use `SQLAlchemy`_'s :class:`sqlalchemy.orm.scoping.scoped_session`: :meth:`Session.remove ` to reset the session. +.. note:: See the excellent :ref:`SQLAlchemy guide on scoped_session ` + for details of :class:`~sqlalchemy.orm.scoping.scoped_session`'s usage. + + The basic idea is that declarative parts of the code (including factories) + need a simple way to access the "current session", + but that session will only be created and configured at a later point. + + The :class:`~sqlalchemy.orm.scoping.scoped_session` handles this, + by virtue of only creating the session when a query is sent to the database. + Here is an example layout: @@ -396,14 +406,14 @@ Here is an example layout: def runtests(): engine = sqlalchemy.create_engine('sqlite://') - # It's a scoped_session, we can configure it later - common.Session.configure(engine=engine) + # 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: + and clear it after each test (for isolation): .. code-block:: python -- cgit v1.2.3 From efa9d3c0d165a4c49def26b423711ed28eb2d264 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Barrois?= Date: Tue, 3 Mar 2015 22:45:45 +0100 Subject: Fix typos in docs (Closes #159, closes #178, closes #188). --- docs/orms.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs/orms.rst') diff --git a/docs/orms.rst b/docs/orms.rst index 9e4d106..a0afc40 100644 --- a/docs/orms.rst +++ b/docs/orms.rst @@ -290,7 +290,7 @@ To work, this class needs an `SQLAlchemy`_ session object affected to the :attr: SQLAlchemy session to use to communicate with the database when creating an object through this :class:`SQLAlchemyModelFactory`. -A (very) simple exemple: +A (very) simple example: .. code-block:: python -- cgit v1.2.3 From 636ca46951d710a4b9d9fd61ec1da02294806d3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Barrois?= Date: Thu, 26 Mar 2015 22:53:15 +0100 Subject: Add support for multidb with Django (Closes #171). The ``factory.django.DjangoModelFactory`` now takes an extra option: ``` class MyFactory(factory.django.DjangoModelFactory): class Meta: model = models.MyModel database = 'replica' ``` This will create all instances of ``models.Model`` in the ``'replica'`` database. --- docs/orms.rst | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'docs/orms.rst') diff --git a/docs/orms.rst b/docs/orms.rst index a0afc40..5105e66 100644 --- a/docs/orms.rst +++ b/docs/orms.rst @@ -42,7 +42,14 @@ All factories for a Django :class:`~django.db.models.Model` should use the .. class:: DjangoOptions(factory.base.FactoryOptions) - The ``class Meta`` on a :class:`~DjangoModelFactory` supports an extra parameter: + 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 -- cgit v1.2.3 From 0e3cdffac41250cddfe93388b1c9fc1547e77a67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Barrois?= Date: Sat, 25 Apr 2015 17:50:50 +0200 Subject: Clarify .build() issue with Django>1.8 (Ref #198). From 1.8 onwards, this crashes: >>> a = MyModel() # Don't save >>> b = MyOtherModel(fkey_to_mymodel=a) In turn, it breaks: class MyModelFactory(factory.django.DjangoModelFactory): class Meta: model = MyModel class MyOtherModelFactory(factory.django.DjangoModelFactory): class Meta: model = MyOtherModel fkey_to_mymodel = factory.SubFactory(MyModelFactory) MyOtherModelFactory.build() # Breaks The error message is: Cannot assign "MyModel()": "MyModel" instance isn't saved in the database. See https://code.djangoproject.com/ticket/10811 for details. --- docs/orms.rst | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'docs/orms.rst') diff --git a/docs/orms.rst b/docs/orms.rst index 5105e66..bbe91e6 100644 --- a/docs/orms.rst +++ b/docs/orms.rst @@ -40,6 +40,14 @@ All factories for a Django :class:`~django.db.models.Model` should use the once all post-generation hooks have run. +.. note:: Starting with Django 1.8, it is no longer possible to call ``.build()`` + on a factory if this factory uses a :class:`~factory.SubFactory` pointing + to another model: Django refuses to set a :class:`~djang.db.models.ForeignKey` + to an unsaved :class:`~django.db.models.Model` instance. + + See https://code.djangoproject.com/ticket/10811 for details. + + .. class:: DjangoOptions(factory.base.FactoryOptions) The ``class Meta`` on a :class:`~DjangoModelFactory` supports extra parameters: -- cgit v1.2.3 From 9246fa6d26ca655c02ae37bbfc389d9f34dfba16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Barrois?= Date: Sun, 31 May 2015 10:57:53 +0100 Subject: Improve ORM layer import paths (Closes #186). You may now use the following code: import factory factory.alchemy.SQLAlchemyModelFactory factory.django.DjangoModelFactory factory.mongoengine.MongoEngineFactory --- docs/orms.rst | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) (limited to 'docs/orms.rst') diff --git a/docs/orms.rst b/docs/orms.rst index bbe91e6..26390b5 100644 --- a/docs/orms.rst +++ b/docs/orms.rst @@ -273,6 +273,34 @@ factory_boy supports `MongoEngine`_-style models, through the :class:`MongoEngin 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 ---------- @@ -327,8 +355,9 @@ A (very) simple example: Base.metadata.create_all(engine) + import factory - class UserFactory(SQLAlchemyModelFactory): + class UserFactory(factory.alchemy.SQLAlchemyModelFactory): class Meta: model = User sqlalchemy_session = session # the SQLAlchemy session object -- cgit v1.2.3 From 41bbff4701ac857bf6c468a4dc53836ee85baa11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Barrois?= Date: Tue, 20 Oct 2015 23:30:18 +0200 Subject: Update note on django's unsaved instance checks This note was added to document a regression in Django 1.8.0; the regression has been fixed in 1.8.4. Closes #232 --- docs/orms.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'docs/orms.rst') diff --git a/docs/orms.rst b/docs/orms.rst index 26390b5..9b209bc 100644 --- a/docs/orms.rst +++ b/docs/orms.rst @@ -40,12 +40,12 @@ All factories for a Django :class:`~django.db.models.Model` should use the once all post-generation hooks have run. -.. note:: Starting with Django 1.8, it is no longer possible to call ``.build()`` - on a factory if this factory uses a :class:`~factory.SubFactory` pointing - to another model: Django refuses to set a :class:`~djang.db.models.ForeignKey` +.. note:: With Django versions 1.8.0 to 1.8.3, it was no longer possible to call ``.build()`` + on a factory if this factory used a :class:`~factory.SubFactory` pointing + to another model: Django refused to set a :class:`~djang.db.models.ForeignKey` to an unsaved :class:`~django.db.models.Model` instance. - See https://code.djangoproject.com/ticket/10811 for details. + See https://code.djangoproject.com/ticket/10811 and https://code.djangoproject.com/ticket/25160 for details. .. class:: DjangoOptions(factory.base.FactoryOptions) -- cgit v1.2.3 From 05082c661655df319ce641dd7976c02d1799ab14 Mon Sep 17 00:00:00 2001 From: mluszczyk Date: Mon, 28 Dec 2015 13:06:13 +0100 Subject: Fixed spelling. --- docs/orms.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs/orms.rst') diff --git a/docs/orms.rst b/docs/orms.rst index 9b209bc..0afda69 100644 --- a/docs/orms.rst +++ b/docs/orms.rst @@ -15,7 +15,7 @@ Django The first versions of factory_boy were designed specifically for Django, -but the library has now evolved to be framework-independant. +but the library has now evolved to be framework-independent. Most features should thus feel quite familiar to Django users. -- cgit v1.2.3 From 4172dd686ce483191b33e3189d716f11b3da921e Mon Sep 17 00:00:00 2001 From: Alejandro Date: Wed, 6 Jan 2016 19:36:10 -0300 Subject: optional forced flush on SQLAlchemyModelFactory fixes rbarrois/factory_boy#81 --- docs/orms.rst | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'docs/orms.rst') diff --git a/docs/orms.rst b/docs/orms.rst index 9b209bc..bd481bd 100644 --- a/docs/orms.rst +++ b/docs/orms.rst @@ -333,6 +333,10 @@ To work, this class needs an `SQLAlchemy`_ session object affected to the :attr: SQLAlchemy session to use to communicate with the database when creating an object through this :class:`SQLAlchemyModelFactory`. + .. attribute:: force_flush + + Force a session flush() at the end of :func:`~factory.alchemy.SQLAlchemyModelFactory._create()`. + A (very) simple example: .. code-block:: python -- cgit v1.2.3 From efd5c65b99a31992001a9581a41ec4627c4d94fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Barrois?= Date: Wed, 10 Feb 2016 00:15:52 +0100 Subject: Clarify precedence on factory.django.FileField (Closes #257). When both ``from_file`` and ``filename`` are provided, ``filename`` takes precedence. Thanks to @darkowic for spotting this :) --- docs/orms.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs/orms.rst') diff --git a/docs/orms.rst b/docs/orms.rst index bd481bd..d1b30fc 100644 --- a/docs/orms.rst +++ b/docs/orms.rst @@ -126,7 +126,7 @@ Extra fields :param str from_path: Use data from the file located at ``from_path``, and keep its filename :param file from_file: Use the contents of the provided file object; use its filename - if available + if available, unless ``filename`` is also provided. :param bytes data: Use the provided bytes as file contents :param str filename: The filename for the FileField -- cgit v1.2.3