@@ -68,6 +68,17 @@ def _relocate(exc: ValidationError, field_name: str) -> ValidationError:
6868 return ValidationError .from_exception_data (exc .title , lines )
6969
7070
71+ def _is_default (cls : type [BaseSettings ], field_name : str , value : Any ) -> bool :
72+ """Whether the validated ``value`` is what the field defaults to."""
73+ info = cls .model_fields [field_name ]
74+ if info .is_required ():
75+ return False
76+ default = info .get_default (call_default_factory = True )
77+ return to_jsonable_python (_unwrap_secrets (default )) == to_jsonable_python (
78+ _unwrap_secrets (value )
79+ )
80+
81+
7182async def apply_changes_and_reload (
7283 app : FastAPI ,
7384 bus : EventBus ,
@@ -95,6 +106,17 @@ async def apply_changes_and_reload(
95106 # rewritten to the store on every save.
96107 merged = current .model_dump ()
97108 diff = {k : v for k , v in changes .items () if merged .get (k ) != v }
109+
110+ # A row that already holds the default (stored before saving a default
111+ # cleared the row) is not a change, so the diff never reaches it; without
112+ # this the field stays "overridden in DB" however often it is saved.
113+ unchanged = set (changes ) - set (diff ) - env_supplied_fields (cls )
114+ if unchanged :
115+ stored = await store .get_overrides (package )
116+ for name in sorted (unchanged & set (stored )):
117+ if _is_default (cls , name , merged .get (name )):
118+ await store .clear_override (package , name )
119+
98120 if not diff :
99121 return current
100122
@@ -106,6 +128,7 @@ async def apply_changes_and_reload(
106128
107129 merged .update (diff )
108130 validated = cls (** merged )
131+ dumped_all = validated .model_dump ()
109132
110133 # A json field stores what validated, not what was sent: a field validator
111134 # may have turned the editor's text into the declared type (``json.loads``
@@ -116,6 +139,12 @@ async def apply_changes_and_reload(
116139 # are unwrapped before the validated value is made JSON-able.
117140 for field_name , raw_value in diff .items ():
118141 vtype = value_type_for_field (cls , field_name )
142+ if _is_default (cls , field_name , dumped_all .get (field_name , raw_value )):
143+ # Saving the default is clearing the override: a stored row would
144+ # keep the field labelled "overridden in DB" while holding exactly
145+ # what the code would have supplied anyway.
146+ await store .clear_override (package , field_name )
147+ continue
119148 if vtype == "json" :
120149 dumped = validated .model_dump (include = {field_name }).get (field_name , raw_value )
121150 raw_value = to_jsonable_python (_unwrap_secrets (dumped ))
0 commit comments