Skip to content

Commit 37dcf5b

Browse files
committed
-
1 parent f15f53b commit 37dcf5b

File tree

3 files changed

+216
-203
lines changed

3 files changed

+216
-203
lines changed

source_py3/python_toolbox/context_management/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,4 +131,5 @@ def do_stuff():
131131

132132
from .blank_context_manager import BlankContextManager
133133
from .delegating_context_manager import DelegatingContextManager
134-
from .functions import nested, as_idempotent, as_reentrant
134+
from .functions import nested
135+
from .modifiers import as_idempotent, as_reentrant

source_py3/python_toolbox/context_management/functions.py

Lines changed: 0 additions & 202 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,8 @@
88
'''
99

1010
import sys
11-
import types
12-
import string
13-
import random
14-
15-
from python_toolbox import caching
1611

1712
from .context_manager_type import ContextManagerType
18-
from .context_manager import ContextManager
1913

2014

2115
@ContextManagerType
@@ -48,199 +42,3 @@ def nested(*managers):
4842
raise exc[1].with_traceback(exc[2])
4943

5044

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

Comments
 (0)