Merge pull request #1436 from jotagesales/config_from_object_string

Config from object string
This commit is contained in:
7 2019-06-16 16:58:43 -07:00 committed by GitHub
commit 8fbbe94fe1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 72 additions and 5 deletions

View File

@ -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.

View File

@ -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)

View File

@ -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()

View File

@ -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()

View File

@ -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')