A context manager that tries to make an object not exhibit side-effects on attribute lookup. Unless explicitly required, prefer `getattr_safe`.
| 75 | |
| 76 | |
| 77 | class AttrCleaner(ContextManager[None]): |
| 78 | """A context manager that tries to make an object not exhibit side-effects |
| 79 | on attribute lookup. |
| 80 | |
| 81 | Unless explicitly required, prefer `getattr_safe`.""" |
| 82 | |
| 83 | def __init__(self, obj: Any) -> None: |
| 84 | self._obj = obj |
| 85 | |
| 86 | def __enter__(self) -> None: |
| 87 | """Try to make an object not exhibit side-effects on attribute |
| 88 | lookup.""" |
| 89 | type_ = type(self._obj) |
| 90 | # Dark magic: |
| 91 | # If __getattribute__ doesn't exist on the class and __getattr__ does |
| 92 | # then __getattr__ will be called when doing |
| 93 | # getattr(type_, '__getattribute__', None) |
| 94 | # so we need to first remove the __getattr__, then the |
| 95 | # __getattribute__, then look up the attributes and then restore the |
| 96 | # original methods. :-( |
| 97 | # The upshot being that introspecting on an object to display its |
| 98 | # attributes will avoid unwanted side-effects. |
| 99 | __getattr__ = getattr(type_, "__getattr__", None) |
| 100 | if __getattr__ is not None: |
| 101 | try: |
| 102 | setattr(type_, "__getattr__", (lambda *_, **__: None)) |
| 103 | except (TypeError, AttributeError): |
| 104 | __getattr__ = None |
| 105 | __getattribute__ = getattr(type_, "__getattribute__", None) |
| 106 | if __getattribute__ is not None: |
| 107 | try: |
| 108 | setattr(type_, "__getattribute__", object.__getattribute__) |
| 109 | except (TypeError, AttributeError): |
| 110 | # XXX: This happens for e.g. built-in types |
| 111 | __getattribute__ = None |
| 112 | self._attribs = (__getattribute__, __getattr__) |
| 113 | # /Dark magic |
| 114 | |
| 115 | def __exit__( |
| 116 | self, |
| 117 | exc_type: type[BaseException] | None, |
| 118 | exc_val: BaseException | None, |
| 119 | exc_tb: TracebackType | None, |
| 120 | ) -> Literal[False]: |
| 121 | """Restore an object's magic methods.""" |
| 122 | type_ = type(self._obj) |
| 123 | __getattribute__, __getattr__ = self._attribs |
| 124 | # Dark magic: |
| 125 | if __getattribute__ is not None: |
| 126 | setattr(type_, "__getattribute__", __getattribute__) |
| 127 | if __getattr__ is not None: |
| 128 | setattr(type_, "__getattr__", __getattr__) |
| 129 | # /Dark magic |
| 130 | return False |
| 131 | |
| 132 | |
| 133 | def parsekeywordpairs(signature: str) -> dict[str, str]: |
nothing calls this directly
no outgoing calls
no test coverage detected