sanic/tests/test_config.py
Adam Hopkins 392a497366
Restructure of CLI and application state (#2295)
* Initial work on restructure of application state

* Updated MOTD with more flexible input and add basic version

* Remove unnecessary type ignores

* Add wrapping and smarter output per process type

* Add support for ASGI MOTD

* Add Windows color support ernable

* Refactor __main__ into submodule

* Renest arguments

* Passing unit tests

* Passing unit tests

* Typing

* Fix num worker test

* Add context to assert failure

* Add some type annotations

* Some linting

* Line aware searching in test

* Test abstractions

* Fix some flappy tests

* Bump up timeout on CLI tests

* Change test for no access logs on gunicornworker

* Add some basic test converage

* Some new tests, and disallow workers and fast on app.run
2021-11-07 21:39:03 +02:00

363 lines
9.8 KiB
Python

from contextlib import contextmanager
from email import message
from os import environ
from pathlib import Path
from tempfile import TemporaryDirectory
from textwrap import dedent
import pytest
from sanic import Sanic
from sanic.config import DEFAULT_CONFIG, Config
from sanic.exceptions import PyFileError
@contextmanager
def temp_path():
"""a simple cross platform replacement for NamedTemporaryFile"""
with TemporaryDirectory() as td:
yield Path(td, "file")
class ConfigTest:
not_for_config = "should not be used"
CONFIG_VALUE = "should be used"
@property
def ANOTHER_VALUE(self):
return self.CONFIG_VALUE
@property
def another_not_for_config(self):
return self.not_for_config
def test_load_from_object(app):
app.config.load(ConfigTest)
assert "CONFIG_VALUE" in app.config
assert app.config.CONFIG_VALUE == "should be used"
assert "not_for_config" not in app.config
def test_load_from_object_string(app):
app.config.load("test_config.ConfigTest")
assert "CONFIG_VALUE" in app.config
assert app.config.CONFIG_VALUE == "should be used"
assert "not_for_config" not in app.config
def test_load_from_instance(app):
app.config.load(ConfigTest())
assert "CONFIG_VALUE" in app.config
assert app.config.CONFIG_VALUE == "should be used"
assert app.config.ANOTHER_VALUE == "should be used"
assert "not_for_config" not in app.config
assert "another_not_for_config" not in app.config
def test_load_from_object_string_exception(app):
with pytest.raises(ImportError):
app.config.load("test_config.Config.test")
def test_auto_env_prefix():
environ["SANIC_TEST_ANSWER"] = "42"
app = Sanic(name=__name__)
assert app.config.TEST_ANSWER == 42
del environ["SANIC_TEST_ANSWER"]
def test_auto_bool_env_prefix():
environ["SANIC_TEST_ANSWER"] = "True"
app = Sanic(name=__name__)
assert app.config.TEST_ANSWER is True
del environ["SANIC_TEST_ANSWER"]
def test_dont_load_env():
environ["SANIC_TEST_ANSWER"] = "42"
app = Sanic(name=__name__, load_env=False)
assert getattr(app.config, "TEST_ANSWER", None) is None
del environ["SANIC_TEST_ANSWER"]
@pytest.mark.parametrize("load_env", [None, False, "", "MYAPP_"])
def test_load_env_deprecation(load_env):
with pytest.warns(DeprecationWarning, match=r"21\.12"):
_ = Sanic(name=__name__, load_env=load_env)
def test_load_env_prefix():
environ["MYAPP_TEST_ANSWER"] = "42"
app = Sanic(name=__name__, load_env="MYAPP_")
assert app.config.TEST_ANSWER == 42
del environ["MYAPP_TEST_ANSWER"]
@pytest.mark.parametrize("env_prefix", [None, ""])
def test_empty_load_env_prefix(env_prefix):
environ["SANIC_TEST_ANSWER"] = "42"
app = Sanic(name=__name__, env_prefix=env_prefix)
assert getattr(app.config, "TEST_ANSWER", None) is None
del environ["SANIC_TEST_ANSWER"]
def test_load_env_prefix_float_values():
environ["MYAPP_TEST_ROI"] = "2.3"
app = Sanic(name=__name__, load_env="MYAPP_")
assert app.config.TEST_ROI == 2.3
del environ["MYAPP_TEST_ROI"]
def test_load_env_prefix_string_value():
environ["MYAPP_TEST_TOKEN"] = "somerandomtesttoken"
app = Sanic(name=__name__, load_env="MYAPP_")
assert app.config.TEST_TOKEN == "somerandomtesttoken"
del environ["MYAPP_TEST_TOKEN"]
def test_env_prefix():
environ["MYAPP_TEST_ANSWER"] = "42"
app = Sanic(name=__name__, env_prefix="MYAPP_")
assert app.config.TEST_ANSWER == 42
del environ["MYAPP_TEST_ANSWER"]
def test_env_prefix_float_values():
environ["MYAPP_TEST_ROI"] = "2.3"
app = Sanic(name=__name__, env_prefix="MYAPP_")
assert app.config.TEST_ROI == 2.3
del environ["MYAPP_TEST_ROI"]
def test_env_prefix_string_value():
environ["MYAPP_TEST_TOKEN"] = "somerandomtesttoken"
app = Sanic(name=__name__, env_prefix="MYAPP_")
assert app.config.TEST_TOKEN == "somerandomtesttoken"
del environ["MYAPP_TEST_TOKEN"]
def test_load_from_file(app):
config = dedent(
"""
VALUE = 'some value'
condition = 1 == 1
if condition:
CONDITIONAL = 'should be set'
"""
)
with temp_path() as config_path:
config_path.write_text(config)
app.config.load(str(config_path))
assert "VALUE" in app.config
assert app.config.VALUE == "some value"
assert "CONDITIONAL" in app.config
assert app.config.CONDITIONAL == "should be set"
assert "condition" not in app.config
def test_load_from_missing_file(app):
with pytest.raises(IOError):
app.config.load("non-existent file")
def test_load_from_envvar(app):
config = "VALUE = 'some value'"
with temp_path() as config_path:
config_path.write_text(config)
environ["APP_CONFIG"] = str(config_path)
app.config.load("${APP_CONFIG}")
assert "VALUE" in app.config
assert app.config.VALUE == "some value"
def test_load_from_missing_envvar(app):
with pytest.raises(IOError) as e:
app.config.load("non-existent variable")
assert str(e.value) == (
"The environment variable 'non-existent "
"variable' is not set and thus configuration "
"could not be loaded."
)
def test_load_config_from_file_invalid_syntax(app):
config = "VALUE = some value"
with temp_path() as config_path:
config_path.write_text(config)
with pytest.raises(PyFileError):
app.config.load(config_path)
def test_overwrite_exisiting_config(app):
app.config.DEFAULT = 1
class Config:
DEFAULT = 2
app.config.load(Config)
assert app.config.DEFAULT == 2
def test_overwrite_exisiting_config_ignore_lowercase(app):
app.config.default = 1
class Config:
default = 2
app.config.load(Config)
assert app.config.default == 1
def test_missing_config(app):
with pytest.raises(AttributeError, match="Config has no 'NON_EXISTENT'"):
_ = app.config.NON_EXISTENT
def test_config_defaults():
"""
load DEFAULT_CONFIG
"""
conf = Config()
for key, value in DEFAULT_CONFIG.items():
assert getattr(conf, key) == value
def test_config_custom_defaults():
"""
we should have all the variables from defaults rewriting them with
custom defaults passed in
Config
"""
custom_defaults = {
"REQUEST_MAX_SIZE": 1,
"KEEP_ALIVE": False,
"ACCESS_LOG": False,
}
conf = Config(defaults=custom_defaults)
for key, value in DEFAULT_CONFIG.items():
if key in custom_defaults.keys():
value = custom_defaults[key]
assert getattr(conf, key) == value
def test_config_custom_defaults_with_env():
"""
test that environment variables has higher priority than DEFAULT_CONFIG
and passed defaults dict
"""
custom_defaults = {
"REQUEST_MAX_SIZE123": 1,
"KEEP_ALIVE123": False,
"ACCESS_LOG123": False,
}
environ_defaults = {
"SANIC_REQUEST_MAX_SIZE123": "2",
"SANIC_KEEP_ALIVE123": "True",
"SANIC_ACCESS_LOG123": "False",
}
for key, value in environ_defaults.items():
environ[key] = value
conf = Config(defaults=custom_defaults)
for key, value in DEFAULT_CONFIG.items():
if "SANIC_" + key in environ_defaults.keys():
value = environ_defaults["SANIC_" + key]
try:
value = int(value)
except ValueError:
if value in ["True", "False"]:
value = value == "True"
assert getattr(conf, key) == value
for key, value in environ_defaults.items():
del environ[key]
def test_config_access_log_passing_in_run(app):
assert app.config.ACCESS_LOG is True
@app.listener("after_server_start")
async def _request(sanic, loop):
app.stop()
app.run(port=1340, access_log=False)
assert app.config.ACCESS_LOG is False
app.run(port=1340, access_log=True)
assert app.config.ACCESS_LOG is True
@pytest.mark.asyncio
async def test_config_access_log_passing_in_create_server(app):
assert app.config.ACCESS_LOG is True
@app.listener("after_server_start")
async def _request(sanic, loop):
app.stop()
await app.create_server(
port=1341, access_log=False, return_asyncio_server=True
)
assert app.config.ACCESS_LOG is False
await app.create_server(
port=1342, access_log=True, return_asyncio_server=True
)
assert app.config.ACCESS_LOG is True
def test_config_rewrite_keep_alive():
config = Config()
assert config.KEEP_ALIVE == DEFAULT_CONFIG["KEEP_ALIVE"]
config = Config(keep_alive=True)
assert config.KEEP_ALIVE is True
config = Config(keep_alive=False)
assert config.KEEP_ALIVE is False
# use defaults
config = Config(defaults={"KEEP_ALIVE": False})
assert config.KEEP_ALIVE is False
config = Config(defaults={"KEEP_ALIVE": True})
assert config.KEEP_ALIVE is True
_test_setting_as_dict = {"TEST_SETTING_VALUE": 1}
_test_setting_as_class = type("C", (), {"TEST_SETTING_VALUE": 1})
_test_setting_as_module = str(
Path(__file__).parent / "static/app_test_config.py"
)
@pytest.mark.parametrize(
"conf_object",
[
_test_setting_as_dict,
_test_setting_as_class,
_test_setting_as_module,
],
ids=["from_dict", "from_class", "from_file"],
)
def test_update(app, conf_object):
app.update_config(conf_object)
assert app.config["TEST_SETTING_VALUE"] == 1
def test_update_from_lowercase_key(app):
d = {"test_setting_value": 1}
app.update_config(d)
assert "test_setting_value" not in app.config
def test_deprecation_notice_when_setting_logo(app):
message = (
"Setting the config.LOGO is deprecated and will no longer be "
"supported starting in v22.6."
)
with pytest.warns(DeprecationWarning, match=message):
app.config.LOGO = "My Custom Logo"