Skip to content

Commit

Permalink
Merge pull request #22 from olegpidsadnyi/fix15
Browse files Browse the repository at this point in the history
fixes _after_postgeneration hook invocation for deferred post-gens…
  • Loading branch information
olegpidsadnyi committed Apr 20, 2016
2 parents 7b97e6e + dc8f801 commit 07cd2f7
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 16 deletions.
1 change: 1 addition & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Changelog
-----

- fixes fixture function module name attribute (olegpidsadnyi)
- fixes _after_postgeneration hook invocation for deferred post-generation declarations (olegpidsadnyi)


1.1.5
Expand Down
20 changes: 12 additions & 8 deletions pytest_factoryboy/fixture.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,34 +176,37 @@ def attributes(cls, create=False, extra=None):
# Defer post-generation declarations
for attr, decl, context in post_decls:
if isinstance(decl, factory.RelatedFactory):
factoryboy_request.defer(make_deferred_related(request, request.fixturename, attr))
factoryboy_request.defer(make_deferred_related(factory_class, request.fixturename, attr))
else:
factoryboy_request.defer(make_deferred_postgen(request, request.fixturename, instance, attr, decl, context))
factoryboy_request.defer(
make_deferred_postgen(factory_class, request.fixturename, instance, attr, decl, context)
)

return instance


def make_deferred_related(request, fixture, attr):
def make_deferred_related(factory, fixture, attr):
"""Make deferred function for the related factory declaration.
:param request: PyTest request.
:param factory: Factory class.
:param fixture: Object fixture name e.g. "book".
:param attr: Declaration attribute name e.g. "publications".
:note: Deferred function name results in "book__publication".
"""
name = SEPARATOR.join((fixture, attr))

def deferred():
def deferred(request):
request.getfuncargvalue(name)
deferred.__name__ = name
deferred._factory = factory
return deferred


def make_deferred_postgen(request, fixture, instance, attr, declaration, context):
def make_deferred_postgen(factory, fixture, instance, attr, declaration, context):
"""Make deferred function for the post-generation declaration.
:param request: PyTest request.
:param factory: Factory class.
:param fixture: Object fixture name e.g. "author".
:param instance: Parent object instance.
:param attr: Declaration attribute name e.g. "register_user".
Expand All @@ -213,11 +216,12 @@ def make_deferred_postgen(request, fixture, instance, attr, declaration, context
"""
name = SEPARATOR.join((fixture, attr))

def deferred():
def deferred(request):
context.value = evaluate(request, request.getfuncargvalue(name))
context.extra = dict((key, evaluate(request, value)) for key, value in context.extra.items())
declaration.call(instance, True, context)
deferred.__name__ = name
deferred._factory = factory
return deferred


Expand Down
40 changes: 32 additions & 8 deletions pytest_factoryboy/plugin.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,48 @@
"""pytest-factoryboy plugin."""

from collections import defaultdict
import pytest


class Request(object):

"""PyTest FactoryBoy request."""

def __init__(self):
def __init__(self, request):
"""Create pytest_factoryboy request.
:param request: pytest request.
"""
self.request = request
self.deferred = []
self.is_finalized = False
self.results = defaultdict(dict)
self.model_factories = {}

def defer(self, function):
"""Defer post-generation declaration execution until the end of the test setup.
:param function: Function to be deferred.
:note: Once already finalized all following defer calls will execute the function directly.
"""
self.deferred.append(function)
if self.is_finalized:
function()
else:
self.deferred.append(function)
self.execute(function)

def execute(self, function):
""""Execute deferred function and store the result."""
model, attr = function.__name__.split("__", 1)
self.results[model][attr] = function(self.request)
self.model_factories[model] = function._factory
self.deferred.remove(function)

def after_postgeneration(self):
"""Call _after_postgeneration hooks."""
for model in list(self.results.keys()):
results = self.results.pop(model)
obj = self.request.getfuncargvalue(model)
factory = self.model_factories[model]
factory._after_postgeneration(obj=obj, create=True, results=results)

def evaluate(self, names=None):
"""Finalize, run deferred post-generation actions, etc."""
Expand All @@ -31,14 +53,16 @@ def evaluate(self, names=None):
deferred = list(self.deferred)

for function in deferred:
function()
self.deferred.remove(function)
self.execute(function)

if not self.deferred:
self.after_postgeneration()


@pytest.fixture
def factoryboy_request():
def factoryboy_request(request):
"""PyTest FactoryBoy request fixture."""
return Request()
return Request(request)


@pytest.mark.tryfirst
Expand Down
11 changes: 11 additions & 0 deletions tests/test_postgen_dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ def set1(foo, create, value, **kwargs):
def set2(foo, create, value, **kwargs):
foo.value = 2

@classmethod
def _after_postgeneration(cls, obj, create, results=None):
obj._postgeneration_results = results
obj._create = create


class BarFactory(factory.Factory):

Expand Down Expand Up @@ -93,3 +98,9 @@ def test_depends_on_2(depends_on_2):
def test_getfuncargvalue(request):
"""Test post-generation declarations via the getfuncargvalue."""
assert request.getfuncargvalue('foo')


def test_after_postgeneration(foo):
"""Test _after_postgeneration is called."""
assert foo._postgeneration_results == {'set1': None, 'set2': None}
assert foo._create is True

0 comments on commit 07cd2f7

Please sign in to comment.