Skip to content

Commit

Permalink
Merge pull request #13 from rochacbruno/add_yaml_support
Browse files Browse the repository at this point in the history
Added YAML support
  • Loading branch information
rochacbruno committed Feb 12, 2017
2 parents b16ea50 + 7049882 commit dcbec6b
Show file tree
Hide file tree
Showing 15 changed files with 240 additions and 6 deletions.
2 changes: 2 additions & 0 deletions .coveragerc
Expand Up @@ -9,3 +9,5 @@ omit =
dynaconf/test_settings.py
dynaconf/utils/functional.py
dynaconf/utils/redis_writer.py
dynaconf/loaders/redis_loader.py
dynaconf/loaders/__init__.py
12 changes: 10 additions & 2 deletions dynaconf/base.py
Expand Up @@ -10,6 +10,7 @@

from dynaconf import default_settings
from dynaconf.loaders import default_loader, module_loader
from dynaconf.loaders import yaml_loader
from dynaconf.utils.functional import LazyObject, empty
from dynaconf.utils.parse_conf import converters, parse_conf_data

Expand Down Expand Up @@ -69,7 +70,10 @@ def __getattr__(self, name):
if self._wrapped is empty:
self._setup()
if name in self._wrapped._deleted: # noqa
raise AttributeError("Attribute %s was deleted" % name)
raise AttributeError(
"Attribute %s was deleted, "
"or belongs to different namespace" % name
)
always_fresh = self._wrapped.DYNACONF_ALWAYS_FRESH_VARS
if self._wrapped._fresh or name in always_fresh: # noqa
return self._wrapped.get_fresh(name)
Expand Down Expand Up @@ -415,7 +419,11 @@ def reload(self, namespace=None, silent=None): # pragma: no cover

def execute_loaders(self, namespace=None, silent=None, key=None):
default_loader(self)
module_loader(self)
module_loader(self, namespace=namespace)
if self.exists('YAML'):
yaml_loader.load(
self, namespace=namespace, filename=self.get('YAML')
)
silent = silent or self.DYNACONF_SILENT_ERRORS
for loader in self.loaders:
loader.load(self, namespace, silent=silent, key=key)
Expand Down
2 changes: 1 addition & 1 deletion dynaconf/default_settings.py
Expand Up @@ -18,7 +18,7 @@
'db': 0
}

# Loaders to read namespace based vars from diferent data stores
# Loaders to read namespace based vars from different data stores
LOADERS_FOR_DYNACONF = [
'dynaconf.loaders.env_loader',
# 'dynaconf.loaders.redis_loader'
Expand Down
9 changes: 7 additions & 2 deletions dynaconf/loaders/__init__.py
@@ -1,6 +1,7 @@
import os
import importlib
from dynaconf import default_settings
from dynaconf.loaders import yaml_loader


def default_loader(obj):
Expand All @@ -9,9 +10,13 @@ def default_loader(obj):
obj.set(key, value)


def module_loader(obj, settings_module=None):
def module_loader(obj, settings_module=None, namespace=None):
settings_module = settings_module or obj.settings_module
if not settings_module:
if not settings_module: # pragma: no cover
return

if settings_module.endswith(('.yaml', '.yml')):
yaml_loader.load(obj, filename=settings_module, namespace=namespace)
return

try:
Expand Down
2 changes: 1 addition & 1 deletion dynaconf/loaders/env_loader.py
Expand Up @@ -20,7 +20,7 @@ def load(obj, namespace=None, silent=True, key=None):
if key.startswith('%s_' % namespace.upper())
}
obj.update(data, loader_identifier=IDENTIFIER)
except Exception as e:
except Exception as e: # pragma: no cover
e.message = 'Unable to load config env namespace (%s)' % e.message
if silent:
obj.logger.error(e.message)
Expand Down
58 changes: 58 additions & 0 deletions dynaconf/loaders/yaml_loader.py
@@ -0,0 +1,58 @@
# coding: utf-8
try:
import yaml
except ImportError as e: # pragma: no cover
yaml = None

IDENTIFIER = 'yaml_loader'


def load(obj, namespace=None, silent=True, key=None, filename=None):
"""
Reads and loads in to "settings" a single key or all keys from yaml file
:param obj: the settings instance
:param namespace: settings namespace default='DYNACONF'
:param silent: if errors should raise
:param key: if defined load a single key, else load all in namespace
:return: None
"""
if yaml is None: # pragma: no cover
raise RuntimeError(
"PyYAML package is not installed in your environment.\n"
"To use this loader you have to install it with\n"
"pip install PyYAML\n"
"or\n"
"pip install dynaconf[yaml]"
)

filename = filename or obj.get('YAML')
if not filename:
return

namespace = namespace or obj.DYNACONF_NAMESPACE

clean(obj, namespace, identifier=filename)

if filename.endswith(('.yaml', '.yml')): # pragma: no cover
yaml_data = yaml.load(open(filename))
else:
# for tests it is possible to pass YAML string
yaml_data = yaml.load(filename)

yaml_data = {key.lower(): value for key, value in yaml_data.items()}

try:
data = yaml_data[namespace.lower()]
except KeyError:
raise KeyError(
'%s namespace not defined in %s' % (namespace, filename)
)

if not key:
obj.update(data, loader_identifier=filename)
else:
obj.set(key, data.get(key))

