Skip to content

Commit 4d75046

Browse files
authored
Add option to load traitlets values from environement. (#856)
1 parent 5064f4e commit 4d75046

2 files changed

Lines changed: 71 additions & 2 deletions

File tree

examples/myapp.py

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,14 @@
3636
from traitlets.config.configurable import Configurable
3737

3838

39+
class SubConfigurable(Configurable):
40+
subvalue = Int(0, help="The integer subvalue.").tag(config=True)
41+
42+
def describe(self):
43+
print("I am SubConfigurable with:")
44+
print(" subvalue =", self.subvalue)
45+
46+
3947
class Foo(Configurable):
4048
"""A class that has configurable, typed attributes."""
4149

@@ -44,9 +52,39 @@ class Foo(Configurable):
4452
name = Unicode("Brian", help="First name.").tag(config=True, shortname="B")
4553
mode = Enum(values=["on", "off", "other"], default_value="on").tag(config=True)
4654

55+
def __init__(self, **kwargs):
56+
super().__init__(**kwargs)
57+
# using parent=self allows configuration in the form c.Foo.SubConfigurable.subvalue=1
58+
# while c.SubConfigurable.subvalue=1 will still work, this allow to
59+
# target specific instances of SubConfigurables
60+
self.subconf = SubConfigurable(parent=self)
61+
62+
def describe(self):
63+
print("I am Foo with:")
64+
print(" i =", self.i)
65+
print(" j =", self.j)
66+
print(" name =", self.name)
67+
print(" mode =", self.mode)
68+
self.subconf.describe()
69+
4770

4871
class Bar(Configurable):
4972
enabled = Bool(True, help="Enable bar.").tag(config=True)
73+
mylist = List([1, 2, 3], help="Just a list.").tag(config=True)
74+
75+
def describe(self):
76+
print("I am Bar with:")
77+
print(" enabled = ", self.enabled)
78+
print(" mylist = ", self.mylist)
79+
self.subconf.describe()
80+
81+
def __init__(self, **kwargs):
82+
super().__init__(**kwargs)
83+
# here we do not use parent=self, so configuration in the form
84+
# c.Bar.SubConfigurable.subvalue=1 will not work. Only
85+
# c.SubConfigurable.subvalue=1 will work and affect all instances of
86+
# SubConfigurable
87+
self.subconf = SubConfigurable(config=self.config)
5088

5189

5290
class MyApp(Application):
@@ -76,8 +114,8 @@ class MyApp(Application):
76114
)
77115

78116
def init_foo(self):
79-
# Pass config to other classes for them to inherit the config.
80-
self.foo = Foo(config=self.config)
117+
# You can pass self as parent to automatically propagate config.
118+
self.foo = Foo(parent=self)
81119

82120
def init_bar(self):
83121
# Pass config to other classes for them to inherit the config.
@@ -87,19 +125,26 @@ def initialize(self, argv=None):
87125
self.parse_command_line(argv)
88126
if self.config_file:
89127
self.load_config_file(self.config_file)
128+
self.load_config_environ()
90129
self.init_foo()
91130
self.init_bar()
92131

93132
def start(self):
94133
print("app.config:")
95134
print(self.config)
135+
self.describe()
96136
print("try running with --help-all to see all available flags")
97137
assert self.log is not None
98138
self.log.debug("Debug Message")
99139
self.log.info("Info Message")
100140
self.log.warning("Warning Message")
101141
self.log.critical("Critical Message")
102142

143+
def describe(self):
144+
print("I am MyApp with", self.name, self.running, "and 2 sub configurables Foo and bar:")
145+
self.foo.describe()
146+
self.bar.describe()
147+
103148

104149
def main():
105150
app = MyApp()

traitlets/config/application.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
ArgumentError,
2424
Config,
2525
ConfigFileNotFound,
26+
DeferredConfigString,
2627
JSONFileConfigLoader,
2728
KVArgParseConfigLoader,
2829
PyFileConfigLoader,
@@ -970,6 +971,29 @@ def load_config_file(
970971
new_config.merge(self.cli_config)
971972
self.update_config(new_config)
972973

974+
@catch_config_error
975+
def load_config_environ(self) -> None:
976+
"""Load config files by environment."""
977+
978+
PREFIX = self.name.upper()
979+
new_config = Config()
980+
981+
self.log.debug('Looping through config variables with prefix "%s"', PREFIX)
982+
983+
for k, v in os.environ.items():
984+
if k.startswith(PREFIX):
985+
self.log.debug('Seeing environ "%s"="%s"', k, v)
986+
# use __ instead of . as separator in env variable.
987+
# Warning, case sensitive !
988+
_, *path, key = k.split("__")
989+
section = new_config
990+
for p in path:
991+
section = section[p]
992+
setattr(section, key, DeferredConfigString(v))
993+
994+
new_config.merge(self.cli_config)
995+
self.update_config(new_config)
996+
973997
def _classes_with_config_traits(
974998
self, classes: ClassesType | None = None
975999
) -> t.Generator[type[Configurable], None, None]:

0 commit comments

Comments
 (0)