Skip to content

Commit de39c24

Browse files
dxxbPaul Sokolovsky
authored and
Paul Sokolovsky
committed
ucontextlib: spin off minimal module from contextlib
1 parent 7472c8a commit de39c24

File tree

4 files changed

+165
-0
lines changed

4 files changed

+165
-0
lines changed

ucontextlib/metadata.txt

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
srctype = micropython-lib
2+
type = module
3+
version = 0.1
4+
license = Python
5+
long_desc = Minimal subset of contextlib for MicroPython low-memory ports

ucontextlib/setup.py

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import sys
2+
# Remove current dir from sys.path, otherwise setuptools will peek up our
3+
# module instead of system.
4+
sys.path.pop(0)
5+
from setuptools import setup
6+
7+
8+
setup(name='micropython-ucontextlib',
9+
version='0.1',
10+
description='ucontextlib module for MicroPython',
11+
long_description='Minimal subset of contextlib for MicroPython low-memory ports',
12+
url='https://github.com/micropython/micropython/issues/405',
13+
author='MicroPython Developers',
14+
author_email='[email protected]',
15+
maintainer='MicroPython Developers',
16+
maintainer_email='[email protected]',
17+
license='Python',
18+
py_modules=['ucontextlib'])

ucontextlib/tests.py

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import unittest
2+
from ucontextlib import contextmanager
3+
4+
5+
class ContextManagerTestCase(unittest.TestCase):
6+
7+
def setUp(self):
8+
self._history = []
9+
10+
@contextmanager
11+
def manager(x):
12+
self._history.append('start')
13+
try:
14+
yield x
15+
finally:
16+
self._history.append('finish')
17+
18+
self._manager = manager
19+
20+
def test_context_manager(self):
21+
with self._manager(123) as x:
22+
self.assertEqual(x, 123)
23+
self.assertEqual(self._history, ['start', 'finish'])
24+
25+
def test_context_manager_on_error(self):
26+
exc = Exception()
27+
try:
28+
with self._manager(123) as x:
29+
raise exc
30+
except Exception as e:
31+
self.assertEqual(exc, e)
32+
self.assertEqual(self._history, ['start', 'finish'])
33+
34+
35+
if __name__ == '__main__':
36+
unittest.main()

ucontextlib/ucontextlib.py

+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
"""Utilities for with-statement contexts. See PEP 343.
2+
3+
Original source code: https://hg.python.org/cpython/file/3.4/Lib/contextlib.py
4+
5+
Not implemented:
6+
- redirect_stdout;
7+
- ExitStack.
8+
- closing
9+
- supress
10+
"""
11+
12+
class ContextDecorator(object):
13+
"A base class or mixin that enables context managers to work as decorators."
14+
15+
def _recreate_cm(self):
16+
"""Return a recreated instance of self.
17+
18+
Allows an otherwise one-shot context manager like
19+
_GeneratorContextManager to support use as
20+
a decorator via implicit recreation.
21+
22+
This is a private interface just for _GeneratorContextManager.
23+
See issue #11647 for details.
24+
"""
25+
return self
26+
27+
def __call__(self, func):
28+
def inner(*args, **kwds):
29+
with self._recreate_cm():
30+
return func(*args, **kwds)
31+
return inner
32+
33+
34+
class _GeneratorContextManager(ContextDecorator):
35+
"""Helper for @contextmanager decorator."""
36+
37+
def __init__(self, func, *args, **kwds):
38+
self.gen = func(*args, **kwds)
39+
self.func, self.args, self.kwds = func, args, kwds
40+
41+
def _recreate_cm(self):
42+
# _GCM instances are one-shot context managers, so the
43+
# CM must be recreated each time a decorated function is
44+
# called
45+
return self.__class__(self.func, *self.args, **self.kwds)
46+
47+
def __enter__(self):
48+
try:
49+
return next(self.gen)
50+
except StopIteration:
51+
raise RuntimeError("generator didn't yield") from None
52+
53+
def __exit__(self, type, value, traceback):
54+
if type is None:
55+
try:
56+
next(self.gen)
57+
except StopIteration:
58+
return
59+
else:
60+
raise RuntimeError("generator didn't stop")
61+
else:
62+
if value is None:
63+
# Need to force instantiation so we can reliably
64+
# tell if we get the same exception back
65+
value = type()
66+
try:
67+
self.gen.throw(type, value, traceback)
68+
raise RuntimeError("generator didn't stop after throw()")
69+
except StopIteration as exc:
70+
# Suppress the exception *unless* it's the same exception that
71+
# was passed to throw(). This prevents a StopIteration
72+
# raised inside the "with" statement from being suppressed
73+
return exc is not value
74+
75+
76+
def contextmanager(func):
77+
"""@contextmanager decorator.
78+
79+
Typical usage:
80+
81+
@contextmanager
82+
def some_generator(<arguments>):
83+
<setup>
84+
try:
85+
yield <value>
86+
finally:
87+
<cleanup>
88+
89+
This makes this:
90+
91+
with some_generator(<arguments>) as <variable>:
92+
<body>
93+
94+
equivalent to this:
95+
96+
<setup>
97+
try:
98+
<variable> = <value>
99+
<body>
100+
finally:
101+
<cleanup>
102+
103+
"""
104+
def helper(*args, **kwds):
105+
return _GeneratorContextManager(func, *args, **kwds)
106+
return helper

0 commit comments

Comments
 (0)