Skip to content

Commit

Permalink
Merge pull request #26 from pytest-dev/remove_post_deps
Browse files Browse the repository at this point in the history
Remove post deps
  • Loading branch information
olegpidsadnyi committed Jun 29, 2016
2 parents 31e8d1b + d5a37f5 commit 3c41cb0
Show file tree
Hide file tree
Showing 8 changed files with 158 additions and 91 deletions.
1 change: 1 addition & 0 deletions AUTHORS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ These people have contributed to `pytest-factoryboy`, in alphabetical order:

* `Anatoly Bubenkov <bubenkoff@gmail.com>`_
* `Daniel Duong`
* `Vasily Kuznetsov https://github.com/kvas-it`_
6 changes: 6 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
Changelog
=========

1.2.0
-----

- automatical resolution of the post-generation dependencies (olegpidsadnyi, kvas-it)


1.1.6
-----

Expand Down
8 changes: 2 additions & 6 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,8 @@ This solves circular dependecy resolution for situations like:

On the other hand deferring the evaluation of post-generation declarations evaluation makes their result unavailable during the generation
of objects that are not in the circular dependecy, but they rely on the post-generation action.
It is possible to declare such dependencies to be evaluated earlier, right before generating the requested object.

pytest-factoryboy is trying to detect cycles and resolve post-generation dependencies automatically.


.. code-block:: python
Expand Down Expand Up @@ -349,7 +350,6 @@ It is possible to declare such dependencies to be evaluated earlier, right befor
register(
BarFactory,
'bar',
_postgen_dependencies=["foo__set1"],
)
"""Forces 'set1' to be evaluated first."""
Expand All @@ -359,10 +359,6 @@ It is possible to declare such dependencies to be evaluated earlier, right befor
assert depends_on_1.foo.value == 1
All post-generation/RelatedFactory attributes specified in the `_postgen_dependencies` list during factory registration
are evaluated before the object generation.


Hooks
-----

Expand Down
2 changes: 1 addition & 1 deletion pytest_factoryboy/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""pytest-factoryboy public API."""
from .fixture import register, LazyFixture

__version__ = '1.1.6'
__version__ = '1.2.0'


__all__ = [
Expand Down
74 changes: 58 additions & 16 deletions pytest_factoryboy/fixture.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def {name}({deps}):
"""


