-
Notifications
You must be signed in to change notification settings - Fork 0
/
config_functions.py
134 lines (107 loc) · 4.54 KB
/
config_functions.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
import inspect
from abc import ABC
from dataclasses import FrozenInstanceError
from copy import deepcopy
from collections.abc import Mapping
from . import print_utils
from .fields import InterfaceField
from . import figutils
class MasterConfig(ABC):
"""
Defines functions that goes into every config class
"""
def __init__(self):
"""
Empty init by default
"""
pass
def allowed_cli_args(self):
''' Returns the attribute names that can be be overwritten from command line input '''
return self.get_parameters()
def post_init(self):
''' A function that is called after overwriting from command line input '''
pass
def cli_help(self):
return print_utils.cli_help(self)
def frozen(self, freeze=True):
"""
Freeze/unfreeze the config. Frozen configs are unmutable
Args:
freeze (bool, optional): Freeze config. Defaults to True.
Returns:
ConfigClass: Config instance
"""
self._frozen = freeze
for _, val in self.get_parameters(copy=False).items():
if figutils.is_config_class(val):
val.frozen(freeze)
return self
def get_parameters(self, copy=True):
''' Returns the config attributes. Doesn't include Anyfig built-ins '''
default_attrs = figutils.default_config_attributes()
params = {k: v for k, v in vars(self).items() if k not in default_attrs}
if copy:
return deepcopy(params)
return params
def __str__(self):
return self.pretty()
def pretty(self):
''' Pretty string representation of the config '''
lines = [f'{type(self).__name__}:']
for key, val in self.get_parameters(copy=False).items():
val_str = str(val)
if figutils.is_config_class(val): # Remove class name info
val_str = val_str.replace(f'{type(val).__name__}:', '')
lines += f'{key} ({type(val).__name__}): {val_str}'.split('\n')
return '\n '.join(lines)
def __setattr__(self, name, value):
config_class = type(self).__name__
# Handle interface values
old_value = getattr(self, name, None)
if isinstance(old_value, InterfaceField):
value = old_value.update_value(name, value, config_class)
# Raise error if frozen unless we're trying to unfreeze the config
if getattr(self, '_frozen', False) and name != '_frozen':
err_msg = f"Can't set attribute '{name}'. Config object is frozen. Unfreeze the config to make it mutable"
raise FrozenInstanceError(err_msg)
# Check for reserved names
name_taken_msg = (
f"The attribute '{name}' can't be assigned to config '{config_class}' since it already has a method by that name"
)
methods = inspect.getmembers(self, predicate=inspect.ismethod)
for method in methods:
assert name != method[0], name_taken_msg
object.__setattr__(self, name, value)
def build(self, external_args=None):
''' Instantiates target object connected to config class '''
if external_args is None:
external_args = dict()
config_name = type(self).__name__
assert self._build_target is not None, f"Config class '{config_name}' isn't connected to a target"
err_msg = f"Expected 'external_args' to be dict like object, was type '{type(external_args)}'"
assert isinstance(external_args, Mapping), err_msg
config_attrs = self.get_parameters()
common_keys = set(config_attrs).intersection(set(external_args))
err_msg = f"Arguments '{', '.join(common_keys)}' aren't allowed as they are already defined in '{config_name}'"
assert common_keys == set(), err_msg
build_args = {**external_args, **config_attrs}
err_msg_base = f'when building {self._build_target} for config {config_name}'
target = self._build_target
# If target is a class and not implemented in C-code
if inspect.isclass(target) and target.__init__ != object.__init__:
target = target.__init__
# Validate inspected arguments
try:
all_args, required_args = figutils.find_arguments(target)
err_msg = f"Unexpected arguments {set(build_args) - set(all_args)} {err_msg_base}"
assert set(build_args) <= set(all_args), err_msg
err_msg = f"Missing required arguments {set(required_args) - set(build_args)} {err_msg_base}"
assert set(required_args) <= set(build_args), err_msg
# C-code (CPython) can't be inspected so arguments can't be validated
except ValueError:
try:
return self._build_target(**build_args)
except Exception as e:
# Add to error message if instantiation didn't succeed
raise type(e)(f"{e} {err_msg_base}") from None
return self._build_target(**build_args)