diff --git a/docs/sanic/config.md b/docs/sanic/config.md index b2552704..bd04bbff 100644 --- a/docs/sanic/config.md +++ b/docs/sanic/config.md @@ -53,6 +53,13 @@ import myapp.default_settings app = Sanic('myapp') app.config.from_object(myapp.default_settings) ``` +or also by path to config: + +``` +app = Sanic('myapp') +app.config.from_object('config.path.config.Class') +``` + You could use a class or any other object as well. diff --git a/sanic/config.py b/sanic/config.py index 97581677..093e9661 100644 --- a/sanic/config.py +++ b/sanic/config.py @@ -2,6 +2,7 @@ import os import types from sanic.exceptions import PyFileError +from sanic.helpers import import_string SANIC_PREFIX = "SANIC_" @@ -102,6 +103,9 @@ class Config(dict): from yourapplication import default_config app.config.from_object(default_config) + or also: + app.config.from_object('myproject.config.MyConfigClass') + You should not use this function to load the actual configuration but rather configuration defaults. The actual config should be loaded with :meth:`from_pyfile` and ideally from a location not within the @@ -109,6 +113,8 @@ class Config(dict): :param obj: an object holding the configuration """ + if isinstance(obj, str): + obj = import_string(obj) for key in dir(obj): if key.isupper(): self[key] = getattr(obj, key) diff --git a/sanic/helpers.py b/sanic/helpers.py index 3312f0c7..1b30e1ad 100644 --- a/sanic/helpers.py +++ b/sanic/helpers.py @@ -1,5 +1,9 @@ """Defines basics of HTTP standard.""" +from importlib import import_module +from inspect import ismodule + + STATUS_CODES = { 100: b"Continue", 101: b"Switching Protocols", @@ -131,3 +135,21 @@ def remove_entity_headers(headers, allowed=("content-location", "expires")): if not is_entity_header(header) or header.lower() in allowed } return headers + + +def import_string(module_name, package=None): + """ + import a module or class by string path. + + :module_name: str with path of module or path to import and + instanciate a class + :returns: a module object or one instance from class if + module_name is a valid path to class + + """ + module, klass = module_name.rsplit(".", 1) + module = import_module(module, package=package) + obj = getattr(module, klass) + if ismodule(obj): + return obj + return obj() diff --git a/tests/test_config.py b/tests/test_config.py index 7b203311..5420b4ce 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -18,17 +18,30 @@ def temp_path(): yield Path(td, "file") -def test_load_from_object(app): - class Config: - not_for_config = "should not be used" - CONFIG_VALUE = "should be used" +class ConfigTest: + not_for_config = 'should not be used' + CONFIG_VALUE = 'should be used' - app.config.from_object(Config) + +def test_load_from_object(app): + app.config.from_object(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.from_object('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_object_string_exception(app): + with pytest.raises(ImportError): + app.config.from_object('test_config.Config.test') + + def test_auto_load_env(): environ["SANIC_TEST_ANSWER"] = "42" app = Sanic() diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 7e7c6ddb..19d0e9e2 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -1,4 +1,8 @@ +import inspect + from sanic import helpers +from sanic.config import Config +import pytest def test_has_message_body(): @@ -56,3 +60,18 @@ def test_remove_entity_headers(): for header, expected in tests: assert helpers.remove_entity_headers(header) == expected + + +def test_import_string_class(): + obj = helpers.import_string('sanic.config.Config') + assert isinstance(obj, Config) + + +def test_import_string_module(): + module = helpers.import_string('sanic.config') + assert inspect.ismodule(module) + + +def test_import_string_exception(): + with pytest.raises(ImportError): + helpers.import_string('test.test.test')