def clean(obj, namespace, silent=True, identifier=IDENTIFIER): # noqa
for key in obj.loaded_by_loaders.get(identifier, {}):
obj.unset(key)
34 changes: 34 additions & 0 deletions example/yaml_example/settings_module/app.py
@@ -0,0 +1,34 @@
from dynaconf import settings

# export DYNACONF_SETTINGS=settings.yaml
# or run $ source env.sh

# print all values in dynaconf: namespace of yaml file
print(settings.HOST)
print(settings.PORT)
print(settings.USERNAME)
print(settings.PASSWORD)
print(settings.LEVELS)
print(settings.TEST_LOADERS)
print(settings.MONEY)
print(settings.AGE)
print(settings.ENABLED)

# using development: namespace values for context
with settings.using_namespace('DEVELOPMENT'):
print(settings.ENVIRONMENT)
print(settings.HOST)

# back to default dynaconf: namespace
print(settings.get('ENVIRONMENT'))
print(settings.HOST)

# set namespace to development:
settings.namespace('development')
print(settings.HOST)
print(settings.ENVIRONMENT)

# back to default namespace again
settings.namespace()
print(settings.HOST)
print(settings.get('ENVIRONMENT'))
2 changes: 2 additions & 0 deletions example/yaml_example/settings_module/env.sh
@@ -0,0 +1,2 @@
#!/usr/bin/env bash
export DYNACONF_SETTINGS=settings.yaml
18 changes: 18 additions & 0 deletions example/yaml_example/settings_module/settings.yaml
@@ -0,0 +1,18 @@
dynaconf:
host: server.com
port: 5000
username: admin
PASSWORD: Secret
levels:
- debug
- info
- warning
test_loaders:
dev: test_dev
prod: test_prod
money: 500.50
age: 42
enabled: true
development:
environment: this is development
host: dev_server.com
15 changes: 15 additions & 0 deletions example/yaml_example/yaml_as_extra_config/app.py
@@ -0,0 +1,15 @@
from dynaconf import settings

print(settings.YAML)
print(settings.HOST)
print(settings.PORT)


# using development: namespace values for context
with settings.using_namespace('DEVELOPMENT'):
print(settings.ENVIRONMENT)
print(settings.HOST)

# back to default dynaconf: namespace
print(settings.get('ENVIRONMENT'))
print(settings.HOST)
3 changes: 3 additions & 0 deletions example/yaml_example/yaml_as_extra_config/env.sh
@@ -0,0 +1,3 @@
#!/usr/bin/env bash
# optional if not defined in settings.py
export DYNACONF_YAML=extra_settings.yaml
6 changes: 6 additions & 0 deletions example/yaml_example/yaml_as_extra_config/extra_settings.yaml
@@ -0,0 +1,6 @@
dynaconf:
host: server.com
port: 5000
development:
environment: this is development
host: dev_server.com
3 changes: 3 additions & 0 deletions example/yaml_example/yaml_as_extra_config/settings.py
@@ -0,0 +1,3 @@
HOST = 'default.com'
PORT = 9000
YAML = 'extra_settings.yaml'
23 changes: 23 additions & 0 deletions tests/test_env_loader.py
@@ -0,0 +1,23 @@
import os
from dynaconf.loaders.env_loader import load

os.environ['DYNACONF_HOSTNAME'] = 'host.com'
os.environ['DYNACONF_PORT'] = '@int 5000'
os.environ['DYNACONF_VALUE'] = '@float 42.1'
os.environ['DYNACONF_ALIST'] = '@json ["item1", "item2", "item3"]'
os.environ['DYNACONF_ADICT'] = '@json {"key": "value"}'
os.environ['DYNACONF_DEBUG'] = '@bool true'
os.environ['PROJECT1_HOSTNAME'] = 'otherhost.com'
os.environ['PROJECT1_PORT'] = '@int 8080'

from dynaconf import settings # noqa
settings.configure()

def test_env_loader():
assert settings.HOSTNAME == 'host.com'


def test_single_key():
load(settings, namespace='PROJECT1', key='HOSTNAME')
assert settings.HOSTNAME == 'otherhost.com'
assert settings.PORT == 5000
57 changes: 57 additions & 0 deletions tests/test_yaml_loader.py
@@ -0,0 +1,57 @@
import pytest
from dynaconf import LazySettings
from dynaconf.loaders.yaml_loader import load

settings = LazySettings(
DYNACONF_NAMESPACE='EXAMPLE',
)


YAML = """
example:
host: server.com
port: 8080
development:
host: dev_server.com
"""

def test_load_from_yaml():
load(settings, filename=YAML)
assert settings.HOST == 'server.com'
assert settings.PORT == 8080
load(settings, filename=YAML, namespace='DEVELOPMENT')
assert settings.HOST == 'dev_server.com'
load(settings, filename=YAML)
assert settings.HOST == 'server.com'


def test_no_filename_is_none():
assert load(settings) is None


def test_error_on_invalid_namespace():
with pytest.raises(KeyError):
load(settings, filename=YAML, namespace='FOOBAR')


def test_load_single_key():
yaml = """
foo:
bar: blaz
zaz: naz
"""
load(settings, filename=yaml, namespace='FOO', key='bar')
assert settings.BAR == 'blaz'
assert settings.exists('BAR') is True
assert settings.exists('ZAZ') is False


def test_extra_yaml():
load(settings, filename=YAML)
yaml = """
example:
hello: world
"""
settings.set('YAML', yaml)
settings.execute_loaders(namespace='EXAMPLE')
assert settings.HELLO == 'world'

0 comments on commit dcbec6b

Please sign in to comment.