sanic/tests/test_config.py

404 lines
11 KiB
Python
Raw Normal View History

2021-12-20 22:50:45 +00:00
import logging
from contextlib import contextmanager
2016-12-16 17:46:07 +00:00
from os import environ
2018-09-29 18:54:47 +01:00
from pathlib import Path
from tempfile import TemporaryDirectory
from textwrap import dedent
from unittest.mock import Mock
2016-12-16 17:46:07 +00:00
import pytest
from pytest import MonkeyPatch
2016-12-16 17:46:07 +00:00
from sanic import Sanic
from sanic.config import DEFAULT_CONFIG, Config
2019-01-02 00:35:25 +00:00
from sanic.exceptions import PyFileError
2016-12-16 17:46:07 +00:00
2018-09-29 18:54:47 +01:00
@contextmanager
def temp_path():
"""a simple cross platform replacement for NamedTemporaryFile"""
2018-09-29 18:54:47 +01:00
with TemporaryDirectory() as td:
2018-12-30 11:18:06 +00:00
yield Path(td, "file")
2018-09-29 18:54:47 +01:00
2019-03-05 17:36:54 +00:00
class ConfigTest:
not_for_config = "should not be used"
CONFIG_VALUE = "should be used"
2016-12-16 17:46:07 +00:00
@property
def ANOTHER_VALUE(self):
return self.CONFIG_VALUE
@property
def another_not_for_config(self):
return self.not_for_config
2016-12-16 17:46:07 +00:00
2021-12-20 22:50:45 +00:00
class UltimateAnswer:
def __init__(self, answer):
self.answer = int(answer)
def test_load_from_object(app: Sanic):
app.config.load(ConfigTest)
2018-12-30 11:18:06 +00:00
assert "CONFIG_VALUE" in app.config
assert app.config.CONFIG_VALUE == "should be used"
assert "not_for_config" not in app.config
2016-12-16 17:46:07 +00:00
def test_load_from_object_string(app: Sanic):
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: Sanic):
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
2018-12-26 20:27:02 +00:00
def test_load_from_object_string_exception(app: Sanic):
with pytest.raises(ImportError):
app.config.load("test_config.Config.test")
def test_auto_env_prefix():
environ["SANIC_TEST_ANSWER"] = "42"
Fix Ctrl+C and tests on Windows. (#1808) * Fix Ctrl+C on Windows. * Disable testing of a function N/A on Windows. * Add test for coverage, avoid crash on missing _stopping. * Initialise StreamingHTTPResponse.protocol = None * Improved comments. * Reduce amount of data in test_request_stream to avoid failures on Windows. * The Windows test doesn't work on Windows :( * Use port numbers more likely to be free than 8000. * Disable the other signal tests on Windows as well. * Windows doesn't properly support SO_REUSEADDR, so that's disabled in Python, and thus rebinding fails. For successful testing, reuse port instead. * app.run argument handling: added server kwargs (alike create_server), added warning on extra kwargs, made auto_reload explicit argument. Another go at Windows tests * Revert "app.run argument handling: added server kwargs (alike create_server), added warning on extra kwargs, made auto_reload explicit argument. Another go at Windows tests" This reverts commit dc5d682448e3f6595bdca5cb764e5f26ca29e295. * Use random test server port on most tests. Should avoid port/addr reuse issues. * Another test to random port instead of 8000. * Fix deprecation warnings about missing name on Sanic() in tests. * Linter and typing * Increase test coverage * Rewrite test for ctrlc_windows_workaround * py36 compat * py36 compat * py36 compat * Don't rely on loop internals but add a stopping flag to app. * App may be restarted. * py36 compat * Linter * Add a constant for OS checking. Co-authored-by: L. Kärkkäinen <tronic@users.noreply.github.com>
2020-03-26 04:42:46 +00:00
app = Sanic(name=__name__)
2017-09-05 09:58:48 +01:00
assert app.config.TEST_ANSWER == 42
del environ["SANIC_TEST_ANSWER"]
def test_auto_bool_env_prefix():
environ["SANIC_TEST_ANSWER"] = "True"
Fix Ctrl+C and tests on Windows. (#1808) * Fix Ctrl+C on Windows. * Disable testing of a function N/A on Windows. * Add test for coverage, avoid crash on missing _stopping. * Initialise StreamingHTTPResponse.protocol = None * Improved comments. * Reduce amount of data in test_request_stream to avoid failures on Windows. * The Windows test doesn't work on Windows :( * Use port numbers more likely to be free than 8000. * Disable the other signal tests on Windows as well. * Windows doesn't properly support SO_REUSEADDR, so that's disabled in Python, and thus rebinding fails. For successful testing, reuse port instead. * app.run argument handling: added server kwargs (alike create_server), added warning on extra kwargs, made auto_reload explicit argument. Another go at Windows tests * Revert "app.run argument handling: added server kwargs (alike create_server), added warning on extra kwargs, made auto_reload explicit argument. Another go at Windows tests" This reverts commit dc5d682448e3f6595bdca5cb764e5f26ca29e295. * Use random test server port on most tests. Should avoid port/addr reuse issues. * Another test to random port instead of 8000. * Fix deprecation warnings about missing name on Sanic() in tests. * Linter and typing * Increase test coverage * Rewrite test for ctrlc_windows_workaround * py36 compat * py36 compat * py36 compat * Don't rely on loop internals but add a stopping flag to app. * App may be restarted. * py36 compat * Linter * Add a constant for OS checking. Co-authored-by: L. Kärkkäinen <tronic@users.noreply.github.com>
2020-03-26 04:42:46 +00:00
app = Sanic(name=__name__)
assert app.config.TEST_ANSWER is True
del environ["SANIC_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_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"]
2021-12-20 22:50:45 +00:00
def test_env_w_custom_converter():
environ["SANIC_TEST_ANSWER"] = "42"
config = Config(converters=[UltimateAnswer])
app = Sanic(name=__name__, config=config)
assert isinstance(app.config.TEST_ANSWER, UltimateAnswer)
assert app.config.TEST_ANSWER.answer == 42
del environ["SANIC_TEST_ANSWER"]
def test_env_lowercase():
with pytest.warns(None) as record:
environ["SANIC_test_answer"] = "42"
app = Sanic(name=__name__)
assert app.config.test_answer == 42
assert str(record[0].message) == (
"[DEPRECATION v22.9] Lowercase environment variables will not be "
"loaded into Sanic config beginning in v22.9."
)
del environ["SANIC_test_answer"]
2021-12-20 22:50:45 +00:00
def test_add_converter_multiple_times(caplog):
def converter():
...
message = (
"Configuration value converter 'converter' has already been registered"
)
config = Config()
config.register_type(converter)
with caplog.at_level(logging.WARNING):
config.register_type(converter)
assert ("sanic.error", logging.WARNING, message) in caplog.record_tuples
assert len(config._converters) == 5
def test_load_from_file(app: Sanic):
2018-12-30 11:18:06 +00:00
config = dedent(
"""
2018-09-29 18:54:47 +01:00
VALUE = 'some value'
condition = 1 == 1
if condition:
CONDITIONAL = 'should be set'
2018-12-30 11:18:06 +00:00
"""
)
2018-09-29 18:54:47 +01:00
with temp_path() as config_path:
config_path.write_text(config)
app.config.load(str(config_path))
2018-12-30 11:18:06 +00:00
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
2016-12-16 17:46:07 +00:00
def test_load_from_missing_file(app: Sanic):
2016-12-16 17:46:07 +00:00
with pytest.raises(IOError):
app.config.load("non-existent file")
2016-12-16 17:46:07 +00:00
def test_load_from_envvar(app: Sanic):
2018-09-29 18:54:47 +01:00
config = "VALUE = 'some value'"
with temp_path() as config_path:
config_path.write_text(config)
2018-12-30 11:18:06 +00:00
environ["APP_CONFIG"] = str(config_path)
app.config.load("${APP_CONFIG}")
2018-12-30 11:18:06 +00:00
assert "VALUE" in app.config
assert app.config.VALUE == "some value"
2016-12-16 17:46:07 +00:00
def test_load_from_missing_envvar(app: Sanic):
with pytest.raises(IOError) as e:
app.config.load("non-existent variable")
2018-12-30 11:18:06 +00:00
assert str(e.value) == (
"The environment variable 'non-existent "
"variable' is not set and thus configuration "
"could not be loaded."
)
2016-12-16 17:46:07 +00:00
def test_load_config_from_file_invalid_syntax(app: Sanic):
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: Sanic):
2016-12-16 17:46:07 +00:00
app.config.DEFAULT = 1
2016-12-16 17:46:07 +00:00
class Config:
DEFAULT = 2
app.config.load(Config)
2016-12-16 17:46:07 +00:00
assert app.config.DEFAULT == 2
def test_overwrite_exisiting_config_ignore_lowercase(app: Sanic):
2018-10-06 23:14:37 +01:00
app.config.default = 1
class Config:
default = 2
app.config.load(Config)
2018-10-06 23:14:37 +01:00
assert app.config.default == 1
def test_missing_config(app: Sanic):
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]
2018-12-30 20:27:20 +00:00
def test_config_access_log_passing_in_run(app: Sanic):
assert app.config.ACCESS_LOG is True
2018-12-30 20:27:20 +00:00
@app.listener("after_server_start")
2018-12-30 20:27:20 +00:00
async def _request(sanic, loop):
app.stop()
app.run(port=1340, access_log=False)
assert app.config.ACCESS_LOG is False
2018-12-30 20:27:20 +00:00
app.run(port=1340, access_log=True)
assert app.config.ACCESS_LOG is True
2018-12-30 20:27:20 +00:00
2019-06-04 08:58:00 +01:00
@pytest.mark.asyncio
async def test_config_access_log_passing_in_create_server(app: Sanic):
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
2018-12-30 20:27:20 +00:00
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
2018-12-30 20:27:20 +00:00
config = Config(keep_alive=False)
assert config.KEEP_ALIVE is False
2018-12-30 20:27:20 +00:00
# use defaults
config = Config(defaults={"KEEP_ALIVE": False})
assert config.KEEP_ALIVE is False
2018-12-30 20:27:20 +00:00
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: Sanic, conf_object):
app.update_config(conf_object)
assert app.config["TEST_SETTING_VALUE"] == 1
def test_update_from_lowercase_key(app: Sanic):
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: Sanic):
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"
def test_config_set_methods(app: Sanic, monkeypatch: MonkeyPatch):
post_set = Mock()
monkeypatch.setattr(Config, "_post_set", post_set)
app.config.FOO = 1
post_set.assert_called_once_with("FOO", 1)
post_set.reset_mock()
app.config["FOO"] = 2
post_set.assert_called_once_with("FOO", 2)
post_set.reset_mock()
app.config.update({"FOO": 3})
post_set.assert_called_once_with("FOO", 3)
post_set.reset_mock()
app.config.update([("FOO", 4)])
post_set.assert_called_once_with("FOO", 4)
post_set.reset_mock()
app.config.update(FOO=5)
post_set.assert_called_once_with("FOO", 5)
post_set.reset_mock()
app.config.update_config({"FOO": 6})
post_set.assert_called_once_with("FOO", 6)