-
Notifications
You must be signed in to change notification settings - Fork 33
/
yamlfile.py
167 lines (118 loc) · 4.93 KB
/
yamlfile.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
"""Holds the ConfigYamlEnv environment.
To use this, you must install the optional requirements::
$ pip install everett[yaml]
"""
import logging
import os
import yaml
from everett import ConfigurationError, NO_VALUE
from everett.manager import generate_uppercase_key, get_key_from_envs, listify
logger = logging.getLogger("everett")
class ConfigYamlEnv(object):
"""Source for pulling configuration from YAML files.
This requires optional dependencies. You can install them with::
$ pip install everett[yaml]
Takes a path or list of possible paths to look for a YAML file. It uses
the first YAML file it can find.
If it finds no YAML files in the possible paths, then this configuration
source will be a no-op.
This will expand ``~`` as well as work relative to the current working
directory.
This example looks just for the YAML file specified in the environment::
from everett.manager import ConfigManager
from everett.ext.yamlfile import ConfigYamlEnv
config = ConfigManager([
ConfigYamlEnv(os.environ.get('FOO_YAML'))
])
If there's no ``FOO_YAML`` in the environment, then the path will be
ignored.
Here's an example that looks for the YAML file specified in the environment
variable ``FOO_YAML`` and failing that will look for ``.antenna.yaml`` in
the user's home directory::
from everett.manager import ConfigManager
from everett.ext.yamlfile import ConfigYamlEnv
config = ConfigManager([
ConfigYamlEnv([
os.environ.get('FOO_YAML'),
'~/.antenna.yaml'
])
])
This example looks for a ``config/local.yaml`` file which overrides values
in a ``config/base.yaml`` file both are relative to the current working
directory::
from everett.manager import ConfigManager
from everett.ext.yamlfile import ConfigYamlEnv
config = ConfigManager([
ConfigYamlEnv('config/local.yaml'),
ConfigYamlEnv('config/base.yaml')
])
Note how you can have multiple ``ConfigYamlEnv`` files. This is how you
can set Everett up to have values in one YAML file override values in
another YAML file.
Everett looks for keys and values in YAML files. YAML files can be split
into multiple documents, but Everett only looks at the first one.
Keys are case-insensitive. You can do namespaces either in the key itself
using ``_`` as a separator or as nested mappings.
All values should be double-quoted.
Here's an example::
foo: "bar"
FOO2: "bar"
namespace_foo: "bar"
namespace:
namespace2:
foo: "bar"
Giving you these namespaced keys:
* ``FOO``
* ``FOO2``
* ``NAMESPACE_FOO``
* ``NAMESPACE_NAMEPSACE2_FOO``
"""
def __init__(self, possible_paths):
self.cfg = {}
self.path = None
possible_paths = listify(possible_paths)
for path in possible_paths:
if not path:
continue
path = os.path.abspath(os.path.expanduser(path.strip()))
if path and os.path.isfile(path):
self.path = path
self.cfg = self.parse_yaml_file(path)
break
if not self.path:
logger.debug("No YAML file found: %s", possible_paths)
def parse_yaml_file(self, path):
"""Parse yaml file at ``path`` and return a dict."""
with open(path, "r") as fp:
data = yaml.safe_load(fp)
if not data:
return {}
def traverse(namespace, d):
cfg = {}
for key, val in d.items():
if isinstance(val, dict):
cfg.update(traverse(namespace + [key], val))
elif isinstance(val, str):
cfg["_".join(namespace + [key]).upper()] = val
else:
# All values should be double-quoted strings so they
# parse as strings; anything else is a configuration
# error at parse-time
raise ConfigurationError(
"Invalid value %r in file %s: values must be double-quoted strings"
% (val, path)
)
return cfg
return traverse([], data)
def get(self, key, namespace=None):
"""Retrieve value for key."""
if not self.path:
return NO_VALUE
logger.debug("Searching %r for key: %s, namepsace: %s", self, key, namespace)
full_key = generate_uppercase_key(key, namespace)
return get_key_from_envs(self.cfg, full_key)
def __repr__(self):
return "<ConfigYamlEnv: %s>" % self.path