Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pytest appears to ignore settings that extend other settings files #273

Closed
jerel opened this issue Sep 25, 2015 · 9 comments
Closed

Pytest appears to ignore settings that extend other settings files #273

jerel opened this issue Sep 25, 2015 · 9 comments

Comments

@jerel
Copy link

jerel commented Sep 25, 2015

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

@blueyed
Copy link
Contributor

blueyed commented Sep 25, 2015

Are you sure DJANGO_SETTINGS_MODULE is set properly?

@jerel
Copy link
Author

jerel commented Sep 25, 2015

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

@blueyed
Copy link
Contributor

blueyed commented Sep 25, 2015

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.

@jerel
Copy link
Author

jerel commented Sep 25, 2015

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.

@pelme
Copy link
Member

pelme commented Dec 5, 2015

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

@smcoll
Copy link

smcoll commented Nov 22, 2017

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.

@merit-mthompson
Copy link

merit-mthompson commented Mar 11, 2020

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

def _get_option_with_source(option, envname):
if option:
return option, "option"
if envname in os.environ:
return os.environ[envname], "env"
cfgval = early_config.getini(envname)
if cfgval:
return cfgval, "ini"
return None, None

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?

@kathawala
Copy link

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

@bluetech
Copy link
Member

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants