New upstream version 2.0.2
Sophie Brun
4 years ago
0 | \.DS_Store | |
1 | *.rej | |
2 | *.py[cod] | |
3 | /.env | |
4 | *.orig | |
5 | ||
6 | # C extensions | |
7 | *.so | |
8 | ||
9 | # Packages | |
10 | *.egg | |
11 | *.egg-info | |
12 | dist | |
13 | build | |
14 | _build | |
15 | eggs | |
16 | parts | |
17 | bin | |
18 | var | |
19 | sdist | |
20 | develop-eggs | |
21 | .installed.cfg | |
22 | lib | |
23 | lib64 | |
24 | ||
25 | # Installer logs | |
26 | pip-log.txt | |
27 | ||
28 | # Unit test / coverage reports | |
29 | .coverage | |
30 | .tox | |
31 | nosetests.xml | |
32 | ||
33 | # Translations | |
34 | *.mo | |
35 | ||
36 | # Mr Developer | |
37 | .mr.developer.cfg | |
38 | .project | |
39 | .pydevproject | |
40 | .cache | |
41 | .pytest_cache | |
42 | .ropeproject | |
43 | ||
44 | # Sublime | |
45 | /*.sublime-* | |
46 | ||
47 | #PyCharm | |
48 | /.idea | |
49 | ||
50 | # virtualenv | |
51 | /.Python | |
52 | /lib | |
53 | /include | |
54 | /src | |
55 | /share | |
56 | /local |
0 | sudo: false | |
1 | language: python | |
2 | # enable Python 3.5 on travis until it will be pre-installed | |
3 | python: 3.5 | |
4 | env: | |
5 | matrix: | |
6 | - TESTENV=py27-pytest2 | |
7 | - TESTENV=py34-pytest2 | |
8 | - TESTENV=py35-pytest2 | |
9 | - TESTENV=py27-pytest3 | |
10 | - TESTENV=py34-pytest3 | |
11 | - TESTENV=py35-pytest3 | |
12 | install: | |
13 | - pip install tox | |
14 | script: tox -e $TESTENV | |
15 | branches: | |
16 | except: | |
17 | - /^\d/ | |
18 | notifications: | |
19 | email: | |
20 | - [email protected] | |
21 | - [email protected] |
0 | Authors | |
1 | ======= | |
2 | ||
3 | `Oleg Pidsadnyi <[email protected]>`_ | |
4 | original idea and implementation | |
5 | ||
6 | These people have contributed to `pytest-factoryboy`, in alphabetical order: | |
7 | ||
8 | * `Anatoly Bubenkov <[email protected]>`_ | |
9 | * `Daniel Duong <https://github.com/dduong42>`_ | |
10 | * `Daniel Hahler <https://github.com/blueyed>`_ | |
11 | * `p13773 <https://github.com/p13773>`_ | |
12 | * `Vasily Kuznetsov <https://github.com/kvas-it>`_ |
0 | Changelog | |
1 | ========= | |
2 | ||
3 | 2.0.2 | |
4 | ----- | |
5 | ||
6 | - Fix warning `use of getfuncargvalue is deprecated, use getfixturevalue` (sliverc) | |
7 | ||
8 | ||
9 | 2.0.1 | |
10 | ----- | |
11 | ||
12 | Breaking change due to the heavy refactor of both pytest and factory_boy. | |
13 | ||
14 | - Failing test for using a `attributes` field on the factory (blueyed) | |
15 | - Minimal pytest version is 3.3.2 (olegpidsadnyi) | |
16 | - Minimal factory_boy version is 2.10.0 (olegpidsadnyi) | |
17 | ||
18 | ||
19 | 1.3.2 | |
20 | ----- | |
21 | ||
22 | - use {posargs} in pytest command (blueyed) | |
23 | - pin factory_boy<2.9 (blueyed) | |
24 | ||
25 | ||
26 | 1.3.1 | |
27 | ----- | |
28 | ||
29 | - fix LazyFixture evaluation order (olegpidsadnyi) | |
30 | ||
31 | ||
32 | 1.3.0 | |
33 | ----- | |
34 | ||
35 | - replace request._fixturedefs by request._fixture_defs (p13773) | |
36 | ||
37 | ||
38 | 1.2.2 | |
39 | ----- | |
40 | ||
41 | - fix post-generation dependencies (olegpidsadnyi) | |
42 | ||
43 | ||
44 | 1.2.1 | |
45 | ----- | |
46 | ||
47 | - automatical resolution of the post-generation dependencies (olegpidsadnyi, kvas-it) | |
48 | ||
49 | ||
50 | 1.1.6 | |
51 | ----- | |
52 | ||
53 | - fixes fixture function module name attribute (olegpidsadnyi) | |
54 | - fixes _after_postgeneration hook invocation for deferred post-generation declarations (olegpidsadnyi) | |
55 | ||
56 | ||
57 | 1.1.5 | |
58 | ----- | |
59 | ||
60 | - support factory models to be passed as strings (bubenkoff) | |
61 | ||
62 | ||
63 | 1.1.3 | |
64 | ----- | |
65 | ||
66 | - circular dependency determination is fixed for the post-generation (olegpidsadnyi) | |
67 | ||
68 | ||
69 | 1.1.2 | |
70 | ----- | |
71 | ||
72 | - circular dependency determination is fixed for the RelatedFactory attributes (olegpidsadnyi) | |
73 | ||
74 | ||
75 | 1.1.1 | |
76 | ----- | |
77 | ||
78 | - fix installation issue when django environment is not set (bubenkoff, amakhnach) | |
79 | ||
80 | ||
81 | 1.1.0 | |
82 | ----- | |
83 | ||
84 | - fixture dependencies on deferred post-generation declarations (olegpidsadnyi) | |
85 | ||
86 | ||
87 | 1.0.3 | |
88 | ----- | |
89 | ||
90 | - post_generation extra parameters fixed (olegpidsadnyi) | |
91 | - fixture partial specialization (olegpidsadnyi) | |
92 | - fixes readme and example (dduong42) | |
93 | - lazy fixtures (olegpidsadnyi) | |
94 | - deferred post-generation evaluation (olegpidsadnyi) | |
95 | - hooks (olegpidsadnyi) | |
96 | ||
97 | ||
98 | 1.0.2 | |
99 | ----- | |
100 | ||
101 | - refactoring of the fixture function compilation (olegpidsadnyi) | |
102 | - related factory fix (olegpidsadnyi) | |
103 | - post_generation fixture dependency fixed (olegpidsadnyi) | |
104 | - model fixture registration with specific name (olegpidsadnyi) | |
105 | - README updated (olegpidsadnyi) | |
106 | ||
107 | 1.0.1 | |
108 | ----- | |
109 | ||
110 | - use ``inflection`` package to convert camel case to underscore (bubenkoff) | |
111 | ||
112 | 1.0.0 | |
113 | ----- | |
114 | ||
115 | - initial release (olegpidsadnyi) |
0 | include *.rst |
0 | # create virtual environment | |
1 | .env: | |
2 | virtualenv .env | |
3 | ||
4 | # install all needed for development | |
5 | develop: .env | |
6 | .env/bin/pip install -e . -r requirements-testing.txt tox | |
7 | ||
8 | # clean the development envrironment | |
9 | clean: | |
10 | -rm -rf .env |
0 | factory_boy_ integration with the pytest_ runner | |
1 | ================================================ | |
2 | ||
3 | .. image:: https://img.shields.io/pypi/v/pytest-factoryboy.svg | |
4 | :target: https://pypi.python.org/pypi/pytest-factoryboy | |
5 | .. image:: https://img.shields.io/pypi/pyversions/pytest-factoryboy.svg | |
6 | :target: https://pypi.python.org/pypi/pytest-factoryboy | |
7 | .. image:: https://img.shields.io/coveralls/pytest-dev/pytest-factoryboy/master.svg | |
8 | :target: https://coveralls.io/r/pytest-dev/pytest-factoryboy | |
9 | .. image:: https://travis-ci.org/pytest-dev/pytest-factoryboy.svg?branch=master | |
10 | :target: https://travis-ci.org/pytest-dev/pytest-factoryboy | |
11 | .. image:: https://readthedocs.org/projects/pytest-factoryboy/badge/?version=latest | |
12 | :target: https://readthedocs.org/projects/pytest-factoryboy/?badge=latest | |
13 | :alt: Documentation Status | |
14 | ||
15 | ||
16 | pytest-factoryboy makes it easy to combine ``factory`` approach to the test setup with the ``dependency`` injection, | |
17 | heart of the `pytest fixtures`_. | |
18 | ||
19 | .. _factory_boy: https://factoryboy.readthedocs.io | |
20 | .. _pytest: http://pytest.org | |
21 | .. _pytest fixtures: https://pytest.org/latest/fixture.html | |
22 | .. _overridden: http://pytest.org/latest/fixture.html#override-a-fixture-with-direct-test-parametrization | |
23 | ||
24 | ||
25 | Install pytest-factoryboy | |
26 | ------------------------- | |
27 | ||
28 | :: | |
29 | ||
30 | pip install pytest-factoryboy | |
31 | ||
32 | ||
33 | Concept | |
34 | ------- | |
35 | ||
36 | Library exports a function to register factories as fixtures. Fixtures are contributed | |
37 | to the same module where register function is called. | |
38 | ||
39 | Factory Fixture | |
40 | --------------- | |
41 | ||
42 | Factory fixtures allow using factories without importing them. Name convention is lowercase-underscore | |
43 | class name. | |
44 | ||
45 | .. code-block:: python | |
46 | ||
47 | import factory | |
48 | from pytest_factoryboy import register | |
49 | ||
50 | class AuthorFactory(factory.Factory): | |
51 | ||
52 | class Meta: | |
53 | model = Author | |
54 | ||
55 | ||
56 | register(AuthorFactory) | |
57 | ||
58 | ||
59 | def test_factory_fixture(author_factory): | |
60 | author = author_factory(name="Charles Dickens") | |
61 | assert author.name == "Charles Dickens" | |
62 | ||
63 | ||
64 | Model Fixture | |
65 | ------------- | |
66 | ||
67 | Model fixture implements an instance of a model created by the factory. Name convention is lowercase-underscore | |
68 | class name. | |
69 | ||
70 | ||
71 | .. code-block:: python | |
72 | ||
73 | import factory | |
74 | from pytest_factoryboy import register | |
75 | ||
76 | @register | |
77 | class AuthorFactory(Factory): | |
78 | ||
79 | class Meta: | |
80 | model = Author | |
81 | ||
82 | name = "Charles Dickens" | |
83 | ||
84 | ||
85 | def test_model_fixture(author): | |
86 | assert author.name == "Charles Dickens" | |
87 | ||
88 | ||
89 | Model fixtures can be registered with specific names. For example if you address instances of some collection | |
90 | by the name like "first", "second" or of another parent as "other": | |
91 | ||
92 | ||
93 | .. code-block:: python | |
94 | ||
95 | register(BookFactory) # book | |
96 | register(BookFactory, "second_book") # second_book | |
97 | ||
98 | register(AuthorFactory) # author | |
99 | register(AuthorFactory, "second_author") # second_author | |
100 | ||
101 | register(BookFactory, "other_book") # other_book, book of another author | |
102 | ||
103 | @pytest.fixture | |
104 | def other_book__author(second_author): | |
105 | """Make the relation of the second_book to another (second) author.""" | |
106 | return second_author | |
107 | ||
108 | ||
109 | ||
110 | Attributes are Fixtures | |
111 | ----------------------- | |
112 | ||
113 | There are fixtures created for factory attributes. Attribute names are prefixed with the model fixture name and | |
114 | double underscore (similar to the convention used by factory_boy). | |
115 | ||
116 | ||
117 | .. code-block:: python | |
118 | ||
119 | @pytest.mark.parametrize("author__name", ["Bill Gates"]) | |
120 | def test_model_fixture(author): | |
121 | assert author.name == "Bill Gates" | |
122 | ||
123 | SubFactory | |
124 | ---------- | |
125 | ||
126 | Sub-factory attribute points to the model fixture of the sub-factory. | |
127 | Attributes of sub-factories are injected as dependencies to the model fixture and can be overridden_ via | |
128 | the parametrization. | |
129 | ||
130 | Related Factory | |
131 | --------------- | |
132 | ||
133 | Related factory attribute points to the model fixture of the related factory. | |
134 | Attributes of related factories are injected as dependencies to the model fixture and can be overridden_ via | |
135 | the parametrization. | |
136 | ||
137 | ||
138 | post-generation | |
139 | --------------- | |
140 | ||
141 | Post-generation attribute fixture implements only the extracted value for the post generation function. | |
142 | ||
143 | ||
144 | Integration | |
145 | ----------- | |
146 | ||
147 | An example of factory_boy_ and pytest_ integration. | |
148 | ||
149 | factories/__init__.py: | |
150 | ||
151 | .. code-block:: python | |
152 | ||
153 | import factory | |
154 | from faker import Factory as FakerFactory | |
155 | ||
156 | faker = FakerFactory.create() | |
157 | ||
158 | ||
159 | class AuthorFactory(factory.django.DjangoModelFactory): | |
160 | ||
161 | """Author factory.""" | |
162 | ||
163 | name = factory.LazyAttribute(lambda x: faker.name()) | |
164 | ||
165 | class Meta: | |
166 | model = 'app.Author' | |
167 | ||
168 | ||
169 | class BookFactory(factory.django.DjangoModelFactory): | |
170 | ||
171 | """Book factory.""" | |
172 | ||
173 | title = factory.LazyAttribute(lambda x: faker.sentence(nb_words=4)) | |
174 | ||
175 | class Meta: | |
176 | model = 'app.Book' | |
177 | ||
178 | author = factory.SubFactory(AuthorFactory) | |
179 | ||
180 | tests/conftest.py: | |
181 | ||
182 | .. code-block:: python | |
183 | ||
184 | from pytest_factoryboy import register | |
185 | ||
186 | from factories import AuthorFactory, BookFactory | |
187 | ||
188 | register(AuthorFactory) | |
189 | register(BookFactory) | |
190 | ||
191 | tests/test_models.py: | |
192 | ||
193 | .. code-block:: python | |
194 | ||
195 | from app.models import Book | |
196 | from factories import BookFactory | |
197 | ||
198 | def test_book_factory(book_factory): | |
199 | """Factories become fixtures automatically.""" | |
200 | assert isinstance(book_factory, BookFactory) | |
201 | ||
202 | def test_book(book): | |
203 | """Instances become fixtures automatically.""" | |
204 | assert isinstance(book, Book) | |
205 | ||
206 | @pytest.mark.parametrize("book__title", ["PyTest for Dummies"]) | |
207 | @pytest.mark.parametrize("author__name", ["Bill Gates"]) | |
208 | def test_parametrized(book): | |
209 | """You can set any factory attribute as a fixture using naming convention.""" | |
210 | assert book.name == "PyTest for Dummies" | |
211 | assert book.author.name == "Bill Gates" | |
212 | ||
213 | ||
214 | Fixture partial specialization | |
215 | ------------------------------ | |
216 | ||
217 | There is a possibility to pass keyword parameters in order to override factory attribute values during fixture | |
218 | registration. This comes in handy when your test case is requesting a lot of fixture flavors. Too much for the | |
219 | regular pytest parametrization. | |
220 | In this case you can register fixture flavors in the local test module and specify value deviations inside ``register`` | |
221 | function calls. | |
222 | ||
223 | ||
224 | .. code-block:: python | |
225 | ||
226 | register(AuthorFactory, "male_author", gender="M", name="John Doe") | |
227 | register(AuthorFactory, "female_author", gender="F") | |
228 | ||
229 | ||
230 | @pytest.fixture | |
231 | def female_author__name(): | |
232 | """Override female author name as a separate fixture.""" | |
233 | return "Jane Doe" | |
234 | ||
235 | ||
236 | @pytest.mark.parametrize("male_author__age", [42]) # Override even more | |
237 | def test_partial(male_author, female_author): | |
238 | """Test fixture partial specialization.""" | |
239 | assert male_author.gender == "M" | |
240 | assert male_author.name == "John Doe" | |
241 | assert male_author.age == 42 | |
242 | ||
243 | assert female_author.gender == "F" | |
244 | assert female_author.name == "Jane Doe" | |
245 | ||
246 | ||
247 | Fixture attributes | |
248 | ------------------ | |
249 | ||
250 | Sometimes it is necessary to pass an instance of another fixture as an attribute value to the factory. | |
251 | It is possible to override the generated attribute fixture where desired values can be requested as | |
252 | fixture dependencies. There is also a lazy wrapper for the fixture that can be used in the parametrization | |
253 | without defining fixtures in a module. | |
254 | ||
255 | ||
256 | LazyFixture constructor accepts either existing fixture name or callable with dependencies: | |
257 | ||
258 | .. code-block:: python | |
259 | ||
260 | import pytest | |
261 | from pytest_factoryboy import register, LazyFixture | |
262 | ||
263 | ||
264 | @pytest.mark.parametrize("book__author", [LazyFixture("another_author")]) | |
265 | def test_lazy_fixture_name(book, another_author): | |
266 | """Test that book author is replaced with another author by fixture name.""" | |
267 | assert book.author == another_author | |
268 | ||
269 | ||
270 | @pytest.mark.parametrize("book__author", [LazyFixture(lambda another_author: another_author)]) | |
271 | def test_lazy_fixture_callable(book, another_author): | |
272 | """Test that book author is replaced with another author by callable.""" | |
273 | assert book.author == another_author | |
274 | ||
275 | ||
276 | # Can also be used in the partial specialization during the registration. | |
277 | register(BookFactory, "another_book", author=LazyFixture("another_author")) | |
278 | ||
279 | ||
280 | Post-generation dependencies | |
281 | ============================ | |
282 | ||
283 | Unlike factory_boy which binds related objects using an internal container to store results of lazy evaluations, | |
284 | pytest-factoryboy relies on the PyTest request. | |
285 | ||
286 | Circular dependencies between objects can be resolved using post-generation hooks/related factories in combination with | |
287 | passing the SelfAttribute, but in the case of PyTest request fixture functions have to return values in order to be cached | |
288 | in the request and to become available to other fixtures. | |
289 | ||
290 | That's why evaluation of the post-generation declaration in pytest-factoryboy is deferred until calling | |
291 | the test funciton. | |
292 | This solves circular dependecy resolution for situations like: | |
293 | ||
294 | :: | |
295 | ||
296 | o->[ A ]-->[ B ]<--[ C ]-o | |
297 | | | | |
298 | o----(C depends on A)----o | |
299 | ||
300 | ||
301 | On the other hand deferring the evaluation of post-generation declarations evaluation makes their result unavailable during the generation | |
302 | of objects that are not in the circular dependecy, but they rely on the post-generation action. | |
303 | ||
304 | pytest-factoryboy is trying to detect cycles and resolve post-generation dependencies automatically. | |
305 | ||
306 | ||
307 | .. code-block:: python | |
308 | ||
309 | from pytest_factoryboy import register | |
310 | ||
311 | ||
312 | class Foo(object): | |
313 | ||
314 | def __init__(self, value): | |
315 | self.value = value | |
316 | ||
317 | ||
318 | class Bar(object): | |
319 | ||
320 | def __init__(self, foo): | |
321 | self.foo = foo | |
322 | ||
323 | ||
324 | @register | |
325 | class FooFactory(factory.Factory): | |
326 | ||
327 | """Foo factory.""" | |
328 | ||
329 | class Meta: | |
330 | model = Foo | |
331 | ||
332 | value = 0 | |
333 | ||
334 | @factory.post_generation | |
335 | def set1(foo, create, value, **kwargs): | |
336 | foo.value = 1 | |
337 | ||
338 | ||
339 | class BarFactory(factory.Factory): | |
340 | ||
341 | """Bar factory.""" | |
342 | ||
343 | foo = factory.SubFactory(FooFactory) | |
344 | ||
345 | @classmethod | |
346 | def _create(cls, model_class, foo): | |
347 | assert foo.value == 1 # Assert that set1 is evaluated before object generation | |
348 | return super(BarFactory, cls)._create(model_class, foo=foo) | |
349 | ||
350 | class Meta: | |
351 | model = Bar | |
352 | ||
353 | ||
354 | register( | |
355 | BarFactory, | |
356 | 'bar', | |
357 | ) | |
358 | """Forces 'set1' to be evaluated first.""" | |
359 | ||
360 | ||
361 | def test_depends_on_set1(bar): | |
362 | """Test that post-generation hooks are done and the value is 2.""" | |
363 | assert depends_on_1.foo.value == 1 | |
364 | ||
365 | ||
366 | Hooks | |
367 | ----- | |
368 | ||
369 | pytest-factoryboy exposes several `pytest hooks <http://pytest.org/latest/plugins.html#well-specified-hooks>`_ | |
370 | which might be helpful for e.g. controlling database transaction, for reporting etc: | |
371 | ||
372 | * pytest_factoryboy_done(request) - Called after all factory based fixtures and their post-generation actions have been evaluated. | |
373 | ||
374 | ||
375 | License | |
376 | ------- | |
377 | ||
378 | This software is licensed under the `MIT license <http://en.wikipedia.org/wiki/MIT_License>`_. | |
379 | ||
380 | © 2015 Oleg Pidsadnyi, Anatoly Bubenkov and others |
0 | # Makefile for Sphinx documentation | |
1 | # | |
2 | ||
3 | # You can set these variables from the command line. | |
4 | SPHINXOPTS = | |
5 | SPHINXBUILD = sphinx-build | |
6 | PAPER = | |
7 | BUILDDIR = _build | |
8 | ||
9 | # Internal variables. | |
10 | PAPEROPT_a4 = -D latex_paper_size=a4 | |
11 | PAPEROPT_letter = -D latex_paper_size=letter | |
12 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . | |
13 | # the i18n builder cannot share the environment and doctrees with the others | |
14 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . | |
15 | ||
16 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext | |
17 | ||
18 | help: | |
19 | @echo "Please use \`make <target>' where <target> is one of" | |
20 | @echo " html to make standalone HTML files" | |
21 | @echo " dirhtml to make HTML files named index.html in directories" | |
22 | @echo " singlehtml to make a single large HTML file" | |
23 | @echo " pickle to make pickle files" | |
24 | @echo " json to make JSON files" | |
25 | @echo " htmlhelp to make HTML files and a HTML help project" | |
26 | @echo " qthelp to make HTML files and a qthelp project" | |
27 | @echo " devhelp to make HTML files and a Devhelp project" | |
28 | @echo " epub to make an epub" | |
29 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" | |
30 | @echo " latexpdf to make LaTeX files and run them through pdflatex" | |
31 | @echo " text to make text files" | |
32 | @echo " man to make manual pages" | |
33 | @echo " texinfo to make Texinfo files" | |
34 | @echo " info to make Texinfo files and run them through makeinfo" | |
35 | @echo " gettext to make PO message catalogs" | |
36 | @echo " changes to make an overview of all changed/added/deprecated items" | |
37 | @echo " linkcheck to check all external links for integrity" | |
38 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" | |
39 | ||
40 | clean: | |
41 | -rm -rf $(BUILDDIR)/* | |
42 | ||
43 | html: | |
44 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html | |
45 | @echo | |
46 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." | |
47 | ||
48 | dirhtml: | |
49 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml | |
50 | @echo | |
51 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." | |
52 | ||
53 | singlehtml: | |
54 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml | |
55 | @echo | |
56 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." | |
57 | ||
58 | pickle: | |
59 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle | |
60 | @echo | |
61 | @echo "Build finished; now you can process the pickle files." | |
62 | ||
63 | json: | |
64 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json | |
65 | @echo | |
66 | @echo "Build finished; now you can process the JSON files." | |
67 | ||
68 | htmlhelp: | |
69 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp | |
70 | @echo | |
71 | @echo "Build finished; now you can run HTML Help Workshop with the" \ | |
72 | ".hhp project file in $(BUILDDIR)/htmlhelp." | |
73 | ||
74 | qthelp: | |
75 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp | |
76 | @echo | |
77 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ | |
78 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" | |
79 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Pytest-BDD.qhcp" | |
80 | @echo "To view the help file:" | |
81 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Pytest-BDD.qhc" | |
82 | ||
83 | devhelp: | |
84 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp | |
85 | @echo | |
86 | @echo "Build finished." | |
87 | @echo "To view the help file:" | |
88 | @echo "# mkdir -p $$HOME/.local/share/devhelp/Pytest-BDD" | |
89 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Pytest-BDD" | |
90 | @echo "# devhelp" | |
91 | ||
92 | epub: | |
93 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub | |
94 | @echo | |
95 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." | |
96 | ||
97 | latex: | |
98 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex | |
99 | @echo | |
100 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." | |
101 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ | |
102 | "(use \`make latexpdf' here to do that automatically)." | |
103 | ||
104 | latexpdf: | |
105 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex | |
106 | @echo "Running LaTeX files through pdflatex..." | |
107 | $(MAKE) -C $(BUILDDIR)/latex all-pdf | |
108 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." | |
109 | ||
110 | text: | |
111 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text | |
112 | @echo | |
113 | @echo "Build finished. The text files are in $(BUILDDIR)/text." | |
114 | ||
115 | man: | |
116 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man | |
117 | @echo | |
118 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." | |
119 | ||
120 | texinfo: | |
121 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo | |
122 | @echo | |
123 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." | |
124 | @echo "Run \`make' in that directory to run these through makeinfo" \ | |
125 | "(use \`make info' here to do that automatically)." | |
126 | ||
127 | info: | |
128 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo | |
129 | @echo "Running Texinfo files through makeinfo..." | |
130 | make -C $(BUILDDIR)/texinfo info | |
131 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." | |
132 | ||
133 | gettext: | |
134 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale | |
135 | @echo | |
136 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." | |
137 | ||
138 | changes: | |
139 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes | |
140 | @echo | |
141 | @echo "The overview file is in $(BUILDDIR)/changes." | |
142 | ||
143 | linkcheck: | |
144 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck | |
145 | @echo | |
146 | @echo "Link check complete; look for any errors in the above output " \ | |
147 | "or in $(BUILDDIR)/linkcheck/output.txt." | |
148 | ||
149 | doctest: | |
150 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest | |
151 | @echo "Testing of doctests in the sources finished, look at the " \ | |
152 | "results in $(BUILDDIR)/doctest/output.txt." |
0 | """Sphinx config.""" | |
1 | # -*- coding: utf-8 -*- | |
2 | # | |
3 | # pytest-factoryboy documentation build configuration file, created by | |
4 | # sphinx-quickstart on Sun Apr 7 21:07:56 2013. | |
5 | # | |
6 | # This file is execfile()d with the current directory set to its containing dir. | |
7 | # | |
8 | # Note that not all possible configuration values are present in this | |
9 | # autogenerated file. | |
10 | # | |
11 | # All configuration values have a default; values that are commented out | |
12 | # serve to show the default. | |
13 | ||
14 | # If extensions (or modules to document with autodoc) are in another directory, | |
15 | # add these directories to sys.path here. If the directory is relative to the | |
16 | # documentation root, use os.path.abspath to make it absolute, like shown here. | |
17 | ||
18 | import sys | |
19 | import os | |
20 | ||
21 | sys.path.insert(0, os.path.abspath('..')) | |
22 | ||
23 | import pytest_factoryboy | |
24 | ||
25 | # -- General configuration ----------------------------------------------------- | |
26 | ||
27 | # If your documentation needs a minimal Sphinx version, state it here. | |
28 | #needs_sphinx = '1.0' | |
29 | ||
30 | # Add any Sphinx extension module names here, as strings. They can be extensions | |
31 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. | |
32 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] | |
33 | ||
34 | # Add any paths that contain templates here, relative to this directory. | |
35 | templates_path = ['_templates'] | |
36 | ||
37 | # The suffix of source filenames. | |
38 | source_suffix = '.rst' | |
39 | ||
40 | # The encoding of source files. | |
41 | #source_encoding = 'utf-8-sig' | |
42 | ||
43 | # The master toctree document. | |
44 | master_doc = 'index' | |
45 | ||
46 | # General information about the project. | |
47 | project = u'pytest-factoryboy' | |
48 | AUTHOR = 'Oleg Pidsadnyi, Anatoly Bubenkov and others' | |
49 | copyright = u'2015, ' + AUTHOR | |
50 | ||
51 | # The version info for the project you're documenting, acts as replacement for | |
52 | # |version| and |release|, also used in various other places throughout the | |
53 | # built documents. | |
54 | # | |
55 | # The short X.Y version. | |
56 | version = pytest_factoryboy.__version__ | |
57 | # The full version, including alpha/beta/rc tags. | |
58 | release = pytest_factoryboy.__version__ | |
59 | ||
60 | # The language for content autogenerated by Sphinx. Refer to documentation | |
61 | # for a list of supported languages. | |
62 | #language = None | |
63 | ||
64 | # There are two options for replacing |today|: either, you set today to some | |
65 | # non-false value, then it is used: | |
66 | #today = '' | |
67 | # Else, today_fmt is used as the format for a strftime call. | |
68 | #today_fmt = '%B %d, %Y' | |
69 | ||
70 | # List of patterns, relative to source directory, that match files and | |
71 | # directories to ignore when looking for source files. | |
72 | exclude_patterns = ['_build'] | |
73 | ||
74 | # The reST default role (used for this markup: `text`) to use for all documents. | |
75 | #default_role = None | |
76 | ||
77 | # If true, '()' will be appended to :func: etc. cross-reference text. | |
78 | #add_function_parentheses = True | |
79 | ||
80 | # If true, the current module name will be prepended to all description | |
81 | # unit titles (such as .. function::). | |
82 | #add_module_names = True | |
83 | ||
84 | # If true, sectionauthor and moduleauthor directives will be shown in the | |
85 | # output. They are ignored by default. | |
86 | #show_authors = False | |
87 | ||
88 | # The name of the Pygments (syntax highlighting) style to use. | |
89 | pygments_style = 'sphinx' | |
90 | ||
91 | # A list of ignored prefixes for module index sorting. | |
92 | #modindex_common_prefix = [] | |
93 | ||
94 | ||
95 | # -- Options for HTML output --------------------------------------------------- | |
96 | ||
97 | # The theme to use for HTML and HTML Help pages. See the documentation for | |
98 | # a list of builtin themes. | |
99 | html_theme = 'default' | |
100 | ||
101 | # Theme options are theme-specific and customize the look and feel of a theme | |
102 | # further. For a list of options available for each theme, see the | |
103 | # documentation. | |
104 | #html_theme_options = {} | |
105 | ||
106 | # Add any paths that contain custom themes here, relative to this directory. | |
107 | #html_theme_path = [] | |
108 | ||
109 | # The name for this set of Sphinx documents. If None, it defaults to | |
110 | # "<project> v<release> documentation". | |
111 | #html_title = None | |
112 | ||
113 | # A shorter title for the navigation bar. Default is the same as html_title. | |
114 | #html_short_title = None | |
115 | ||
116 | # The name of an image file (relative to this directory) to place at the top | |
117 | # of the sidebar. | |
118 | #html_logo = None | |
119 | ||
120 | # The name of an image file (within the static path) to use as favicon of the | |
121 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 | |
122 | # pixels large. | |
123 | #html_favicon = None | |
124 | ||
125 | # Add any paths that contain custom static files (such as style sheets) here, | |
126 | # relative to this directory. They are copied after the builtin static files, | |
127 | # so a file named "default.css" will overwrite the builtin "default.css". | |
128 | html_static_path = ['_static'] | |
129 | ||
130 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, | |
131 | # using the given strftime format. | |
132 | #html_last_updated_fmt = '%b %d, %Y' | |
133 | ||
134 | # If true, SmartyPants will be used to convert quotes and dashes to | |
135 | # typographically correct entities. | |
136 | #html_use_smartypants = True | |
137 | ||
138 | # Custom sidebar templates, maps document names to template names. | |
139 | #html_sidebars = {} | |
140 | ||
141 | # Additional templates that should be rendered to pages, maps page names to | |
142 | # template names. | |
143 | #html_additional_pages = {} | |
144 | ||
145 | # If false, no module index is generated. | |
146 | #html_domain_indices = True | |
147 | ||
148 | # If false, no index is generated. | |
149 | #html_use_index = True | |
150 | ||
151 | # If true, the index is split into individual pages for each letter. | |
152 | #html_split_index = False | |
153 | ||
154 | # If true, links to the reST sources are added to the pages. | |
155 | #html_show_sourcelink = True | |
156 | ||
157 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. | |
158 | #html_show_sphinx = True | |
159 | ||
160 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. | |
161 | #html_show_copyright = True | |
162 | ||
163 | # If true, an OpenSearch description file will be output, and all pages will | |
164 | # contain a <link> tag referring to it. The value of this option must be the | |
165 | # base URL from which the finished HTML is served. | |
166 | #html_use_opensearch = '' | |
167 | ||
168 | # This is the file name suffix for HTML files (e.g. ".xhtml"). | |
169 | #html_file_suffix = None | |
170 | ||
171 | # Output file base name for HTML help builder. | |
172 | htmlhelp_basename = 'pytest-factoryboy-doc' | |
173 | ||
174 | ||
175 | # -- Options for LaTeX output -------------------------------------------------- | |
176 | ||
177 | latex_elements = { | |
178 | # The paper size ('letterpaper' or 'a4paper'). | |
179 | #'papersize': 'letterpaper', | |
180 | ||
181 | # The font size ('10pt', '11pt' or '12pt'). | |
182 | #'pointsize': '10pt', | |
183 | ||
184 | # Additional stuff for the LaTeX preamble. | |
185 | #'preamble': '', | |
186 | } | |
187 | ||
188 | # Grouping the document tree into LaTeX files. List of tuples | |
189 | # (source start file, target name, title, author, documentclass [howto/manual]). | |
190 | latex_documents = [ | |
191 | ('index', 'pytest-factoryboy.tex', u'pytest-factoryboy Documentation', AUTHOR, 'manual'), | |
192 | ] | |
193 | ||
194 | # The name of an image file (relative to this directory) to place at the top of | |
195 | # the title page. | |
196 | #latex_logo = None | |
197 | ||
198 | # For "manual" documents, if this is true, then toplevel headings are parts, | |
199 | # not chapters. | |
200 | #latex_use_parts = False | |
201 | ||
202 | # If true, show page references after internal links. | |
203 | #latex_show_pagerefs = False | |
204 | ||
205 | # If true, show URL addresses after external links. | |
206 | #latex_show_urls = False | |
207 | ||
208 | # Documents to append as an appendix to all manuals. | |
209 | #latex_appendices = [] | |
210 | ||
211 | # If false, no module index is generated. | |
212 | #latex_domain_indices = True | |
213 | ||
214 | ||
215 | # -- Options for manual page output -------------------------------------------- | |
216 | ||
217 | # One entry per manual page. List of tuples | |
218 | # (source start file, name, description, authors, manual section). | |
219 | man_pages = [ | |
220 | ('index', 'pytest-factoryboy', u'pytest-factoryboy Documentation', | |
221 | [AUTHOR], 1) | |
222 | ] | |
223 | ||
224 | # If true, show URL addresses after external links. | |
225 | #man_show_urls = False | |
226 | ||
227 | ||
228 | # -- Options for Texinfo output ------------------------------------------------ | |
229 | ||
230 | # Grouping the document tree into Texinfo files. List of tuples | |
231 | # (source start file, target name, title, author, | |
232 | # dir menu entry, description, category) | |
233 | texinfo_documents = [ | |
234 | ('index', 'pytest-factoryboy', u'pytest-factoryboy Documentation', | |
235 | AUTHOR, 'pytest-factoryboy', 'factory_boy integration the pytest runner.', | |
236 | 'Miscellaneous'), | |
237 | ] | |
238 | ||
239 | # Documents to append as an appendix to all manuals. | |
240 | #texinfo_appendices = [] | |
241 | ||
242 | # If false, no module index is generated. | |
243 | #texinfo_domain_indices = True | |
244 | ||
245 | # How to display URL addresses: 'footnote', 'no', or 'inline'. | |
246 | #texinfo_show_urls = 'footnote' |
0 | Welcome to pytest-factoryboy's documentation! | |
1 | ============================================= | |
2 | ||
3 | .. contents:: | |
4 | ||
5 | .. include:: ../README.rst | |
6 | ||
7 | .. include:: ../AUTHORS.rst | |
8 | ||
9 | .. include:: ../CHANGES.rst |
0 | """pytest-factoryboy public API.""" | |
1 | from .fixture import register, LazyFixture | |
2 | ||
3 | __version__ = '2.0.2' | |
4 | ||
5 | ||
6 | __all__ = [ | |
7 | register.__name__, | |
8 | LazyFixture.__name__, | |
9 | ] |
0 | """Factory boy fixture integration.""" | |
1 | ||
2 | import sys | |
3 | import inspect | |
4 | ||
5 | import factory | |
6 | import factory.builder | |
7 | import factory.declarations | |
8 | import factory.enums | |
9 | import inflection | |
10 | import pytest | |
11 | ||
12 | ||
13 | SEPARATOR = "__" | |
14 | ||
15 | ||
16 | FIXTURE_FUNC_FORMAT = """ | |
17 | def {name}({deps}): | |
18 | return _fixture_impl(request, **kwargs) | |
19 | """ | |
20 | ||
21 | ||
22 | def make_fixture(name, module, func, args=None, related=None, **kwargs): | |
23 | """Make fixture function and inject arguments. | |
24 | ||
25 | :param name: Fixture name. | |
26 | :param module: Python module to contribute the fixture into. | |
27 | :param func: Fixture implementation function. | |
28 | :param args: Argument names. | |
29 | """ | |
30 | args = [] if args is None else list(args) | |
31 | if "request" not in args: | |
32 | args.insert(0, "request") | |
33 | deps = ", ".join(args) | |
34 | context = dict(_fixture_impl=func, kwargs=kwargs) | |
35 | context.update(kwargs) | |
36 | exec(FIXTURE_FUNC_FORMAT.format(name=name, deps=deps), context) | |
37 | fixture_func = context[name] | |
38 | fixture_func.__module__ = module.__name__ | |
39 | ||
40 | if related: | |
41 | fixture_func._factoryboy_related = related | |
42 | ||
43 | fixture = pytest.fixture(fixture_func) | |
44 | setattr(module, name, fixture) | |
45 | return fixture | |
46 | ||
47 | ||
48 | def register(factory_class, _name=None, **kwargs): | |
49 | r"""Register fixtures for the factory class. | |
50 | ||
51 | :param factory_class: Factory class to register. | |
52 | :param _name: Name of the model fixture. By default is lowercase-underscored model name. | |
53 | :param \**kwargs: Optional keyword arguments that override factory attributes. | |
54 | """ | |
55 | assert not factory_class._meta.abstract, "Can't register abstract factories." | |
56 | assert factory_class._meta.model is not None, "Factory model class is not specified." | |
57 | ||
58 | module = get_caller_module() | |
59 | model_name = get_model_name(factory_class) if _name is None else _name | |
60 | factory_name = get_factory_name(factory_class) | |
61 | ||
62 | deps = get_deps(factory_class, model_name=model_name) | |
63 | related = [] | |
64 | ||
65 | for attr, value in factory_class._meta.declarations.items(): | |
66 | args = None | |
67 | attr_name = SEPARATOR.join((model_name, attr)) | |
68 | ||
69 | if isinstance(value, factory.declarations.PostGeneration): | |
70 | value = kwargs.get(attr, None) | |
71 | if isinstance(value, LazyFixture): | |
72 | args = value.args | |
73 | ||
74 | make_fixture( | |
75 | name=attr_name, | |
76 | module=module, | |
77 | func=attr_fixture, | |
78 | value=value, | |
79 | args=args, | |
80 | ) | |
81 | else: | |
82 | value = kwargs.get(attr, value) | |
83 | ||
84 | if isinstance(value, (factory.SubFactory, factory.RelatedFactory)): | |
85 | subfactory_class = value.get_factory() | |
86 | subfactory_deps = get_deps(subfactory_class, factory_class) | |
87 | ||
88 | args = list(subfactory_deps) | |
89 | if isinstance(value, factory.RelatedFactory): | |
90 | related_model = get_model_name(subfactory_class) | |
91 | args.append(related_model) | |
92 | related.append(related_model) | |
93 | related.append(attr_name) | |
94 | related.extend(subfactory_deps) | |
95 | ||
96 | if isinstance(value, factory.SubFactory): | |
97 | args.append(inflection.underscore(subfactory_class._meta.model.__name__)) | |
98 | ||
99 | make_fixture( | |
100 | name=attr_name, | |
101 | module=module, | |
102 | func=subfactory_fixture, | |
103 | args=args, | |
104 | factory_class=subfactory_class, | |
105 | ) | |
106 | else: | |
107 | if isinstance(value, LazyFixture): | |
108 | args = value.args | |
109 | ||
110 | make_fixture( | |
111 | name=attr_name, | |
112 | module=module, | |
113 | func=attr_fixture, | |
114 | value=value, | |
115 | args=args, | |
116 | ) | |
117 | ||
118 | if not hasattr(module, factory_name): | |
119 | make_fixture( | |
120 | name=factory_name, | |
121 | module=module, | |
122 | func=factory_fixture, | |
123 | factory_class=factory_class, | |
124 | ) | |
125 | ||
126 | make_fixture( | |
127 | name=model_name, | |
128 | module=module, | |
129 | func=model_fixture, | |
130 | args=deps, | |
131 | factory_name=factory_name, | |
132 | related=related, | |
133 | ) | |
134 | return factory_class | |
135 | ||
136 | ||
137 | def get_model_name(factory_class): | |
138 | """Get model fixture name by factory.""" | |
139 | return ( | |
140 | inflection.underscore(factory_class._meta.model.__name__) | |
141 | if not isinstance(factory_class._meta.model, str) else factory_class._meta.model) | |
142 | ||
143 | ||
144 | def get_factory_name(factory_class): | |
145 | """Get factory fixture name by factory.""" | |
146 | return inflection.underscore(factory_class.__name__) | |
147 | ||
148 | ||
149 | def get_deps(factory_class, parent_factory_class=None, model_name=None): | |
150 | """Get factory dependencies. | |
151 | ||
152 | :return: List of the fixture argument names for dependency injection. | |
153 | """ | |
154 | model_name = get_model_name(factory_class) if model_name is None else model_name | |
155 | parent_model_name = get_model_name(parent_factory_class) if parent_factory_class is not None else None | |
156 | ||
157 | def is_dep(value): | |
158 | if isinstance(value, factory.RelatedFactory): | |
159 | return False | |
160 | if isinstance(value, factory.SubFactory) and get_model_name(value.get_factory()) == parent_model_name: | |
161 | return False | |
162 | if isinstance(value, factory.declarations.PostGeneration): | |
163 | # Dependency on extracted value | |
164 | return True | |
165 | ||
166 | return True | |
167 | ||
168 | return [ | |
169 | SEPARATOR.join((model_name, attr)) | |
170 | for attr, value in factory_class._meta.declarations.items() | |
171 | if is_dep(value) | |
172 | ] | |
173 | ||
174 | ||
175 | def evaluate(request, value): | |
176 | """Evaluate the declaration (lazy fixtures, etc).""" | |
177 | return value.evaluate(request) if isinstance(value, LazyFixture) else value | |
178 | ||
179 | ||
180 | def model_fixture(request, factory_name): | |
181 | """Model fixture implementation.""" | |
182 | factoryboy_request = request.getfixturevalue("factoryboy_request") | |
183 | ||
184 | # Try to evaluate as much post-generation dependencies as possible | |
185 | factoryboy_request.evaluate(request) | |
186 | ||
187 | factory_class = request.getfixturevalue(factory_name) | |
188 | prefix = "".join((request.fixturename, SEPARATOR)) | |
189 | ||
190 | # Create model fixture instance | |
191 | ||
192 | class Factory(factory_class): | |
193 | pass | |
194 | ||
195 | Factory._meta.base_declarations = dict( | |
196 | (k, v) for k, v in Factory._meta.base_declarations.items() | |
197 | if not isinstance(v, factory.declarations.PostGenerationDeclaration) | |
198 | ) | |
199 | Factory._meta.post_declarations = factory.builder.DeclarationSet() | |
200 | ||
201 | kwargs = {} | |
202 | for key in factory_class._meta.pre_declarations: | |
203 | argname = "".join((prefix, key)) | |
204 | if argname in request._fixturedef.argnames: | |
205 | kwargs[key] = evaluate(request, request.getfixturevalue(argname)) | |
206 | ||
207 | strategy = factory.enums.CREATE_STRATEGY | |
208 | builder = factory.builder.StepBuilder(Factory._meta, kwargs, strategy) | |
209 | step = factory.builder.BuildStep(builder=builder, sequence=Factory._meta.next_sequence()) | |
210 | ||
211 | instance = Factory(**kwargs) | |
212 | ||
213 | # Cache the instance value on pytest level so that the fixture can be resolved before the return | |
214 | request._fixturedef.cached_result = (instance, 0, None) | |
215 | request._fixture_defs[request.fixturename] = request._fixturedef | |
216 | ||
217 | # Defer post-generation declarations | |
218 | deferred = [] | |
219 | ||
220 | for attr in factory_class._meta.post_declarations.sorted(): | |
221 | ||
222 | decl = factory_class._meta.post_declarations.declarations[attr] | |
223 | ||
224 | if isinstance(decl, factory.RelatedFactory): | |
225 | deferred.append(make_deferred_related(factory_class, request.fixturename, attr)) | |
226 | else: | |
227 | argname = "".join((prefix, attr)) | |
228 | extra = {} | |
229 | for k, v in factory_class._meta.post_declarations.contexts[attr].items(): | |
230 | if k == '': | |
231 | continue | |
232 | post_attr = SEPARATOR.join((argname, k)) | |
233 | ||
234 | if post_attr in request._fixturedef.argnames: | |
235 | extra[k] = evaluate(request, request.getfixturevalue(post_attr)) | |
236 | else: | |
237 | extra[k] = v | |
238 | ||
239 | postgen_context = factory.builder.PostGenerationContext( | |
240 | value_provided=True, | |
241 | value=evaluate(request, request.getfixturevalue(argname)), | |
242 | extra=extra, | |
243 | ) | |
244 | deferred.append( | |
245 | make_deferred_postgen(step, factory_class, request.fixturename, instance, attr, decl, postgen_context) | |
246 | ) | |
247 | factoryboy_request.defer(deferred) | |
248 | ||
249 | # Try to evaluate as much post-generation dependencies as possible | |
250 | factoryboy_request.evaluate(request) | |
251 | return instance | |
252 | ||
253 | ||
254 | def make_deferred_related(factory, fixture, attr): | |
255 | """Make deferred function for the related factory declaration. | |
256 | ||
257 | :param factory: Factory class. | |
258 | :param fixture: Object fixture name e.g. "book". | |
259 | :param attr: Declaration attribute name e.g. "publications". | |
260 | ||
261 | :note: Deferred function name results in "book__publication". | |
262 | """ | |
263 | name = SEPARATOR.join((fixture, attr)) | |
264 | ||
265 | def deferred(request): | |
266 | request.getfixturevalue(name) | |
267 | ||
268 | deferred.__name__ = name | |
269 | deferred._factory = factory | |
270 | deferred._fixture = fixture | |
271 | deferred._is_related = True | |
272 | return deferred | |
273 | ||
274 | ||
275 | def make_deferred_postgen(step, factory_class, fixture, instance, attr, declaration, context): | |
276 | """Make deferred function for the post-generation declaration. | |
277 | ||
278 | :param step: factory_boy builder step. | |
279 | :param factory_class: Factory class. | |
280 | :param fixture: Object fixture name e.g. "author". | |
281 | :param instance: Parent object instance. | |
282 | :param attr: Declaration attribute name e.g. "register_user". | |
283 | :param context: Post-generation declaration context. | |
284 | ||
285 | :note: Deferred function name results in "author__register_user". | |
286 | """ | |
287 | name = SEPARATOR.join((fixture, attr)) | |
288 | ||
289 | def deferred(request): | |
290 | declaration.call(instance, step, context) | |
291 | ||
292 | deferred.__name__ = name | |
293 | deferred._factory = factory_class | |
294 | deferred._fixture = fixture | |
295 | deferred._is_related = False | |
296 | return deferred | |
297 | ||
298 | ||
299 | def factory_fixture(request, factory_class): | |
300 | """Factory fixture implementation.""" | |
301 | return factory_class | |
302 | ||
303 | ||
304 | def attr_fixture(request, value): | |
305 | """Attribute fixture implementation.""" | |
306 | return value | |
307 | ||
308 | ||
309 | def subfactory_fixture(request, factory_class): | |
310 | """SubFactory/RelatedFactory fixture implementation.""" | |
311 | fixture = inflection.underscore(factory_class._meta.model.__name__) | |
312 | return request.getfixturevalue(fixture) | |
313 | ||
314 | ||
315 | def get_caller_module(depth=2): | |
316 | """Get the module of the caller.""" | |
317 | frame = sys._getframe(depth) | |
318 | module = inspect.getmodule(frame) | |
319 | # Happens when there's no __init__.py in the folder | |
320 | if module is None: | |
321 | return get_caller_module(depth=depth) # pragma: no cover | |
322 | return module | |
323 | ||
324 | ||
325 | class LazyFixture(object): | |
326 | """Lazy fixture.""" | |
327 | ||
328 | def __init__(self, fixture): | |
329 | """Lazy pytest fixture wrapper. | |
330 | ||
331 | :param fixture: Fixture name or callable with dependencies. | |
332 | """ | |
333 | self.fixture = fixture | |
334 | if callable(self.fixture): | |
335 | self.args = list(inspect.getargspec(self.fixture).args) | |
336 | else: | |
337 | self.args = [self.fixture] | |
338 | ||
339 | def evaluate(self, request): | |
340 | """Evaluate the lazy fixture. | |
341 | ||
342 | :param request: pytest request object. | |
343 | :return: evaluated fixture. | |
344 | """ | |
345 | if callable(self.fixture): | |
346 | kwargs = dict((arg, request.getfixturevalue(arg)) for arg in self.args) | |
347 | return self.fixture(**kwargs) | |
348 | else: | |
349 | return request.getfixturevalue(self.fixture) |
0 | """pytest-factoryboy pytest hooks.""" | |
1 | ||
2 | ||
3 | def pytest_factoryboy_done(request): | |
4 | """Called after all factory based fixtures and their post-generation actions were evaluated.""" |
0 | """pytest-factoryboy plugin.""" | |
1 | ||
2 | from collections import defaultdict | |
3 | import pytest | |
4 | ||
5 | ||
6 | class CycleDetected(Exception): | |
7 | pass | |
8 | ||
9 | ||
10 | class Request(object): | |
11 | """PyTest FactoryBoy request.""" | |
12 | ||
13 | def __init__(self): | |
14 | """Create pytest_factoryboy request.""" | |
15 | self.deferred = [] | |
16 | self.results = defaultdict(dict) | |
17 | self.model_factories = {} | |
18 | self.in_progress = set() | |
19 | ||
20 | def defer(self, functions): | |
21 | """Defer post-generation declaration execution until the end of the test setup. | |
22 | ||
23 | :param functions: Functions to be deferred. | |
24 | :note: Once already finalized all following defer calls will execute the function directly. | |
25 | """ | |
26 | self.deferred.append(functions) | |
27 | ||
28 | def get_deps(self, request, fixture, deps=None): | |
29 | request = request.getfixturevalue('request') | |
30 | ||
31 | if deps is None: | |
32 | deps = set([fixture]) | |
33 | if fixture == 'request': | |
34 | return deps | |
35 | ||
36 | for fixturedef in request._fixturemanager.getfixturedefs(fixture, request._pyfuncitem.parent.nodeid) or []: | |
37 | for argname in fixturedef.argnames: | |
38 | if argname not in deps: | |
39 | deps.add(argname) | |
40 | deps.update(self.get_deps(request, argname, deps)) | |
41 | return deps | |
42 | ||
43 | def get_current_deps(self, request): | |
44 | deps = set() | |
45 | while hasattr(request, '_parent_request'): | |
46 | if request.fixturename and request.fixturename not in getattr(request, "_fixturedefs", {}): | |
47 | deps.add(request.fixturename) | |
48 | request = request._parent_request | |
49 | return deps | |
50 | ||
51 | def execute(self, request, function, deferred): | |
52 | """"Execute deferred function and store the result.""" | |
53 | if function in self.in_progress: | |
54 | raise CycleDetected() | |
55 | fixture = function.__name__ | |
56 | model, attr = fixture.split("__", 1) | |
57 | if function._is_related: | |
58 | deps = self.get_deps(request, fixture) | |
59 | if deps.intersection(self.get_current_deps(request)): | |
60 | raise CycleDetected() | |
61 | self.model_factories[model] = function._factory | |
62 | ||
63 | self.in_progress.add(function) | |
64 | self.results[model][attr] = function(request) | |
65 | deferred.remove(function) | |
66 | self.in_progress.remove(function) | |
67 | ||
68 | def after_postgeneration(self, request): | |
69 | """Call _after_postgeneration hooks.""" | |
70 | for model in list(self.results.keys()): | |
71 | results = self.results.pop(model) | |
72 | obj = request.getfixturevalue(model) | |
73 | factory = self.model_factories[model] | |
74 | factory._after_postgeneration(obj, create=True, results=results) | |
75 | ||
76 | def evaluate(self, request): | |
77 | """Finalize, run deferred post-generation actions, etc.""" | |
78 | while self.deferred: | |
79 | try: | |
80 | deferred = self.deferred[-1] | |
81 | for function in list(deferred): | |
82 | self.execute(request, function, deferred) | |
83 | if not deferred: | |
84 | self.deferred.remove(deferred) | |
85 | except CycleDetected: | |
86 | return | |
87 | ||
88 | if not self.deferred: | |
89 | self.after_postgeneration(request) | |
90 | ||
91 | ||
92 | @pytest.fixture | |
93 | def factoryboy_request(): | |
94 | """PyTest FactoryBoy request fixture.""" | |
95 | return Request() | |
96 | ||
97 | ||
98 | @pytest.mark.tryfirst | |
99 | def pytest_runtest_call(item): | |
100 | """Before the test item is called.""" | |
101 | try: | |
102 | request = item._request | |
103 | except AttributeError: | |
104 | # pytest-pep8 plugin passes Pep8Item here during tests. | |
105 | return | |
106 | factoryboy_request = request.getfixturevalue("factoryboy_request") | |
107 | factoryboy_request.evaluate(request) | |
108 | assert not factoryboy_request.deferred | |
109 | request.config.hook.pytest_factoryboy_done(request=request) | |
110 | ||
111 | ||
112 | def pytest_addhooks(pluginmanager): | |
113 | """Register plugin hooks.""" | |
114 | from pytest_factoryboy import hooks | |
115 | # addhooks is for older py.test and deprecated; replaced by add_hookspecs | |
116 | add_hookspecs = getattr(pluginmanager, 'add_hookspecs', pluginmanager.addhooks) | |
117 | add_hookspecs(hooks) | |
118 | ||
119 | ||
120 | def pytest_generate_tests(metafunc): | |
121 | related = [] | |
122 | for arg2fixturedef in metafunc._arg2fixturedefs.values(): | |
123 | fixturedef = arg2fixturedef[-1] | |
124 | related.extend(getattr(fixturedef.func, "_factoryboy_related", [])) | |
125 | ||
126 | metafunc.funcargnames.extend(related) |
0 | #!/usr/bin/env python | |
1 | """pytest-factoryboy package config.""" | |
2 | ||
3 | import codecs | |
4 | import os | |
5 | import re | |
6 | from setuptools import setup | |
7 | ||
8 | dirname = os.path.dirname(__file__) | |
9 | ||
10 | long_description = ( | |
11 | codecs.open(os.path.join(dirname, "README.rst"), encoding="utf-8").read() + "\n" + | |
12 | codecs.open(os.path.join(dirname, "AUTHORS.rst"), encoding="utf-8").read() + "\n" + | |
13 | codecs.open(os.path.join(dirname, "CHANGES.rst"), encoding="utf-8").read() | |
14 | ) | |
15 | ||
16 | with codecs.open(os.path.join(dirname, 'pytest_factoryboy', '__init__.py'), encoding='utf-8') as fd: | |
17 | VERSION = re.compile(r".*__version__ = '(.*?)'", re.S).match(fd.read()).group(1) | |
18 | ||
19 | setup( | |
20 | name="pytest-factoryboy", | |
21 | description="Factory Boy support for pytest.", | |
22 | long_description=long_description, | |
23 | author="Oleg Pidsadnyi, Anatoly Bubenkov and others", | |
24 | license="MIT license", | |
25 | author_email="[email protected]", | |
26 | url="https://github.com/pytest-dev/pytest-factoryboy", | |
27 | version=VERSION, | |
28 | classifiers=[ | |
29 | "Development Status :: 6 - Mature", | |
30 | "Intended Audience :: Developers", | |
31 | "License :: OSI Approved :: MIT License", | |
32 | "Operating System :: POSIX", | |
33 | "Operating System :: Microsoft :: Windows", | |
34 | "Operating System :: MacOS :: MacOS X", | |
35 | "Topic :: Software Development :: Testing", | |
36 | "Topic :: Software Development :: Libraries", | |
37 | "Topic :: Utilities", | |
38 | "Programming Language :: Python :: 2", | |
39 | "Programming Language :: Python :: 3" | |
40 | ] + [("Programming Language :: Python :: %s" % x) for x in "2.7 3.0 3.1 3.2 3.3 3.4 3.5".split()], | |
41 | install_requires=[ | |
42 | "inflection", | |
43 | "factory_boy>=2.10.0", | |
44 | "pytest>=3.3.2", | |
45 | ], | |
46 | # the following makes a plugin available to py.test | |
47 | entry_points={ | |
48 | "pytest11": [ | |
49 | "pytest-factoryboy = pytest_factoryboy.plugin", | |
50 | ], | |
51 | }, | |
52 | tests_require=["tox"], | |
53 | packages=["pytest_factoryboy"], | |
54 | include_package_data=True, | |
55 | ) |
0 | from pytest_factoryboy import register | |
1 | import factory | |
2 | ||
3 | import pytest | |
4 | ||
5 | ||
6 | class EmptyModel(object): | |
7 | pass | |
8 | ||
9 | ||
10 | class AttributesFactory(factory.Factory): | |
11 | class Meta: | |
12 | model = EmptyModel | |
13 | ||
14 | attributes = None | |
15 | ||
16 | ||
17 | register(AttributesFactory, "with_attributes") | |
18 | ||
19 | ||
20 | @pytest.mark.skip(reason="Doesn't work in FactoryBoy at the moment") | |
21 | def test_factory_with_attributes(): | |
22 | """Test that a factory can have a `attributes` field when used as a factory.""" | |
23 | AttributesFactory() | |
24 | ||
25 | ||
26 | @pytest.mark.skip(reason="Doesn't work in FactoryBoy at the moment") | |
27 | def test_factory_fixture_with_attributes(with_attributes): | |
28 | """Test that a factory can have a `attributes` field when used as a fixture.""" | |
29 | pass |
0 | """Test circular definitions.""" | |
1 | ||
2 | import factory | |
3 | ||
4 | from pytest_factoryboy import register | |
5 | ||
6 | ||
7 | class Book(object): | |
8 | ||
9 | """Book model.""" | |
10 | ||
11 | def __init__(self, name=None, price=None, author=None): | |
12 | self.editions = [] | |
13 | self.name = name | |
14 | self.price = price | |
15 | self.author = author | |
16 | self.author.books.append(self) | |
17 | ||
18 | ||
19 | class Author(object): | |
20 | ||
21 | """Author model.""" | |
22 | ||
23 | def __init__(self, name): | |
24 | self.books = [] | |
25 | self.name = name | |
26 | self.user = None | |
27 | ||
28 | ||
29 | class AuthorFactory(factory.Factory): | |
30 | ||
31 | class Meta: | |
32 | model = Author | |
33 | ||
34 | name = "Charles Dickens" | |
35 | ||
36 | book = factory.RelatedFactory('tests.test_circular.BookFactory', 'author') | |
37 | ||
38 | ||
39 | class BookFactory(factory.Factory): | |
40 | ||
41 | class Meta: | |
42 | model = Book | |
43 | ||
44 | name = "Alice in Wonderland" | |
45 | price = factory.LazyAttribute(lambda f: 3.99) | |
46 | author = factory.SubFactory(AuthorFactory) | |
47 | ||
48 | ||
49 | register(AuthorFactory) | |
50 | register(BookFactory) | |
51 | ||
52 | ||
53 | def test_circular(author, factoryboy_request, request): | |
54 | assert author.books |
0 | """Factory fixtures tests.""" | |
1 | ||
2 | import factory | |
3 | from factory import fuzzy | |
4 | import pytest | |
5 | ||
6 | from pytest_factoryboy import register, LazyFixture | |
7 | ||
8 | ||
9 | class User(object): | |
10 | """User account.""" | |
11 | ||
12 | def __init__(self, username, password, is_active): | |
13 | self.username = username | |
14 | self.password = password | |
15 | self.is_active = is_active | |
16 | ||
17 | ||
18 | class Book(object): | |
19 | """Book model.""" | |
20 | ||
21 | def __init__(self, name=None, price=None, author=None): | |
22 | self.editions = [] | |
23 | self.name = name | |
24 | self.price = price | |
25 | self.author = author | |
26 | ||
27 | ||
28 | class Author(object): | |
29 | """Author model.""" | |
30 | ||
31 | def __init__(self, name): | |
32 | self.name = name | |
33 | self.user = None | |
34 | ||
35 | ||
36 | class Edition(object): | |
37 | """Book edition.""" | |
38 | ||
39 | def __init__(self, book, year): | |
40 | self.book = book | |
41 | self.year = year | |
42 | book.editions.append(self) | |
43 | ||
44 | ||
45 | class UserFactory(factory.Factory): | |
46 | """User factory.""" | |
47 | ||
48 | class Meta: | |
49 | model = User | |
50 | ||
51 | password = fuzzy.FuzzyText(length=7) | |
52 | ||
53 | ||
54 | @register | |
55 | class AuthorFactory(factory.Factory): | |
56 | """Author factory.""" | |
57 | ||
58 | class Meta: | |
59 | model = Author | |
60 | ||
61 | name = "Charles Dickens" | |
62 | ||
63 | register_user__is_active = True # Make sure fixture is generated | |
64 | register_user__password = "qwerty" # Make sure fixture is generated | |
65 | ||
66 | @factory.post_generation | |
67 | def register_user(author, create, username, **kwargs): | |
68 | """Register author as a user in the system.""" | |
69 | if username is not None: | |
70 | author.user = UserFactory(username=username, **kwargs) | |
71 | ||
72 | ||
73 | class BookFactory(factory.Factory): | |
74 | """Test factory with all the features.""" | |
75 | ||
76 | class Meta: | |
77 | model = Book | |
78 | ||
79 | name = "Alice in Wonderland" | |
80 | price = factory.LazyAttribute(lambda f: 3.99) | |
81 | author = factory.SubFactory(AuthorFactory) | |
82 | book_edition = factory.RelatedFactory("tests.test_factory_fixtures.EditionFactory", "book") | |
83 | ||
84 | ||
85 | class EditionFactory(factory.Factory): | |
86 | """Book edition factory.""" | |
87 | ||
88 | class Meta: | |
89 | model = Edition | |
90 | ||
91 | book = factory.SubFactory(BookFactory) | |
92 | year = 1999 | |
93 | ||
94 | ||
95 | register(BookFactory) | |
96 | register(EditionFactory) | |
97 | ||
98 | ||
99 | def test_factory(book_factory): | |
100 | """Test model factory fixture.""" | |
101 | assert book_factory == BookFactory | |
102 | ||
103 | ||
104 | def test_model(book): | |
105 | """Test model fixture.""" | |
106 | assert book.name == "Alice in Wonderland" | |
107 | assert book.price == 3.99 | |
108 | assert book.author.name == "Charles Dickens" | |
109 | assert book.author.user is None | |
110 | assert book.editions[0].year == 1999 | |
111 | assert book.editions[0].book == book | |
112 | ||
113 | ||
114 | def test_attr(book__name, book__price, author__name, edition__year): | |
115 | """Test attribute fixtures. | |
116 | ||
117 | :note: Most of the attributes are lazy definitions. Use attribute fixtures in | |
118 | order to override the initial values. | |
119 | """ | |
120 | assert book__name == "Alice in Wonderland" | |
121 | assert book__price == BookFactory.price | |
122 | assert author__name == "Charles Dickens" | |
123 | assert edition__year == 1999 | |
124 | ||
125 | ||
126 | @pytest.mark.parametrize("book__name", ["PyTest for Dummies"]) | |
127 | @pytest.mark.parametrize("book__price", [1.0]) | |
128 | @pytest.mark.parametrize("author__name", ["Bill Gates"]) | |
129 | @pytest.mark.parametrize("edition__year", [2000]) | |
130 | def test_parametrized(book): | |
131 | """Test model factory fixture.""" | |
132 | assert book.name == "PyTest for Dummies" | |
133 | assert book.price == 1.0 | |
134 | assert book.author.name == "Bill Gates" | |
135 | assert len(book.editions) == 1 | |
136 | assert book.editions[0].year == 2000 | |
137 | ||
138 | ||
139 | @pytest.mark.parametrize("author__register_user", ["admin"]) | |
140 | def test_post_generation(author): | |
141 | """Test post generation declaration.""" | |
142 | assert author.user.username == "admin" | |
143 | assert author.user.is_active is True | |
144 | ||
145 | ||
146 | register(AuthorFactory, "second_author") | |
147 | ||
148 | ||
149 | @pytest.mark.parametrize("second_author__name", ["Mr. Hyde"]) | |
150 | def test_second_author(author, second_author): | |
151 | """Test factory registration with specific name.""" | |
152 | assert author != second_author | |
153 | assert second_author.name == "Mr. Hyde" | |
154 | ||
155 | ||
156 | register(AuthorFactory, "partial_author", name="John Doe", register_user=LazyFixture(lambda: "[email protected]")) | |
157 | ||
158 | ||
159 | def test_partial(partial_author): | |
160 | """Test fixture partial specialization.""" | |
161 | assert partial_author.name == "John Doe" | |
162 | assert partial_author.user.username == "[email protected]" | |
163 | ||
164 | ||
165 | register(AuthorFactory, "another_author", name=LazyFixture(lambda: "Another Author")) | |
166 | ||
167 | ||
168 | @pytest.mark.parametrize("book__author", [LazyFixture("another_author")]) | |
169 | def test_lazy_fixture_name(book, another_author): | |
170 | """Test that book author is replaced with another author by fixture name.""" | |
171 | assert book.author == another_author | |
172 | assert book.author.name == "Another Author" | |
173 | ||
174 | ||
175 | @pytest.mark.parametrize("book__author", [LazyFixture(lambda another_author: another_author)]) | |
176 | def test_lazy_fixture_callable(book, another_author): | |
177 | """Test that book author is replaced with another author by callable.""" | |
178 | assert book.author == another_author | |
179 | assert book.author.name == "Another Author" | |
180 | ||
181 | ||
182 | @pytest.mark.parametrize( | |
183 | ("author__register_user", "author__register_user__password"), | |
184 | [ | |
185 | (LazyFixture(lambda: "lazyfixture"), LazyFixture(lambda: "asdasd")), | |
186 | ] | |
187 | ) | |
188 | def test_lazy_fixture_post_generation(author): | |
189 | """Test that post-generation values are replaced with lazy fixtures.""" | |
190 | # assert author.user.username == "lazyfixture" | |
191 | assert author.user.password == "asdasd" |
0 | """Test LazyFixture related features.""" | |
1 | ||
2 | import factory | |
3 | import pytest | |
4 | ||
5 | from pytest_factoryboy import register, LazyFixture | |
6 | ||
7 | ||
8 | class User(object): | |
9 | """User account.""" | |
10 | ||
11 | def __init__(self, username, password, is_active): | |
12 | self.username = username | |
13 | self.password = password | |
14 | self.is_active = is_active | |
15 | ||
16 | ||
17 | class UserFactory(factory.Factory): | |
18 | """User factory.""" | |
19 | ||
20 | class Meta: | |
21 | model = User | |
22 | ||
23 | username = factory.faker.Faker("user_name") | |
24 | password = factory.faker.Faker("password") | |
25 | is_active = factory.LazyAttribute(lambda f: f.password == "ok") | |
26 | ||
27 | ||
28 | register(UserFactory) | |
29 | ||
30 | ||
31 | register( | |
32 | UserFactory, | |
33 | "partial_user", | |
34 | password=LazyFixture("ok_password"), | |
35 | ) | |
36 | ||
37 | ||
38 | @pytest.fixture | |
39 | def ok_password(): | |
40 | return "ok" | |
41 | ||
42 | ||
43 | @pytest.mark.parametrize("user__password", [LazyFixture("ok_password")]) | |
44 | def test_lazy_attribute(user): | |
45 | """Test LazyFixture value is extracted before the LazyAttribute is called.""" | |
46 | assert user.is_active | |
47 | ||
48 | ||
49 | def test_lazy_attribute_partial(partial_user): | |
50 | """Test LazyFixture value is extracted before the LazyAttribute is called. Partial.""" | |
51 | assert partial_user.is_active |
0 | """Test post-generation dependecies.""" | |
1 | ||
2 | import factory | |
3 | import pytest | |
4 | ||
5 | from pytest_factoryboy import register | |
6 | ||
7 | ||
8 | class Foo(object): | |
9 | ||
10 | def __init__(self, value, expected): | |
11 | self.value = value | |
12 | self.expected = expected | |
13 | ||
14 | ||
15 | class Bar(object): | |
16 | ||
17 | def __init__(self, foo): | |
18 | self.foo = foo | |
19 | ||
20 | ||
21 | @register | |
22 | class FooFactory(factory.Factory): | |
23 | ||
24 | """Foo factory.""" | |
25 | ||
26 | class Meta: | |
27 | model = Foo | |
28 | ||
29 | value = 0 | |
30 | expected = 0 | |
31 | """Value that is expected at the constructor.""" | |
32 | ||
33 | @factory.post_generation | |
34 | def set1(foo, create, value, **kwargs): | |
35 | foo.value = 1 | |
36 | ||
37 | @classmethod | |
38 | def _after_postgeneration(cls, obj, create, results=None): | |
39 | obj._postgeneration_results = results | |
40 | obj._create = create | |
41 | ||
42 | ||
43 | class BarFactory(factory.Factory): | |
44 | ||
45 | """Bar factory.""" | |
46 | ||
47 | foo = factory.SubFactory(FooFactory) | |
48 | ||
49 | @classmethod | |
50 | def _create(cls, model_class, foo): | |
51 | assert foo.value == foo.expected | |
52 | bar = super(BarFactory, cls)._create(model_class, foo=foo) | |
53 | foo.bar = bar | |
54 | return bar | |
55 | ||
56 | class Meta: | |
57 | model = Bar | |
58 | ||
59 | ||
60 | def test_postgen_invoked(foo): | |
61 | """Test that post-generation hooks are done and the value is 2.""" | |
62 | assert foo.value == 1 | |
63 | ||
64 | ||
65 | register(BarFactory) | |
66 | ||
67 | ||
68 | @pytest.mark.parametrize('foo__value', [3]) | |
69 | @pytest.mark.parametrize('foo__expected', [1]) | |
70 | def test_depends_on(bar): | |
71 | """Test that post-generation hooks are done and the value is 1.""" | |
72 | assert bar.foo.value == 1 | |
73 | ||
74 | ||
75 | def test_getfixturevalue(request, factoryboy_request): | |
76 | """Test post-generation declarations via the getfixturevalue.""" | |
77 | foo = request.getfixturevalue('foo') | |
78 | assert not factoryboy_request.deferred | |
79 | assert foo.value == 1 | |
80 | ||
81 | ||
82 | def test_after_postgeneration(foo): | |
83 | """Test _after_postgeneration is called.""" | |
84 | assert foo._postgeneration_results == {'set1': None} | |
85 | assert foo._create is True | |
86 | ||
87 | ||
88 | class Ordered(object): | |
89 | value = None | |
90 | ||
91 | ||
92 | @register | |
93 | class OrderedFactory(factory.Factory): | |
94 | ||
95 | class Meta: | |
96 | model = Ordered | |
97 | ||
98 | @factory.post_generation | |
99 | def zzz(obj, create, val, **kwargs): | |
100 | obj.value = "zzz" | |
101 | ||
102 | @factory.post_generation | |
103 | def aaa(obj, create, val, **kwargs): | |
104 | obj.value = "aaa" | |
105 | ||
106 | ||
107 | def test_ordered(ordered): | |
108 | """Test post generation are ordered by creation counter.""" | |
109 | assert ordered.value == "aaa" |
0 | """Test factory registration validation.""" | |
1 | ||
2 | import factory | |
3 | import pytest | |
4 | from pytest_factoryboy import register | |
5 | ||
6 | ||
7 | class WithoutModelFactory(factory.Factory): | |
8 | """A factory without model.""" | |
9 | ||
10 | ||
11 | class AbstractFactory(factory.Factory): | |
12 | """Abstract factory.""" | |
13 | ||
14 | class Meta: | |
15 | abstract = True | |
16 | model = dict | |
17 | ||
18 | ||
19 | def test_without_model(): | |
20 | """Test that factory without model can't be registered.""" | |
21 | with pytest.raises(AssertionError): | |
22 | register(WithoutModelFactory) | |
23 | ||
24 | ||
25 | def test_abstract(): | |
26 | with pytest.raises(AssertionError): | |
27 | register(AbstractFactory) |
0 | [tox] | |
1 | distshare = {homedir}/.tox/distshare | |
2 | envlist = py{27,34,35}-pytest{2,3} | |
3 | ||
4 | ||
5 | [testenv] | |
6 | commands = py.test --junitxml={envlogdir}/junit-{envname}.xml {posargs:tests} | |
7 | deps = -r{toxinidir}/requirements-testing.txt | |
8 | pytest2: pytest<3.0 | |
9 | pytest3: pytest>3.0 | |
10 | ||
11 | [pytest] | |
12 | addopts = -vv -l --pep8 | |
13 | pep8maxlinelength = 120 |