On this page
Aether config two doors๏
๐ Advanced ยท Lesson 07
TWO DOORS TO ONE CONFIG, AND THE LADDER BEHIND THEM.
Aether hands you two ways to build its root configuration: aether.create_configuration() -> AetherConfiguration aether.create_configuration_builder() -> AetherConfigurationBuilder
They are NOT redundant, and they do NOT end in the same place by accident - they end in the same place ON PURPOSE:
config.with_(...).finalize() -> FROZEN builder.with_(...).build() -> FROZEN
finalize() freezes and returns THE SAME OBJECT. build() freezes and HANDS OVER OWNERSHIP - it is one-shot and consuming, so the builder is spent afterwards. Reach for the builder when construction happens somewhere that should not keep a handle on the result.
THE PART EVERYONE GETS WRONG: FROZEN IS NOT READY.
Freezing and activating are TWO SEPARATE STATE BITS, and this is the single most confusing thing about melder's configuration model until you see it written down:
frozen - "no more edits" (finalize / build / freeze) activated - "in force" (activate)
A config can sit frozen for a long time before anything turns it on. So the ladder is three rungs, in this order, and the order is a RULE rather than a convention:
finalize() or build() -> frozen 2. configuration.activate() -> activated 3. aether.activate(config) -> Aether itself comes up
Skipping rung 2 raises. Aether's own contract says so in capitals: "THE CONFIGURATION MUST BE ACTIVATED BEFORE AETHER CAN BE." The two failure modes stay distinct on purpose - "not configured" and "configuration not activated" are different sentences because they are different bugs.
RUNG 3 IS NOT PERFORMED HERE, DELIBERATELY. Aether is a process-wide singleton, and aether.activate(cfg) installs the config BEFORE it checks the activated bit - so even the refusing call mutates the world. A lesson that shares an interpreter with every other lesson must not do that. The refusal is pinned in pytest_examples/test_advanced_probes.py, where the reset fixture owns a clean singleton.
Before you run๏
Use the Advanced guide for prerequisite concepts. Run from a checkout with Melder installed and Python 3.14 free-threading selected. The collection download includes the level's local helper modules.
Run the saved script๏
python UX_and_AIX_experiences/03_advanced/07_aether_config_two_doors.py
py -3.14t UX_and_AIX_experiences/03_advanced/07_aether_config_two_doors.py
Public surface๏
md.Aether().create_configuration, create_configuration_builder, md.AetherConfiguration, md.AetherConfigurationBuilder, the freeze/activate split
Code๏
1"""
2TIER: advanced (07)
3GOAL: TWO DOORS TO ONE CONFIG, AND THE LADDER BEHIND THEM.
4
5 Aether hands you two ways to build its root configuration:
6 aether.create_configuration() -> AetherConfiguration
7 aether.create_configuration_builder() -> AetherConfigurationBuilder
8
9 They are NOT redundant, and they do NOT end in the same place by
10 accident - they end in the same place ON PURPOSE:
11
12 config.with_*(...).finalize() -> FROZEN
13 builder.with_*(...).build() -> FROZEN
14
15 finalize() freezes and returns THE SAME OBJECT. build() freezes and
16 HANDS OVER OWNERSHIP - it is one-shot and consuming, so the builder
17 is spent afterwards. Reach for the builder when construction happens
18 somewhere that should not keep a handle on the result.
19
20 THE PART EVERYONE GETS WRONG: FROZEN IS NOT READY.
21
22 Freezing and activating are TWO SEPARATE STATE BITS, and this is the
23 single most confusing thing about melder's configuration model until
24 you see it written down:
25
26 frozen - "no more edits" (finalize / build / freeze)
27 activated - "in force" (activate)
28
29 A config can sit frozen for a long time before anything turns it on.
30 So the ladder is three rungs, in this order, and the order is a RULE
31 rather than a convention:
32
33 1. finalize() or build() -> frozen
34 2. configuration.activate() -> activated
35 3. aether.activate(config) -> Aether itself comes up
36
37 Skipping rung 2 raises. Aether's own contract says so in capitals:
38 "THE CONFIGURATION MUST BE ACTIVATED BEFORE AETHER CAN BE." The two
39 failure modes stay distinct on purpose - "not configured" and
40 "configuration not activated" are different sentences because they
41 are different bugs.
42
43 RUNG 3 IS NOT PERFORMED HERE, DELIBERATELY. Aether is a process-wide
44 singleton, and `aether.activate(cfg)` installs the config BEFORE it
45 checks the activated bit - so even the refusing call mutates the
46 world. A lesson that shares an interpreter with every other lesson
47 must not do that. The refusal is pinned in
48 pytest_examples/test_advanced_probes.py, where the reset fixture
49 owns a clean singleton.
50SURFACE EXERCISED: md.Aether().create_configuration,
51 create_configuration_builder, md.AetherConfiguration,
52 md.AetherConfigurationBuilder, the freeze/activate split
53VERIFY: rides the owner's 3.14t run; asserts are the contract.
54"""
55import melder as md
56
57
58def main() -> None:
59 aether = md.Aether()
60
61 # DOOR 1 - the configuration object, driven fluently.
62 # create_configuration() is a FACTORY: it hands back a fresh, UNATTACHED
63 # config. Making one does not install it anywhere, which is exactly why
64 # this lesson can build several without disturbing the running world.
65 config = aether.create_configuration()
66 assert isinstance(config, md.AetherConfiguration)
67 assert config.frozen is False
68 assert config.activated is False
69 print("door 1: fresh config - frozen:", config.frozen,
70 "activated:", config.activated)
71
72 # finalize() is the fluent terminator: freeze, return THIS object.
73 sealed = config.with_defaults().finalize()
74 assert sealed is config, "finalize must not clone the configuration"
75 assert config.frozen is True
76 print("after finalize - frozen:", config.frozen,
77 "activated:", config.activated)
78
79 # THE POINT OF THE LESSON. Two bits, and freezing set only one of them.
80 assert config.frozen is True and config.activated is False
81 print("frozen is NOT ready - activated is still False")
82
83 # Rung 2. Now it is in force.
84 config.activate()
85 assert config.activated is True
86 print("after activate - frozen:", config.frozen,
87 "activated:", config.activated)
88
89 # DOOR 2 - the builder. Same destination, different ownership story.
90 builder = aether.create_configuration_builder()
91 assert isinstance(builder, md.AetherConfigurationBuilder)
92 built = builder.with_defaults().build()
93 assert isinstance(built, md.AetherConfiguration)
94
95 # build() lands on EXACTLY the rung finalize() did: frozen, not
96 # activated. The builder is spent; the config belongs to the caller now.
97 assert built.frozen is True
98 assert built.activated is False
99 print("door 2: built config - frozen:", built.frozen,
100 "activated:", built.activated)
101 print("both doors land on the same rung: frozen, not yet in force")
102
103 # The two configs are separate objects with separate bits - proof that
104 # neither door reaches into shared state to do its work.
105 assert built is not config
106 assert config.activated is True and built.activated is False
107 print("independent objects, independent bits")
108
109 print()
110 print("finalize/build = frozen. activate = in force. two bits, not one.")
111 print("the config activates BEFORE aether does - that ordering is a rule")
112
113
114if __name__ == "__main__":
115 main()
Check the outcome๏
The script contains its own assertions or demonstrated refusal paths. Run it to evaluate those checks against your installed version. The code above is taken directly from the saved file; no run output is invented here.