Author: Carl Friedrich Bolz-Tereick <[email protected]>
Branch: py3.7
Changeset: r98358:f9a14309d6d1
Date: 2019-12-23 15:46 +0100
http://bitbucket.org/pypy/pypy/changeset/f9a14309d6d1/
Log: start implementing PEP-0567
Python code is taken from there, pure Python apart from two
functions in __pypy__ to read and write to a new field in the
executioncontext
diff --git a/lib-python/3/test/test_context.py
b/lib-python/3/test/test_context.py
--- a/lib-python/3/test/test_context.py
+++ b/lib-python/3/test/test_context.py
@@ -24,7 +24,7 @@
class ContextTest(unittest.TestCase):
def test_context_var_new_1(self):
- with self.assertRaisesRegex(TypeError, 'takes exactly 1'):
+ with self.assertRaises(TypeError):
contextvars.ContextVar()
with self.assertRaisesRegex(TypeError, 'must be a str'):
@@ -76,11 +76,11 @@
pass
def test_context_new_1(self):
- with self.assertRaisesRegex(TypeError, 'any arguments'):
+ with self.assertRaises(TypeError):
contextvars.Context(1)
- with self.assertRaisesRegex(TypeError, 'any arguments'):
+ with self.assertRaises(TypeError):
contextvars.Context(1, a=1)
- with self.assertRaisesRegex(TypeError, 'any arguments'):
+ with self.assertRaises(TypeError):
contextvars.Context(a=1)
contextvars.Context(**{})
diff --git a/lib_pypy/_contextvars.py b/lib_pypy/_contextvars.py
new file mode 100644
--- /dev/null
+++ b/lib_pypy/_contextvars.py
@@ -0,0 +1,219 @@
+from __pypy__ import get_contextvar_context, set_contextvar_context
+# implementation taken from PEP-0567 https://www.python.org/dev/peps/pep-0567/
+
+_NO_DEFAULT = object()
+
+
+class Unsubclassable(type):
+ def __new__(cls, name, bases, dct):
+ for base in bases:
+ if isinstance(base, Unsubclassable):
+ raise TypeError(f"type '{base.__name__}' is not an acceptable
base type")
+ return type.__new__(cls, name, bases, dict(dct))
+
+
+class _ContextData:
+ # XXX wrong complexity! need to implement a real immutable dict instead
+
+ def __init__(self):
+ self._mapping = dict()
+
+ def __getitem__(self, key):
+ return self._mapping[key]
+
+ def __contains__(self, key):
+ return key in self._mapping
+
+ def __len__(self):
+ return len(self._mapping)
+
+ def __iter__(self):
+ return iter(self._mapping)
+
+ def set(self, key, value):
+ copy = _ContextData()
+ copy._mapping = self._mapping.copy()
+ copy._mapping[key] = value
+ return copy
+
+ def delete(self, key):
+ copy = _ContextData()
+ copy._mapping = self._mapping.copy()
+ del copy._mapping[key]
+ return copy
+
+
+def get_context():
+ context = get_contextvar_context()
+ if context is None:
+ context = Context()
+ set_contextvar_context(context)
+ return context
+
+
+class Context(metaclass=Unsubclassable):
+
+ #_data: _ContextData
+ #_prev_context: Optional[Context]
+
+ def __init__(self):
+ self._data = _ContextData()
+ self._prev_context = None
+
+ def run(self, callable, *args, **kwargs):
+ if self._prev_context is not None:
+ raise RuntimeError(
+ f'cannot enter context: {self} is already entered')
+
+ self._prev_context = get_context()
+ try:
+ set_contextvar_context(self)
+ return callable(*args, **kwargs)
+ finally:
+ set_contextvar_context(self._prev_context)
+ self._prev_context = None
+
+ def copy(self):
+ new = Context()
+ new._data = self._data
+ return new
+
+ # Implement abstract Mapping.__getitem__
+ def __getitem__(self, var):
+ if not isinstance(var, ContextVar):
+ raise TypeError("ContextVar key was expected")
+ return self._data[var]
+
+ # Implement abstract Mapping.__contains__
+ def __contains__(self, var):
+ if not isinstance(var, ContextVar):
+ raise TypeError("ContextVar key was expected")
+ return var in self._data
+
+ # Implement abstract Mapping.__len__
+ def __len__(self):
+ return len(self._data)
+
+ # Implement abstract Mapping.__iter__
+ def __iter__(self):
+ return iter(self._data)
+
+ def get(self, key, default=None):
+ if not isinstance(key, ContextVar):
+ raise TypeError("ContextVar key was expected")
+ try:
+ return self._data[key]
+ except KeyError:
+ return default
+
+ def keys(self):
+ from collections.abc import KeysView
+ return KeysView(self)
+
+ def values(self):
+ from collections.abc import ValuesView
+ return ValuesView(self)
+
+ def items(self):
+ from collections.abc import ItemsView
+ return ItemsView(self)
+
+ def __eq__(self, other):
+ if not isinstance(other, Context):
+ return NotImplemented
+ return dict(self.items()) == dict(other.items())
+
+
+def copy_context():
+ return get_context().copy()
+
+class ContextVar(metaclass=Unsubclassable):
+
+ def __init__(self, name, *, default=_NO_DEFAULT):
+ if not isinstance(name, str):
+ raise TypeError("context variable name must be a str")
+ self._name = name
+ self._default = default
+
+ @property
+ def name(self):
+ return self._name
+
+ def get(self, default=_NO_DEFAULT):
+ context = get_context()
+ try:
+ return context[self]
+ except KeyError:
+ pass
+
+ if default is not _NO_DEFAULT:
+ return default
+
+ if self._default is not _NO_DEFAULT:
+ return self._default
+
+ raise LookupError
+
+ def set(self, value):
+ context = get_context()
+
+ data: _ContextData = context._data
+ try:
+ old_value = data[self]
+ except KeyError:
+ old_value = Token.MISSING
+
+ updated_data = data.set(self, value)
+ context._data = updated_data
+ return Token(context, self, old_value)
+
+ def reset(self, token):
+ if token._used:
+ raise RuntimeError("Token has already been used once")
+
+ if token._var is not self:
+ raise ValueError(
+ "Token was created by a different ContextVar")
+
+ context = get_context()
+ if token._context is not context:
+ raise ValueError(
+ "Token was created in a different Context")
+
+ if token._old_value is Token.MISSING:
+ context._data = context._data.delete(token._var)
+ else:
+ context._data = context._data.set(token._var, token._old_value)
+
+ token._used = True
+
+ @classmethod
+ def __class_getitem__(self, key):
+ return None
+
+ def __repr__(self):
+ default = ''
+ if self._default is not _NO_DEFAULT:
+ default = f"default={self._default} "
+ return f"<ContextVar name={self.name!r} {default}at 0x{id(self):x}>"
+
+
+class Token(metaclass=Unsubclassable):
+ MISSING = object()
+
+ def __init__(self, context, var, old_value):
+ self._context = context
+ self._var = var
+ self._old_value = old_value
+ self._used = False
+
+ @property
+ def var(self):
+ return self._var
+
+ @property
+ def old_value(self):
+ return self._old_value
+
+ def __repr__(self):
+ return f"<Token {'used ' if self._used else ''}var={self._var} at
0x{id(self):x}>"
diff --git a/pypy/interpreter/executioncontext.py
b/pypy/interpreter/executioncontext.py
--- a/pypy/interpreter/executioncontext.py
+++ b/pypy/interpreter/executioncontext.py
@@ -44,6 +44,7 @@
self.in_coroutine_wrapper = False
self.w_asyncgen_firstiter_fn = None
self.w_asyncgen_finalizer_fn = None
+ self.contextvar_context = None
@staticmethod
def _mark_thread_disappeared(space):
diff --git a/pypy/module/__pypy__/interp_magic.py
b/pypy/module/__pypy__/interp_magic.py
--- a/pypy/module/__pypy__/interp_magic.py
+++ b/pypy/module/__pypy__/interp_magic.py
@@ -215,3 +215,17 @@
def set_exc_info(space, w_type, w_value, w_traceback=None):
ec = space.getexecutioncontext()
ec.set_sys_exc_info3(w_type, w_value, w_traceback)
+
+def get_contextvar_context(space):
+ ec = space.getexecutioncontext()
+ context = ec.contextvar_context
+ if context:
+ return context
+ else:
+ return space.w_None
+
+def set_contextvar_context(space, w_obj):
+ ec = space.getexecutioncontext()
+ ec.contextvar_context = w_obj
+ return space.w_None
+
diff --git a/pypy/module/__pypy__/moduledef.py
b/pypy/module/__pypy__/moduledef.py
--- a/pypy/module/__pypy__/moduledef.py
+++ b/pypy/module/__pypy__/moduledef.py
@@ -119,6 +119,9 @@
'pyos_inputhook' : 'interp_magic.pyos_inputhook',
'newmemoryview' : 'interp_buffer.newmemoryview',
'set_exc_info' : 'interp_magic.set_exc_info',
+
+ 'get_contextvar_context' : 'interp_magic.get_contextvar_context',
+ 'set_contextvar_context' : 'interp_magic.set_contextvar_context',
}
submodules = {
diff --git a/pypy/module/__pypy__/test/test_magic.py
b/pypy/module/__pypy__/test/test_magic.py
--- a/pypy/module/__pypy__/test/test_magic.py
+++ b/pypy/module/__pypy__/test/test_magic.py
@@ -92,3 +92,16 @@
terr = TypeError("hello world")
set_exc_info(TypeError, terr, tb)
assert sys.exc_info()[2] is tb
+
+ def test_get_set_contextvar_context(self):
+ from __pypy__ import get_contextvar_context, set_contextvar_context
+ context = get_contextvar_context()
+ try:
+ set_contextvar_context(1)
+ assert get_contextvar_context() == 1
+ set_contextvar_context(5)
+ assert get_contextvar_context() == 5
+
+ finally:
+ set_contextvar_context(context)
+
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit