diff -r 44253ce374fc Doc/library/types.rst --- a/Doc/library/types.rst Mon Jun 22 12:31:24 2015 -0400 +++ b/Doc/library/types.rst Mon Jun 22 18:06:17 2015 -0400 @@ -287,9 +287,13 @@ .. function:: coroutine(gen_func) The function transforms a generator function to a :term:`coroutine function`, - so that it returns a :term:`coroutine` object. + so that it returns a generator-based :term:`coroutine` object. - *gen_func* is modified in-place, hence the function can be used as a - decorator. + If *gen_func* is a generator function, it will be modified in-place. + + If *gen_func* is not a generator function, it will be wrapped. If it + returns an instance of :class:`collections.abc.Generator`, it will be + wrapped in an *awaitable* proxy object. All other types of objects will + be returned as is. .. versionadded:: 3.5 diff -r 44253ce374fc Lib/asyncio/coroutines.py --- a/Lib/asyncio/coroutines.py Mon Jun 22 12:31:24 2015 -0400 +++ b/Lib/asyncio/coroutines.py Mon Jun 22 18:06:17 2015 -0400 @@ -143,6 +143,11 @@ def gi_code(self): return self.gen.gi_code + if _PY35: + cr_frame = gi_frame + cr_code = gi_code + cr_running = gi_running + def __del__(self): # Be careful accessing self.gen.frame -- self.gen might not exist. gen = getattr(self, 'gen', None) diff -r 44253ce374fc Lib/test/test_asyncio/test_pep492.py --- a/Lib/test/test_asyncio/test_pep492.py Mon Jun 22 12:31:24 2015 -0400 +++ b/Lib/test/test_asyncio/test_pep492.py Mon Jun 22 18:06:17 2015 -0400 @@ -1,6 +1,7 @@ """Tests support for new syntax introduced by PEP 492.""" import collections.abc +import types import unittest from test import support @@ -119,7 +120,7 @@ self.assertEqual(coro.send(None), 'spam') coro.close() - def test_async_ded_coroutines(self): + def test_async_def_coroutines(self): async def bar(): return 'spam' async def foo(): @@ -134,5 +135,25 @@ data = self.loop.run_until_complete(foo()) self.assertEqual(data, 'spam') + def test_types_coroutine(self): + import _types + + def gen(): + yield from () + return 'spam' + + @types.coroutine + def func(): + return gen() + + async def coro(): + wrapper = func() + self.assertIsInstance(wrapper, _types.GeneratorWrapper) + return await wrapper + + data = self.loop.run_until_complete(coro()) + self.assertEqual(data, 'spam') + + if __name__ == '__main__': unittest.main() diff -r 44253ce374fc Lib/test/test_types.py --- a/Lib/test/test_types.py Mon Jun 22 12:31:24 2015 -0400 +++ b/Lib/test/test_types.py Mon Jun 22 18:06:17 2015 -0400 @@ -7,7 +7,9 @@ import locale import sys import types -import unittest +import _types +import unittest.mock +import weakref class TypesTests(unittest.TestCase): @@ -1191,23 +1193,27 @@ class CoroutineTests(unittest.TestCase): def test_wrong_args(self): - class Foo: - def __call__(self): - pass - def bar(): pass - samples = [None, 1, object()] for sample in samples: with self.assertRaisesRegex(TypeError, 'types.coroutine.*expects a callable'): types.coroutine(sample) - def test_wrong_func(self): + def test_non_gen_values(self): @types.coroutine def foo(): return 'spam' self.assertEqual(foo(), 'spam') + class Awaitable: + def __await__(self): + return () + aw = Awaitable() + @types.coroutine + def foo(): + return aw + self.assertIs(aw, foo()) + def test_async_def(self): # Test that types.coroutine passes 'async def' coroutines # without modification @@ -1258,29 +1264,172 @@ self.assertIs(foo(), coro) self.assertIs(foo().__await__(), coro) + def test_wrapper_wrong_arg(self): + with self.assertRaisesRegex(TypeError, 'int is not an iterator'): + _types.GeneratorWrapper(1) + + class NonIter: + def __iter__(self): + return self + with self.assertRaisesRegex(TypeError, 'NonIter is not an iterator'): + _types.GeneratorWrapper(NonIter()) + def test_duck_gen(self): class GenLike: def send(self): pass def throw(self): pass def close(self): pass - def __iter__(self): return self + def __iter__(self): pass def __next__(self): pass - gen = GenLike() + # Setup generator mock object + gen = unittest.mock.MagicMock(GenLike) + gen.__iter__ = lambda gen: gen + gen.__name__ = 'gen' + gen.__qualname__ = 'test.gen' + self.assertIsInstance(gen, collections.abc.Generator) + self.assertIs(gen, iter(gen)) + @types.coroutine - def foo(): - return gen - self.assertIs(foo().__await__(), gen) - self.assertTrue(isinstance(foo(), collections.abc.Coroutine)) - with self.assertRaises(AttributeError): - foo().gi_code + def foo(): return gen + + wrapper = foo() + self.assertIsInstance(wrapper, _types.GeneratorWrapper) + self.assertIs(wrapper.__await__(), wrapper) + # Wrapper proxies duck generators completely: + self.assertIs(iter(wrapper), wrapper) + + self.assertIsInstance(wrapper, collections.abc.Coroutine) + self.assertIsInstance(wrapper, collections.abc.Awaitable) + + self.assertIs(wrapper.__qualname__, gen.__qualname__) + self.assertIs(wrapper.__name__, gen.__name__) + + # Test AttributeErrors + for name in {'gi_running', 'gi_frame', 'gi_code', + 'cr_running', 'cr_frame', 'cr_code'}: + with self.assertRaises(AttributeError): + getattr(wrapper, name) + + # Test attributes pass-through + gen.gi_running = object() + gen.gi_frame = object() + gen.gi_code = object() + self.assertIs(wrapper.gi_running, gen.gi_running) + self.assertIs(wrapper.gi_frame, gen.gi_frame) + self.assertIs(wrapper.gi_code, gen.gi_code) + self.assertIs(wrapper.cr_running, gen.gi_running) + self.assertIs(wrapper.cr_frame, gen.gi_frame) + self.assertIs(wrapper.cr_code, gen.gi_code) + + wrapper.close() + gen.close.assert_called_once_with() + + wrapper.send(1) + gen.send.assert_called_once_with(1) + + wrapper.throw(1, 2, 3) + gen.throw.assert_called_once_with(1, 2, 3) + gen.reset_mock() + + wrapper.throw(1, 2) + gen.throw.assert_called_once_with(1, 2) + gen.reset_mock() + + wrapper.throw(1) + gen.throw.assert_called_once_with(1) + gen.reset_mock() + + # Test exceptions propagation + error = Exception() + gen.throw.side_effect = error + try: + wrapper.throw(1) + except Exception as ex: + self.assertIs(ex, error) + else: + self.fail('wrapper did not propagate an exception') + + # Test invalid args + gen.reset_mock() + with self.assertRaises(TypeError): + wrapper.throw() + self.assertFalse(gen.throw.called) + with self.assertRaises(TypeError): + wrapper.close(1) + self.assertFalse(gen.close.called) + with self.assertRaises(TypeError): + wrapper.send() + self.assertFalse(gen.send.called) + + # Test that we do not double wrap + @types.coroutine + def bar(): return wrapper + self.assertIs(wrapper, bar()) + + # Test weakrefs support + ref = weakref.ref(wrapper) + self.assertIs(ref(), wrapper) + + def test_duck_functional_gen(self): + class Generator: + """Emulates the following generator (very clumsy): + + def gen(fut): + result = yield fut + return result * 2 + """ + def __init__(self, fut): + self._i = 0 + self._fut = fut + def __iter__(self): + return self + def __next__(self): + return self.send(None) + def send(self, v): + try: + if self._i == 0: + assert v is None + return self._fut + if self._i == 1: + raise StopIteration(v * 2) + if self._i > 1: + raise StopIteration + finally: + self._i += 1 + def throw(self, tp, *exc): + self._i = 100 + if tp is not GeneratorExit: + raise tp + def close(self): + self.throw(GeneratorExit) + + @types.coroutine + def foo(): return Generator('spam') + + wrapper = foo() + self.assertIsInstance(wrapper, _types.GeneratorWrapper) + + async def corofunc(): + return await foo() + 100 + coro = corofunc() + + self.assertEqual(coro.send(None), 'spam') + try: + coro.send(20) + except StopIteration as ex: + self.assertEqual(ex.args[0], 140) + else: + self.fail('StopIteration was expected') def test_gen(self): def gen(): yield gen = gen() @types.coroutine def foo(): return gen - self.assertIs(foo().__await__(), gen) + wrapper = foo() + self.assertIsInstance(wrapper, _types.GeneratorWrapper) + self.assertIs(wrapper.__await__(), gen) for name in ('__name__', '__qualname__', 'gi_code', 'gi_running', 'gi_frame'): @@ -1289,19 +1438,8 @@ self.assertIs(foo().cr_code, gen.gi_code) def test_genfunc(self): - def gen(): - yield - - self.assertFalse(isinstance(gen(), collections.abc.Coroutine)) - self.assertFalse(isinstance(gen(), collections.abc.Awaitable)) - - gen_code = gen.__code__ - decorated_gen = types.coroutine(gen) - self.assertIs(decorated_gen, gen) - self.assertIsNot(decorated_gen.__code__, gen_code) - - decorated_gen2 = types.coroutine(decorated_gen) - self.assertIs(decorated_gen2.__code__, decorated_gen.__code__) + def gen(): yield + self.assertIs(types.coroutine(gen), gen) self.assertTrue(gen.__code__.co_flags & inspect.CO_ITERABLE_COROUTINE) self.assertFalse(gen.__code__.co_flags & inspect.CO_COROUTINE) @@ -1309,10 +1447,27 @@ g = gen() self.assertTrue(g.gi_code.co_flags & inspect.CO_ITERABLE_COROUTINE) self.assertFalse(g.gi_code.co_flags & inspect.CO_COROUTINE) - self.assertTrue(isinstance(g, collections.abc.Coroutine)) - self.assertTrue(isinstance(g, collections.abc.Awaitable)) + self.assertIsInstance(g, collections.abc.Coroutine) + self.assertIsInstance(g, collections.abc.Awaitable) g.close() # silence warning + self.assertIs(types.coroutine(gen), gen) + + def test_wrapper_object(self): + def gen(): + yield + @types.coroutine + def coro(): + return gen() + + wrapper = coro() + self.assertIn('GeneratorWrapper', repr(wrapper)) + self.assertEqual(repr(wrapper), str(wrapper)) + self.assertTrue(set(dir(wrapper)).issuperset({ + '__await__', '__iter__', '__next__', 'cr_code', 'cr_running', + 'cr_frame', 'gi_code', 'gi_frame', 'gi_running', 'send', + 'close', 'throw'})) + if __name__ == '__main__': unittest.main() diff -r 44253ce374fc Lib/types.py --- a/Lib/types.py Mon Jun 22 12:31:24 2015 -0400 +++ b/Lib/types.py Mon Jun 22 18:06:17 2015 -0400 @@ -165,74 +165,25 @@ import functools as _functools import collections.abc as _collections_abc +import _types def coroutine(func): - """Convert regular generator function to a coroutine.""" + """Convert a regular generator function to a coroutine.""" if not callable(func): raise TypeError('types.coroutine() expects a callable') - if (func.__class__ is FunctionType and - getattr(func, '__code__', None).__class__ is CodeType): - - co_flags = func.__code__.co_flags - - # Check if 'func' is a coroutine function. - # (0x180 == CO_COROUTINE | CO_ITERABLE_COROUTINE) - if co_flags & 0x180: - return func - - # Check if 'func' is a generator function. - # (0x20 == CO_GENERATOR) - if co_flags & 0x20: - # TODO: Implement this in C. - co = func.__code__ - func.__code__ = CodeType( - co.co_argcount, co.co_kwonlyargcount, co.co_nlocals, - co.co_stacksize, - co.co_flags | 0x100, # 0x100 == CO_ITERABLE_COROUTINE - co.co_code, - co.co_consts, co.co_names, co.co_varnames, co.co_filename, - co.co_name, co.co_firstlineno, co.co_lnotab, co.co_freevars, - co.co_cellvars) - return func + try: + return _types.coroutine(func) + except TypeError: + pass # The following code is primarily to support functions that # return generator-like objects (for instance generators # compiled with Cython). - class GeneratorWrapper: - def __init__(self, gen): - self.__wrapped__ = gen - self.__name__ = getattr(gen, '__name__', None) - self.__qualname__ = getattr(gen, '__qualname__', None) - def send(self, val): - return self.__wrapped__.send(val) - def throw(self, *args): - return self.__wrapped__.throw(*args) - def close(self): - return self.__wrapped__.close() - @property - def gi_code(self): - return self.__wrapped__.gi_code - @property - def gi_frame(self): - return self.__wrapped__.gi_frame - @property - def gi_running(self): - return self.__wrapped__.gi_running - cr_code = gi_code - cr_frame = gi_frame - cr_running = gi_running - def __next__(self): - return next(self.__wrapped__) - def __iter__(self): - return self.__wrapped__ - def __await__(self): - return self.__wrapped__ - @_functools.wraps(func) - def wrapped(*args, **kwargs): + def wrapper(*args, **kwargs): coro = func(*args, **kwargs) if coro.__class__ is CoroutineType: # 'coro' is a native coroutine object. @@ -243,12 +194,12 @@ # 'coro' is either a pure Python generator iterator, or it # implements collections.abc.Generator (and does not implement # collections.abc.Coroutine). - return GeneratorWrapper(coro) + return _types.GeneratorWrapper(coro) # 'coro' is either an instance of collections.abc.Coroutine or # some other object -- pass it through. return coro - return wrapped + return wrapper __all__ = [n for n in globals() if n[:1] != '_'] diff -r 44253ce374fc Modules/Setup.dist --- a/Modules/Setup.dist Mon Jun 22 12:31:24 2015 -0400 +++ b/Modules/Setup.dist Mon Jun 22 18:06:17 2015 -0400 @@ -119,6 +119,7 @@ atexit atexitmodule.c # Register functions to be run at interpreter-shutdown _stat _stat.c # stat.h interface time timemodule.c # -lm # time operations and variables +_types _typesmodule.c # Types module speedups # access to ISO C locale support _locale _localemodule.c # -lintl diff -r 44253ce374fc Modules/_typesmodule.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Modules/_typesmodule.c Mon Jun 22 18:06:17 2015 -0400 @@ -0,0 +1,298 @@ +/* Helpers for types module. */ + +#include "Python.h" +#include "structmember.h" + +static PyObject * +types_coroutine(PyObject *self, PyObject *func) +{ + PyCodeObject *func_code; + + if (!PyFunction_Check(func)) { + PyErr_Format(PyExc_TypeError, + "callable expected, got %.50s", + Py_TYPE(func)->tp_name); + return NULL; + } + + func_code = (PyCodeObject *)PyFunction_GET_CODE(func); + assert(func_code); + + if (func_code->co_flags & (CO_COROUTINE | CO_ITERABLE_COROUTINE)) { + Py_INCREF(func); + return func; + } + + if (!(func_code->co_flags & CO_GENERATOR)) { + PyErr_SetString(PyExc_TypeError, + "generator function expected"); + return NULL; + } + + func_code->co_flags |= CO_ITERABLE_COROUTINE; + + Py_INCREF(func); + return func; +} + +PyDoc_STRVAR(types_coroutine_doc, +"coroutine(func) -> None\n\ +\n\ +Applies CO_ITERABLE_COROUTINE to generator function's code object.\n\ +This is an internal helper for types.coroutine(), do not use this \n\ +function directly.\n"); + +typedef struct { + PyObject_HEAD + PyObject *gw_wrapped; + PyObject *gw_weakreflist; +} PyGenWrapper; + +static PyObject * +PyGenWrapper_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + PyObject *gen; + PyGenWrapper *gw; + + if (!PyArg_UnpackTuple(args, "GeneratorWrapper", 1, 1, &gen)) + return NULL; + + if (!PyIter_Check(gen)) { + PyErr_Format(PyExc_TypeError, + "%.50s is not an iterator", + Py_TYPE(gen)->tp_name); + return NULL; + } + + gw = PyObject_GC_New(PyGenWrapper, type); + if (gw == NULL) + return NULL; + + gw->gw_weakreflist = NULL; + + Py_INCREF(gen); + gw->gw_wrapped = gen; + _PyObject_GC_TRACK(gw); + return (PyObject *)gw; +} + +static void +PyGenWrapper_dealloc(PyGenWrapper *gw) +{ + _PyObject_GC_UNTRACK(gw); + + Py_CLEAR(((PyGenWrapper*)gw)->gw_wrapped); + if (gw->gw_weakreflist != NULL) + PyObject_ClearWeakRefs((PyObject *)gw); + + PyObject_GC_Del(gw); +} + +static int +PyGenWrapper_traverse(PyGenWrapper *gw, visitproc visit, void *arg) +{ + Py_VISIT((PyObject *)gw->gw_wrapped); + return 0; +} + +static PyObject * +PyGenWrapper_await(PyGenWrapper *gw) +{ + PyObject *wrapped = gw->gw_wrapped; + if (PyGen_CheckExact(wrapped)) { + /* Speed things up a little bit for pure generators. */ + Py_INCREF(wrapped); + return wrapped; + } + + Py_INCREF(gw); + return gw; +} + +static PyObject * +PyGenWrapper_iternext(PyGenWrapper *gw) +{ + iternextfunc next = NULL; + PyObject *wrapped = gw->gw_wrapped; + PyTypeObject *type = Py_TYPE(wrapped); + + next = type->tp_iternext; + if (next == NULL) { + PyErr_Format(PyExc_TypeError, + "%.50s is not an iterator", + type->tp_name); + return NULL; + } + + return (*next)(wrapped); +} + +static PyObject * +PyGenWrapper_send(PyGenWrapper *gw, PyObject *arg) +{ + _Py_IDENTIFIER(send); + return _PyObject_CallMethodIdObjArgs(gw->gw_wrapped, &PyId_send, + arg, NULL); +} + +static PyObject * +PyGenWrapper_throw(PyGenWrapper *gw, PyObject *args) +{ + _Py_IDENTIFIER(throw); + PyObject *typ; + PyObject *tb = NULL; + PyObject *val = NULL; + + if (!PyArg_UnpackTuple(args, "throw", 1, 3, &typ, &val, &tb)) + return NULL; + + return _PyObject_CallMethodIdObjArgs(gw->gw_wrapped, &PyId_throw, + typ, val, tb, NULL); +} + +static PyObject * +PyGenWrapper_close(PyGenWrapper *gw, PyObject *arg) +{ + _Py_IDENTIFIER(close); + return _PyObject_CallMethodId(gw->gw_wrapped, &PyId_close, NULL); +} + +static PyObject * +PyGenWrapper_get_name(PyGenWrapper *gw) +{ + _Py_IDENTIFIER(__name__); + return _PyObject_GetAttrId(gw->gw_wrapped, &PyId___name__); +} + +static PyObject * +PyGenWrapper_get_qualname(PyGenWrapper *gw) +{ + _Py_IDENTIFIER(__qualname__); + return _PyObject_GetAttrId(gw->gw_wrapped, &PyId___qualname__); +} + +static PyObject * +PyGenWrapper_get_gi_running(PyGenWrapper *gw) +{ + _Py_IDENTIFIER(gi_running); + return _PyObject_GetAttrId(gw->gw_wrapped, &PyId_gi_running); +} + +static PyObject * +PyGenWrapper_get_gi_frame(PyGenWrapper *gw) +{ + _Py_IDENTIFIER(gi_frame); + return _PyObject_GetAttrId(gw->gw_wrapped, &PyId_gi_frame); +} + +static PyObject * +PyGenWrapper_get_gi_code(PyGenWrapper *gw) +{ + _Py_IDENTIFIER(gi_code); + return _PyObject_GetAttrId(gw->gw_wrapped, &PyId_gi_code); +} + +static PyAsyncMethods GeneratorWrapperType_as_async = { + (unaryfunc)PyGenWrapper_await, /* am_await */ + 0, /* am_aiter */ + 0 /* am_anext */ +}; + +static PyMethodDef GeneratorWrapperType_methods[] = { + {"send", (PyCFunction)PyGenWrapper_send, METH_O, NULL}, + {"throw", (PyCFunction)PyGenWrapper_throw, METH_VARARGS, NULL}, + {"close", (PyCFunction)PyGenWrapper_close, METH_NOARGS, NULL}, + {NULL, NULL} /* Sentinel */ +}; + +static PyGetSetDef GeneratorWrapperType_getsetlist[] = { + {"__name__", (getter)PyGenWrapper_get_name, NULL, NULL}, + {"__qualname__", (getter)PyGenWrapper_get_qualname, NULL, NULL}, + {"gi_running", (getter)PyGenWrapper_get_gi_running, NULL, NULL}, + {"gi_code", (getter)PyGenWrapper_get_gi_code, NULL, NULL}, + {"gi_frame", (getter)PyGenWrapper_get_gi_frame, NULL, NULL}, + {"cr_running", (getter)PyGenWrapper_get_gi_running, NULL, NULL}, + {"cr_code", (getter)PyGenWrapper_get_gi_code, NULL, NULL}, + {"cr_frame", (getter)PyGenWrapper_get_gi_frame, NULL, NULL}, + {NULL} /* Sentinel */ +}; + +static PyTypeObject GeneratorWrapperType = { + PyVarObject_HEAD_INIT(&PyType_Type, 0) + "GeneratorWrapper", + sizeof(PyGenWrapper), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor)PyGenWrapper_dealloc, /* destructor tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + &GeneratorWrapperType_as_async, /* tp_as_async */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + PyObject_GenericGetAttr, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, /* tp_flags */ + "Wrapper around generator objects with __await__ method", + (traverseproc)PyGenWrapper_traverse, /* traverseproc tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + offsetof(PyGenWrapper, gw_weakreflist), /* tp_weaklistoffset */ + PyObject_SelfIter, /* tp_iter */ + (iternextfunc)PyGenWrapper_iternext, /* tp_iternext */ + GeneratorWrapperType_methods, /* tp_methods */ + 0, /* tp_members */ + GeneratorWrapperType_getsetlist, /* tp_getset */ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + PyGenWrapper_new, /* tp_new */ + PyObject_Del, /* tp_free */ +}; + + +static PyMethodDef types_methods[] = { + {"coroutine", types_coroutine, METH_O, types_coroutine_doc}, + {NULL, NULL} /* sentinel */ +}; + +PyDoc_STRVAR(module_doc, "Helpers for types module.\n"); + +static struct PyModuleDef _typesmodule = { + PyModuleDef_HEAD_INIT, + "_types", + module_doc, + -1, + types_methods, + NULL, + NULL, + NULL, + NULL +}; + +PyMODINIT_FUNC +PyInit__types(void) +{ + PyObject *m; + + m = PyModule_Create(&_typesmodule); + if (m == NULL) + return NULL; + + if (PyType_Ready(&GeneratorWrapperType) < 0) + return NULL; + Py_INCREF(&GeneratorWrapperType); + PyModule_AddObject(m, "GeneratorWrapper", + (PyObject *)&GeneratorWrapperType); + + return m; +} diff -r 44253ce374fc PC/config.c --- a/PC/config.c Mon Jun 22 12:31:24 2015 -0400 +++ b/PC/config.c Mon Jun 22 18:06:17 2015 -0400 @@ -38,6 +38,7 @@ extern PyObject* PyInit__collections(void); extern PyObject* PyInit__heapq(void); extern PyObject* PyInit__bisect(void); +extern PyObject* PyInit__types(void); extern PyObject* PyInit__symtable(void); extern PyObject* PyInit_mmap(void); extern PyObject* PyInit__csv(void); @@ -117,6 +118,7 @@ {"itertools", PyInit_itertools}, {"_collections", PyInit__collections}, {"_symtable", PyInit__symtable}, + {"_types", PyInit__types}, {"mmap", PyInit_mmap}, {"_csv", PyInit__csv}, {"_sre", PyInit__sre}, diff -r 44253ce374fc PCbuild/pythoncore.vcxproj --- a/PCbuild/pythoncore.vcxproj Mon Jun 22 12:31:24 2015 -0400 +++ b/PCbuild/pythoncore.vcxproj Mon Jun 22 18:06:17 2015 -0400 @@ -228,6 +228,7 @@ + @@ -417,4 +418,4 @@ - \ No newline at end of file + diff -r 44253ce374fc PCbuild/pythoncore.vcxproj.filters --- a/PCbuild/pythoncore.vcxproj.filters Mon Jun 22 12:31:24 2015 -0400 +++ b/PCbuild/pythoncore.vcxproj.filters Mon Jun 22 18:06:17 2015 -0400 @@ -473,6 +473,9 @@ Modules + + Modules + Modules @@ -974,4 +977,4 @@ Resource Files - \ No newline at end of file +