diff --git a/CHANGES.rst b/CHANGES.rst index 095d3988..3f3e1ae4 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,12 @@ Changelog ========= +1.0.2 +----- + +- refactoring of the fixture function compilation (olegpidsadnyi) +- related factory fix (olegpidsadnyi) + 1.0.1 ----- @@ -9,4 +15,4 @@ Changelog 1.0.0 ----- -- initial release +- initial release (olegpidsadnyi) diff --git a/pytest_factoryboy/__init__.py b/pytest_factoryboy/__init__.py index fa55ea44..3157d7a4 100644 --- a/pytest_factoryboy/__init__.py +++ b/pytest_factoryboy/__init__.py @@ -1,6 +1,6 @@ """pytest-factoryboy public API.""" -__version__ = '1.0.1' +__version__ = '1.0.2' try: from .fixture import register diff --git a/pytest_factoryboy/fixture.py b/pytest_factoryboy/fixture.py index 90b2314b..b4da8f33 100644 --- a/pytest_factoryboy/fixture.py +++ b/pytest_factoryboy/fixture.py @@ -1,17 +1,44 @@ """Factory boy fixture integration.""" + import sys import inspect -from types import CodeType import factory import inflection import pytest -import six SEPARATOR = "__" +FIXTURE_FUNC_FORMAT = """ +def {name}({deps}): + return _fixture_impl(request, **kwargs) +""" + + +def make_fixture(name, module, func, args=None, **kwargs): + """Make fixture function and inject arguments. + + :param name: Fixture name. + :param module: Python module to contribute the fixture into. + :param func: Fixture implementation function. + :param args: Argument names. + """ + args = [] if args is None else list(args) + if "request" not in args: + args.insert(0, "request") + deps = ", ".join(args) + context = dict(_fixture_impl=func, kwargs=kwargs) + context.update(kwargs) + exec(FIXTURE_FUNC_FORMAT.format(name=name, deps=deps), context) + fixture_func = context[name] + fixture_func.__module__ = module + fixture = pytest.fixture(fixture_func) + setattr(module, name, fixture) + return fixture + + def register(factory_class): """Register fixtures for the factory class.""" module = get_caller_module() @@ -20,23 +47,40 @@ def register(factory_class): deps = get_deps(factory_class) for attr, value in factory_class.declarations(factory_class._meta.postgen_declarations).items(): - fixture = None attr_name = SEPARATOR.join((model_name, attr)) - add_args = [] + if isinstance(value, (factory.SubFactory, factory.RelatedFactory)): subfactory_class = value.get_factory() - fixture = make_subfactory_fixture(subfactory_class) - add_args = get_deps(subfactory_class, factory_class) + make_fixture( + name=attr_name, + module=module, + func=subfactory_fixture, + args=get_deps(subfactory_class, factory_class), + factory_class=subfactory_class, + ) if isinstance(value, factory.RelatedFactory): deps.extend(get_deps(subfactory_class, factory_class)) else: - fixture = make_attr_fixture(value) - - if fixture is not None: - contribute_to_module(module, attr_name, fixture, add_args=add_args) - - contribute_to_module(module, factory_name, make_factory_fixture(factory_class)) - contribute_to_module(module, model_name, make_model_fixture(factory_name), add_args=deps) + make_fixture( + name=attr_name, + module=module, + func=attr_fixture, + value=value, + ) + make_fixture( + name=factory_name, + module=module, + func=factory_fixture, + factory_class=factory_class, + ) + + make_fixture( + name=model_name, + module=module, + func=model_fixture, + args=deps, + factory_name=factory_name, + ) return factory_class @@ -58,129 +102,49 @@ def get_deps(factory_class, parent_factory_class=None): ] -def make_model_fixture(factory_name): - @pytest.fixture - def model_fixture(request): - factory_class = request.getfuncargvalue(factory_name) - prefix = "".join((request.fixturename, SEPARATOR)) - data = {} +def model_fixture(request, factory_name): + factory_class = request.getfuncargvalue(factory_name) + prefix = "".join((request.fixturename, SEPARATOR)) + data = {} - for argname in request._fixturedef.argnames: - if argname.startswith(prefix): - data[argname[len(prefix):]] = request.getfuncargvalue(argname) + for argname in request._fixturedef.argnames: + if argname.startswith(prefix): + data[argname[len(prefix):]] = request.getfuncargvalue(argname) - class Factory(factory_class): - pass + class Factory(factory_class): + pass - related_factories = [] - for attr, value in Factory._meta.postgen_declarations.items(): - if isinstance(value, factory.RelatedFactory): - related_factories.append(value) - Factory._meta.postgen_declarations.pop(attr) + related_factories = [] + for attr, value in dict(Factory._meta.postgen_declarations).items(): + if isinstance(value, factory.RelatedFactory): + related_factories.append(value) + Factory._meta.postgen_declarations.pop(attr) - result = Factory(**data) + result = Factory(**data) - if related_factories: - request._fixturedef.cached_result = (result, 0, None) - request._fixturedefs[request.fixturename] = request._fixturedef + if related_factories: + request._fixturedef.cached_result = (result, 0, None) + request._fixturedefs[request.fixturename] = request._fixturedef - for related_factory in related_factories: - related_factory_class = related_factory.get_factory() - model_name = get_model_name(related_factory_class) - request.getfuncargvalue(model_name) - return result + for related_factory in related_factories: + related_factory_class = related_factory.get_factory() + model_name = get_model_name(related_factory_class) + request.getfuncargvalue(prefix + model_name) - return model_fixture + return result -def make_factory_fixture(factory_class): - @pytest.fixture - def factory_fixture(): - return factory_class - return factory_fixture +def factory_fixture(request, factory_class): + return factory_class -def make_attr_fixture(value): - @pytest.fixture - def attr_fixture(): - return value - return attr_fixture +def attr_fixture(request, value): + return value -def make_subfactory_fixture(factory_class): +def subfactory_fixture(request, factory_class): fixture = inflection.underscore(factory_class._meta.model.__name__) - - @pytest.fixture - def subfactory_fixture(request): - return request.getfuncargvalue(fixture) - return subfactory_fixture - - -def recreate_function(func, module=None, name=None, add_args=[], firstlineno=None): - """Recreate a function, replacing some info. - - :param func: Function object. - :param module: Module to contribute to. - :param add_args: Additional arguments to add to the function. - - :return: Function copy. - """ - def get_code(func): - return func.__code__ if six.PY3 else func.func_code - - def set_code(func, code): - if six.PY3: - func.__code__ = code - else: - func.func_code = code - - argnames = [ - "co_argcount", "co_nlocals", "co_stacksize", "co_flags", "co_code", "co_consts", "co_names", - "co_varnames", "co_filename", "co_name", "co_firstlineno", "co_lnotab", "co_freevars", "co_cellvars", - ] - if six.PY3: - argnames.insert(1, "co_kwonlyargcount") - - for arg in inspect.getargspec(func).args: - if arg in add_args: - add_args.remove(arg) - - args = [] - code = get_code(func) - for arg in argnames: - if module is not None and arg == "co_filename": - args.append(module.__file__) - elif name is not None and arg == "co_name": - args.append(name) - elif arg == "co_argcount": - args.append(getattr(code, arg) + len(add_args)) - elif arg == "co_varnames": - co_varnames = getattr(code, arg) - args.append(co_varnames[:code.co_argcount] + tuple(add_args) + co_varnames[code.co_argcount:]) - elif arg == "co_firstlineno": - args.append(firstlineno if firstlineno else 0) - else: - args.append(getattr(code, arg)) - - set_code(func, CodeType(*args)) - if name is not None: - func.__name__ = name - return func - - -def contribute_to_module(module, name, func, add_args=[]): - """Contribute a function to a module. - - :param module: Module to contribute to. - :param name: Attribute name. - :param func: Function object. - :param add_args: Additional arguments to add to the function. - - :return: New function copy contributed to the module - """ - func = recreate_function(func, module=module, add_args=add_args, name=name) - setattr(module, name, func) - return func + return request.getfuncargvalue(fixture) def get_caller_module(depth=2): diff --git a/setup.py b/setup.py index 4ac20ae0..cf8cc342 100755 --- a/setup.py +++ b/setup.py @@ -39,7 +39,6 @@ "Programming Language :: Python :: 3" ] + [("Programming Language :: Python :: %s" % x) for x in "2.6 2.7 3.0 3.1 3.2 3.3 3.4".split()], install_requires=[ - "six", "inflection", "factory_boy", ], diff --git a/tests/test_factory_fixtures.py b/tests/test_factory_fixtures.py index 7255012a..0765dc66 100644 --- a/tests/test_factory_fixtures.py +++ b/tests/test_factory_fixtures.py @@ -9,9 +9,8 @@ class Book(object): """Book model.""" - editions = [] - def __init__(self, name=None, price=None, author=None): + self.editions = [] self.name = name self.price = price self.author = author @@ -79,7 +78,6 @@ def test_factory(book_factory): assert book_factory == BookFactory -@pytest.mark.xfail def test_model(book): """Test model fixture.""" assert book.name == "Alice in Wonderland" @@ -105,7 +103,6 @@ def test_attr(book__name, book__price, author__name, edition__year): @pytest.mark.parametrize("book__price", [1.0]) @pytest.mark.parametrize("author__name", ["Bill Gates"]) @pytest.mark.parametrize("edition__year", [2000]) -@pytest.mark.xfail def test_parametrized(book): """Test model factory fixture.""" assert book.name == "PyTest for Dummies"