Pytest-django: Pytest appears to ignore settings that extend other settings files

Created on 25 Sep 2015  路  9Comments  路  Source: pytest-dev/pytest-django

I have the following project structure:

myapp/settings/__init__.py
myapp/settings/base.py
myapp/settings/test.py

Inside base.py I have a setting called REST_FRAMEWORK from the Django REST Framework project. test.py does from myapp.settings.base import * at the top and then adds some database settings of its own. Now, for some reason pytest completely ignores the REST_FRAMEWORK setting even though the test.py file is loaded and I can print the contents of REST_FRAMEWORK inside it. Django works as expected.

If I manually copy the REST_FRAMEWORK setting to the test.py file instead of relying on extending then my tests pass. Any ideas? This seems like such a simple case that I must be missing something but I can't track it down.

pytest 2.7.2 and pytest-django 2.8.0

Most helpful comment

i discovered that the DJANGO_SETTINGS_MODULE value in pytest.ini does not override a DJANGO_SETTINGS_MODULE environment variable. i also have a settings file for testing which is derived from another settings file- pytest is using the base settings file since that's the value in the environment variable.

All 9 comments

Are you sure DJANGO_SETTINGS_MODULE is set properly?

Here's my pytest.ini:

[pytest]
DJANGO_SETTINGS_MODULE=myapp.settings.test
norecursedirs=venv .git build

and I've also set it on the command line with --ds=myapp.settings.test to no avail

Ok, strange.
Can you add import pdb; pdb.set_trace() before the import of base and then see where it is being called from (bt), and then step into it to see what is going on.

Here's where it comes from:

$ py.test -vxsk test_public_api
> ~/projects/myapp/settings/test.py(2)<module>()
      1 import ipdb;ipdb.set_trace()
----> 2 from myapp.settings.base import *   # pylint: disable=W0614,W0401
      3

ipdb> bt
  ~/.virtualenvs/myapp/bin/py.test(11)<module>()
      7 from pytest import main
      8
      9 if __name__ == '__main__':
     10     sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
