|
8 | 8 | ''' |
9 | 9 |
|
10 | 10 | import sys |
11 | | -import types |
12 | | -import string |
13 | | -import random |
14 | | - |
15 | | -from python_toolbox import caching |
16 | 11 |
|
17 | 12 | from .context_manager_type import ContextManagerType |
18 | | -from .context_manager import ContextManager |
19 | 13 |
|
20 | 14 |
|
21 | 15 | @ContextManagerType |
@@ -48,199 +42,3 @@ def nested(*managers): |
48 | 42 | raise exc[1].with_traceback(exc[2]) |
49 | 43 |
|
50 | 44 |
|
51 | | -def as_idempotent(context_manager): |
52 | | - ''' |
53 | | - Wrap a context manager so repeated calls to enter and exit will be ignored. |
54 | | - |
55 | | - This means that if you call `__enter__` a second time on the context |
56 | | - manager, nothing will happen. The `__enter__` method won't be called and an |
57 | | - exception would not be raised. Same goes for the `__exit__` method, after |
58 | | - calling it once, if you try to call it again it will be a no-op. But now |
59 | | - that you've called `__exit__` you can call `__enter__` and it will really |
60 | | - do the enter action again, and then `__exit__` will be available again, |
61 | | - etc. |
62 | | - |
63 | | - This is useful when you have a context manager that you want to put in an |
64 | | - `ExitStack`, but you also possibly want to exit it manually before the |
65 | | - `ExitStack` closes. This way you don't risk an exception by having the |
66 | | - context manager exit twice. |
67 | | -
|
68 | | - Note: The first value returned by `__enter__` will be returned by all the |
69 | | - subsequent no-op `__enter__` calls. |
70 | | - |
71 | | - This can be used when calling an existing context manager: |
72 | | - |
73 | | - with as_idempotent(some_context_manager): |
74 | | - # Now we're idempotent! |
75 | | - |
76 | | - Or it can be used when defining a context manager to make it idempotent: |
77 | | - |
78 | | - @as_idempotent |
79 | | - class MyContextManager(ContextManager): |
80 | | - def __enter__(self): |
81 | | - # ... |
82 | | - def __exit__(self, exc_type, exc_value, exc_traceback): |
83 | | - # ... |
84 | | - |
85 | | - And also like this... |
86 | | -
|
87 | | - |
88 | | - @as_idempotent |
89 | | - @ContextManagerType |
90 | | - def Meow(): |
91 | | - yield # ... |
92 | | - |
93 | | - blocktodo: add tests for decorating ContextManager class and |
94 | | - @ContextManagerType generator. |
95 | | - ''' |
96 | | - return _IdempotentContextManager._wrap_context_manager_or_class( |
97 | | - context_manager, |
98 | | - ) |
99 | | - |
100 | | - |
101 | | -def as_reentrant(context_manager): |
102 | | - ''' |
103 | | - Wrap a context manager to make it reentant. |
104 | | - |
105 | | - A context manager wrapped with `as_reentrant` could be entered multiple |
106 | | - times, and only after it's been exited the same number of times that it has |
107 | | - been entered will the original `__exit__` method be called. |
108 | | - |
109 | | - Note: The first value returned by `__enter__` will be returned by all the |
110 | | - subsequent no-op `__enter__` calls. |
111 | | - |
112 | | - This can be used when calling an existing context manager: |
113 | | - |
114 | | - with as_reentrant(some_context_manager): |
115 | | - # Now we're reentrant! |
116 | | - |
117 | | - Or it can be used when defining a context manager to make it reentrant: |
118 | | - |
119 | | - @as_reentrant |
120 | | - class MyContextManager(ContextManager): |
121 | | - def __enter__(self): |
122 | | - # ... |
123 | | - def __exit__(self, exc_type, exc_value, exc_traceback): |
124 | | - # ... |
125 | | - |
126 | | - And also like this... |
127 | | -
|
128 | | - |
129 | | - @as_reentrant |
130 | | - @ContextManagerType |
131 | | - def Meow(): |
132 | | - yield # ... |
133 | | -
|
134 | | - ''' |
135 | | - return _ReentrantContextManager._wrap_context_manager_or_class( |
136 | | - context_manager, |
137 | | - ) |
138 | | - |
139 | | - |
140 | | -class _ContextManagerWrapper(ContextManager): |
141 | | - _enter_value = None |
142 | | - __wrapped__ = None |
143 | | - def __init__(self, wrapped_context_manager): |
144 | | - if hasattr(wrapped_context_manager, '__enter__'): |
145 | | - self.__wrapped__ = wrapped_context_manager |
146 | | - self._wrapped_enter = wrapped_context_manager.__enter__ |
147 | | - self._wrapped_exit = wrapped_context_manager.__exit__ |
148 | | - else: |
149 | | - self._wrapped_enter, self._wrapped_exit = wrapped_context_manager |
150 | | - |
151 | | - @classmethod |
152 | | - def _wrap_context_manager_or_class(cls, thing): |
153 | | - from .abstract_context_manager import AbstractContextManager |
154 | | - if isinstance(thing, AbstractContextManager): |
155 | | - return cls(thing) |
156 | | - else: |
157 | | - assert issubclass(thing, AbstractContextManager) |
158 | | - # It's a context manager class. |
159 | | - property_name = '__%s_context_manager_%s' % ( |
160 | | - thing.__name__, |
161 | | - ''.join(random.choice(string.ascii_letters) for _ in range(30)) |
162 | | - ) |
163 | | - # We're exposing the wrapped context manager under two names, |
164 | | - # `__wrapped__` and a randomly created one. The first one is used |
165 | | - # for convenience but we still define the second one to ensure our |
166 | | - # mechanism can rely on it even when the `__wrapped__` attribute is |
167 | | - # being overridden. |
168 | | - return type( |
169 | | - thing.__name__, |
170 | | - (thing,), |
171 | | - { |
172 | | - property_name: caching.CachedProperty( |
173 | | - lambda self: cls(( |
174 | | - lambda: thing.__enter__(self), |
175 | | - lambda exc_type, exc_value, exc_traceback: |
176 | | - thing.__exit__( |
177 | | - self, exc_type, exc_value, exc_traceback |
178 | | - ) |
179 | | - )) |
180 | | - ), |
181 | | - '__enter__': |
182 | | - lambda self: getattr(self, property_name).__enter__(), |
183 | | - '__exit__': lambda self, exc_type, exc_value, exc_traceback: |
184 | | - getattr(self, property_name). |
185 | | - __exit__(exc_type, exc_value, exc_traceback), |
186 | | - '__wrapped__': caching.CachedProperty( |
187 | | - lambda self: getattr(self, property_name) |
188 | | - ), |
189 | | - |
190 | | - } |
191 | | - ) |
192 | | - |
193 | | - |
194 | | -class _IdempotentContextManager(_ContextManagerWrapper): |
195 | | - _entered = False |
196 | | - |
197 | | - def __enter__(self): |
198 | | - if not self._entered: |
199 | | - self._enter_value = self._wrapped_enter() |
200 | | - self._entered = True |
201 | | - return self._enter_value |
202 | | - |
203 | | - |
204 | | - def __exit__(self, exc_type=None, exc_value=None, exc_traceback=None): |
205 | | - if self._entered: |
206 | | - exit_value = self._wrapped_exit(exc_type, exc_value, exc_traceback) |
207 | | - self._entered = False |
208 | | - self._enter_value = None |
209 | | - return exit_value |
210 | | - |
211 | | - |
212 | | -class _ReentrantContextManager(_ContextManagerWrapper): |
213 | | - |
214 | | - depth = caching.CachedProperty( |
215 | | - 0, |
216 | | - doc=''' |
217 | | - The number of nested suites that entered this context manager. |
218 | | - |
219 | | - When the context manager is completely unused, it's `0`. When |
220 | | - it's first used, it becomes `1`. When its entered again, it |
221 | | - becomes `2`. If it is then exited, it returns to `1`, etc. |
222 | | - ''' |
223 | | - ) |
224 | | - |
225 | | - |
226 | | - def __enter__(self): |
227 | | - if self.depth == 0: |
228 | | - self._enter_value = self._wrapped_enter() |
229 | | - self.depth += 1 |
230 | | - return self._enter_value |
231 | | - |
232 | | - |
233 | | - def __exit__(self, exc_type=None, exc_value=None, exc_traceback=None): |
234 | | - assert self.depth >= 1 |
235 | | - if self.depth == 1: |
236 | | - exit_value = self._wrapped_exit( |
237 | | - exc_type, exc_value, exc_traceback |
238 | | - ) |
239 | | - self._enter_value = None |
240 | | - else: |
241 | | - exit_value = None |
242 | | - self.depth -= 1 |
243 | | - return exit_value |
244 | | - |
245 | | - |
246 | | - |
0 commit comments