@@ -213,6 +213,18 @@ class _ConfigEntry:
213
213
# environment variables are read at install time
214
214
env_value_force : Any = _UNSET_SENTINEL
215
215
env_value_default : Any = _UNSET_SENTINEL
216
+ # Used to work arounds bad assumptions in unittest.mock.patch
217
+ # The code to blame is
218
+ # https://github.com/python/cpython/blob/94a7a4e22fb8f567090514785c69e65298acca42/Lib/unittest/mock.py#L1637
219
+ # Essentially, mock.patch requires, that if __dict__ isn't accessible
220
+ # (which it isn't), that after delattr is called on the object, the
221
+ # object must throw when hasattr is called. Otherwise, it doesn't call
222
+ # setattr again.
223
+ # Technically we'll have an intermediate state of hiding the config while
224
+ # mock.patch is unpatching itself, but it calls setattr after the delete
225
+ # call so the final state is correct. It's just very unintuitive.
226
+ # upstream bug - python/cpython#126886
227
+ hide : bool = False
216
228
217
229
def __init__ (self , config : Config ):
218
230
self .default = config .default
@@ -253,11 +265,15 @@ def __setattr__(self, name: str, value: object) -> None:
253
265
else :
254
266
self ._config [name ].user_override = value
255
267
self ._is_dirty = True
268
+ self ._config [name ].hide = False
256
269
257
270
def __getattr__ (self , name : str ) -> Any :
258
271
try :
259
272
config = self ._config [name ]
260
273
274
+ if config .hide :
275
+ raise AttributeError (f"{ self .__name__ } .{ name } does not exist" )
276
+
261
277
if config .env_value_force is not _UNSET_SENTINEL :
262
278
return config .env_value_force
263
279
@@ -288,6 +304,7 @@ def __delattr__(self, name: str) -> None:
288
304
# must support delete because unittest.mock.patch deletes
289
305
# then recreate things
290
306
self ._config [name ].user_override = _UNSET_SENTINEL
307
+ self ._config [name ].hide = True
291
308
292
309
def _is_default (self , name : str ) -> bool :
293
310
return self ._config [name ].user_override is _UNSET_SENTINEL
0 commit comments