---> 11     sys.exit(main())

  ~/.virtualenvs/myapp/local/lib/python2.7/site-packages/_pytest/config.py(32)main()
     30     """
     31     try:
---> 32         config = _prepareconfig(args, plugins)
     33     except ConftestImportFailure:
     34         e = sys.exc_info()[1]

  ~/.virtualenvs/myapp/local/lib/python2.7/site-packages/_pytest/config.py(85)_prepareconfig()
     83                 pluginmanager.register(plugin)
     84         return pluginmanager.hook.pytest_cmdline_parse(
---> 85                 pluginmanager=pluginmanager, args=args)
     86     except Exception:
     87         pluginmanager.ensure_shutdown()

  ~/.virtualenvs/myapp/local/lib/python2.7/site-packages/_pytest/core.py(521)__call__()
    519
    520     def __call__(self, **kwargs):
--> 521         return self._docall(self.methods, kwargs)
    522
    523     def callextra(self, methods, **kwargs):

  ~/.virtualenvs/myapp/local/lib/python2.7/site-packages/_pytest/core.py(528)_docall()
    526     def _docall(self, methods, kwargs):
    527         return MultiCall(methods, kwargs,
--> 528                          firstresult=self.firstresult).execute()
    529
    530

  ~/.virtualenvs/myapp/local/lib/python2.7/site-packages/_pytest/core.py(393)execute()
    391             args = [all_kwargs[argname] for argname in varnames(method)]
    392             if hasattr(method, "hookwrapper"):
--> 393                 return wrapped_call(method(*args), self.execute)
    394             res = method(*args)
    395             if res is not None:

  ~/.virtualenvs/myapp/local/lib/python2.7/site-packages/_pytest/core.py(107)wrapped_call()
    105     except StopIteration:
    106         raise_wrapfail(wrap_controller, "did not yield")
--> 107     call_outcome = CallOutcome(func)
    108     try:
    109         wrap_controller.send(call_outcome)

  ~/.virtualenvs/myapp/local/lib/python2.7/site-packages/_pytest/core.py(123)__init__()
    121     def __init__(self, func):
    122         try:
--> 123             self.result = func()
    124         except BaseException:
    125             self.excinfo = sys.exc_info()

  ~/.virtualenvs/myapp/local/lib/python2.7/site-packages/_pytest/core.py(394)execute()
    392             if hasattr(method, "hookwrapper"):
    393                 return wrapped_call(method(*args), self.execute)
--> 394             res = method(*args)
    395             if res is not None:
    396                 self.results.append(res)

  ~/.virtualenvs/myapp/local/lib/python2.7/site-packages/_pytest/config.py(636)pytest_cmdline_parse()
    634     def pytest_cmdline_parse(self, pluginmanager, args):
    635         assert self == pluginmanager.config, (self, pluginmanager.config)
--> 636         self.parse(args)
    637         return self
    638

  ~/.virtualenvs/myapp/local/lib/python2.7/site-packages/_pytest/config.py(746)parse()
    744                 "can only parse cmdline args at most once per Config object")
    745         self._origargs = args
--> 746         self._preparse(args)
    747         # XXX deprecated hook:
    748         self.hook.pytest_cmdline_preparse(config=self, args=args)

  ~/.virtualenvs/myapp/local/lib/python2.7/site-packages/_pytest/config.py(718)_preparse()
    716         try:
    717             self.hook.pytest_load_initial_conftests(early_config=self,
--> 718                     args=args, parser=self._parser)
    719         except ConftestImportFailure:
    720             e = sys.exc_info()[1]

  ~/.virtualenvs/myapp/local/lib/python2.7/site-packages/_pytest/core.py(521)__call__()
    519
    520     def __call__(self, **kwargs):
--> 521         return self._docall(self.methods, kwargs)
    522
    523     def callextra(self, methods, **kwargs):

  ~/.virtualenvs/myapp/local/lib/python2.7/site-packages/_pytest/core.py(528)_docall()
    526     def _docall(self, methods, kwargs):
    527         return MultiCall(methods, kwargs,
--> 528                          firstresult=self.firstresult).execute()
    529
    530

  ~/.virtualenvs/myapp/local/lib/python2.7/site-packages/_pytest/core.py(393)execute()
    391             args = [all_kwargs[argname] for argname in varnames(method)]
    392             if hasattr(method, "hookwrapper"):
--> 393                 return wrapped_call(method(*args), self.execute)
    394             res = method(*args)
    395             if res is not None:

  ~/.virtualenvs/myapp/local/lib/python2.7/site-packages/_pytest/core.py(107)wrapped_call()
    105     except StopIteration:
    106         raise_wrapfail(wrap_controller, "did not yield")
--> 107     call_outcome = CallOutcome(func)
    108     try:
    109         wrap_controller.send(call_outcome)

  ~/.virtualenvs/myapp/local/lib/python2.7/site-packages/_pytest/core.py(123)__init__()
    121     def __init__(self, func):
    122         try:
--> 123             self.result = func()
    124         except BaseException:
    125             self.excinfo = sys.exc_info()

  ~/.virtualenvs/myapp/local/lib/python2.7/site-packages/_pytest/core.py(394)execute()
    392             if hasattr(method, "hookwrapper"):
    393                 return wrapped_call(method(*args), self.execute)
--> 394             res = method(*args)
    395             if res is not None:
    396                 self.results.append(res)

  ~/.virtualenvs/myapp/local/lib/python2.7/site-packages/pytest_django/plugin.py(87)pytest_load_initial_conftests()
     85 if pytest.__version__[:3] >= "2.4":
     86     def pytest_load_initial_conftests(early_config, parser, args):
---> 87         _load_settings(early_config, parser.parse_known_args(args))
     88
     89

  ~/.virtualenvs/myapp/local/lib/python2.7/site-packages/pytest_django/plugin.py(79)_load_settings()
     77         from django.conf import settings
     78         try:
---> 79             settings.DATABASES
     80         except ImportError:
     81             e = sys.exc_info()[1]

  ~/.virtualenvs/myapp/local/lib/python2.7/site-packages/django/conf/__init__.py(48)__getattr__()
     46     def __getattr__(self, name):
     47         if self._wrapped is empty:
---> 48             self._setup(name)
     49         return getattr(self._wrapped, name)
     50

  ~/.virtualenvs/myapp/local/lib/python2.7/site-packages/django/conf/__init__.py(44)_setup()
     42                 % (desc, ENVIRONMENT_VARIABLE))
     43
---> 44         self._wrapped = Settings(settings_module)
     45
     46     def __getattr__(self, name):

  ~/.virtualenvs/myapp/local/lib/python2.7/site-packages/django/conf/__init__.py(92)__init__()
     90         self.SETTINGS_MODULE = settings_module
     91
---> 92         mod = importlib.import_module(self.SETTINGS_MODULE)
     93
     94         tuple_settings = (

  /usr/lib/python2.7/importlib/__init__.py(37)import_module()
     34                 break
     35             level += 1
     36         name = _resolve_name(name[level:], package, level)
---> 37     __import__(name)
     38     return sys.modules[name]

> ~/projects/myapp/settings/test.py(2)<module>()
      1 import ipdb;ipdb.set_trace()
----> 2 from myapp.settings.base import *   # pylint: disable=W0614,W0401
      3
      4 DEBUG = False
      5 TEMPLATE_DEBUG = DEBUG

and as soon as I step over the import REST_FRAMEWORK is available and has the correct settings from base.py. Once I step through this file and it goes into django settings internals I can get settings.REST_FRAMEWORK and it is also correct. But if I hit c to continue the test it fails with errors indicating it isn't actually running with the values imported from base.py. The values from DATABASES stick, just not from REST_FRAMEWORK.

Can you reduce this to a minimal project with multiple settings files where the issue can be reproduced?

i discovered that the DJANGO_SETTINGS_MODULE value in pytest.ini does not override a DJANGO_SETTINGS_MODULE environment variable. i also have a settings file for testing which is derived from another settings file- pytest is using the base settings file since that's the value in the environment variable.

@smcoll is right. Seems like the ENV value overrides the INI/config value:

https://github.com/pytest-dev/pytest-django/blob/877cd711b61b06385e0eb5900acf8eb775244810/pytest_django/plugin.py#L292-L300

For us we're able to have things work via DJANGO_SETTINGS_MODULE=config.settings.test pytest because we're running things locally with Docker/Compose which for us defaults to config.settings.local in the ENV.

I feel like pytest-django is doing the right thing but I also wonder if maybe the config should should outweigh the env?

I just got bit by this issue. I think config should outweigh the env, personally...

The order of discovery is documented here.

An envvar traditionally is stronger than a configuration file, so this is expected behavior, and due to backward compatibility is unlikely to change anyway.

One way to trick this is to use

[pytest]
addopts = --ds=my_settings

instead of the DJANGO_SETTINGS_MODULE setting. A command-line flag takes precedence over anything else.

Was this page helpful?
0 / 5 - 0 ratings