Skip to content

Commit 87817b9

Browse files
V2: Support for pytest>=3.3.2 and factory_boy>=2.10.0 (#57)
Release 2.0.0
1 parent 85f3960 commit 87817b9

File tree

9 files changed

+160
-90
lines changed

9 files changed

+160
-90
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ nosetests.xml
3939
.project
4040
.pydevproject
4141
.cache
42+
.pytest_cache
4243
.ropeproject
4344

4445
# Sublime

CHANGES.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
Changelog
22
=========
33

4+
2.0.0
5+
-----
6+
7+
Breaking change due to the heavy refactor of both pytest and factory_boy.
8+
9+
- Failing test for using a `attributes` field on the factory (blueyed)
10+
- Minimal pytest version is 3.3.2 (olegpidsadnyi)
11+
- Minimal factory_boy version is 2.10.0 (olegpidsadnyi)
12+
413

514
1.3.2
615
-----

pytest_factoryboy/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""pytest-factoryboy public API."""
22
from .fixture import register, LazyFixture
33

4-
__version__ = '1.3.2'
4+
__version__ = '2.0.0'
55

66

77
__all__ = [

pytest_factoryboy/fixture.py

Lines changed: 107 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
import inspect
55

66
import factory
7+
import factory.builder
8+
import factory.declarations
9+
import factory.enums
710
import inflection
811
import pytest
912

@@ -59,38 +62,15 @@ def register(factory_class, _name=None, **kwargs):
5962

6063
deps = get_deps(factory_class, model_name=model_name)
6164
related = []
62-
for attr, value in factory_class.declarations(factory_class._meta.postgen_declarations).items():
63-
value = kwargs.get(attr, value) # Partial specialization
64-
attr_name = SEPARATOR.join((model_name, attr))
65-
66-
if isinstance(value, (factory.SubFactory, factory.RelatedFactory)):
67-
subfactory_class = value.get_factory()
68-
subfactory_deps = get_deps(subfactory_class, factory_class)
6965

70-
args = list(subfactory_deps)
71-
if isinstance(value, factory.RelatedFactory):
72-
related_model = get_model_name(subfactory_class)
73-
args.append(related_model)
74-
related.append(related_model)
75-
related.append(attr_name)
76-
related.extend(subfactory_deps)
77-
78-
if isinstance(value, factory.SubFactory):
79-
args.append(inflection.underscore(subfactory_class._meta.model.__name__))
66+
for attr, value in factory_class._meta.declarations.items():
67+
args = None
68+
attr_name = SEPARATOR.join((model_name, attr))
8069

81-
make_fixture(
82-
name=attr_name,
83-
module=module,
84-
func=subfactory_fixture,
85-
args=args,
86-
factory_class=subfactory_class,
87-
)
88-
else:
89-
args = None
70+
if isinstance(value, factory.declarations.PostGeneration):
71+
value = kwargs.get(attr, None)
9072
if isinstance(value, LazyFixture):
9173
args = value.args
92-
if isinstance(value, factory.declarations.PostGeneration):
93-
value = None
9474

9575
make_fixture(
9676
name=attr_name,
@@ -99,6 +79,43 @@ def register(factory_class, _name=None, **kwargs):
9979
value=value,
10080
args=args,
10181
)
82+
else:
83+
value = kwargs.get(attr, value)
84+
85+
if isinstance(value, (factory.SubFactory, factory.RelatedFactory)):
86+
subfactory_class = value.get_factory()
87+
subfactory_deps = get_deps(subfactory_class, factory_class)
88+
89+
args = list(subfactory_deps)
90+
if isinstance(value, factory.RelatedFactory):
91+
related_model = get_model_name(subfactory_class)
92+
args.append(related_model)
93+
related.append(related_model)
94+
related.append(attr_name)
95+
related.extend(subfactory_deps)
96+
97+
if isinstance(value, factory.SubFactory):
98+
args.append(inflection.underscore(subfactory_class._meta.model.__name__))
99+
100+
make_fixture(
101+
name=attr_name,
102+
module=module,
103+
func=subfactory_fixture,
104+
args=args,
105+
factory_class=subfactory_class,
106+
)
107+
else:
108+
if isinstance(value, LazyFixture):
109+
args = value.args
110+
111+
make_fixture(
112+
name=attr_name,
113+
module=module,
114+
func=attr_fixture,
115+
value=value,
116+
args=args,
117+
)
118+
102119
if not hasattr(module, factory_name):
103120
make_fixture(
104121
name=factory_name,
@@ -151,7 +168,7 @@ def is_dep(value):
151168

152169
return [
153170
SEPARATOR.join((model_name, attr))
154-
for attr, value in factory_class.declarations(factory_class._meta.postgen_declarations).items()
171+
for attr, value in factory_class._meta.declarations.items()
155172
if is_dep(value)
156173
]
157174

@@ -163,56 +180,71 @@ def evaluate(request, value):
163180

164181
def model_fixture(request, factory_name):
165182
"""Model fixture implementation."""
166-
factoryboy_request = request.getfuncargvalue("factoryboy_request")
183+
factoryboy_request = request.getfixturevalue("factoryboy_request")
167184

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

171-
factory_class = request.getfuncargvalue(factory_name)
188+
factory_class = request.getfixturevalue(factory_name)
172189
prefix = "".join((request.fixturename, SEPARATOR))
173-
data = {}
174-
for argname in request._fixturedef.argnames:
175-
if argname.startswith(prefix) and argname[len(prefix):] not in factory_class._meta.postgen_declarations:
176-
data[argname[len(prefix):]] = evaluate(request, request.getfuncargvalue(argname))
190+
191+
# Create model fixture instance
177192

178193
class Factory(factory_class):
194+
pass
179195

180-
@classmethod
181-
def attributes(cls, *args, **kwargs):
182-
return dict(
183-
(key, value)
184-
for key, value in super(Factory, cls).attributes(*args, **kwargs).items()
185-
if key in data
186-
)
196+
Factory._meta.base_declarations = dict(
197+
(k, v) for k, v in Factory._meta.base_declarations.items()
198+
if not isinstance(v, factory.declarations.PostGenerationDeclaration)
199+
)
200+
Factory._meta.post_declarations = factory.builder.DeclarationSet()
187201

188-
Factory._meta.postgen_declarations = {}
189-
Factory._meta.exclude = [value for value in Factory._meta.exclude if value in data]
202+
kwargs = {}
203+
for key in factory_class._meta.pre_declarations:
204+
argname = "".join((prefix, key))
205+
if argname in request._fixturedef.argnames:
206+
kwargs[key] = evaluate(request, request.getfixturevalue(argname))
190207

191-
# Extract post-generation context
192-
post_decls = []
193-
if factory_class._meta.postgen_declarations:
194-
for attr, decl in sorted(factory_class._meta.postgen_declarations.items()):
195-
post_decls.append((attr, decl, decl.extract(attr, data)))
208+
strategy = factory.enums.CREATE_STRATEGY
209+
builder = factory.builder.StepBuilder(Factory._meta, kwargs, strategy)
210+
step = factory.builder.BuildStep(builder=builder, sequence=Factory._meta.next_sequence())
196211

197-
# Create model fixture instance
198-
instance = Factory(**data)
199-
request._fixturedef.cached_result = (instance, None, None)
200-
if hasattr(request, '_fixture_defs'):
201-
request._fixture_defs[request.fixturename] = request._fixturedef
202-
else:
203-
request._fixturedefs[request.fixturename] = request._fixturedef
212+
instance = Factory(**kwargs)
213+
214+
# Cache the instance value on pytest level so that the fixture can be resolved before the return
215+
request._fixturedef.cached_result = (instance, 0, None)
216+
request._fixture_defs[request.fixturename] = request._fixturedef
204217

205218
# Defer post-generation declarations
206-
related = []
207-
postgen = []
208-
for attr, decl, context in post_decls:
219+
deferred = []
220+
221+
for attr in factory_class._meta.post_declarations.sorted():
222+
223+
decl = factory_class._meta.post_declarations.declarations[attr]
224+
209225
if isinstance(decl, factory.RelatedFactory):
210-
related.append(make_deferred_related(factory_class, request.fixturename, attr))
226+
deferred.append(make_deferred_related(factory_class, request.fixturename, attr))
211227
else:
212-
postgen.append(
213-
make_deferred_postgen(factory_class, request.fixturename, instance, attr, decl, context)
228+
argname = "".join((prefix, attr))
229+
extra = {}
230+
for k, v in factory_class._meta.post_declarations.contexts[attr].items():
231+
if k == '':
232+
continue
233+
post_attr = SEPARATOR.join((argname, k))
234+
235+
if post_attr in request._fixturedef.argnames:
236+
extra[k] = evaluate(request, request.getfixturevalue(post_attr))
237+
else:
238+
extra[k] = v
239+
240+
postgen_context = factory.builder.PostGenerationContext(
241+
value_provided=True,
242+
value=evaluate(request, request.getfixturevalue(argname)),
243+
extra=extra,
244+
)
245+
deferred.append(
246+
make_deferred_postgen(step, factory_class, request.fixturename, instance, attr, decl, postgen_context)
214247
)
215-
deferred = related + postgen
216248
factoryboy_request.defer(deferred)
217249

218250
# Try to evaluate as much post-generation dependencies as possible
@@ -232,35 +264,34 @@ def make_deferred_related(factory, fixture, attr):
232264
name = SEPARATOR.join((fixture, attr))
233265

234266
def deferred(request):
235-
request.getfuncargvalue(name)
236-
# return request.getfuncargvalue(name)
267+
request.getfixturevalue(name)
268+
237269
deferred.__name__ = name
238270
deferred._factory = factory
239271
deferred._fixture = fixture
240272
deferred._is_related = True
241273
return deferred
242274

243275

244-
def make_deferred_postgen(factory, fixture, instance, attr, declaration, context):
276+
def make_deferred_postgen(step, factory_class, fixture, instance, attr, declaration, context):
245277
"""Make deferred function for the post-generation declaration.
246278
247-
:param factory: Factory class.
279+
:param step: factory_boy builder step.
280+
:param factory_class: Factory class.
248281
:param fixture: Object fixture name e.g. "author".
249282
:param instance: Parent object instance.
250283
:param attr: Declaration attribute name e.g. "register_user".
251-
:param context: Post-generation declaration extraction context.
284+
:param context: Post-generation declaration context.
252285
253286
:note: Deferred function name results in "author__register_user".
254287
"""
255288
name = SEPARATOR.join((fixture, attr))
256289

257290
def deferred(request):
258-
context.value = evaluate(request, request.getfuncargvalue(name))
259-
context.extra = dict((key, evaluate(request, value)) for key, value in context.extra.items())
260-
declaration.call(instance, True, context)
261-
# return context.value
291+
declaration.call(instance, step, context)
292+
262293
deferred.__name__ = name
263-
deferred._factory = factory
294+
deferred._factory = factory_class
264295
deferred._fixture = fixture
265296
deferred._is_related = False
266297
return deferred
@@ -279,7 +310,7 @@ def attr_fixture(request, value):
279310
def subfactory_fixture(request, factory_class):
280311
"""SubFactory/RelatedFactory fixture implementation."""
281312
fixture = inflection.underscore(factory_class._meta.model.__name__)
282-
return request.getfuncargvalue(fixture)
313+
return request.getfixturevalue(fixture)
283314

284315

285316
def get_caller_module(depth=2):
@@ -293,7 +324,6 @@ def get_caller_module(depth=2):
293324

294325

295326
class LazyFixture(object):
296-
297327
"""Lazy fixture."""
298328

299329
def __init__(self, fixture):
@@ -314,7 +344,7 @@ def evaluate(self, request):
314344
:return: evaluated fixture.
315345
"""
316346
if callable(self.fixture):
317-
kwargs = dict((arg, request.getfuncargvalue(arg)) for arg in self.args)
347+
kwargs = dict((arg, request.getfixturevalue(arg)) for arg in self.args)
318348
return self.fixture(**kwargs)
319349
else:
320-
return request.getfuncargvalue(self.fixture)
350+
return request.getfixturevalue(self.fixture)

pytest_factoryboy/plugin.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ def defer(self, functions):
2727
self.deferred.append(functions)
2828

2929
def get_deps(self, request, fixture, deps=None):
30-
request = request.getfuncargvalue('request')
30+
request = request.getfixturevalue('request')
3131

3232
if deps is None:
3333
deps = set([fixture])
@@ -43,12 +43,8 @@ def get_deps(self, request, fixture, deps=None):
4343

4444
def get_current_deps(self, request):
4545
deps = set()
46-
if hasattr(request, '_fixture_defs'):
47-
fixture_defs = request._fixture_defs
48-
else:
49-
fixture_defs = request._fixturedefs
5046
while hasattr(request, '_parent_request'):
51-
if request.fixturename and request.fixturename not in fixture_defs:
47+
if request.fixturename and request.fixturename not in getattr(request, "_fixturedefs", {}):
5248
deps.add(request.fixturename)
5349
request = request._parent_request
5450
return deps
@@ -76,7 +72,7 @@ def after_postgeneration(self, request):
7672
results = self.results.pop(model)
7773
obj = request.getfuncargvalue(model)
7874
factory = self.model_factories[model]
79-
factory._after_postgeneration(obj=obj, create=True, results=results)
75+
factory._after_postgeneration(obj, create=True, results=results)
8076

8177
def evaluate(self, request):
8278
"""Finalize, run deferred post-generation actions, etc."""

setup.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@
4141
] + [("Programming Language :: Python :: %s" % x) for x in "2.7 3.0 3.1 3.2 3.3 3.4 3.5".split()],
4242
install_requires=[
4343
"inflection",
44-
"factory_boy<2.9",
45-
"pytest",
44+
"factory_boy>=2.10.0",
45+
"pytest>=3.3.2",
4646
],
4747
# the following makes a plugin available to py.test
4848
entry_points={

tests/test_attributes_field.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
from pytest_factoryboy import register
22
import factory
33

4+
import pytest
5+
46

57
class EmptyModel(object):
68
pass
@@ -16,6 +18,13 @@ class Meta:
1618
register(AttributesFactory, "with_attributes")
1719

1820

19-
def test_factory_with_attributes(with_attributes):
20-
"""Test that a factory can have a `attributes` field."""
21+
@pytest.mark.skip(reason="Doesn't work in FactoryBoy at the moment")
22+
def test_factory_with_attributes():
23+
"""Test that a factory can have a `attributes` field when used as a factory."""
24+
AttributesFactory()
25+
26+
27+
@pytest.mark.skip(reason="Doesn't work in FactoryBoy at the moment")
28+
def test_factory_fixture_with_attributes(with_attributes):
29+
"""Test that a factory can have a `attributes` field when used as a fixture."""
2130
pass

0 commit comments

Comments
 (0)