From 03c40fd80707ad4837523a07cdf3f82564ab0259 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Barrois?= Date: Sat, 2 Apr 2016 16:14:06 +0200 Subject: Add Traits (Closes #251). Based on a boolean flag, those will alter the definitions of the current factory, taking precedence over pre-defined behavior but overridden by callsite-level arguments. --- docs/introduction.rst | 55 ++++++++++++ docs/reference.rst | 230 ++++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 240 insertions(+), 45 deletions(-) (limited to 'docs') diff --git a/docs/introduction.rst b/docs/introduction.rst index 9a16c39..5b535c9 100644 --- a/docs/introduction.rst +++ b/docs/introduction.rst @@ -266,6 +266,61 @@ This is handled by the :data:`~factory.FactoryOptions.inline_args` attribute: +Altering a factory's behaviour: parameters and traits +----------------------------------------------------- + +Some classes are better described with a few, simple parameters, that aren't fields on the actual model. +In that case, use a :attr:`~factory.Factory.Params` declaration: + +.. code-block:: python + + class RentalFactory(factory.Factory): + class Meta: + model = Rental + + begin = factory.fuzzy.FuzzyDate(start_date=datetime.date(2000, 1, 1)) + end = factory.LazyAttribute(lambda o: o.begin + o.duration) + + class Params: + duration = 12 + +.. code-block:: pycon + + >>> RentalFactory(duration=0) + 2012-03-03> + >>> RentalFactory(duration=10) + 2012-12-26> + + +When many fields should be updated based on a flag, use :class:`Traits ` instead: + +.. code-block:: python + + class OrderFactory(factory.Factory): + status = 'pending' + shipped_by = None + shipped_on = None + + class Meta: + model = Order + + class Params: + shipped = factory.Trait( + status='shipped', + shipped_by=factory.SubFactory(EmployeeFactory), + shipped_on=factory.LazyFunction(datetime.date.today), + ) + +A trait is toggled by a single boolean value: + +.. code-block:: pycon + + >>> OrderFactory() + + >>> OrderFactory(shipped=True) + + + Strategies ---------- diff --git a/docs/reference.rst b/docs/reference.rst index 8550f88..ad68faf 100644 --- a/docs/reference.rst +++ b/docs/reference.rst @@ -11,6 +11,9 @@ For internals and customization points, please refer to the :doc:`internals` sec The :class:`Factory` class -------------------------- +Meta options +"""""""""""" + .. class:: FactoryOptions .. versionadded:: 2.4.0 @@ -135,11 +138,16 @@ The :class:`Factory` class +Attributes and methods +"""""""""""""""""""""" + + .. class:: Factory **Class-level attributes:** + .. attribute:: Meta .. attribute:: _meta .. versionadded:: 2.4.0 @@ -147,6 +155,14 @@ The :class:`Factory` class 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 @@ -353,6 +369,175 @@ The :class:`Factory` class factory in the chain. +.. _parameters: + +Parameters +"""""""""" + +.. versionadded:: 2.7.0 + +Some models have many fields that can be summarized by a few parameters; for instance, +a train with many cars — each complete with serial number, manufacturer, ...; +or an order that can be pending/shipped/received, with a few fields to describe each step. + +When building instances of such models, a couple of parameters can be enough to determine +all other fields; this is handled by the :class:`~Factory.Params` section of a :class:`Factory` declaration. + + +Simple parameters +~~~~~~~~~~~~~~~~~ + +Some factories only need little data: + +.. code-block:: python + + class ConferenceFactory(factory.Factory): + class Meta: + model = Conference + + class Params: + duration = 'short' # Or 'long' + + start_date = factory.fuzzy.FuzzyDate() + end_date = factory.LazyAttribute( + lambda o: o.start_date + datetime.timedelta(days=2 if o.duration == 'short' else 7) + ) + sprints_start = factory.LazyAttribute( + lambda o: o.end_date - datetime.timedelta(days=0 if o.duration == 'short' else 1) + ) + +.. code-block:: pycon + + >>> Conference(duration='short') + + >>> Conference(duration='long') + + + +Any simple parameter provided to the :class:`Factory.Params` section is available to the whole factory, +but not passed to the final class (similar to the :attr:`~FactoryOptions.exclude` behavior). + + +Traits +~~~~~~ + +.. class:: Trait(**kwargs) + + .. OHAI VIM** + + .. versionadded:: 2.7.0 + + A trait's parameters are the fields it sohuld 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 @@ -1299,51 +1484,6 @@ with the :class:`Dict` and :class:`List` attributes: argument, if another type (tuple, set, ...) is required. -Parameters -"""""""""" - -Some models have many fields that can be summarized by a few parameters; for instance, -a train with many cars — each complete with serial number, manufacturer, ...; -or an order that can be pending/shipped/received, with a few fields to describe each step. - -When building instances of such models, a couple of parameters can be enough to determine -all other fields; this is handled by the :class:`~Factory.Params` section of a :class:`Factory` declaration. - - -Simple parameters -~~~~~~~~~~~~~~~~~ - -Some factories only need little data: - -.. code-block:: python - - class ConferenceFactory(factory.Factory): - class Meta: - model = Conference - - class Params: - duration = 'short' # Or 'long' - - start_date = factory.fuzzy.FuzzyDate() - end_date = factory.LazyAttribute( - lambda o: o.start_date + datetime.timedelta(days=2 if o.duration == 'short' else 7) - ) - sprints_start = factory.LazyAttribute( - lambda o: o.end_date - datetime.timedelta(days=0 if o.duration == 'short' else 1) - ) - -.. code-block:: pycon - - >>> Conference(duration='short') - - >>> Conference(duration='long') - - - -Any simple parameter provided to the :class:`Factory.Params` section is available to the whole factory, -but not passed to the final class (similar to the :attr:`~FactoryOptions.exclude` behavior). - - Post-generation hooks """"""""""""""""""""" -- cgit v1.2.3