@@ -213,6 +213,18 @@ class _ConfigEntry:
213213 # environment variables are read at install time
214214 env_value_force : Any = _UNSET_SENTINEL
215215 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
216228
217229 def __init__ (self , config : Config ):
218230 self .default = config .default
@@ -253,11 +265,15 @@ def __setattr__(self, name: str, value: object) -> None:
253265 else :
254266 self ._config [name ].user_override = value
255267 self ._is_dirty = True
268+ self ._config [name ].hide = False
256269
257270 def __getattr__ (self , name : str ) -> Any :
258271 try :
259272 config = self ._config [name ]
260273
274+ if config .hide :
275+ raise AttributeError (f"{ self .__name__ } .{ name } does not exist" )
276+
261277 if config .env_value_force is not _UNSET_SENTINEL :
262278 return config .env_value_force
263279
@@ -288,6 +304,7 @@ def __delattr__(self, name: str) -> None:
288304 # must support delete because unittest.mock.patch deletes
289305 # then recreate things
290306 self ._config [name ].user_override = _UNSET_SENTINEL
307+ self ._config [name ].hide = True
291308
292309 def _is_default (self , name : str ) -> bool :
293310 return self ._config [name ].user_override is _UNSET_SENTINEL
0 commit comments