Hello community, here is the log from the commit of package python-curio for openSUSE:Factory checked in at 2020-06-15 20:33:11 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-curio (Old) and /work/SRC/openSUSE:Factory/.python-curio.new.3606 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-curio" Mon Jun 15 20:33:11 2020 rev:5 rq:814701 version:1.2 Changes: -------- --- /work/SRC/openSUSE:Factory/python-curio/python-curio.changes 2020-03-05 23:24:33.201385854 +0100 +++ /work/SRC/openSUSE:Factory/.python-curio.new.3606/python-curio.changes 2020-06-15 20:33:20.506976803 +0200 @@ -1,0 +2,10 @@ +Mon Jun 15 11:58:37 UTC 2020 - Ondřej Súkup <mimi...@gmail.com> + +- Update to 1.2 + * Removed hard dependency on contextvars + * Added a default selector timeout of 1 second for Windows. + * First crack at a Curio repl. + * Added a pytest plugin + * Slight refinement to TaskGroup result reporting. + +------------------------------------------------------------------- Old: ---- curio-1.1.tar.gz New: ---- curio-1.2.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-curio.spec ++++++ --- /var/tmp/diff_new_pack.UcRCVn/_old 2020-06-15 20:33:22.038982303 +0200 +++ /var/tmp/diff_new_pack.UcRCVn/_new 2020-06-15 20:33:22.042982317 +0200 @@ -19,7 +19,7 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} %define skip_python2 1 Name: python-curio -Version: 1.1 +Version: 1.2 Release: 0 Summary: Concurrent I/O library for Python 3 License: BSD-Source-Code ++++++ curio-1.1.tar.gz -> curio-1.2.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/curio-1.1/.gitignore new/curio-1.2/.gitignore --- old/curio-1.1/.gitignore 2020-03-01 13:27:43.000000000 +0100 +++ new/curio-1.2/.gitignore 2020-04-07 03:43:23.000000000 +0200 @@ -2,6 +2,7 @@ __pycache__/ .vscode venv* +*.egg-info benchmarks/curio/ benchmarks/env/ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/curio-1.1/CHANGES new/curio-1.2/CHANGES --- old/curio-1.1/CHANGES 2020-03-01 13:27:43.000000000 +0100 +++ new/curio-1.2/CHANGES 2020-04-07 03:43:23.000000000 +0200 @@ -1,7 +1,43 @@ CHANGES ------- -Version 1.1 - March 1, 2020 +Version 1.2 - In Progress + +04/06/2020 Removed hard dependency on contextvars. It was unnecessary and + needlessly broken some things on Python 3.6. + +04/06/2020 Added a default selector timeout of 1 second for Windows. This + makes everything a bit more friendly for Control-C. This can be + disabled or changed using the max_select_timeout argument to Kernel + or curio.run(). + +04/04/2020 First crack at a Curio repl. Idea borrowed from the asyncio + REPL. If you run `python -m curio` it will start a REPL + in which Curio is already running in the background. You + can directly await operations at the top level. For example: + + bash $ python -m curio + Use "await" directly instead of "curio.run()". + Type "help", "copyright", "credits" or "license" for more information. + >>> import curio + >>> await curio.sleep(4) + >>> + + Pressing Control-C causes any `await` operation to be cancelled + with a `curio.TaskCancelled` exception. For normal operations + not involving `await, Control-C raises a `KeyboardInterrupt` as + normal. Note: This feature requires Python 3.8 or newer. +03/24/2020 Added a pytest plugin for Curio. Contributed by Keith Dart. + See curio/pytest_plugin.py. + +03/02/2020 Slight refinement to TaskGroup result reporting. If no tasks + are spawned in a TaskGroup g, then g.exception will return None + to indicate no errors. g.result will raise an exception + indicating that no successful result was obtained. Addresses + issue #314. + +Version 1.1 - March 1, 2020 +--------------------------- 02/24/2020 Fixed a very subtle edge case involving epoll() and duplicated file descriptors on Linux. The fix required a new trap _io_release() to explitly unregister a file descriptor diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/curio-1.1/curio/__init__.py new/curio-1.2/curio/__init__.py --- old/curio-1.1/curio/__init__.py 2020-03-01 13:27:43.000000000 +0100 +++ new/curio-1.2/curio/__init__.py 2020-04-07 03:43:23.000000000 +0200 @@ -1,6 +1,6 @@ # curio/__init__.py -__version__ = '1.1' +__version__ = '1.2' from .errors import * from .queue import * diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/curio-1.1/curio/__main__.py new/curio-1.2/curio/__main__.py --- old/curio-1.1/curio/__main__.py 1970-01-01 01:00:00.000000000 +0100 +++ new/curio-1.2/curio/__main__.py 2020-04-07 03:43:23.000000000 +0200 @@ -0,0 +1,113 @@ +import ast +import curio +import code +import inspect +import sys +import types +import warnings +import threading +import signal +import os + +class CurioIOInteractiveConsole(code.InteractiveConsole): + + def __init__(self, locals): + super().__init__(locals) + self.compile.compiler.flags |= ast.PyCF_ALLOW_TOP_LEVEL_AWAIT + self.requests = curio.UniversalQueue() + self.response = curio.UniversalQueue() + + def runcode(self, code): + # This coroutine is handed from the thread running the REPL to the + # task runner in the main thread. + async def run_it(): + func = types.FunctionType(code, self.locals) + try: + # We restore the default REPL signal handler for running normal code + hand = signal.signal(signal.SIGINT, signal.default_int_handler) + try: + coro = func() + finally: + signal.signal(signal.SIGINT, hand) + except BaseException as ex: + await self.response.put((None, ex)) + return + if not inspect.iscoroutine(coro): + await self.response.put((coro, None)) + return + + # For a coroutine... We're going to try and do some magic to intercept + # Control-C in an Event/Task. + async def watch_ctrl_c(evt, repl_task): + await evt.wait() + await repl_task.cancel() + evt = curio.UniversalEvent() + try: + hand = signal.signal(signal.SIGINT, lambda signo, frame: evt.set()) + repl_task = await curio.spawn(coro) + watch_task = await curio.spawn(watch_ctrl_c, evt, repl_task) + try: + result = await repl_task.join() + response = (result, None) + except SystemExit: + raise + except BaseException as e: + await repl_task.wait() + response = (None, e.__cause__) + await watch_task.cancel() + finally: + signal.signal(signal.SIGINT, hand) + await self.response.put(response) + + self.requests.put(run_it()) + # Get the result here... + result, exc = self.response.get() + if exc is not None: + try: + raise exc + except BaseException: + self.showtraceback() + else: + return result + + # Task that runs in the main thread, executing input fed to it from above + async def runmain(self): + try: + hand = signal.signal(signal.SIGINT, signal.SIG_IGN) + while True: + coro = await self.requests.get() + if coro is None: + break + await coro + finally: + signal.signal(signal.SIGINT, hand) + +def run_repl(console): + try: + banner = ( + f'curio REPL {sys.version} on {sys.platform}\n' + f'Use "await" directly instead of "curio.run()".\n' + f'Type "help", "copyright", "credits" or "license" ' + f'for more information.\n' + f'{getattr(sys, "ps1", ">>> ")}import curio' + ) + console.interact( + banner=banner, + exitmsg='exiting curio REPL...') + finally: + warnings.filterwarnings( + 'ignore', + message=r'^coroutine .* was never awaited$', + category=RuntimeWarning) + console.requests.put(None) + +if __name__ == '__main__': + repl_locals = { 'curio': curio } + for key in {'__name__', '__package__', + '__loader__', '__spec__', + '__builtins__', '__file__'}: + repl_locals[key] = locals()[key] + + console = CurioIOInteractiveConsole(repl_locals) + threading.Thread(target=run_repl, args=[console], daemon=True).start() + curio.run(console.runmain) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/curio-1.1/curio/kernel.py new/curio-1.2/curio/kernel.py --- old/curio-1.1/curio/kernel.py 2020-03-01 13:27:43.000000000 +0100 +++ new/curio-1.2/curio/kernel.py 2020-04-07 03:43:23.000000000 +0200 @@ -81,7 +81,8 @@ Use the kernel run() method to submit work to the kernel. ''' - def __init__(self, *, selector=None, debug=None, activations=None, taskcls=Task): + def __init__(self, *, selector=None, debug=None, activations=None, taskcls=Task, + max_select_timeout=None if os.name != 'nt' else 1.0): # Functions to call at shutdown self._shutdown_funcs = [] @@ -107,6 +108,8 @@ # Task creation class self._taskcls = taskcls + self._max_select_timeout = max_select_timeout + def __del__(self): if self._shutdown_funcs is not None: @@ -202,6 +205,7 @@ selector_modify = selector.modify selector_select = selector.select selector_getkey = selector.get_key + selector_max_timeout = kernel._max_select_timeout ready_popleft = ready.popleft ready_append = ready.append @@ -632,6 +636,8 @@ else: current_time = time_monotonic() timeout = sleepq.next_deadline(current_time) + if selector_max_timeout and (timeout is None or timeout > selector_max_timeout): + timeout = selector_max_timeout try: events = selector_select(timeout) except OSError as e: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/curio-1.1/curio/pytest_plugin.py new/curio-1.2/curio/pytest_plugin.py --- old/curio-1.1/curio/pytest_plugin.py 1970-01-01 01:00:00.000000000 +0100 +++ new/curio-1.2/curio/pytest_plugin.py 2020-04-07 03:43:23.000000000 +0200 @@ -0,0 +1,91 @@ +# python3.7 + +"""Plugin module for pytest. + +This enables easier unit tests for applications that use both Curio and Pytest. If you have Curio +installed, you have the plugin and can write unit tests per the example below. + +Provides a fixture named `kernel`, and a marker (pytest.mark.curio) that will run a bare coroutine +in a new Kernel instance. + +Example: + + from curio import sleep + import pytest + + # Use marker + + @pytest.mark.curio + async def test_coro(): + await sleep(1) + + + # Use kernel fixture + + def test_app(kernel): + + async def my_aapp(): + await sleep(1) + + kernel.run(my_aapp) +""" + +import inspect +import functools + +import pytest + +from curio import Kernel +from curio import meta +from curio import monitor +from curio.debug import longblock, logcrash + + +def _is_coroutine(obj): + """Check to see if an object is really a coroutine.""" + return meta.iscoroutinefunction(obj) or inspect.isgeneratorfunction(obj) + + +def pytest_configure(config): + """Inject documentation.""" + config.addinivalue_line("markers", + "curio: " + "mark the test as a coroutine, it will be run using a Curio kernel.") + + +@pytest.mark.tryfirst +def pytest_pycollect_makeitem(collector, name, obj): + """A pytest hook to collect coroutines in a test module.""" + if collector.funcnamefilter(name) and _is_coroutine(obj): + item = pytest.Function(name, parent=collector) + if 'curio' in item.keywords: + return list(collector._genfunctions(name, obj)) + + +@pytest.hookimpl(tryfirst=True, hookwrapper=True) +def pytest_pyfunc_call(pyfuncitem): + """Run curio marked test functions in a Curio kernel instead of a normal function call. + """ + if 'curio' in pyfuncitem.keywords: + pyfuncitem.obj = wrap_in_sync(pyfuncitem.obj) + yield + + +def wrap_in_sync(func): + """Return a sync wrapper around an async function executing it in a Kernel.""" + @functools.wraps(func) + def inner(**kwargs): + coro = func(**kwargs) + Kernel().run(coro, shutdown=True) + return inner + + +# Fixture for explicitly running in Kernel instance. +@pytest.fixture(scope='session') +def kernel(request): + """Provide a Curio Kernel object for running co-routines.""" + k = Kernel(debug=[longblock, logcrash]) + m = monitor.Monitor(k) + request.addfinalizer(lambda: k.run(shutdown=True)) + request.addfinalizer(m.close) + return k diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/curio-1.1/curio/task.py new/curio-1.2/curio/task.py --- old/curio-1.1/curio/task.py 2020-03-01 13:27:43.000000000 +0100 +++ new/curio-1.2/curio/task.py 2020-04-07 03:43:23.000000000 +0200 @@ -14,7 +14,6 @@ import linecache import traceback import os.path -import contextvars log = logging.getLogger(__name__) @@ -142,9 +141,6 @@ # Timeout deadline stack self._deadlines = [] - # Contextvars support - self._context = contextvars.copy_context() - def __repr__(self): return f'{type(self).__name__}(id={self.id}, name={self.name!r}, state={self.state!r})' @@ -265,6 +261,7 @@ taskcls keyword argument to the Curio kernel. ''' def __init__(self, coro): + import contextvars super().__init__(coro) self._context = contextvars.copy_context() @@ -391,6 +388,8 @@ def result(self): if not self._joined: raise RuntimeError("Task group not yet terminated") + if not self.completed: + raise RuntimeError("No task successfully completed") return self.completed.result # Property that returns the exception of the first completed task @@ -398,7 +397,7 @@ def exception(self): if not self._joined: raise RuntimeError("Task group not yet terminated") - return self.completed.exception + return self.completed.exception if self.completed else None # Property that returns all task results (in task creation order) @property diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/curio-1.1/docs/conf.py new/curio-1.2/docs/conf.py --- old/curio-1.1/docs/conf.py 2020-03-01 13:27:43.000000000 +0100 +++ new/curio-1.2/docs/conf.py 2020-04-07 03:43:23.000000000 +0200 @@ -60,9 +60,9 @@ # built documents. # # The short X.Y version. -version = '1.1' +version = '1.2' # The full version, including alpha/beta/rc tags. -release = '1.1' +release = '1.2' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/curio-1.1/docs/tutorial.rst new/curio-1.2/docs/tutorial.rst --- old/curio-1.1/docs/tutorial.rst 2020-03-01 13:27:43.000000000 +0100 +++ new/curio-1.2/docs/tutorial.rst 2020-04-07 03:43:23.000000000 +0200 @@ -361,7 +361,8 @@ # Dispatch task that forwards incoming messages to subscribers async def dispatcher(): - async for msg in messages: + while True: + msg = await messages.get() for q in list(subscribers): await q.put(msg) @@ -374,7 +375,8 @@ queue = Queue() subscribers.add(queue) try: - async for msg in queue: + while True: + msg = await queue.get() print(name, 'got', msg) finally: subscribers.discard(queue) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/curio-1.1/setup.cfg new/curio-1.2/setup.cfg --- old/curio-1.1/setup.cfg 2020-03-01 13:27:43.000000000 +0100 +++ new/curio-1.2/setup.cfg 2020-04-07 03:43:23.000000000 +0200 @@ -8,3 +8,5 @@ testpaths = tests addopts = --verbose --ignore=setup.py --ignore=docs/conf.py +markers = + internet: mark tests as requiring internet connectivity (deselect with '-m "not internet"') diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/curio-1.1/setup.py new/curio-1.2/setup.py --- old/curio-1.1/setup.py 2020-03-01 13:27:43.000000000 +0100 +++ new/curio-1.2/setup.py 2020-04-07 03:43:23.000000000 +0200 @@ -14,7 +14,7 @@ description="Curio", long_description=long_description, license="BSD", - version="1.1", + version="1.2", author="David Beazley", author_email="d...@dabeaz.com", maintainer="David Beazley", @@ -25,6 +25,9 @@ extras_require={ 'test': tests_require, }, + python_requires='>= 3.6', + entry_points={"pytest11": ["curio = curio.pytest_plugin"]}, classifiers=[ 'Programming Language :: Python :: 3', + "Framework :: Pytest", ]) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/curio-1.1/tests/test_network.py new/curio-1.2/tests/test_network.py --- old/curio-1.1/tests/test_network.py 2020-03-01 13:27:43.000000000 +0100 +++ new/curio-1.2/tests/test_network.py 2020-04-07 03:43:23.000000000 +0200 @@ -250,6 +250,7 @@ kernel.run(main()) +@pytest.mark.internet def test_ssl_outgoing(kernel): async def main(): c = await network.open_connection('google.com', 443, ssl=True, server_hostname='google.com') @@ -312,6 +313,7 @@ kernel.run(main()) +@pytest.mark.internet def test_errors(kernel): async def main(): with pytest.raises(ValueError): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/curio-1.1/tests/test_sync.py new/curio-1.2/tests/test_sync.py --- old/curio-1.1/tests/test_sync.py 2020-03-01 13:27:43.000000000 +0100 +++ new/curio-1.2/tests/test_sync.py 2020-04-07 03:43:23.000000000 +0200 @@ -788,6 +788,7 @@ async def main(): evt = UniversalEvent() t1 = await spawn(event_waiter, evt) + await sleep(0.05) t2 = threading.Thread(target=asyncio.run, args=[event_setter(evt, 1)]) t2.start() await t1.join() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/curio-1.1/tests/test_task.py new/curio-1.2/tests/test_task.py --- old/curio-1.1/tests/test_task.py 2020-03-01 13:27:43.000000000 +0100 +++ new/curio-1.2/tests/test_task.py 2020-04-07 03:43:23.000000000 +0200 @@ -439,6 +439,19 @@ kernel.run(main()) +def test_task_group_empty(kernel): + async def main(): + async with TaskGroup() as g: + pass + + assert g.exception is None + assert g.exceptions == [] + assert g.results == [] + with pytest.raises(RuntimeError): + g.result + + kernel.run(main()) + def test_defer_cancellation(kernel): async def cancel_me(e1, e2): with pytest.raises(CancelledError): @@ -556,10 +569,9 @@ await c.send(i) await c.send(None) # Sentinel -import contextvars -x = contextvars.ContextVar('x', default=0) - def test_contextvars(): + import contextvars + x = contextvars.ContextVar('x', default=0) from curio.task import ContextTask events = [] async def countdown(n):