def make_fixture(name, module, func, args=None, **kwargs):
def make_fixture(name, module, func, args=None, related=None, **kwargs):
"""Make fixture function and inject arguments.
:param name: Fixture name.
Expand All @@ -34,18 +34,20 @@ def make_fixture(name, module, func, args=None, **kwargs):
exec(FIXTURE_FUNC_FORMAT.format(name=name, deps=deps), context)
fixture_func = context[name]
fixture_func.__module__ = module.__name__

if related:
fixture_func._factoryboy_related = related

fixture = pytest.fixture(fixture_func)
setattr(module, name, fixture)
return fixture


def register(factory_class, _name=None, _postgen_dependencies=None, **kwargs):
def register(factory_class, _name=None, **kwargs):
"""Register fixtures for the factory class.
:param factory_class: Factory class to register.
:param _name: Name of the model fixture. By default is lowercase-underscored model name.
:param _postgen_dependencies: Optional list of post-generation dependencies
e.g. <model>__<post-generation attribute>.
:param \**kwargs: Optional keyword arguments that override factory attributes.
"""
assert not factory_class._meta.abstract, "Can't register abstract factories."
Expand All @@ -56,28 +58,46 @@ def register(factory_class, _name=None, _postgen_dependencies=None, **kwargs):
factory_name = get_factory_name(factory_class)

deps = get_deps(factory_class, model_name=model_name)
related = []
for attr, value in factory_class.declarations(factory_class._meta.postgen_declarations).items():
value = kwargs.get(attr, value) # Partial specialization
attr_name = SEPARATOR.join((model_name, attr))

if isinstance(value, (factory.SubFactory, factory.RelatedFactory)):
subfactory_class = value.get_factory()
subfactory_deps = get_deps(subfactory_class, factory_class)

args = list(subfactory_deps)
if isinstance(value, factory.RelatedFactory):
related_model = get_model_name(subfactory_class)
args.append(related_model)
related.append(related_model)
related.append(attr_name)
related.extend(subfactory_deps)

if isinstance(value, factory.SubFactory):
args.append(inflection.underscore(subfactory_class._meta.model.__name__))

make_fixture(
name=attr_name,
module=module,
func=subfactory_fixture,
args=subfactory_deps,
args=args,
factory_class=subfactory_class,
)
if isinstance(value, factory.RelatedFactory):
deps.extend(subfactory_deps)
else:
args = None
if isinstance(value, LazyFixture):
args = value.args
if isinstance(value, factory.declarations.PostGeneration):
value = None

make_fixture(
name=attr_name,
module=module,
func=attr_fixture,
value=value if not isinstance(value, factory.declarations.PostGeneration) else None,
value=value,
args=args,
)
if not hasattr(module, factory_name):
make_fixture(
Expand All @@ -93,7 +113,7 @@ def register(factory_class, _name=None, _postgen_dependencies=None, **kwargs):
func=model_fixture,
args=deps,
factory_name=factory_name,
postgen_dependencies=_postgen_dependencies,
related=related,
)
return factory_class

Expand Down Expand Up @@ -122,7 +142,10 @@ def is_dep(value):
if isinstance(value, factory.RelatedFactory):
return False
if isinstance(value, factory.SubFactory) and get_model_name(value.get_factory()) == parent_model_name:
return False
return False
if isinstance(value, factory.declarations.PostGeneration):
# Dependency on extracted value
return True

return True

Expand All @@ -138,11 +161,12 @@ def evaluate(request, value):
return value.evaluate(request) if isinstance(value, LazyFixture) else value


def model_fixture(request, factory_name, postgen_dependencies):
def model_fixture(request, factory_name):
"""Model fixture implementation."""
factoryboy_request = request.getfuncargvalue("factoryboy_request")
if postgen_dependencies:
factoryboy_request.evaluate(postgen_dependencies)

# Try to evaluate as much post-generation dependencies as possible
factoryboy_request.evaluate(request)

factory_class = request.getfuncargvalue(factory_name)
prefix = "".join((request.fixturename, SEPARATOR))
Expand Down Expand Up @@ -172,16 +196,24 @@ def attributes(cls, create=False, extra=None):

# Create model fixture instance
instance = Factory(**data)
request._fixturedef.cached_result = (instance, None, None)
request._fixturedefs[request.fixturename] = request._fixturedef

# Defer post-generation declarations
related = []
postgen = []
for attr, decl, context in post_decls:
if isinstance(decl, factory.RelatedFactory):
factoryboy_request.defer(make_deferred_related(factory_class, request.fixturename, attr))
related.append(make_deferred_related(factory_class, request.fixturename, attr))
else:
factoryboy_request.defer(
postgen.append(
make_deferred_postgen(factory_class, request.fixturename, instance, attr, decl, context)
)
deferred = related + postgen
factoryboy_request.defer(deferred)

# Try to evaluate as much post-generation dependencies as possible
factoryboy_request.evaluate(request)
return instance


Expand All @@ -198,8 +230,11 @@ def make_deferred_related(factory, fixture, attr):

def deferred(request):
request.getfuncargvalue(name)
# return request.getfuncargvalue(name)
deferred.__name__ = name
deferred._factory = factory
deferred._fixture = fixture
deferred._is_related = True
return deferred


Expand All @@ -220,8 +255,11 @@ 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)
# return context.value
deferred.__name__ = name
deferred._factory = factory
deferred._fixture = fixture
deferred._is_related = False
return deferred


Expand Down Expand Up @@ -261,6 +299,10 @@ def __init__(self, fixture):
:param fixture: Fixture name or callable with dependencies.
"""
self.fixture = fixture
if callable(self.fixture):
self.args = list(inspect.getargspec(self.fixture).args)
else:
self.args = [self.fixture]

def evaluate(self, request):
"""Evaluate the lazy fixture.
Expand All @@ -269,7 +311,7 @@ def evaluate(self, request):
:return: evaluated fixture.
"""
if callable(self.fixture):
kwargs = dict((arg, request.getfuncargvalue(arg)) for arg in inspect.getargspec(self.fixture).args)
kwargs = dict((arg, request.getfuncargvalue(arg)) for arg in self.args)
return self.fixture(**kwargs)
else:
return request.getfuncargvalue(self.fixture)

0 comments on commit 3c41cb0

Please sign in to comment.