|
1
|
|
|
#!/usr/bin/env python |
|
2
|
|
|
# coding=utf-8 |
|
3
|
|
|
from __future__ import division, print_function, unicode_literals |
|
4
|
|
|
|
|
5
|
|
|
import jsonpickle.tags |
|
6
|
|
|
|
|
7
|
|
|
from sacred import SETTINGS |
|
8
|
|
|
import sacred.optional as opt |
|
9
|
|
|
from sacred.config.custom_containers import DogmaticDict, DogmaticList |
|
10
|
|
|
from sacred.utils import PYTHON_IDENTIFIER |
|
11
|
|
|
from sacred.optional import basestring |
|
12
|
|
|
|
|
13
|
|
|
|
|
14
|
|
|
def assert_is_valid_key(key): |
|
15
|
|
|
""" |
|
16
|
|
|
Raise KeyError if a given config key violates any requirements. |
|
17
|
|
|
|
|
18
|
|
|
The requirements are the following and can be individually deactivated |
|
19
|
|
|
in ``sacred.SETTINGS.CONFIG_KEYS``: |
|
20
|
|
|
* ENFORCE_MONGO_COMPATIBLE (default: True): |
|
21
|
|
|
make sure the keys don't contain a '.' or start with a '$' |
|
22
|
|
|
* ENFORCE_JSONPICKLE_COMPATIBLE (default: True): |
|
23
|
|
|
make sure the keys do not contain any reserved jsonpickle tags |
|
24
|
|
|
This is very important. Only deactivate if you know what you are doing. |
|
25
|
|
|
* ENFORCE_STRING (default: False): |
|
26
|
|
|
make sure all keys are string. |
|
27
|
|
|
* ENFORCE_VALID_PYTHON_IDENTIFIER (default: False): |
|
28
|
|
|
make sure all keys are valid python identifiers. |
|
29
|
|
|
|
|
30
|
|
|
Parameters |
|
31
|
|
|
---------- |
|
32
|
|
|
key: |
|
33
|
|
|
The key that should be checked |
|
34
|
|
|
|
|
35
|
|
|
Raises |
|
36
|
|
|
------ |
|
37
|
|
|
KeyError: |
|
38
|
|
|
if the key violates any requirements |
|
39
|
|
|
""" |
|
40
|
|
|
if SETTINGS.CONFIG.ENFORCE_KEYS_MONGO_COMPATIBLE and ( |
|
41
|
|
|
isinstance(key, basestring) and (key.find('.') > -1 or |
|
42
|
|
|
key.startswith('$'))): |
|
43
|
|
|
raise KeyError('Invalid key "{}". Config-keys cannot ' |
|
44
|
|
|
'contain "." or start with "$"'.format(key)) |
|
45
|
|
|
|
|
46
|
|
|
if SETTINGS.CONFIG.ENFORCE_KEYS_JSONPICKLE_COMPATIBLE and ( |
|
47
|
|
|
key in jsonpickle.tags.RESERVED or key.startswith('json://')): |
|
48
|
|
|
raise KeyError('Invalid key "{}". Config-keys cannot be one of the' |
|
49
|
|
|
'reserved jsonpickle tags: {}' |
|
50
|
|
|
.format(key, jsonpickle.tags.RESERVED)) |
|
51
|
|
|
|
|
52
|
|
|
if SETTINGS.CONFIG.ENFORCE_STRING_KEYS and ( |
|
53
|
|
|
not isinstance(key, basestring)): |
|
54
|
|
|
raise KeyError('Invalid key "{}". Config-keys have to be strings, ' |
|
55
|
|
|
'but was {}'.format(key, type(key))) |
|
56
|
|
|
|
|
57
|
|
|
if SETTINGS.CONFIG.ENFORCE_VALID_PYTHON_IDENTIFIER_KEYS and ( |
|
58
|
|
|
isinstance(key, basestring) and not PYTHON_IDENTIFIER.match(key)): |
|
59
|
|
|
raise KeyError('Key "{}" is not a valid python identifier' |
|
60
|
|
|
.format(key)) |
|
61
|
|
|
|
|
62
|
|
|
if SETTINGS.CONFIG.ENFORCE_KEYS_NO_EQUALS and ( |
|
63
|
|
|
isinstance(key, basestring) and '=' in key): |
|
64
|
|
|
raise KeyError('Invalid key "{}". Config keys may not contain an' |
|
65
|
|
|
'equals sign ("=").'.format('=')) |
|
66
|
|
|
|
|
67
|
|
|
|
|
68
|
|
|
def normalize_numpy(obj): |
|
69
|
|
|
if opt.has_numpy and isinstance(obj, opt.np.generic): |
|
70
|
|
|
try: |
|
71
|
|
|
return opt.np.asscalar(obj) |
|
72
|
|
|
except ValueError: |
|
73
|
|
|
pass |
|
74
|
|
|
return obj |
|
75
|
|
|
|
|
76
|
|
|
|
|
77
|
|
|
def normalize_or_die(obj): |
|
78
|
|
|
if isinstance(obj, dict): |
|
79
|
|
|
res = dict() |
|
80
|
|
|
for key, value in obj.items(): |
|
81
|
|
|
assert_is_valid_key(key) |
|
82
|
|
|
res[key] = normalize_or_die(value) |
|
83
|
|
|
return res |
|
84
|
|
|
elif isinstance(obj, (list, tuple)): |
|
85
|
|
|
return list([normalize_or_die(value) for value in obj]) |
|
86
|
|
|
return normalize_numpy(obj) |
|
87
|
|
|
|
|
88
|
|
|
|
|
89
|
|
|
def recursive_fill_in(config, preset): |
|
90
|
|
|
for key in preset: |
|
91
|
|
|
if key not in config: |
|
92
|
|
|
config[key] = preset[key] |
|
93
|
|
|
elif isinstance(config[key], dict): |
|
94
|
|
|
recursive_fill_in(config[key], preset[key]) |
|
95
|
|
|
|
|
96
|
|
|
|
|
97
|
|
|
def chain_evaluate_config_scopes(config_scopes, fixed=None, preset=None, |
|
98
|
|
|
fallback=None): |
|
99
|
|
|
fixed = fixed or {} |
|
100
|
|
|
fallback = fallback or {} |
|
101
|
|
|
final_config = dict(preset or {}) |
|
102
|
|
|
config_summaries = [] |
|
103
|
|
|
for config in config_scopes: |
|
104
|
|
|
cfg = config(fixed=fixed, |
|
105
|
|
|
preset=final_config, |
|
106
|
|
|
fallback=fallback) |
|
107
|
|
|
config_summaries.append(cfg) |
|
108
|
|
|
final_config.update(cfg) |
|
109
|
|
|
|
|
110
|
|
|
if not config_scopes: |
|
111
|
|
|
final_config.update(fixed) |
|
112
|
|
|
|
|
113
|
|
|
return undogmatize(final_config), config_summaries |
|
114
|
|
|
|
|
115
|
|
|
|
|
116
|
|
|
def dogmatize(obj): |
|
117
|
|
|
if isinstance(obj, dict): |
|
118
|
|
|
return DogmaticDict({key: dogmatize(val) for key, val in obj.items()}) |
|
119
|
|
|
elif isinstance(obj, list): |
|
120
|
|
|
return DogmaticList([dogmatize(value) for value in obj]) |
|
121
|
|
|
elif isinstance(obj, tuple): |
|
122
|
|
|
return tuple(dogmatize(value) for value in obj) |
|
123
|
|
|
else: |
|
124
|
|
|
return obj |
|
125
|
|
|
|
|
126
|
|
|
|
|
127
|
|
|
def undogmatize(obj): |
|
128
|
|
|
if isinstance(obj, DogmaticDict): |
|
129
|
|
|
return dict({key: undogmatize(value) for key, value in obj.items()}) |
|
130
|
|
|
elif isinstance(obj, DogmaticList): |
|
131
|
|
|
return list([undogmatize(value) for value in obj]) |
|
132
|
|
|
elif isinstance(obj, tuple): |
|
133
|
|
|
return tuple(undogmatize(value) for value in obj) |
|
134
|
|
|
else: |
|
135
|
|
|
return obj |
|
136
|
|
|
|