python-aiojobs_1.1.0.orig/CHANGES.rst0000644000000000000000000000543114322773230014235 0ustar00========= Changelog ========= .. You should *NOT* be adding new change log entries to this file, this file is managed by towncrier. You *may* edit previous change logs to fix problems like typo corrections or such. To add a new change log entry, please see https://pip.pypa.io/en/latest/development/#adding-a-news-entry we named the news folder "changes". WARNING: Don't drop the next directive! .. towncrier release notes start 1.1.0 (2022-10-16) ================== Features -------- - Complete type annotations have been added. (`#352 `_) - ``Scheduler`` can (and should be) instantiated directly. (`#353 `_) - ``Job`` is also exported by default now, to aid type annotations. (`#355 `_) Bugfixes -------- - Fix scheduler blocking forever when pending limit is reached. (`#135 `_) - Fix @atomic wrapper not passing ``self`` to methods. (`#344 `_) - ``Job.wait()`` now returns the task value if the job is already closed. (`#343 `_) - Fix ``exception_handler`` being called twice in some situations. (`#354 < https://github.com/aio-libs/aiojobs/pull/354`_) Deprecations and Removals ------------------------- - Dropped Python 3.6 support. (`#338 `_) - ``create_scheduler()`` is deprecated and will be removed in v2. (`#353 `_) 1.0.0 (2021-11-09) ================== Features -------- - Switch to ``async-timeout>=4.0.0``. (`#275 `_) - Added Python 3.10 support. (`#277 `_) - Added type hints support. (`#280 `_) Deprecations and Removals ------------------------- - Dropped Python 3.5 support. (`#279 `_) 0.3.0 (2020-11-26) ================== Features -------- - Make aiohttp as extra requirement (#80) Bugfixes -------- - Fix AttributeError when calling wait() or close() on failed job. (#29) 0.2.2 (2018-10-17) ================== - Fix AttributeError when calling ``wait()`` or ``close()`` on failed job (#64) 0.2.1 (2018-03-10) ================== - Add missing decription file 0.2.0 (2018-03-10) ================== Features -------- - Add a new scheduler parameter for control pending jobs size. (#19) - Cancelling a task suspended on ``job.wait()`` doesn't cancel inner job task but timeout exemption does. (#28) Bugfixes -------- - Fix AttributeError when `@atomic` decorator is used in Class Based Views. (#21) python-aiojobs_1.1.0.orig/LICENSE0000644000000000000000000000107013762505767013452 0ustar00Copyright 2017-2020 aio-libs collaboration. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. python-aiojobs_1.1.0.orig/MANIFEST.in0000644000000000000000000000024313130645576014174 0ustar00include LICENSE include CHANGES.rst include README.rst include Makefile graft aiojobs graft docs graft examples graft tests global-exclude *.pyc prune docs/_build python-aiojobs_1.1.0.orig/Makefile0000644000000000000000000000127414322600662014072 0ustar00.PHONY: all all: test .install-deps: $(shell find requirements -type f) @pip install -r requirements/dev.txt @pre-commit install @touch .install-deps .develop: .install-deps $(shell find aiojobs -type f) @pip install -e . @touch .develop .PHONY: setup setup: .develop .PHONY: lint lint: pre-commit run --all-files mypy . .PHONY: test test: .develop @pytest tests .PHONY: cov cov: .develop @PYTHONASYNCIODEBUG=1 pytest --cov=aiojobs tests @pytest --cov=aiojobs --cov-append --cov-report=html --cov-report=term tests @echo "open file://`pwd`/htmlcov/index.html" .PHONY: doc doc: .develop @make -C docs html SPHINXOPTS="-W -E" @echo "open file://`pwd`/docs/_build/html/index.html" python-aiojobs_1.1.0.orig/PKG-INFO0000644000000000000000000000764014365524433013542 0ustar00Metadata-Version: 2.1 Name: aiojobs Version: 1.1.0 Summary: Job scheduler for managing background tasks (asyncio) Home-page: https://github.com/aio-libs/aiojobs Maintainer: aiohttp team Maintainer-email: team@aiohttp.org License: Apache 2 Project-URL: Chat: Gitter, https://gitter.im/aio-libs/Lobby Project-URL: CI: GitHub Actions, https://github.com/aio-libs/aiojobs/actions?query=workflow%3ACI Project-URL: Coverage: codecov, https://codecov.io/github/aio-libs/aiojobs Project-URL: Docs: Changelog, https://docs.aiojobs.org/en/stable/changes.html Project-URL: Docs: RTD, https://docs.aiojobs.org Project-URL: GitHub: issues, https://github.com/aio-libs/aiojobs/issues Project-URL: GitHub: repo, https://github.com/aio-libs/aiojobs Classifier: Development Status :: 5 - Production/Stable Classifier: Framework :: AsyncIO Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX Classifier: Operating System :: MacOS :: MacOS X Classifier: Operating System :: Microsoft :: Windows Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Topic :: Software Development Requires-Python: >=3.7 Description-Content-Type: text/x-rst Provides-Extra: aiohttp License-File: LICENSE ======= aiojobs ======= .. image:: https://travis-ci.org/aio-libs/aiojobs.svg?branch=master :target: https://travis-ci.org/aio-libs/aiojobs .. image:: https://codecov.io/gh/aio-libs/aiojobs/branch/master/graph/badge.svg :target: https://codecov.io/gh/aio-libs/aiojobs .. image:: https://img.shields.io/pypi/v/aiojobs.svg :target: https://pypi.python.org/pypi/aiojobs .. image:: https://readthedocs.org/projects/aiojobs/badge/?version=latest :target: http://aiojobs.readthedocs.io/en/latest/?badge=latest :alt: Documentation Status .. image:: https://badges.gitter.im/Join%20Chat.svg :target: https://gitter.im/aio-libs/Lobby :alt: Chat on Gitter Job scheduler for managing background tasks (asyncio) The library gives a controlled way for scheduling background tasks for asyncio applications. Installation ============ .. code-block:: bash $ pip3 install aiojobs Usage example ============= .. code-block:: python import asyncio import aiojobs async def coro(timeout): await asyncio.sleep(timeout) async def main(): scheduler = aiojobs.Scheduler() for i in range(100): # spawn jobs await scheduler.spawn(coro(i/10)) await asyncio.sleep(5.0) # not all scheduled jobs are finished at the moment # gracefully close spawned jobs await scheduler.close() asyncio.get_event_loop().run_until_complete(main()) Integration with aiohttp.web ============================ .. code-block:: python from aiohttp import web from aiojobs.aiohttp import setup, spawn async def handler(request): await spawn(request, coro()) return web.Response() app = web.Application() app.router.add_get('/', handler) setup(app) or just .. code-block:: python from aiojobs.aiohttp import atomic @atomic async def handler(request): return web.Response() For more information read documentation: https://aiojobs.readthedocs.io Communication channels ====================== *aio-libs* google group: https://groups.google.com/forum/#!forum/aio-libs Feel free to post your questions and ideas here. *Gitter Chat* https://gitter.im/aio-libs/Lobby We support `Stack Overflow `_. Please add *python-asyncio* or *aiohttp* tag to your question there. Author and License ================== The ``aiojobs`` package is written by Andrew Svetlov. It's *Apache 2* licensed and freely available. python-aiojobs_1.1.0.orig/README.rst0000644000000000000000000000464714322600662014130 0ustar00======= aiojobs ======= .. image:: https://travis-ci.org/aio-libs/aiojobs.svg?branch=master :target: https://travis-ci.org/aio-libs/aiojobs .. image:: https://codecov.io/gh/aio-libs/aiojobs/branch/master/graph/badge.svg :target: https://codecov.io/gh/aio-libs/aiojobs .. image:: https://img.shields.io/pypi/v/aiojobs.svg :target: https://pypi.python.org/pypi/aiojobs .. image:: https://readthedocs.org/projects/aiojobs/badge/?version=latest :target: http://aiojobs.readthedocs.io/en/latest/?badge=latest :alt: Documentation Status .. image:: https://badges.gitter.im/Join%20Chat.svg :target: https://gitter.im/aio-libs/Lobby :alt: Chat on Gitter Job scheduler for managing background tasks (asyncio) The library gives a controlled way for scheduling background tasks for asyncio applications. Installation ============ .. code-block:: bash $ pip3 install aiojobs Usage example ============= .. code-block:: python import asyncio import aiojobs async def coro(timeout): await asyncio.sleep(timeout) async def main(): scheduler = aiojobs.Scheduler() for i in range(100): # spawn jobs await scheduler.spawn(coro(i/10)) await asyncio.sleep(5.0) # not all scheduled jobs are finished at the moment # gracefully close spawned jobs await scheduler.close() asyncio.get_event_loop().run_until_complete(main()) Integration with aiohttp.web ============================ .. code-block:: python from aiohttp import web from aiojobs.aiohttp import setup, spawn async def handler(request): await spawn(request, coro()) return web.Response() app = web.Application() app.router.add_get('/', handler) setup(app) or just .. code-block:: python from aiojobs.aiohttp import atomic @atomic async def handler(request): return web.Response() For more information read documentation: https://aiojobs.readthedocs.io Communication channels ====================== *aio-libs* google group: https://groups.google.com/forum/#!forum/aio-libs Feel free to post your questions and ideas here. *Gitter Chat* https://gitter.im/aio-libs/Lobby We support `Stack Overflow `_. Please add *python-asyncio* or *aiohttp* tag to your question there. Author and License ================== The ``aiojobs`` package is written by Andrew Svetlov. It's *Apache 2* licensed and freely available. python-aiojobs_1.1.0.orig/aiojobs.egg-info/0000755000000000000000000000000014365524433015556 5ustar00python-aiojobs_1.1.0.orig/aiojobs/0000755000000000000000000000000013125515323014053 5ustar00python-aiojobs_1.1.0.orig/docs/0000755000000000000000000000000013125712452013357 5ustar00python-aiojobs_1.1.0.orig/pyproject.toml0000644000000000000000000000051514322600662015343 0ustar00[build-system] requires = ["setuptools >= 46.4"] build-backend = "setuptools.build_meta" [tool.isort] profile = "black" [tool.towncrier] package = "aiojobs" filename = "CHANGES.rst" directory = "CHANGES/" title_format = "{version} ({project_date})" issue_format = "`#{issue} `_" python-aiojobs_1.1.0.orig/setup.cfg0000644000000000000000000000342714365524433014265 0ustar00[metadata] name = aiojobs version = attr: aiojobs.__version__ url = https://github.com/aio-libs/aiojobs project_urls = Chat: Gitter = https://gitter.im/aio-libs/Lobby CI: GitHub Actions = https://github.com/aio-libs/aiojobs/actions?query=workflow%%3ACI Coverage: codecov = https://codecov.io/github/aio-libs/aiojobs Docs: Changelog = https://docs.aiojobs.org/en/stable/changes.html Docs: RTD = https://docs.aiojobs.org GitHub: issues = https://github.com/aio-libs/aiojobs/issues GitHub: repo = https://github.com/aio-libs/aiojobs description = Job scheduler for managing background tasks (asyncio) long_description = file: README.rst long_description_content_type = text/x-rst maintainer = aiohttp team maintainer_email = team@aiohttp.org license = Apache 2 license_files = LICENSE classifiers = Development Status :: 5 - Production/Stable Framework :: AsyncIO Intended Audience :: Developers License :: OSI Approved :: Apache Software License Operating System :: POSIX Operating System :: MacOS :: MacOS X Operating System :: Microsoft :: Windows Programming Language :: Python Programming Language :: Python :: 3 Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 Topic :: Software Development [options] python_requires = >=3.7 packages = find: include_package_data = True install_requires = async-timeout >= 4.0.0 [options.extras_require] aiohttp = aiohttp >= 3.8.0 [flake8] ignore = N801,N802,N803,E203,E226,E305,W504,E252,E301,E302,E704,W503,W504,F811 max-line-length = 88 [tool:pytest] addopts = --cov=aiojobs/ --cov=tests/ asyncio_mode = auto filterwarnings = error testpaths = tests/ xfail_strict = true [egg_info] tag_build = tag_date = 0 python-aiojobs_1.1.0.orig/tests/0000755000000000000000000000000013125515323013567 5ustar00python-aiojobs_1.1.0.orig/aiojobs.egg-info/PKG-INFO0000644000000000000000000000764014365524433016662 0ustar00Metadata-Version: 2.1 Name: aiojobs Version: 1.1.0 Summary: Job scheduler for managing background tasks (asyncio) Home-page: https://github.com/aio-libs/aiojobs Maintainer: aiohttp team Maintainer-email: team@aiohttp.org License: Apache 2 Project-URL: Chat: Gitter, https://gitter.im/aio-libs/Lobby Project-URL: CI: GitHub Actions, https://github.com/aio-libs/aiojobs/actions?query=workflow%3ACI Project-URL: Coverage: codecov, https://codecov.io/github/aio-libs/aiojobs Project-URL: Docs: Changelog, https://docs.aiojobs.org/en/stable/changes.html Project-URL: Docs: RTD, https://docs.aiojobs.org Project-URL: GitHub: issues, https://github.com/aio-libs/aiojobs/issues Project-URL: GitHub: repo, https://github.com/aio-libs/aiojobs Classifier: Development Status :: 5 - Production/Stable Classifier: Framework :: AsyncIO Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX Classifier: Operating System :: MacOS :: MacOS X Classifier: Operating System :: Microsoft :: Windows Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Topic :: Software Development Requires-Python: >=3.7 Description-Content-Type: text/x-rst Provides-Extra: aiohttp License-File: LICENSE ======= aiojobs ======= .. image:: https://travis-ci.org/aio-libs/aiojobs.svg?branch=master :target: https://travis-ci.org/aio-libs/aiojobs .. image:: https://codecov.io/gh/aio-libs/aiojobs/branch/master/graph/badge.svg :target: https://codecov.io/gh/aio-libs/aiojobs .. image:: https://img.shields.io/pypi/v/aiojobs.svg :target: https://pypi.python.org/pypi/aiojobs .. image:: https://readthedocs.org/projects/aiojobs/badge/?version=latest :target: http://aiojobs.readthedocs.io/en/latest/?badge=latest :alt: Documentation Status .. image:: https://badges.gitter.im/Join%20Chat.svg :target: https://gitter.im/aio-libs/Lobby :alt: Chat on Gitter Job scheduler for managing background tasks (asyncio) The library gives a controlled way for scheduling background tasks for asyncio applications. Installation ============ .. code-block:: bash $ pip3 install aiojobs Usage example ============= .. code-block:: python import asyncio import aiojobs async def coro(timeout): await asyncio.sleep(timeout) async def main(): scheduler = aiojobs.Scheduler() for i in range(100): # spawn jobs await scheduler.spawn(coro(i/10)) await asyncio.sleep(5.0) # not all scheduled jobs are finished at the moment # gracefully close spawned jobs await scheduler.close() asyncio.get_event_loop().run_until_complete(main()) Integration with aiohttp.web ============================ .. code-block:: python from aiohttp import web from aiojobs.aiohttp import setup, spawn async def handler(request): await spawn(request, coro()) return web.Response() app = web.Application() app.router.add_get('/', handler) setup(app) or just .. code-block:: python from aiojobs.aiohttp import atomic @atomic async def handler(request): return web.Response() For more information read documentation: https://aiojobs.readthedocs.io Communication channels ====================== *aio-libs* google group: https://groups.google.com/forum/#!forum/aio-libs Feel free to post your questions and ideas here. *Gitter Chat* https://gitter.im/aio-libs/Lobby We support `Stack Overflow `_. Please add *python-asyncio* or *aiohttp* tag to your question there. Author and License ================== The ``aiojobs`` package is written by Andrew Svetlov. It's *Apache 2* licensed and freely available. python-aiojobs_1.1.0.orig/aiojobs.egg-info/SOURCES.txt0000644000000000000000000000131614365524433017443 0ustar00CHANGES.rst LICENSE MANIFEST.in Makefile README.rst pyproject.toml setup.cfg aiojobs/__init__.py aiojobs/_job.py aiojobs/_scheduler.py aiojobs/aiohttp.py aiojobs/py.typed aiojobs.egg-info/PKG-INFO aiojobs.egg-info/SOURCES.txt aiojobs.egg-info/dependency_links.txt aiojobs.egg-info/requires.txt aiojobs.egg-info/top_level.txt docs/Makefile docs/aiojobs-icon.ico docs/aiojobs-icon.svg docs/api.rst docs/conf.py docs/index.rst docs/intro.rst docs/make.bat docs/quickstart.rst docs/_static/aiojobs-icon-128x128.png docs/_static/aiojobs-icon-32x32.png docs/_static/aiojobs-icon-64x64.png docs/_static/aiojobs-icon-96x96.png tests/__init__.py tests/conftest.py tests/test_aiohttp.py tests/test_job.py tests/test_scheduler.pypython-aiojobs_1.1.0.orig/aiojobs.egg-info/dependency_links.txt0000644000000000000000000000000114365524433021624 0ustar00 python-aiojobs_1.1.0.orig/aiojobs.egg-info/requires.txt0000644000000000000000000000005714365524433020160 0ustar00async-timeout>=4.0.0 [aiohttp] aiohttp>=3.8.0 python-aiojobs_1.1.0.orig/aiojobs.egg-info/top_level.txt0000644000000000000000000000001614365524433020305 0ustar00aiojobs tests python-aiojobs_1.1.0.orig/aiojobs/__init__.py0000644000000000000000000000201014322773230016160 0ustar00"""Jobs scheduler for managing background task (asyncio). The library gives controlled way for scheduling background tasks for asyncio applications. """ import warnings from typing import Optional from ._job import Job from ._scheduler import ExceptionHandler, Scheduler __version__ = "1.1.0" async def create_scheduler( *, close_timeout: Optional[float] = 0.1, limit: Optional[int] = 100, pending_limit: int = 10000, exception_handler: Optional[ExceptionHandler] = None ) -> Scheduler: warnings.warn("Scheduler can now be instantiated directly.", DeprecationWarning) if exception_handler is not None and not callable(exception_handler): raise TypeError( "A callable object or None is expected, " "got {!r}".format(exception_handler) ) return Scheduler( close_timeout=close_timeout, limit=limit, pending_limit=pending_limit, exception_handler=exception_handler, ) __all__ = ("Job", "Scheduler", "create_scheduler") python-aiojobs_1.1.0.orig/aiojobs/_job.py0000644000000000000000000001201514322571604015341 0ustar00import asyncio import sys import traceback from typing import TYPE_CHECKING, Coroutine, Generic, Optional, TypeVar import async_timeout if TYPE_CHECKING: from ._scheduler import Scheduler else: Scheduler = None _T = TypeVar("_T", covariant=True) class Job(Generic[_T]): def __init__(self, coro: Coroutine[object, object, _T], scheduler: Scheduler): self._coro = coro self._scheduler: Optional[Scheduler] = scheduler loop = asyncio.get_running_loop() self._started = loop.create_future() self._closed = False self._explicit = False self._task: Optional["asyncio.Task[_T]"] = None tb = traceback.extract_stack(sys._getframe(2)) if loop.get_debug() else None self._source_traceback = tb def __repr__(self) -> str: info = [] if self._closed: info.append("closed") elif self._task is None: info.append("pending") state = " ".join(info) if state: state += " " return f">" @property def active(self) -> bool: return not self.closed and not self.pending @property def pending(self) -> bool: return self._task is None and not self.closed @property def closed(self) -> bool: return self._closed async def _do_wait(self, timeout: Optional[float]) -> _T: async with async_timeout.timeout(timeout): # TODO: add a test for waiting for a pending coro await self._started assert self._task is not None # Task should have been created before this. return await self._task async def wait(self, *, timeout: Optional[float] = None) -> _T: if self._closed: assert self._task is not None # Task must have been created if closed. return await self._task assert self._scheduler is not None # Only removed when not _closed. self._explicit = True scheduler = self._scheduler try: return await asyncio.shield(self._do_wait(timeout)) except asyncio.CancelledError: # Don't stop inner coroutine on explicit cancel raise except Exception: await self._close(scheduler.close_timeout) raise async def close(self, *, timeout: Optional[float] = None) -> None: if self._closed: return assert self._scheduler is not None # Only removed when not _closed. self._explicit = True if timeout is None: timeout = self._scheduler.close_timeout await self._close(timeout) async def _close(self, timeout: Optional[float]) -> None: self._closed = True if self._task is None: # the task is closed immediately without actual execution # it prevents a warning like # RuntimeWarning: coroutine 'coro' was never awaited self._start() assert self._task is not None self._task.cancel() # self._scheduler is None after _done_callback() scheduler = self._scheduler try: async with async_timeout.timeout(timeout): await self._task except asyncio.CancelledError: pass except asyncio.TimeoutError as exc: if self._explicit: raise context = { "message": "Job closing timed out", "job": self, "exception": exc, } if self._source_traceback is not None: context["source_traceback"] = self._source_traceback # scheduler is only None if job was already finished, in which case # there's no timeout. self._scheduler will now be None though. assert scheduler is not None scheduler.call_exception_handler(context) except Exception: if self._explicit: raise def _start(self) -> None: assert self._task is None self._task = asyncio.create_task(self._coro) self._task.add_done_callback(self._done_callback) self._started.set_result(None) def _done_callback(self, task: "asyncio.Task[_T]") -> None: assert self._scheduler is not None scheduler = self._scheduler scheduler._done(self) try: exc = task.exception() except asyncio.CancelledError: pass else: if exc is not None and not self._explicit: self._report_exception(exc) scheduler._failed_tasks.put_nowait(task) # type: ignore[arg-type] self._scheduler = None # drop backref self._closed = True def _report_exception(self, exc: BaseException) -> None: assert self._scheduler is not None context = {"message": "Job processing failed", "job": self, "exception": exc} if self._source_traceback is not None: context["source_traceback"] = self._source_traceback self._scheduler.call_exception_handler(context) python-aiojobs_1.1.0.orig/aiojobs/_scheduler.py0000644000000000000000000001160614322563232016550 0ustar00import asyncio from typing import ( Any, Callable, Collection, Coroutine, Dict, Iterator, Optional, Set, TypeVar, ) from ._job import Job _T = TypeVar("_T") ExceptionHandler = Callable[["Scheduler", Dict[str, Any]], None] class Scheduler(Collection[Job[object]]): def __init__( self, *, close_timeout: Optional[float] = 0.1, limit: Optional[int] = 100, pending_limit: int = 10000, exception_handler: Optional[ExceptionHandler] = None, ): if exception_handler is not None and not callable(exception_handler): raise TypeError( "A callable object or None is expected, " "got {!r}".format(exception_handler) ) self._jobs: Set[Job[object]] = set() self._close_timeout = close_timeout self._limit = limit self._exception_handler = exception_handler self._failed_tasks: asyncio.Queue[ Optional[asyncio.Task[object]] ] = asyncio.Queue() self._failed_task = asyncio.create_task(self._wait_failed()) self._pending: asyncio.Queue[Job[object]] = asyncio.Queue(maxsize=pending_limit) self._closed = False def __iter__(self) -> Iterator[Job[Any]]: return iter(self._jobs) def __len__(self) -> int: return len(self._jobs) def __contains__(self, obj: object) -> bool: return obj in self._jobs def __repr__(self) -> str: info = [] if self._closed: info.append("closed") state = " ".join(info) if state: state += " " return f"" @property def limit(self) -> Optional[int]: return self._limit @property def pending_limit(self) -> int: return self._pending.maxsize @property def close_timeout(self) -> Optional[float]: return self._close_timeout @property def active_count(self) -> int: return len(self._jobs) - self._pending.qsize() @property def pending_count(self) -> int: return self._pending.qsize() @property def closed(self) -> bool: return self._closed async def spawn(self, coro: Coroutine[object, object, _T]) -> Job[_T]: if self._closed: raise RuntimeError("Scheduling a new job after closing") job = Job(coro, self) should_start = self._limit is None or self.active_count < self._limit if should_start: job._start() else: try: # wait for free slot in queue await self._pending.put(job) except asyncio.CancelledError: await job.close() raise self._jobs.add(job) return job async def close(self) -> None: if self._closed: return self._closed = True # prevent adding new jobs jobs = self._jobs if jobs: # cleanup pending queue # all job will be started on closing while not self._pending.empty(): self._pending.get_nowait() await asyncio.gather( *[job._close(self._close_timeout) for job in jobs], return_exceptions=True, ) self._jobs.clear() self._failed_tasks.put_nowait(None) await self._failed_task def call_exception_handler(self, context: Dict[str, Any]) -> None: handler = self._exception_handler if handler is None: handler = asyncio.get_running_loop().call_exception_handler(context) else: handler(self, context) @property def exception_handler(self) -> Optional[ExceptionHandler]: return self._exception_handler def _done(self, job: Job[object]) -> None: self._jobs.discard(job) if not self.pending_count: return # No pending jobs when limit is None # Safe to subtract. ntodo = self._limit - self.active_count # type: ignore[operator] i = 0 while i < ntodo: if not self.pending_count: return new_job = self._pending.get_nowait() if new_job.closed: continue new_job._start() i += 1 async def _wait_failed(self) -> None: # a coroutine for waiting failed tasks # without awaiting for failed tasks async raises a warning while True: task = await self._failed_tasks.get() if task is None: return # closing try: await task # should raise exception except Exception: # Cleanup a warning # self.call_exception_handler() is already called # by Job._add_done_callback # Thus we caught an task exception and we are good citizens pass python-aiojobs_1.1.0.orig/aiojobs/aiohttp.py0000644000000000000000000000372314322563232016104 0ustar00from functools import wraps from typing import ( Any, AsyncIterator, Awaitable, Callable, Coroutine, Optional, TypeVar, Union, ) from aiohttp import web from ._job import Job from ._scheduler import Scheduler __all__ = ("setup", "spawn", "get_scheduler", "get_scheduler_from_app", "atomic") _T = TypeVar("_T") _RequestView = TypeVar("_RequestView", bound=Union[web.Request, web.View]) # TODO(aiohttp 3.9+): Use AppKey def get_scheduler(request: web.Request) -> Scheduler: scheduler = get_scheduler_from_request(request) if scheduler is None: raise RuntimeError("Call aiojobs.aiohttp.setup() on application initialization") return scheduler def get_scheduler_from_app(app: web.Application) -> Optional[Scheduler]: return app.get("AIOJOBS_SCHEDULER") def get_scheduler_from_request(request: web.Request) -> Optional[Scheduler]: return request.config_dict.get("AIOJOBS_SCHEDULER") # type: ignore[no-any-return] async def spawn(request: web.Request, coro: Coroutine[object, object, _T]) -> Job[_T]: return await get_scheduler(request).spawn(coro) def atomic( coro: Callable[[_RequestView], Coroutine[object, object, _T]] ) -> Callable[[_RequestView], Awaitable[_T]]: @wraps(coro) async def wrapper(request_or_view: _RequestView) -> _T: if isinstance(request_or_view, web.View): # Class Based View decorated. request = request_or_view.request else: # https://github.com/python/mypy/issues/13896 request = request_or_view # type: ignore[assignment] job = await spawn(request, coro(request_or_view)) return await job.wait() return wrapper def setup(app: web.Application, **kwargs: Any) -> None: async def cleanup_context(app: web.Application) -> AsyncIterator[None]: app["AIOJOBS_SCHEDULER"] = scheduler = Scheduler(**kwargs) yield await scheduler.close() app.cleanup_ctx.append(cleanup_context) python-aiojobs_1.1.0.orig/aiojobs/py.typed0000644000000000000000000000001614142444367015560 0ustar00# Placeholder python-aiojobs_1.1.0.orig/docs/Makefile0000644000000000000000000000114014142440116015006 0ustar00# Minimal makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = python -msphinx SPHINXPROJ = aiojobs SOURCEDIR = . BUILDDIR = _build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) python-aiojobs_1.1.0.orig/docs/_static/0000755000000000000000000000000013125720600014777 5ustar00python-aiojobs_1.1.0.orig/docs/aiojobs-icon.ico0000644000000000000000000000627613125720600016434 0ustar00  ( @vsIGXGSA\UGE^JN_lJG[V_U>:EBTDUgofU98yxPI@ALXAUol>@@oMHC84TaHVkoBNHNvZHA>?[fLS]\ORLRp[NSUIoN7CVbufSJ;YQKah__T5HaXk[]mS^B{Jkg_~=<^Y>NhBLQXNVeH>GOHcOXQMH_DAKtjdH^I67>45QiZP>I\7>63JgUY=j`b[dR@gvcGOcHKC8WTakLf_\seID86?CAUsSNn}phJfl\dkUnuD=53LKHRh}al[]hUkvDOHBBDIIFFKRtvMNDv.>vzHLW\>CD09Dp__bbaalmM@:WOB9=]ej[GE=27obb``_^]MrHDI4ZrdSJ?92=drB;gBCA=Q`@=ADFEQTDgNNACYO>AEXpX7=?Hj}?python-aiojobs_1.1.0.orig/docs/aiojobs-icon.svg0000644000000000000000000014174613125720600016463 0ustar00 aiohttp-icon Created with Sketch. python-aiojobs_1.1.0.orig/docs/api.rst0000644000000000000000000001627014322563232014670 0ustar00.. _aiojobs-api: API === .. module:: aiojobs .. currentmodule:: aiojobs Scheduler --------- .. class:: Scheduler(*, close_timeout=0.1, limit=100, \ pending_limit=10000, exception_handler=None) A container for managed jobs. Jobs are created by :meth:`spawn()`. :meth:`close` should be used for finishing all scheduled jobs. The class implements :class:`collections.abc.Collection` contract, jobs could be iterated etc.: ``len(scheduler)``, ``for job in scheduler``, ``job in scheduler`` operations are supported. Class must be instantiated within a running event loop (e.g. in an ``async`` function). * *close_timeout* is a timeout for job closing, ``0.1`` by default. If job's closing time takes more than timeout a message is logged by :meth:`Scheduler.call_exception_handler`. * *limit* is a limit for jobs spawned by scheduler, ``100`` by default. * *pending_limit* is a limit for amount of jobs awaiting starting, ``10000`` by default. Use ``0`` for infinite pending queue size. * *exception_handler* is a callable with ``handler(scheduler, context)`` signature to log unhandled exceptions from jobs (see :meth:`Scheduler.call_exception_handler` for documentation about *context* and default implementation). .. note:: *close_timeout* pinned down to ``0.1`` second, it looks too small at first glance. But it is a timeout for waiting cancelled jobs. Normally job is finished immediately if it doesn't swallow :exc:`asyncio.CancelledError`. But in last case there is no reasonable timeout with good number for everybody, user should pass a value suitable for his environment anyway. .. attribute:: limit Concurrency limit (``100`` by default) or ``None`` if the limit is disabled. .. attribute:: pending_limit A limit for *pending* queue size (``0`` for unlimited queue). See :meth:`spawn` for details. .. versionadded:: 0.2 .. attribute:: close_timeout Timeout for waiting for jobs closing, ``0.1`` by default. .. attribute:: active_count Count of active (executed) jobs. .. attribute:: pending_count Count of scheduled but not executed yet jobs. .. attribute:: closed ``True`` if scheduler is closed (:meth:`close` called). .. comethod:: spawn(coro) Spawn a new job for execution *coro* coroutine. Return a new :class:`Job` object. The job might be started immediately or pushed into pending list if concurrency :attr:`limit` exceeded. If :attr:`pending_count` is greater than :attr:`pending_limit` and the limit is *finite* (not ``0``) the method suspends execution without scheduling a new job (adding it into pending queue) until penging queue size will be reduced to have a free slot. .. versionchanged:: 0.2 The method respects :attr:`pending_limit` now. .. comethod:: close() Close scheduler and all its jobs. It finishing time for particular job exceeds :attr:`close_timeout` this job is logged by :meth:`call_exception_handler`. .. attribute:: exception_handler A callable with signature ``(scheduler, context)`` or ``None`` for default handler. Used by :meth:`call_exception_handler`. .. method:: call_exception_handler(context) Log an information about errors in not explicitly awaited jobs and jobs that close procedure exceeds :attr:`close_timeout`. By default calls :meth:`asyncio.AbstractEventLoop.call_exception_handler`, the behavior could be overridden by passing *exception_handler* parameter into :class:`Scheduler`. *context* is a :class:`dict` with the following keys: * *message*: error message, :class:`str` * *job*: failed job, :class:`Job` instance * *exception*: caught exception, :exc:`Exception` instance * *source_traceback*: a traceback at the moment of job creation (present only for debug event loops, see also :envvar:`PYTHONASYNCIODEBUG`). Job --- .. class:: Job A wrapper around spawned async function. Job has three states: * *pending*: spawn but not executed yet because of concurrency limit * *active*: is executing now * *closed*: job has finished or stopped. All exception not explicitly awaited by :meth:`wait` and :meth:`close` are logged by :meth:`Scheduler.call_exception_handler` .. attribute:: active Job is executed now .. attribute:: pending Job was spawned by actual execution is delayed because *scheduler* reached concurrency limit. .. attribute:: closed Job is finished. .. comethod:: wait(*, timeout=None) Wait for job finishing. If *timeout* exceeded :exc:`asyncio.TimeoutError` raised. The job is in *closed* state after finishing the method. .. comethod:: close(*, timeout=None) Close the job. If *timeout* exceeded :exc:`asyncio.TimeoutError` raised. The job is in *closed* state after finishing the method. Integration with aiohttp web server ----------------------------------- .. module:: aiojobs.aiohttp .. currentmodule:: aiojobs.aiohttp For using the project with *aiohttp* server a scheduler should be installed into app and new function should be used for spawning new jobs. .. function:: setup(app, **kwargs) Register :attr:`aiohttp.web.Application.on_startup` and :attr:`aiohttp.web.Application.on_cleanup` hooks for creating :class:`aiojobs.Scheduler` on application initialization stage and closing it on web server shutdown. * *app* - :class:`aiohttp.web.Application` instance. * *kwargs* - additional named parameters passed to :class:`aiojobs.Scheduler`. .. cofunction:: spawn(request, coro) Spawn a new job using scheduler registered into ``request.app``, or a parent :attr:`aiohttp.web.Application`. * *request* -- :class:`aiohttp.web.Request` given from :term:`web-handler` * *coro* a coroutine to be executed inside a new job Return :class:`aiojobs.Job` instance Helpers .. function:: get_scheduler(request) Return a scheduler from request, raise :exc:`RuntimeError` if scheduler was not registered on application startup phase (see :func:`setup`). The scheduler will be resolved from the current or any parent :attr:`aiohttp.web.Application`, if available. .. function:: get_scheduler_from_app(app) Return a scheduler from aiohttp application or ``None`` if scheduler was not registered on application startup phase (see :func:`setup`). .. function:: get_scheduler_from_request(request) Return a scheduler from aiohttp request or ``None`` if scheduler was not registered on any application in the hierarchy of parent applications (see :func:`setup`) .. decorator:: atomic Wrap a web-handler to execute the entire handler as a new job. .. code-block:: python @atomic async def handler(request): return web.Response() is a functional equivalent of .. code-block:: python async def inner(request): return web.Response() async def handler(request): job = await spawn(request, inner(request)) return await job.wait() python-aiojobs_1.1.0.orig/docs/conf.py0000644000000000000000000001461514312614607014667 0ustar00#!/usr/bin/env python3 # # aiojobs documentation build configuration file, created by # sphinx-quickstart on Sat Jul 1 15:24:45 2017. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # # import os # import sys # sys.path.insert(0, os.path.abspath('.')) import codecs import os import re from typing import Dict _docs_path = os.path.dirname(__file__) _version_path = os.path.abspath( os.path.join(_docs_path, "..", "aiojobs", "__init__.py") ) _version_info = None with codecs.open(_version_path, "r", "latin1") as fp: try: match = re.search( r'^__version__ = "' r"(?P\d+)" r"\.(?P\d+)" r"\.(?P\d+)" r'(?P.*)?"$', fp.read(), re.M, ) if match is not None: _version_info = match.groupdict() except IndexError: pass if _version_info is None: raise RuntimeError("Unable to determine version.") # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ "sphinx.ext.intersphinx", "sphinx.ext.viewcode", "alabaster", "sphinxcontrib.asyncio", ] # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] source_suffix = ".rst" # The master toctree document. master_doc = "index" # General information about the project. project = "aiojobs" copyright = "2017, Andrew Svetlov" author = "Andrew Svetlov" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = "{major}.{minor}".format(**_version_info) # The full version, including alpha/beta/rc tags. release = "{major}.{minor}.{patch}-{tag}".format(**_version_info) # The default language to highlight source code in. highlight_language = "python3" # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] # The name of the Pygments (syntax highlighting) style to use. pygments_style = "sphinx" # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = "alabaster" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. html_theme_options = { "logo": "aiojobs-icon-128x128.png", "description": "Jobs scheduler for managing asyncio background tasks", "github_user": "aio-libs", "github_repo": "aiojobs", "github_button": True, "github_type": "star", "github_banner": True, "travis_button": True, "codecov_button": True, "pre_bg": "#FFF6E5", "note_bg": "#E5ECD1", "note_border": "#BFCF8C", "body_text": "#482C0A", "sidebar_text": "#49443E", "sidebar_header": "#4B4032", } # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. html_favicon = "aiojobs-icon.ico" # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ["_static"] # Custom sidebar templates, maps document names to template names. html_sidebars = { "**": [ "about.html", "navigation.html", "searchbox.html", ] } # -- Options for HTMLHelp output ------------------------------------------ # Output file base name for HTML help builder. htmlhelp_basename = "aiojobsdoc" # -- Options for LaTeX output --------------------------------------------- latex_elements: Dict[str, str] = { # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # # 'preamble': '', # Latex figure (float) alignment # # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ (master_doc, "aiojobs.tex", "aiojobs Documentation", "Andrew Svetlov", "manual"), ] # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [(master_doc, "aiojobs", "aiojobs Documentation", [author], 1)] # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ( master_doc, "aiojobs", "aiojobs Documentation", author, "aiojobs", "One line description of project.", "Miscellaneous", ), ] # Example configuration for intersphinx: # refer to the Python standard library. intersphinx_mapping = { "https://docs.python.org/3": None, "aiohttp": ("https://aiohttp.readthedocs.io/en/stable/", None), } python-aiojobs_1.1.0.orig/docs/index.rst0000644000000000000000000000450114322563232015220 0ustar00.. aiojobs documentation master file, created by sphinx-quickstart on Sat Jul 1 15:24:45 2017. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. aiojobs: Jobs scheduler for managing background task ==================================================== The library gives controlled way for scheduling background tasks for :mod:`asyncio` applications. Installation ------------ .. code-block:: bash $ pip3 install aiojobs Usage example ------------- .. code-block:: python import asyncio import aiojobs async def coro(timeout): await asyncio.sleep(timeout) async def main(): scheduler = aiojobs.Scheduler() for i in range(100): # spawn jobs await scheduler.spawn(coro(i/10)) await asyncio.sleep(5.0) # not all scheduled jobs are finished at the moment # gracefully close spawned jobs await scheduler.close() asyncio.run(main()) For further information read :ref:`aiojobs-quickstart`, :ref:`aiojobs-intro` and :ref:`aiojobs-api`. Integration with aiohttp.web ---------------------------- .. code-block:: python from aiohttp import web from aiojobs.aiohttp import setup, spawn import aiojobs async def handler(request): await spawn(request, coro()) return web.Response() app = web.Application() app.router.add_get('/', handler) setup(app) or just .. code-block:: python from aiojobs.aiohttp import atomic @atomic async def handler(request): return web.Response() Source code ----------- The project is hosted on GitHub: https://github.com/aio-libs/aiojobs Please feel free to file an issue on the bug tracker if you have found a bug or have some suggestion in order to improve the library. Communication channels ---------------------- *Gitter Chat* https://gitter.im/aio-libs/Lobby We support `Stack Overflow `_. Please add *python-asyncio* or *aiohttp* tag to your question there. Author and License ------------------- The ``aiojobs`` package is written by Andrew Svetlov. It's *Apache 2* licensed and freely available. .. toctree:: :maxdepth: 2 :caption: Contents: quickstart intro api Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` python-aiojobs_1.1.0.orig/docs/intro.rst0000644000000000000000000000763114322563232015253 0ustar00.. _aiojobs-intro: Introduction ============ .. currentmodule:: aiojobs Rationale --------- Asyncio has builtin support for starting new tasks. But for many reasons raw tasks are not sufficient for daily needs: #. *Fire-and-forget* call like ``asyncio.create_task(f())`` doesn't give control about errors raised from ``f()`` async function: all exceptions are thrown into :meth:`asyncio.AbstractEventLoop.call_exception_handler`. #. Tasks are not grouped into a container. :meth:`asyncio.Task.get_tasks()` gets all of them but it is not very helpful: typical asyncio program creates a lot of internal tasks. #. Graceful shutdown requires correct cancellation for task spawned by user. #. Very often a limit for amount of concurrent user tasks a desirable to prevent over-flooding. Web servers like :mod:`aiohttp.web` introduces more challenges: a code executed by :term:`web-handler` might be closed at every ``await`` by HTTP client disconnection. Sometimes it is desirable behavior. If server makes long call to database on processing GET HTTP request and the request is cancelled there is no reason to continue data collecting: output HTTP channel is closed anyway. But sometimes HTTP POST processing requires atomicity: data should be put into DB or sent to other server regardless of HTTP client disconnection. It could be done by spawning a new task for data sending but see concerns above. Solution -------- :mod:`aiojobs` provides two concepts: *Job* and *Scheduler*. Job is a wrapper around asyncio task. Jobs could be spawned to start async function execution, awaited for result/exception and closed. Scheduler is a container for spawned jobs with implied concurrency limit. Scheduler's jobs could be enumerated and closed. There is simple usage example:: scheduler = aiojobs.Scheduler() job = await scheduler.spawn(f()) await scheduler.close() Every job could be explicitly awaited for its result or closed:: result = await job.wait(timeout=5.0) result = await job.close() All exceptions raised by job's async function are propagated to caller by explicit :meth:`Job.wait()` and :meth:`Job.close()` calls. In case of *fire-and-forget* ``scheduler.spawn(f())`` call without explicit awaiting the error is passed to :meth:`Scheduler.call_exception_handler` for logging. Concurrency limit ----------------- The :class:`Scheduler` has implied limit for amount of concurrent jobs (``100`` by default). Suppose we have 100 active jobs already. Next :meth:`Scheduler.spawn` call pushed a new job into pending list. Once one of already running jobs stops next job from pending list is executed. It prevents a program over-flooding by running a billion of jobs at the same time. The limit could be relaxed by passing *limit* parameter into :class:`Scheduler`: ``aiojobs.Scheduler(limit=100000)`` or even disabled by ``limit=None``. Graceful Shutdown ----------------- All spawned jobs are stopped and closed by :meth:`Scheduler.close()`. The call has a timeout for waiting for close: :attr:`Scheduler.close_timeout` (``0.1`` second by default). If spawned job's closing time takes more than timeout a message is logged by :meth:`Scheduler.call_exception_handler`. Close timeout could be overridden in :class:`Scheduler`: ``aiojobs.Scheduler(close_timeout=10)`` Introspection -------------- A scheduler is container for jobs. Atomicity --------- The library has no guarantee for job execution starting. The problem is:: task = asyncio.create_task(coro()) task.cancel() cancels a task immediately, a code from ``coro()`` has no chance to be executed. Adding a context switch like ``asyncio.sleep(0)`` between ``create_task()`` and ``cancel()`` calls doesn't solve the problem: callee could be cancelled on waiting for ``sleep()`` also. Thus shielding an async function ``task = asyncio.create_task(asyncio.shield(coro()))`` still doesn't guarantee that ``coro()`` will be executed if callee is cancelled. python-aiojobs_1.1.0.orig/docs/make.bat0000644000000000000000000000144513125712452014770 0ustar00@ECHO OFF pushd %~dp0 REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=python -msphinx ) set SOURCEDIR=. set BUILDDIR=_build set SPHINXPROJ=aiojobs if "%1" == "" goto help %SPHINXBUILD% >NUL 2>NUL if errorlevel 9009 ( echo. echo.The Sphinx module was not found. Make sure you have Sphinx installed, echo.then set the SPHINXBUILD environment variable to point to the full echo.path of the 'sphinx-build' executable. Alternatively you may add the echo.Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.http://sphinx-doc.org/ exit /b 1 ) %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% goto end :help %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% :end popd python-aiojobs_1.1.0.orig/docs/quickstart.rst0000644000000000000000000000520714322563232016307 0ustar00.. _aiojobs-quickstart: Quickstart ========== .. currentmodule:: aiojobs The library gives controlled way for scheduling background tasks. Installation ------------- Install the library: .. code-block:: bash $ pip3 install aiojobs Simple example ---------------- The library API is pretty minimalistic: make a scheduler, spawn jobs, close scheduler. Instantiate a scheduler:: import aiojobs scheduler = aiojobs.Scheduler() Spawn a new job:: await scheduler.spawn(coro()) At the end of program gracefully close the scheduler:: await scheduler.close() Let's collect it altogether into very small but still functional example:: import asyncio import aiojobs async def coro(timeout): await asyncio.sleep(timeout) async def main(): scheduler = aiojobs.Scheduler() for i in range(100): # spawn jobs await scheduler.spawn(coro(i/10)) await asyncio.sleep(5.0) # not all scheduled jobs are finished at the moment # gracefully close spawned jobs await scheduler.close() asyncio.run(main()) Our jobs are very simple :func:`asyncio.sleep` calls with variable timeout -- pretty enough for demonstration. Example schedules ``100`` jobs, every job takes from ``0`` to ``10`` seconds for its execution. Next we waits for ``5`` seconds. Roughly half of scheduled jobs should be finished already but ``50`` jobs are still active. For closing them we calls ``await scheduler.close()``, the call sends :exc:`asyncio.CancelledError` into every non-closed job to stop it. That's pretty much it. Integration with aiohttp web server ----------------------------------- .. currentmodule:: aiojobs.aiohttp In aiohttp web-handlers might be cancelled at any time on client disconnection. But sometimes user need to prevent unexpected cancellation of some code executed by web handler. Other use case is spawning background tasks like statistics update or email sending and returning HTTP response as fast as possible. Both needs could be solved by *aiojobs.aiohttp* integration module. The library has two helpers: :func:`setup` for installing web application initialization and finalization hooks and :func:`spawn` for spawning new jobs: .. code-block:: python from aiohttp import web from aiojobs.aiohttp import setup, spawn import aiojobs async def handler(request): await spawn(request, coro()) return web.Response() app = web.Application() app.router.add_get('/', handler) setup(app) Future reading -------------- For more info about library design and principles read :ref:`aiojobs-intro`. API reference is here: :ref:`aiojobs-api`. python-aiojobs_1.1.0.orig/docs/_static/aiojobs-icon-128x128.png0000644000000000000000000015332513356322675021045 0ustar00PNG  IHDR*gAMA a cHRMz&u0`:pQ<bKGD X pHYsHHFk>IDATxuײ.jd|-  ,AA xH  !ø~ݫ~MBdsϽ^/n p‰(TRUTFeTF4A@ ԜgKAT@T@~G~"8P82 6nv_ܓ{rO ~%o[^NNb\q=Ne1@)B)@ ;((elDDDBPI'tzI/%ݤt!:yy}A_}QQyyox`08Tގ؎QQY$+XKRT~H+Ҋ>m3|UXU&7EqQ\$\L4L"O >zy,V}Iq"'r"<4ɐ~ZH irQ\eDQFTSc8F9(堓tNjZMX5P?>TzWxX܎q;Ygb^,rjh5Z]ܕrWH|J)RJq+n-oEl!&&,¬,em\\0 0VYeU+ߕnXED+JXu:|L;NSݤnR7)ZJ-REB5Gaf`FB3Àq=Ѓf$AVmUud*d2̆59~%xRZ57jݠbJ==iȍ\7܋kU*X|p_hmc>r*|2g Z[-^L/C/?)#vfh ԕ'Z|\Rg<奞 ]( ?HAy[|"d9Ua6+QJ94Q@ E{XKM_LQODe{zd;`߫uZ?,u̕hp*%gkjupJTv ߻Fԕ`ދzNx8MΥFz\@WaEDtI#䂧{ IUkh- G!W`Qf%dnc<_S2/j"ZyNWd岏p'p(e2J hE"mZ% $C"*?7 2,`0 x{{[w]G]G]G]nnn\e\^!.9]׸Eϓڹr׋΅j]]^TX>\ύ!ԐgKGd/ٍ( |MU5M_ثE01kp?3hO6cK& W)3HmI.h!uy*Ӝ])Q̉ ϯ_Jǯ'VEkDwR"KQ}^0q;R:G#9J,"A{”+9b ҵl0Tsk9'HGFg8P4CoUa@LޯC[`g,ᤩ?ز_ | h14. Ԗb>){JN۬⭟lk#KwSdwꢀ9#PhAvp9]NjmhZmS99=Y3_b(ʊhF[===3{gCgCgCzz dҮ _$g;0vPxpr GlB@0kPp]k: {7?YenFL`/N/> SWse\boJ%o|ruN~d6&_ѿ=c}k;g0ItX'l/J1{#6r$\rv6i|W#~ďo7%BNh'lll2]Y`7((Yuhv4;fꚸ+&M@rX9G o&5cG7m<(D;8%?M]GQ+\W*x]z `h4HtOC $`#0h5}H tXX_ ֋`P-% ng-/f_L ٔ dC9 IHN `Nկ!#>o8)zIzW,g$Β^pYn~af2!nquw@irXTkj̐qC}}}&bX+f!Po3@/=d#Sq+nŭznF~QgL8uUAʝ?oyݽ:1""!Ab!)m䳍4~"[HfM6Ai*! E0kP@|rWPLsL%h7 uޭf>J#h/1$:.s bZiտJ9>şEO>~ %C}@-!/j-kjnou#$fvr-"Bzk '1ޱ`ha.'T)g١B翋<(&gC&*uuuc18w|^"Ldkf4h**oMb-vvvwZNi9TP [\ |VV|WVw\u>5{LSJhgW.(r Q HTΝX;j̀oo6r7giA[HA%"yq(Oů3D3> o@ 4:xGO݇fY,/AVx{3UE#$^ќDFyz]#8R8Rp}tFhڀP444QQQ&&&^F/OȤ_HS8S^s\ y Gz sS{K^I~~SՕöD)tìǔgq*`=9ԏO36)KWstA_jyoad]^_x4@+I6N^A ̇(~jboI LbL{xM4slA=ɏoaIɟ@fb @Ew.)LSvSfZ{2?_;!G5/Vjw3&JS?XA EN/^,X&^^^XX%P% aKd2?0?0?p777={P'7ߓ5 #ͻ62w[vqMpǓC 4S~to9Nbl5ՃBiv` גCٲ؇d,Uq_Q_A [y4,[JZDǯxiw넡rҹS3&;~+lDd79Q #{X+Vm= ʑً ' %,`1"\@ϥ#ltINTy>gC>`  jإ  s U aߣ@9ydE0ZnKTgX=,-Ve*,iom>0{HiiZew]Ⲹ,.gf!p x,fwݭ5f5E= zR[chvJ+W4t5UZmҁo.<Ū F r0Fۜm Ƞld)гުby Jv^-[XNHNdYͅ|sQȩhvo鿼wpۭףO=n/i $^v8Q l("CWv ROA>c| g 6(Ieo#g_e0RO/&Nڎn&(\rq`(r5MVn{YdFw#vJPdAJ~Q \h&&& , ST$%$ʠ,,˦;3,>@!$W}ηTXYޜG:Ab, pE)qP6}7T뷻nD'_k쇼,yh)Ýar:p |Vv~)ACv$eFz`,Z`5y ;F!%ќ4fUbVzD~ B + l٫nH9 X `!"%}d6eB!"_:MOUf^خ|4`n07N˝666@YTVfOe#נdL˴L\\\o9o9o?$ɗ:Pԉ*wD Xo͇tcX@BBpQSnCdL'Ptp H\m(YS>6Th:"}_^_^_^rrrYYYے7%}2:$|B<9;@zZ{ ?!!ˆA ݖ͎,)/q0Г LHWwf9P]-^47m5ϥ\`o`!?}af}`!(LePVtb $^־j E<RXz`G*"D.Aښ3x*`(_.H8֪mޠy4N#O݈YYyp~ﵣPOsss7͙f6[ͦrTeUCo{{eYAV0Oͧz#YYaN5Mita~9*z7sRutVڱIh߃~V¹pɕnzy@T#_ \0s+:qm|&u;e9/\F/rDŽf땖Cj L{%]L_t)xCRAk4ih[0Ksk]BoqU- ױoQ}NHB %t HA?R4:$bǹ=B RiRil`EC0oYlWKh Mt/UB1wҫ$Y9$:'@k?G~G~G~SSSw[w[w[FhzM^T "A1A o-Ŋ8%%%tEWt -`[/赻 rNvwU83Q/a B qn+`d#57VaTiz - L\\ȃFd Ƈj~q>se?+R,[r 7/wm6O?mX\m-Roا6_}iYt27ީ^5Y~Jo. +c~Jc]ݏ} 󀹯o$L^=#՚G3L$Q) >"V6͒݋0]S/b+rh /.Q,&Ṟ\`pIa Gm]JgU΂eUW~< < Άt6Ў3˾VnQ&'=E飣Nhs .Z`޸G(T۟"EJ 'OxTf*3qq HDk5{nvt QMW~Ғ?>³FuZ]u<LCٰރ!NyGl.j8/7iڷ]^ktE({xCo\-\B)zW3.?] "X!^9̳{xKʓR'ۇ١MrsϙF7 81lۿ sG9qWp(\U@T!Km4_\&qUKxdB:^A@ ِaT}PҼ*xOq"2i"luWt,e6Ϣiby\-"lޫtN*VAK DsbjBmmmɬgnn$?g&]ͮfWEEE~3AcqW=ofݝ.JDyL`HXI5sCWD:Pf'`RB Sd r^=&5PqC"^{4);A\)ԣz^α2=٩XLk s7he{\+Us? Fϝs;]WYI[G% H zU,|=.|u$4]v~߭W4[~$ND6}9%m LS@*-X.:k_cxWWt<k%ϸ4s] <]\U&9qDA0:(htE8牔 sh.W=?BBj'9Fw E'u_E9dNq3h: M![ȣ ]ϯVZI{엳-V5Q8;;{|^iTZj[-ږ"_ȁY1 /swn6F3ve؅!C&LFl׸yDo.x}燘T5H̷FAj@ @*b>G}~rvn}k1VyW/a~[(2jAG {B0XPvBT9Zd󻟯NZI+@0~EYf-<*$6 p xAEi&U !R_rna9JNBsvx:$+dt9:Υq{[6rw{ft4[6Mߩ3yh;|]nBt_]]U챼Q.GMR'!CTP! t4ѷ2K|펌MўV 6tvK>7B^'R¤(AʌU|`|^X6^}Nu+Տf o_(]f^!e}s+s,3+~2+93٬F٪W9i9s};wZ74W鷹跘 ̠5&?wR`>86'ێ߅zF̯EqPczB{ C+ݷӲ95z[UX_ѿEo5{euY]V6lr P0&lba;;;)cwfYk^69+h>~)bZ2nW~gԈs&)fPAHC9?ebtmRWqgmy/]tU;e= px4 /?ATͧ-",JA`\VnhhT[DCf쁣 & M}߶ 䘚#n4_&8 Aqt7!זEɬJq\Nf~?%LtnNƱZ([JoO'Pw;jeE:@&L|y|a,7FAҷkÂ)G^I :巫zf\Luy2 C'|4?.-_g~©o輐xħ)YhZS~ǻQi# JZSN'9|YVOv>%iն%Usa}u#HT{l,/$ Eh+PJh϶zK4$n$ [ၿ=gM]2Q-?:3Xֲ -~D? `៸%@`1L~,3T?E6>l9[F8xݨI"^>4MoEwN9 .;Qy[ }G|+5rΒҙ*i1ퟄDxqL#Z\MnX yv.<;9 9 9@U*988dI_VMeڻi尿,Y`3{={={~&[]q7f$ZZY}64_uM`G9s) _\r9ʥ|tZj -s:S5g$F+& A;GPi(?ᨨv6]/~ u17qx!qWhU򏥺t1r;N>W m( ~g8oՐ+*hs~4R*{K ?Dk5'R qs=M9䧲t؅GOb:@t[Vi_2)i`Y/T&32Dۭ<C -KVxD54>,ed\еU0wݜCQ^c?dȘ1 zބڃȎT d c9c|V>+ggg@f)666C=Z/hYxg֤LȏPNE[6qB3|r? =OT F`*KD m,j<һo#:1͉ ;CwFdEÅ^:/eӋГ| &zRK[Jl5gAiqžN"4\LfvtOPO[! ("o,elV˜ ]gbYK;vyV}n ȍ9 ^}ʏ#Gޏ27 _+DP]n1٠r=)VFdS9GGx%<#6Y'쒥<[HV#+aFaA;(>Uh/tj#)o m/H_@x!B`o5@T͜$a.(;H1b/MH b:'h' 9X)ס!Ԃy48M΁A%z?Ox;/>=Vok^+9l0zd|Q "9iA*u="]@ jqd(ۨq3Tu(Yo{{8Jeܓg0kq9=:\f8x^2NKDy\a!;|Rmt$̓XʮjDV:u1cxTg(e򶷉Z]VPPr,d5Y杔]λlPk#$qSrM ?kGm^Ԅ֫TPP{'j3_רZ 'JP?0 c3`bmʪE r'TU+DƋ+BNpgH`łhu7*y?A?d-+Qq~ɾZ-<-eI+>*[v1^4Jc S?!* : DͧI\RT_֤ƣ]t@[g 4$j% 7lӘR N42wq=A5 T)lj:Ps!^50@"[53*Mꔀɮ^ﬔu=J{fY{=YYY=VZZ|s9_3LJDJD(Baq.ą)gʙqqqJMRvvN8>MZuwVy9T8:demJmش dȎ>LGF .r{RȔNlB=`ofMa.D̪DS̾BFܛ9& K#"1~ʯ} f).Wr/ph6g)nfȇS 0z 9ӷ3wXDIe y-W^Ҏ]N%} qHsnxz=-{H t38ڂb)xyhRdc [_M8[Tl\x])(ےQX=AřUTJj w2|C X`0g^wAl(>GVLFw0<@_L%. T6RQ7@چk\{ ;F->͏6-* .|,аLV=ulQHSz"een;41=1R$G`8N: A(%ϟN|&wtV μX[iu,[!5p:\+גZk':AG2 BB*8AK F]{$3 ylW1%p{V1n!/b;zUꥒTJ!ƀC/P RŹ8J>2ZLA_Sȶ=a3;8vz']<#9@p!oxRY\2z?2ϽFmg^KKlfUJY>0`ܨp"lHb ^u[ףEJ >J[!KNAG2rωH͞FC'(' u޷+Yl>Q3 gcg0Jrac`a[#yux59>ڒ!qKN_Wu" [gmqȶ!~_a7Gq1Fi% q^l<ǒ|сSyFW>6uR2@GOǬ1),=@ W#Ljwr\Jm-gSmL_+--(4Wz-]Zv/_VUUUլKll9o7Ifx3f7J:CpkCww0/;aqG%nwQZ qگs&76t~ON H q_bpsI̩_ё]N1b6nTH_1C%i>ο N>zfsyY5d .cXXL%x I)?<ޫ W\x8238(N"z??W 豜>`Qj ' vB'QEz1#t"S"xy\Cԅ:l:r}n\~Uj:3EF_Y}3&q)y4r̒3M.1 [D^שD$ߐb6pUB sJI"A(B !?#(/Tmyޓ7'dItl(:}}}wʗ)_|Yg6-%S2%# {pGl9[6G/k.ͥ!JF-|MaS9ar?|vr+uJb>>ӅJ jk| {}(+㯸05ԧ~깐Wdǫ2dt햣, \_Wg F<}UxI%c6c(0/@W{kk0k3ԝd 7P戳(Vp vV4 _YJ)=9ZV 4~)g\&\ը]%% Sej@֬AS8>+٭" GE^rmŗto 2;kmArϨʈ(A8= e"XœSR:¸Y}4 '&fCѦ ( 2 5SͩlS)21$"Š!g^444zFk bW/ŏWCK{VO2؀ʘixu訃9sPHދV Qak˘!ٮ67$(U${Q>:NSS`c)(P X]ţ)(^|Qt@YNV3=*%s(q_֤(Pd#ٕSdzVpsL$Baq/&3&b6˯9҆ bR2G9[\_hߨϔ|XBx/{ցY_?111W{98)n/%d;c1O jsw=P0cx0O!p _ hYSǗTqg;?xJ!*.agj b1T:TŔ}kOe;溚| 5h"\Kz2GxsYس vA_A@ r y]ƫ@ƮXU~@gҬRذZ#> t wYe,H뵫R*1A`;97@$ ㇱ/wW!ŇR4=00r=ޡ4ojdG1c{o)5iB} %I ^.?tҘ6VȆ2,^Mࠦ4S;1ܛ`C לS'5,͆3,$!f̪2+k'I60a?8+ SR>`CwLx. 0gKsqc8?{FnT;J5 W6Y B$ }p#'TӬEpWBQêXܼg tΧ gY?v uI|Sc=~I]Tw96Cg5}&3.|O :SŌbc1_יMߤ=<`ɒ!wfbl  *k*1y~Ҳu#1= D=Ʌ!;4}V ,z*Oz\JĹm?o0܏ y:{؋4\L(w38OG}-BT_/uss#9lg?7,\~/%U*3wi7݊zhiЋr[ =cC6JB{ktNt(TNy*qثq6QtvM|иl8\^|l} +fBkEI}~XtHh Mz I;khՖpS59%tVBs{x~na_lXӍL)H%]|iI`ځa5"ZBu03Jj`ɸZ=!7rS_/Q7GhFh5ߜ*\ L6^\L,ԠY,\F6Go*TX< 1|c{T@^B<){hѬ`?4QZ "PD]됃oYut~ݱt5[ 9(Τ. 7P0Z掤yۉFk2!sr\eaP5{*Py ,ꂰS6gtǟW->eg )F޲}N澿Ý]k 4V'k P} 8oe畾9 8xys&oCvr+" @v!qkLqO*aMPowiRPfP02T=dUR*"a6CE/3`F'c=?$!^HEn6E2iǾ'*VJsN ٪UtJ1^xG!(Pd3FT5!or`ovh5x gsw2`7\( KZ|R/2bfusVD@(󕠀i'r%qOA;&ɨpXC_ 2 'u5ޠ䅨~ؤ<{x.UH}e*,MÔ}5'Wf}|ޅTTV@)g( op4W)t#mzܜŮ1ߔhPSѓb##=4帼i'ͪIxFmųYT;tyVT A惤m06s6F?bN4&fThc1xV&at@KRHlOy=J) gRS,6ds٬ZF (P+Ory."Py&lL-JCŵp1JT8Jg+}o͂Lﺙ%_m@E) ^6?*XZpgNG{!DYsKƾw_'iuItO]XP 4[!wp gI1˸{;QM] MD*arK| |JxT!qzܼc1j^ͫ1Ӊ">7M^ yAـg:b .ޣ^=Zii`9$,!* Z㸃1='r#QI%b*o;x8cI3'4_\4[3#+`>C9u'P4 dJOwU_U}x{MC\TtB[0lvHC:DPſZ-V*Wl>neMn嘕=hpTE>X4b}IO"Uy7B 3tyI+rO_~9'r!?ե2JlT޷]A>s{+ob PX]|W%|^?p YgZ'։P2.2W\]/K#y@m6xC$< fWhF| ͉`4Ljߙ\7b*.; VegwWA`E/Xw+-xEGa@4scc/wOE./ RqCgcچDfo o%rg a7ϰC $H+*MsBCמʳ?8vX7\+У" z}NEJ!4h-C\ɻS/h{"zi{HyİҡyG^.\|h7/6UCm>Ar`"~G=;Hr UerZOtR}YƗ Ozbҳ]gHO)JTSmG7J5>Kŏ8b+rgs 滅VIBbpB~T9ם=) nqM4"bi2wkې E^b=՞jOKd YU܀ S)soH$v) 7Al zFQ.S`voz)O=.- t딦ְɺbbAhN:Gj#}H2tur=K8%fyfoH`-y8(پswYiyUۡa%}IC}*fPÇLӾԯq`X`浯D2T[Y|\V}_>0"|34'|)6WlMDRg53>Z nn8\+rzZi<_I4}r{L,Ğk23 һ4bDжbq7"]mĀ;Ji%wJ./aK𴫯YbUy (-ବAS<,y%S'zL[kqߓ pvVnR CqH4T 3] M MBɢaQZs웑@}ޥRve pyxA^+y2JF(BChHfRTQVd3MS0h(d%vuރ:GJT{س#auW&eC N٤VV>?~4O<ٿ>@Ȁ)*ރ ?VX0ew^Pð1ɐc[HU^8kWW|r( l0Pd"P'!?Mkk^O3HjOi]8V!u?Ξ"*]aZќFS^n6 J|-k+bRݠ-+ߺ=|9 ga'?<V&(6HDyMNcd̤"HIP]eITM#(͟py+ɧIOK̗ྟK!LcVb7o*ݡuOljȴvé1ATO]Fro"(ޡz VÇL)0;3u;=2krg*Kr 4&2 lm-gYyli4[K/t.ѥ~§DwM?O~ujFE]͡]Jn]enb O9Q]Nh/a]y-*͊t&+kk]aF@”r6`H^'dV6_ QXOyE;X9!iC-B%=Q2VkLv3ie9S_-C){Q\c!Rr8\+g(EDC+ <ZÁH (r6 5=mJ~nNb0*2MYg=q_x*#QsNS)*[?*b&A4peLajˏ:ڶIBTR=6@)o[/}^v'm|Sp:_`@P.fUs}`\QlUj,c39nZܴi|||J@`+bkf؋pC8lVym+o>$,2"!`TNs9`B_n|lb-j.P_dxZɑ4Oq{ӂf_HhUh ahґt(M4tIPMH|`DG쓷=P͟{ @ ;,[,b`" &:~0.D` nNZѺR9-y 6D5M Қ.@>6r+45i>[5JB14v4_ 2 ۸uzEXnPڮjB_gCJ#S=~b}j篖peKIoFzevIy|]zo𪸫XG^,>4ӗ{5m0ˤP*DBK~)#שvYy dGBϢ?꒿fxcM%J_IT<Я2kW3担?*mVu5]kx1<6n=\$ETRY,zMTYjDio+*=,fNXvv& !X@@W"VϨZiHmhdY3g{j&]Gm Ż}hnE2ԑ@j1Z2 me0?U?fP`#Y-e>KSkOQ{F^_ "P%LV'{"&$dTIi:}}T̉ԓޚMAuTFȦ~ oݬV~2,ȆhHA~][:*(M܃B׷S:jr^+W W݉#>pTxpKn-VhzǼHF#fڇI}+ ^$~ү UJkvE4J #U݄ o ?զ .supxY6XljrljyW'0 VN̞߱ZB;>hMa/4i, jNzT>U͞sQZб 󚍢p4ؓ9*nɕTRPޞ/2nQqtpZ{j^4Ң^Ky,ntf 6'x*KB } j B+Yb*8qpD`8Q=* J h5@U:Hs}KzV(g91UFb|UZj]GiwiXTޥuo7}r{_؇htPYDc~.Dzz NRDLLE$#OsGk mM4d'Vc5Vg6v!u: BSRRp$Hqc8?_6)n.(I~ߋ4ǬTۨ [@,,.,QϢU⒕ţ*Ovꐎ&g{Skp3yD˰twhfǣ8rMVnoTQZF} $"P,@5P^Uo! 2DiFcJw?i.>/EYU|v!)^t"{ Q SI 4~=\?qiF 9ZjtM:fnjI>!1nVFM~Jת&p'X+֊ iL[:t88),2C}ۃʷ:mV6r3 {-_Ҵ./Ùn"syEAj*_.<)wV擴;(r<"3~&@k57`EJ(+e}I,鯂T&*rVzz9?~flh&^a}z<g—O<< TNnRz_mה.#_0s9 n j8n-QHԋ&ߊZXAyO}fǎ˧]}&q-{ jg]-0WE yY:统@eIUs-w 4/]Vcڟ}t(ػDو }'|FD78?NysZԳ_\cP0rfar"LxBEIpFt|"@TjAoBr>mEP!Z?XE wqwqWq +`60o"wR&@,zZ*@٠BVaKE|/%=[iq@0C=#O c35l -ײjD|۞} o4; Ճ!R!V"Vjҵ/*r-<.37{dK BKk:Ga~Miv@b#L\Lh}'L%&HG48-KF@|7F`Q.8hbZBhy/HK6w 歧4T*6j6p".ť*/#[ C LnNDSe6i`-c#]|A^%uH&?t| o!  ''Үqw'ZŌ(߅e>ID?#fy3gIkC6oE,B+BQl* ʺCߓNuqC1$U'_`$8ew&lkcSoe4b&mbbOtJ[DxF$Rs6YЄ>A}:+~HYeZ)go:VKچ4, t3R΀ +4DB@GN$lmdw@lD,('P&P_k/7}7=2J-ъ=}%.N^ 5,*]$ОBX‹?dC |?X}y-'ɮOp4=TGݐ&)o8W QfbCE!BaƓy2OSQJ۝B#TʓLiɨ4-6`}훲p5!&ﺋƥ@kة+DO%|xEs{2> d͍BPTvRsņӻTm,{]xߥk $cT{x AVw oY uCkWD.vjSMTﻎrtFLP~Qxlb~[Dy;Tsm.{Tife#˺8|Vlw> Jq6mJn;> Pc(G ǯ` '#Қ9h*g 9Qow VSt,~bnZgSh~D*S$~o09C:zag4TNXzFQQ)j}4Y77;jCgn[Rk%"c+ >{ hR q7]e?c? pGŨ((hƴvn9ENS2;@>D:Sۣy^)hxեK`)Fqgx_ r^CcSAȃi(f:dKghc+c/Q;nHގʯ@H){eQh L+2:AA!+P}e ``u6JyQw3+=?riF솉ضv8sibsTd$( I "@3PAQs9t>'P}yw{-T]Zk)Eh1 {KY.D8p4kuټ!2ٌ~ەZ'x=5}]P $$|8Oi|> ܟpns?RV/w +uQH| W"WSb{޽ց/^Z ,(##gw2}…NBx75gdWs4T4 ?eP}fnW0]"Ԍ-{! P7`v'bb,$źɵLCE3BE~ Arh#RT:9դuJ>C}!_xbG.BEm>6a0,\lهIC t|M#~ wU.GkGH@%VL뼌LtDF&atj1yMMG8R.0qMyt rQN"ASC/Biޯ^^caH(@P:rGlWA̐ U%>L r*P+1(USf/kVD6Q"2Ա|/Q@YUtDGt烠<ʣ<^͇GR}O ZZFCQj0T;jAղD8Fz|uȉ4M$,9ao?zz'>t_ (odxj@EeQ_ + lΒ@HDc7כ'uvHNQWA\83MeeC2SdI: hhBCުa^iwxͱ1M s8'*Om7¡f?ĥd%DQ)@"+MF'. $6,89̈́ `d yb-dߛt"[*w\&b5{[$i - + *ukӺV@_,Dž?͝lw kR[׫;qby껝'ڼ Ӈwon~n=yswaeg3b$Ϳ;E9yл=uײ=7{ܳfٳYQEͩ95nԍ'TA1S3iQs9 Q;:V\.;@g`)`"cd^iΞCE_U`&|\rN- h,o> 1KEHD):Ra, Hto/Bȥ¼<1H̷Zt޴ڇ!9.D@{^SfPu@ ~gAR1KDF4T??j\.ʊ/_hub&)b(1e< pB^a ϋ10~`;+/?B$nxNx\/t_ È߳q.ՖxGPJ,`sz+Ȅn%NDe ̿ d6:o K;5k;ҴJ‰hJF5[ Kv`]$ o>{7M*ij[A*{BHhE\6ҡ?E'H>[/Mr_UBs}y@0#xA=v zE#,>̛FEt NhCyJ HH16>Ei15UxIDAT;ˌ:x#QReo̥ k8~HON|qVw:I-/&MU606X ;]+tT_"*"ͧ4tPJ(%ffb&f `>'(2>oG;+te(ΕPAAQp+\0X*O:U6#llÏ4KB"U\֖u9TePӮǶ F%] 1>,uh&:Qhm5#1Z^ދnt%b@?NOXQTxJ.'5U[y #Uhb WՌ%G2x9{ &PM./Bi4/u3 ۹\ ~O^kFCRZ -{PB QߵC8Mju*Ũ?b=G&j=IY0zzqPȇ`cTh*,HYopF:=/ n@kJL(!̖MXԉM{'+0 (#\Xk|ڱSkKWzv=}۾W+)5߿p%RA`>7n~}-\r^ErŅ>:{/YgC zF !sQlĦPR5!kTqQM~Jt7Cn! "=:o `*"ʻ PETN Pk4a wpTM%2h>-łqKzx|%7j ވy-%>P?o::,^ Wɢ:U%qM'⑯H$EvInh2PqL9,-QS q*AɈ5c!„Ggdž-%m 8t"k#ש6\!'f#8G 2U6w+=wpbAu=%I&6= @ npHį)@kdU=2ݙs'(N%K,f q갸dS1#ʂ؇STX~Y=IW1WJd&h_s9.C;|$]t5x)x)x'fxR?B>6yq8챚3.(F.̪ΒZ/-@+U53>3B4QO{ܵ +#pJ̻H]$OOnEXvUOq; ڥGH"#ӫTQ;'U|"~e$US7駨VU.#O8E6ֿ~UZ&"E8xH_ ,C.dVi||Й5yMZxM(9l>'oò/{wanc;τn= @JP3XKQG:vsPJL>#r}Mj8^():Nn.1&L ΩW=΀JJԔ* 0BUu2"4~3RVU}rFo񵀮1f\>`P6dld{T6DӜ;|kע}ѯR0p_hzz@i4P)ՔjT Q{ꋾKң2ILhu:EEE؄M"WVʖ`p+!S"ЗnJy,96" NMH=+EvI_u8y~w8cONujF| :L= SW6yg3Ns5nD#뼉<۱7U阏 a=_CYPQ<O4H?wPH WKUEۈ,^|  }L\9z-|d}# hA qżFH=@>:7K@=)8e @__8duPz-!]fQв}N91r@:"4g>yoc@)y*[XHjc#A*L*R@:rؠ Uفm ŔEZ5fQ F6 ˁe:E*6A=-OShG2˅5S4Y^VCCCD5QM"ZD|Ci.uQu)R)U<.kw;#ڈ6:V~4h! N(C 41ANDEH\&{Y<-Pk% n.ܑyy({9rLT Yջty;!\F.Jqꮽ։@6Av+laAmщZrmR7|ٳݞz @HԱ2i"*Rjs a\%U4pYowfʿ:GS=6K˯J_aӺ3>  CheGtzsu /5c`n`2ܨ\~!TS(z7POz^&n&>DWS. }:>J>3~"]1zb'ӓdXHBH\_Rldy'KAE'?<[DףH.{g/LP7xۘmx%<6+V >}}| qN!_ IH"y#*\>j&T&T&TT}RK]UM*'KBzf8| \m\!y9:\:Vmg6Mvsx\ϏvxF3'q9GEWu[|FjSǡӸ-ϦiW'O[zV]#v%=5dU3L52ZV}@F@?(Qs|O=$( USżR%YvE<4TlQ $ i^>+qZ;ޤVN;oݏ@EdR{_ӗ?_q:Bk0}^(쥢;{MM~bzFDY3!>dESH0?kxFE)^.^ŋb('8[TŦ E8t i gf)Q-#Tȃ*SQe3[CPK2^y'@جL` {_a*Qjkn ,gS|@o8[mA+R;@ĢJ3ZqdaY$L:7VR/1~8nv-Q)z*^ZFx7)_U۫Kq).xAP#p pQtx%^oہցց}@[5.'}}ݻ\ s\񊮬p8D!L1vt'jFjTK>L&Ź=a#\>dZK S5ژ|O%9GgEi"~>ig_/XU|)_yžj'?i1^v*jhE m#?qs0weۮ>( y"q(KǝqLG!ԓ@A«TY TP ή!F:pn_ 0B~W1zTq|QPt}awZrxM ^nnN4i}Nl k -m> ([C_ }嚥<<nRJו(;މ#l]ʿ<5ښ1h[A.،p:DPm0,M>?ͼ??٤LTNAL3`y?"(^'NkVOAs *Fhxa f&YzFD C4⢎4cO`}˯Dr[+!MqQb]QEJQ`(ώOKRRXa Q[ ?Zex)#p,O>0:Ɵcd%p |W|W|Wc1:O[_᫻? x⺸Rg> 3d28R\nF6`76R~"?yx[較u' Ar9! x^9|ӗGќ!ILLjJW@Q%዁Jgy}YNE=N̛*/<8yF **.Dl\ded&։h4<ܙY(_kea~wj{h+,G&a&UR8}9eװC_R1;þNIh*b~Zh.4bB1333ţxQD`(1JMߦoW |@DHW..MjfpPhXj%`1q2T ߩu2v\2 \E*>U hB.N(o}dBGϾ$R|C|'hosL{V۩?L!䟿<ڝ.X3iR;s.˕7o<#M=.ߩ딨ج~=Q jtDAJ|* I.XJMVmkvg,@4 .0 h 6='p!;.S2ep!Iur"\=%"&j |˽R;'3U%59P_x"RnG:4OKR1T C:]?(P6ݦʛʛʛpu::J^* }P~X2JΡT^2d(pv&6|Vm/Mٺ_y_xZ FósE{]VgQ| P3Vz_<J&D v0&hJ\p?Ӯ}鹹[Og2a m<'§8T(ؑTB--[5,D"_ T&c-}u2\p_Q(G*1@ax5nR71G=c~^heow8c|_m[ nse33s`hpj,|xa ,Kj/9p ? \(VwZ_]z7.% ̂* P.?rHap5yKs+2-*Eym)!{!g͡x+`NZ-97R]|u"Ee?d>,wBK2 Oy%IG2s{0ɨimBtp@=1C71ozg΁k A$%7!k@rK` ޯb P\>f'`4930L,! 7D4E[Ccv1UᓍrTe9^qxEM^?#+(,ʈ2)qÎӞӞ"fE`Q^2"K[VxwM)MZdZ"#WνQ4Ot2 j[}2oT%W M1"/^&SVb`Rs\1p*<>HˊafnR$hi@H~vl (l}.3h\xRx2>a!?1n*ҭe,u=2'kIO-ݴ<*6|TҢ_-߸ֲ8w&4̤r$OV6J׶OoWS웦ˑŇi~Üywbٝ 6n{l'bt׈+ˎR[is䜔j\=^>w}g5 @6%V L05R0c8 k h/mn¯$84 ƻH:N:?mDZS͆fn 1om(&kZ6|***u: }G~0jxp@.b6l 444"/:DuGR9)"+/]hDa;}=XHB 4¼׶R6Wh}לGGdcxxHFMπ~BZZ׎Rys 訍!}*?ڴ2jEJ%iɏΖ׏- 6ڂ\WZՌ}z % Z$h$iZ@BcXڇ(Ixj$s_oɉt#lՃnVM9'q\J5FD況yo[[=MfԋGyGdzg9+ەVg3ՙ;8-3me[mmm7so޴>>>*%EIV^ʳZ.[^yAi{ϝuccFkDǵ_xfZy0b@1/~?3wriw_NV*|u`sڌ8C_PN('^>GK1m)b5صٵٵ99ȁgggklJlJ}]j]{Hs]'Ec]ݢ8T/xjj u=1FcݧG#n|Jm~-I)L-D3G .g'A%!c18'h/TT !E_M~=v /ک }iɢ L% riP߇#@I?gȯ@Fdp^~3T :)8.wב0sp[X%y+}E[err0tT. TWPjme&R #m{% *I_RcL2'rSrSrS\\\ǴfZ3Y#/s|ۏ/N6Rt|BBBBf̆ H" hMtyuo,v9`%yze+[>JM>.bb(熏܍Ɏ&%/oxZ\q})5/։}c*7Yv8,(wiHc-g @c*UF]@w*AT->g8j9|'#.Q>js :.)>ŧٮlWKuE]2225IMRqGAODϿ>DMp1\Y,U www y6x3ތp&H^\nfY_]}qBk\쀠tMG!Aϙ|& *q>Qt5C}4F9@B;qs$AG䴺x&Aѧ'g( g)BtROك hN͙l`-G&5H8q^P&Pp4)`]\_ Tt @r%'b-M#]Ctv-`u` Ute6 _CňaGI yV~0/%ʮ5i]^q˾˾#G.֚hM&jejSm _ <2,2e2Jvj;mmm3eL˘f[u;v^)O(&Q5vB ґ*KB0L8 )R) m! *$9!36I%8},=Φ,{44x Ãۈ"QH_\ۂPzRg l$fx bFk"v(KG=a:1yOGwa.DG9]]AĻ'lȈtŽ ,S F(B'`w11ӤLB^dPQI>55DHt)me= mGY`V+W,G#K^R/ bؐp7ʗC}2D /nŭ*Sejƙ3g0JuGρ G%]SaPC@SW 6"VF'q)$DQ2L F ,[>6 ':ZHTFnǣJ{=9 xϛPDu(;2Uv)M!ȷ1RPpi_ ] xЂB@%\]FY6PQ^4*tyZpV)+=C磖KkߝUlO?e/qǵL-ST>W>W>OHHQDEPmh#lXٰyyy=/z^|J+u\o:(_jjer@AUR>2C,H83vVA#p4;I'WD9< .!P΁nF n3Bʙ Kd(&8BaJ/@‚c|1Y*3मb&Ll>#?e/nxAţr0tE9Y` ` pgH\@ 9VmCMN7\w}s7?o~|_}_}_a{z|: 4%Q%iРC5qM\kZ {¼n^7g(QޑHw;]N>b Gd*|uco~O}]Da ՆD3@sg?]2 ҧ u8fc99 k h= ? P^!@];I8qRvG D91wI~kB[6)Fqr&0"uQ,Ìqv+J7Q/_dX/ 79}&fblf KG 1`POD,/G4M mIXac:gH91'[zk0287(і[h  UX ~ JS]eF{壡l0aɅmԘbEs_9\Twr>5>?eJ_$}z]^w.ݥkYZ|||@I_:5ذhD[E"c1w~Ku.@^V?oŸ-#a $O;5snsrY<)lh%I 0f?kOpnZ2lUҭ?i&CccqSaaq|TJ͗Fq+ ktś⵿7ZMccfƅ;(BsȻnPTiiNbRxVZIQA4b\]ZN.Y=vHj)Fc-t_ / wb܉ U U Uz>-zE @6_0gqgdeQutuu{ٽ^r7nJ3g01ajGH4o~,oљ߭BՔ+ً;8 q:(Ѥ˟lۗ'2` @Od_3MFʟaED]z4$p{w8O>gޠ6r](v)UُS4@oI&^cB ؁Bt:#k<.qܜ(%|=H%͚aV7p7 04E|53^Xe?G%O5IP4I<'3 g,H$#H䗸iݵZw Mhs{CuP%AO'HtH26LΙ3vu=vO=-̡ȧ˧wh 36o_ Sx|]mp156p+`CAJBŧdN7X\zU3PJw=[(P:Z4$-؆ByBC q3iZNZr*밵͕%1gbDHː4mxqK$Tr ֓Y\vvIS_ucިwcкA;x a# 6"hBNo!o%`A"| ?P_:Pa!tTr >.j7RG5O'lllb,&6WP4Vkl5[v|;ǝ K K Kh$IN9,O;>"חYj^j{Qs ''R"Dc#GAB :٪oDS+vCHK  <>A+%pK|Ծۼt $E6K`BʣFϪs3H}z>? *%?#ўў+Wf k.+ JDS P ~a@w?P{=o7ӂi4ooollāFn:k8yD/8l͎OJ h/.r=9lʖ$ AAгDv!"ܣ8NyۄCV| ;xYkeqD3F 6U^E&6M" 1#\/4UUO➦4Unbwto6NȓY\KKր:%7)}gvkND*Qܘ:ȭzQkPM~f5#5#5#5,#,#,#,",",w5j|# ;&,X (v]\k lllUUU+~ů$lKؖ-bŘNRj1\z.G=iA1q9gkaKW>I" WaA- 9ES}n6E| W@;r 6;y)~XyL=r?S+t\sD4%]!\Q}8x\Oaw`0b<}?yn$ _[" .yzI)YCoh'2;E =o>aKSyq_UکLX2]\իէOS 5I[\7fWِcZ=ӥ%J5|H⤣|"t wFcԶ=TȢpTB _,vEΪ,調*ڨqh%)q++r*T̩9?s~Ia& VӑHq].jA7z/ٶ 5$*J"(9> djaȹr7>n|| ν鳢S^q>]$ؕwWw?g|Nj*6`gIݮ`@e^K6/Vsydn893@CSR  B]p~I{2|Mi(2A|ڪXg Z\kQpsܥ@+%cɍԳU-C}n6&~u8yJQ˹Sr a׸垲f,CTatؐ:YX<:Ki׆w+1 A_EWHl$!.) |H'JTh=OXX3JSZVhr~A~YeOϞ==Jؕ+sssʎʎj Bd,ogZr6la1kB-B-B-|_UWCѡP+$BKF;Wrƽ޾ǟG/S{!vi:*8EB_J$ؠ+a{z!DB#9)e (yZҔ/,]բ&On}8ɋȡ*"y8Mr=4aEY\r#}KlS(z$[D+ojVg.^lOkDj_K2';.ynq / 8aV9K)P},0`+v5J9eC.>W_yJNd2a;v k2[I"I$a"&b"KL3Y8/΋z "o A8|9eJ41i}(L<?[ m]fs%ZzI#ɮKqXgB(V?jw c>'HKQi<+T;!!:) Kԯ)5X ğ 5$~|\x儞u?8u3G)aaK4&( R&waQv'O'^Q^P/GxonqqYyXyXy8_#6MbxH<$ k;3!J))>?|Rh\vt3֧-~0=<+5FWrC[A3'+xbQ 7x膝X5|Sw]!h %o"`Eɶi]!][P֝]۲xpFаfKe"y2052aR} OEJ4$d T0_dÍ |]ϣ}o^l]a>k 3r v/+bk:ƕ+|]Tm:?dvϔd-Vb#?4Syyy~N?Pz(=EqQ\5=y a)xmMnnnnԻQ:88ywfOOOЧUdž"o斷KG\^4*,j=8Bp H:$ |V3r;K p1Ma;Z#(rELrJQ\4ms6Sn1vM t ^*[*%"-ݼڱy |[jrO7i{d'` ?Ep DqJ]Xh'L!vTMKE\BF@kQ1cnU}ꏧiu֑[G2dɘ|._?wwqwqwqxzCGq$__la<º`].fŇłłżc>Nh(Qȑ#բ?eF_o)+;fV>VxpqGWܙ H WjS4jwYAei C9#MpJP Y3q [W,JycR‹r&Y(\$T#(7Cƌ9ABGߩmiwěEod> _3de>ofBˢcG:TިM+ܓREd:'D^Vy2ftaaÇwpp[-Z֣K2(2&'ȷ؏R9J̭Vs2BF(bЋ> v'N Q(g)xE;>`&22V^}`;vzg_gc|R%'Q)@rz#> V+"jroo M,* XWZϣ*-kog2k<^4Z@oD5G*a?Y@ڐ&OXC oAΡ࢒tؼ ^tJ[+)G]Wb y9&έ{ iR IS7CIKtEi~ 7'50O·T]vpQ33*GW,)~1;IK _&5&#+"v?[vx[{% ֪iմjfɚ5ǓIϫϫϻ?uyyyJWuUWQ(mr^9-z C1CLBBb`/奼4?jdy-5ǙqCt t tD')fH̐!jC/ůщ{"v8(|f"BFQsϱaXU~g|r|.w ! [)r4nuRުL\Bʄk8_+^HjAD)Ѻh]y~ٜ9gy,Η/;_v˻,+v!s9ߜo2{B-C-C-o %^W=ўhO={ggg5 u`'h${ 0,oU ^]qӈRtօbfigy[ҩT!4 3IEe$1A\GE9|E5ӛ$&w‹QS4}Qܲqzf Cbp vs[6d3jjjTTT)))PkKW>Q>Q>Q4FUW:39?b~k|ɫjw;Cs9&z۴VꈪU#F>}999PjA9[n iBUN}k`2yQ;]5o`W=3xOXW5F\VSh}<[WcZt$آs|BgJkG^R8twxMO N8N^{='='='V[s8sdg3Yo7՛jZTR)}bX/c/b/ʢ,Os{-xx^ūxk}JkҼj^5F it.p.p.Y'N!CV}}`)CI \pͭ-KQ&*?nggge/=L3!q%Bp;G8vFQc8DYd-ڢviF`ռھl_/~ovit~e)YJar{rY.e~i~i~iX)Vhn4M M M1)M_i\1W{{{^kW)u[XXnǃL L L 4 4 44 B:VF+m6h-mLm6S7JD*=JOKsi.O~C?+H_/p#阌ɘov.ʾ}>kXVqUܺf]-͖fKsl&f}>b--ůJkrqqg3ZUVU+u:JQJYztJtGh} y xx3xF }>i7ϛ 1٘lLί5ov;]]jw*UJVohk5F?c1R,ʧWq%S? uNp@2[fly2^~%_nv7mm}>o k"f5YVGcC:x 0[UTD5JU*uԝn-UPN:/żq9.x ί&}O~H1?+AbEݢnQO|u:_tU*JJ%%VUbs92]L D@l[VJ]+ePeuna9c9A<.[V8'ʆT"|p_-{K +$I' *ʛy3opp='b.dFazZO]zQ/%9b8)NsRD!EHEEQQ4JU(!J"M4~x*GSt oMh;N oVlTLTŽ\6ï6^kx\"%W xS|}c'q'q n-x 17p7 psQ,ųMi7(D!JJTjR[jKmE7Mt+XPjSO_pjߍ؝/8WFeTlͷg$ߖo˷;α}>k#p\f.35͚fMq8_ai@ B)R(R**8%P%PP_L,£#D%tEXtdate:create2018-10-06T17:06:57+00:00%tEXtdate:modify2018-10-06T17:06:57+00:004RGtEXtsvg:base-urifile:///home/andrew/projects/aiohttp/docs/aiohttp-icon.svg!hIENDB`python-aiojobs_1.1.0.orig/docs/_static/aiojobs-icon-32x32.png0000644000000000000000000001210713356322675020661 0ustar00PNG  IHDR 1gAMA a cHRMz&u0`:pQ<bKGD X pHYsHHFk>IDATXõ{՚߽|OIHHHB !J  *BJRQ4(.U@!Qi#@ !!Sew~qg3wϝY{=뇵"""8p1-Ebq_d&<9AG 6P!.LC4Ρ:E E[./כHϐ0ֈa 3O|}^E;Ǥv&'ΜGwsxH06dA7-a9Ck`%7$nh-ܟ@X Qڏ}g_s?̯w!ouI9$-ˑf^oJn8'DZIwr\ĥ7@׽=k6ZPd5ޅ(@O8v(=_9pX=Y +|k*?x ~B7B Vb44j6jhh`wh7ҝLzWNЯ5..kt?9l; ɲofc28">v="4 :R!Y<Eg^c@N<|l;{Sma]q8,þ'w߱0Zp BpQ';Hǿ$]rc 7!>+Wl"8;_?ƍ>{1KkE_{^t)L~jRÕPN& = d0) }rF <\ńC٣+C͸ '4CWC 9I6%BNF?9p=P90|o%pY:ۓia* EdY;d_%(߱n~$ HV )iTƷX'7azO=e(}?$ eq|"CMBu,%Ŀs313JuPImzi:d;0HyJXmƺQVTEB j!݃aC.:`4Vizr@,N#r6pB׸ɡRi[X->'Ճ.z7)ߒ6A@_O+N~ ffs"@@_$MD9#oAH  KYB}aaI1qұ-<5rq$<Tu ]` dQ\$du&B3{h{sS?'/ )̵;Y[n Z`Ѫ2NohgnZr!-}[ `| B[r^mߏRfoӐ?83mpDfMWwzU\d})sȂlm{N!L?+8%ym fNyhTn3|hBQYʆ𭫣5sevnO,E(7md 9_ʜ[Wi>u+Kv <'RlFNMqXH^]5kV{([& ]@Ch{(y= 9`ӥp9@EP lj$ q$ jY=`nwkP3}]?B!|0K|_O17)ϐɠz? mYm04k\e=o}y6sv׌Y0;3Xɾ2<' 7rɉ w{b6AqўS80c^zA=<Rf҃uܠPjKyR$>JxY!\t 3) i^Uqn8FPMc d'0cMhK(Y"ѐbI]E!i0<#tM/ TbӣpfsuGE@"Ӝ]7_c=J=l1~]_=lMsa.ۏghuQ8B,5JGMo͡ 籖?JqA?<y!q D_J7ru)pF~`#}"ï|nqJm4j/[Ӎx}m13ٓ P}#2IY6lctE:(<B~Jql|HVXPMD5<0b' H37@)qOYYoѴ:pVt/q5v "vᠯH~mVwxxHyj+K+%ѯ<kkSės0?IiP*)*VQVQa=&oLK\ +=2 []{C~Z]^ya wjH|:6 !~n=4Ns%U]=V-F}o"iՆ0GIZZD?Ѷ:W |@hG+ڥzm}}J LT ]w%F xɣ!R97N߃1.Xg,$*$:HtδTܢ5TI?«&\ ޥ@ % [&5xR)Yk\nCY|e-1N zx…jH}۽v͸Ĕlς #0oh, c=Y,ІE\' vH^4!1lc|1>.E< iOG:OZEʤߑ Y<B|4= 0촷 { {K+b\#jC#~'yQXa9\[o+N%DowQg,}BW 1BmGxsԊQWIqGdv8<̓Ȗvٻ`]p ԗ#!%W}֒#>wU4Ht#|EV B_+Ke]b|#VQeQxU")w]wD}z,sJjM{( Q~Jmӥ*Q$6 /sXVbVh_buqJiS'͐?cױ]üZ0ra/X ֿT}u~lIbɀ4Hլ!mَ<0Lbia\sV܅%&FOkyAL"LxW+Q@3SasdR--^|w4bDVK0q΢ƓPdZT _dZTAU_/xzL1&o40՞9܎n=Z|m\;P x/e74}ۨ/9cL Wcv,_ZzR`y`642*X4hZ}.)"հ)ͦDVd&~CF`=75y2ύ8 K.}u%yi4G S` LyWTXsy.Uv(;N8Hwd8:8'8tNvososoSeUVe&DVb%V+\4xn"%tEXtdate:create2018-10-06T17:06:57+00:00%tEXtdate:modify2018-10-06T17:06:57+00:004RGtEXtsvg:base-urifile:///home/andrew/projects/aiohttp/docs/aiohttp-icon.svg!hIENDB`python-aiojobs_1.1.0.orig/docs/_static/aiojobs-icon-64x64.png0000644000000000000000000001765613125720600020671 0ustar00PNG  IHDR@@iqbKGD pHYs B(xtIME 1;IDATx͛i]uks=w|YI !! 2cvc:$TTWIꊡOIoᆨ^v5gϞݽs GV}"pu1Ƙ8_j>|ٳg+JO_} _׊}}}T*\./rE}bn|j̣Ǐ?Tj[3#Diz#lؿ%7nH LBZ =BM Cҏ ]C@ Wa]@$,Ce$3 !.#{m`h:!ijkϝ={{}9DO}S߾}dd7 gL9H4Px\NP|0 "0<+PBrEpk 0qe"?r<0>O<㏷ rzhM7N)ULj$I^#*DZ6gll[o۷ϼO>{^R=68yoT@b@ EӆtP݈TX<2 qI  JS *d*hQ:%rGFFvp }_ r~kJlxGh{UHq4._B.C_v ;o^l @)  EAYD  n{Q p###3 =3w4}>h=aeWijbZ >1\:"X X]H.Bq>[ uhمzh]^K/| P宻@ԩS~?\{\2{l!(_ tut3e$#p9oAku\XeY i Ibj-,@#tK~kmsqqyCpw|FDr7C^{\<iK`H`zEd`77$;KpQ?@y+]x7sלCm_#A#Ţ!lL%V\~.M?K&5‰}4N>RTV?~ȑc x衇׿/r|CwMo[x I[>M ao(.!WE//wIGv}ť]Z/CΦO\Z2~4'pW) *W5fp lyHD C8AǥmLs4&©#ްq_(.,l{0.3::?>u||W~wAW[c/36 #W i jC qg%+AeP\W?A $MCЅ^KhaXC] Q?sۭ@ge2jn Yp͕W^yp`ooUvqf,vR Wl=1s{qH4=8z`*DHFh~dtwMyH븠)nu 3;yz?D#8qc әGoiCVPILE7\5P.VVk{ @u``8Y|OB J#A*![.DzbO]W2@)Dž0׎ E8I@p6@er57b0 71k7qr\T8!jE~{sq 7lذX,4%gKglgyAQ×`OYkys:9M&|B I#Ç,ĬzJ&L l2uX~]# n=/ | w}5k(%^pe|/ݸjkg,zCΪ$ˈҸ 1o}xc`lU,b H}` mN,'EѶT iTTƉ/mMg P܋ug!Wx#-H7&7.{|11-$i XT{Iۋ!j1.@X7߈KS@jk5o 6n-"z+ (J|Xل~\&86Eo&|ͿR ؤ3)Dյz?.{ H%B=f%$nSjw@+0Wnܻ#.bhkQqt"DC:5a墋.f@4GQٙӜ' C*爓e(UvH0^CL냠eR^ 7zX cנ:0s鿐x?;wbNZj`fr\IijGѨIDj~D‰]8uRz ȇac-lgYQcT%/:6o I5*rCrXc4ˁpH!A(o-C˄A$PFw%^E=Hю1tWQLmʕҬrJ &ae:*|^vED%@ z4w;Hu*G%!x?XCzgN ɕ , C+<PBŘ)LBu6AAQ#c( iꋢ\Y\ Y%TaBA+.,HAE)鴂\/qҦt| H؏<2|%nUHZ|. EGۭeRIP9=[E:CrkA"b^1&(%y\x.0 S"JCu#.=o*IW] ABuBRn7DyJQ 퓘qA E6*ŵ;w@;\<rٓ)pv;]CeDpIv9Ng |\K>u~ #iu~wOt…ւZVuajc\ 1 |9xA>TiV|g?wW]g~0RxgUݳgPA\oQLC~w{B2 Ȗ.A|SlV.<&@ 5 #HF$PAPR(I3`UPu4Gx.8?sHq=vf̍SJ4c-],$~AB&似]ـ\ 蝿5t+ng4Kf B gL4>G/B\4TF͇U8(n"Mtd^RcLzZbooAK!P8q;P? E$@:C(![-e8jXadx/ [%H6iӗOgw5(:)~$X¥tt7x}/87xcSm뉹+ @.Pa$LM,㳉 0D*4>hMz^թ]ҳģ}I2{?ԧ>w?8g|Lh4_?p*=oiKZ|52-_D}q7U{*!mc"so3RyjVb_,5^@+(_P:daa۷=OA,m/#׷4M_z>ܽgjJ)toƇN8yk-m3~ ]U'*x.|b!Bt|QZ t 8y$4 N֬khw)Z=}׮??vtLZ:v۟LL:@ec#'H?^YeɮWTmWR=?vil5eKk ]p-7KۺugXD̏5(944&&&/K~Rta4G:Fcj'Hyؙl"!J[|9_y3~D.SGH%$nȄ^SJaf8дP~sfii? ԣ(J4Fe[7on"a[{5GM?zA$ˤj(:ꁤv^ϊQ9?>88 `aݺu~iQ555o{ccc 4gh} J)U8iCPF*!˕ށz\z&+"RވۏTCagƒ2+\1omذׁ͛'Ow;v9t|< Ǿ=tT8TL>ǧHa;/L +^X> ;E<84t}\H) :H[ЙHwKHŰ_ E}%:\D#г``/ֺho~Ō]$o桡՞݅B0ީfqKfl\& aUХܗ۝sss?^:~c_W_5p'?Mwy֭ۻ\.o-KBt\{l+5q܏"4?u!x!khZ~/H$X4߰IENDB`python-aiojobs_1.1.0.orig/docs/_static/aiojobs-icon-96x96.png0000644000000000000000000010375513356322675020717 0ustar00PNG  IHDR``=j<,gAMA a cHRMz&u0`:pQ<bKGD X pHYsHHFk>IDATxuVe._ꀵXtwt7H ! b!Rݱqtg}oox31qkg/M6.廼W nm"++___YCh>5O#y,2*ʫ2Cf 9WΕs98-GMF6^ūxGhР[ߩbt5D4MDEQQTT)rtaQZs~Z%Z"q)G/ .Żx>cE"{5ךk͕>>\\S1EEmMtD1(h]x o-TDET̽bεb!r+nŭ>Yrch yBOS7fތVhV"^ċxW4T* EYQV0 06@'Wo-|CPkg%ZV"_K|ITD%թ:USݩd1JQ)* ~\"%Xk5֮cױ8' u.T*U]U]tEgJ$JBs4G\yx#wK%mmmCdx,Ykσσe)YJyxURdq}y_^geyp 53e?SZg/AYRemYۨe2jyzzf3~LLLcX&+J<G" cd1{=͞FQ%LJFj#=~O}DB!,{YklwDnI̤%Vщ}YYO޳/dxT7e5-++dQ*nʉ&0$$J/#豒&palJI߫'BUP~ aJ+!TV7$}x(Y,h$?ɯiZ ͠41sL01 0M-d  ؀ vmP*g.b{/!i8?;p*NNOP'cEQey1!ɚ'6{4;> B?-Z Fi\`,uNy/? ;Z'Oz`rWx2n̰rƢWo:N݉S 4v'u:xD/^R/TbԎQ;4C34 |OXFV0+0`x؀1{Qg_\Jsql_gՕzT릧艊6˞ x  t吾0l,ká2{´qr/(Cm!J-DfrT-ƙIKf2ӝw;H3ovsCA/IDՌjFe2TW+ՊjEbkY`M7*Jʱ4F9QN@@vFfL_:w Ҭ5-qgcqa-7{^p=\n/Պ*%bd(UTڵ#l4gx;AO+OAMÂ*TmX969< 5:نytZua"⿬}E{ztLw"؏Z-*݅{4]ۍl4r|W#^GPŪz㡣D7|G/Y7M~3,,|||Kߥwi{=qR'=c<|oX[zAA&b"&ֺֺ֪5j С瞵S|/ȓWn-n8r]I"Y;."rCiiiK} _$iJ3^?uݰ9ź.izπv h I B_pE2iˢ"Ѣ߳^Zлh)8{ ` مoI:;&9&9&jc_+p?Kb/Ze0K%%}>|'.}o>sxmO+;_fFy5H ngJ_rNp9.)Q_Ɂ Qo!aq{JkgzEzcS+wao1ϏAK|vC,Bq<$$ΐMc\_)0xT=v00W92EW|}>]cTzdg 3'MT#?#+,¬`C`C`CpQQQ'''U>c_43P'KӍE$R~'-Q~_(]¿\bװ={2py9WɃgRUBH[=Zj^/QD]_o_bHRE^w?=Іr:5f#BK7G:vB}Y~_6?|{V.rO ɡ*G_=|QV.rp\崜VmmmJJ \gqgo@1m^^^l/{y[ZZ%蕬/erx;g@5(Y..MUbBxTv4t*cr}=^YM b dE24kW%$PBEM*V[[#tpEgLR U,?Rnv;;;*RE%fbzzVhVWWW:4iSKcilGd/w,q%ie'wO)n s'}j4!a0TZq?-] {JgBqSg{Ɲ 1*ӊ|~NMgSNo )ΉTqf|?փ6# Q4o&f*!p]0RoP; {aJM>D8ìwSaQ$lxDA#6֑Us0dQ^z"Q{o^蛈hD#yyyZ٠lP6+?l}b}b}K%h!mBڄFԈrMbVP{V!a((k˷|0QjhڏH* rc)ɚ Tr ǂA`H|M64rꦨ`J[>Bط;__){hRþd:"hﺒs:_ޢޢޢ4Ѐ32h=YYj___8!yC}IuWfK?uzaip;jǠS=DEea4qQ[W@xVCPbl*zLjF6 e:Hcxz"L9y,Q~hFM<p-( P/Ruf 4u*8(ƚRѿ /Je˺M bЕ1S`q;yG,Y}=籷C6^N;eqY*6m(Mggg ,wwwr-+k ~or"ڕvН;CwBȭOʆVK~rnv_-˧dN>µ VA r(Bz~5u@Nݥ?U(ٷ\{!1Sv:yzVeUZEN"aх-ײhJr 8  7e!`^[jI YCR~4*Az3lR P<PQ6η(lRvŹw⠖Z+WqXvR٥לǜǜ'~Bx粆!k9A6Waj~IuΛ=26߽ɠk5b~Dd@aSr-lr",TsCX6V2(u0jv'+]E[/^<]yuV)&M0эc zKڎD~ߺ RM( LB#&2aIP`sx~aja9^{¢ŷlB1h?v >ll@~ʿ:>p IE@nŎ0dvNR(gY+gbY 5St5zTVY5o ir 'aZ+@UubNb^I]쑝dMf\×_m@d ;塣Le_oC_S?Pp?d f9>,vE,I@>|s19)AOݽp4s5~l}F\_A_hJCc1o7{:x:x:yrq}o^5ϛI>"87Q&B&vkRXċp9R-;$_ig &SEx9AJsU^WEgWbͧ摲F꤈k>C"D9[YW*d zNŔhM|? Kd"N{dyrRTzRǯw9%.TMεV]9^iYU9hX)gǶ7oMgVոJqVCys/_Ç21HFXՄ@qh%h-l7rSG =JA0 ' C|=d=W[*Iے$jjjlo.7: 阞({Q"ݩ;u;˝EL{9)Y)λ`Is@^@`3Up}6ZfB^l,y;T%,L5ӓևG=Jnw=xeWSnQ)Acd_%|+>$}CA^$^ŋ+WUt ņt?]myѩwKE<9{y0Sa5wlixm[sT;،[J;5lE{%G{.iF:.JЌ3_yU@ۍ/fa|NjhkoG_/*E5y[V(*wgZV8$C((slYv'''Sʮʮc;<{KW, <EQpח^q}>(8 ۺM, bZnbdYCMH$Jmi{|Ge]|(N`gOHaPMϗ܃WO ڂY0ۃ 3?%2J2S;_Ҿb;[\ itc:dn.?xh/|B>4ng/vc'/a!H;o=a]%p{(ϼ]Ϡtڣ#0*4ذa>` tFF$zk3(YYY*yfvcݡ8 = ڼe2oeEeEeExOίFQ: %Or_I7ؗ T3ނ.DT:L.)z/,O@^?Dp>Mvvf4NEB.gyiPjARCY6%8žv7޷)ab9V}E+ڭsC QŢUXb>Jf3D_7R^_G{_ *~He]- REqºu7"{a {O*?zHF3mj45$$$+۔m64B#4@#9g3QoM%%%0oּBuR_MM*[7=XC6V/!%B1"x BaC Ŧ݄T\ZZ4A=]o;#ݷWȭUu;4F!!eT`M:=~xBY6P* |xDTagTBQ_ͯ`a1_3+.C9b969B9h*N)R+';29{ٮa So>z_ $\q ~ӏ[;G5R}Rei™IGuՍ9IaZ7q$/5 +fbll_jQ`Iu~BT6ap6W tl佸-x^-w:#jSi2(\1>ⲍ)"N4 B9fcetZ5᧒{zDQnΞ,Te^L>T?<1tXIմ . |z@=h@)?8s9. C;юwY%'_>hM;| mg0$bПs|=@ׯ%'5f`*H>p!J(0nYb'⯴G\ xZǕ1KDoF,(tUc*ٿ/>ZO{{/+}}Qvzs͡94'5ߒ=9i|'9amzgj%(kAF xhŅҲoub}f53SD+:ug>8~ӷyHF!s,NWi+dY&뚰Ĩ`-6$B(ȇ|?K5{sb /%Ω=9v pE@&ndrd}O*jC/jc`]G 6  B-@fR:_.A<Ӕ؋_m5!lDO!/|Eg&lGyD>0+*C_ߵtF [o4cN^ȿقtPoۆƛ|퓺sO`˞ Ҭq J/@4f?ց }B&q8X}I2؉nOfhBQC S'N #[Vr6$!fPDV_>F\UhZ(8oK<~<^MPGArdO'ٓ+-VXU!z(YJ7[GӶztQ DYH>,_`H ( K<>MjWM{r/6`Qxe؈H>S".{:\_! .IG y̺íMšsHXcsR40Y(@mKU@BۙaabLbM{ivFH|y%G qRK55.Xy Ș f-s6ܝC?RʽO.: /ڣ?Uy}Zb~XBg_H]H_[#z6cxX@]fā*l {($FQ0ĪhL Pk}_ȫt* >y<a<,g#94G|1+ʧRWrO"#YZ0y.[Pd9bd&;lU`:WUoӍK"ŀRd͔l3V (Po|G5/[oڥgR췼 $T*\{=UQUC8C5_[g3O%:lȴgR'~빥@m'Xxek+V >ár}6QV涽L;Jw) #Oދ b)u;@' ށ%tqѿb fA_`,c1>{ ,wăM 'x%n@,樻~ 9bqD"u=ve/OvIirq( e_kݬ{7P9Fg\l&&۲#RXq:\ډbt㕲2?Zdi:>x2u)Fˏ2\_xK[) R9ԓ *Ȅ'ԍV٢u%+G"jAQs3 a 6()[**Mv!yHRwջ/ g7KlQ7GEc>cGi L{%NREdQnP{6xWLAFO@i+( [BDIa[ w7 Z`셉^7N~)$NR;`6̶ }*߯4*&_6aE!a]vM)(^]X,4ĒF H+ߩq`C _%9 klt*Mi8K \^A%_TNS?0ʣ.,3>PFMaJ9q.DX|S%qX}a3g?'u9&=p-H,rR-b) ^|h\lNJy((K P V:ZinQgfmۗ"153vڃX%]mBC~ԒSyfgBG~TD Q׷DNoBs3/d^ȼ9lsfVP+b&aF~#flC^|`Ջ.8mׁ# /6R .\S+ 1ٱل&~XI<Ϸo_|AA"ba'Ua,(|p@ 1)}=I53Ծ:&`N5إ Dc naÄQ-;Hjo6y/ AszK>|2nkt[뒅~JZQJkTz,˘w_^=nВwϕY#;ߐYuS~ /*"o7!hIF,UR#]nds]bGu+"S[*I{F \{׾3 "*Rǽx#)36μ].W1qG7hcYxaJnjeU9M˅&xOOC q3sL2[(cN$yk d癆6~_[QP:P@7!T K\濖Y&cCS_|HF!bSFm99ăxz7z;y'M3+s ~s*?Z$DaT(5WKQzrd ̈Pq p qs4G%mEhi} y˿49a-┷5YjύG٭VrP]j^Bsn 4ߢa @zjca鲵9Kkd>M$hv >db Vc$_>e /Q8BgoY>ۉ\bt|XFopISu>0{^韜ytw868:Bq'(ͮUčQ|wڗ'0PTsb` Jol1KMlHo֛>'V=J5gR1޷u|>xTBOcFc(K}3_}#BW*B*-Qa m[rϺKppF4&$8o&ZVpQ/)[-n+M$a?·=iקiZͳ#B&\*p[# g|+gYf2,2@ ~X0#ٲO¢&XT4SAtFP@;pp'j*F^)#zb6l9߈a8ȉEh.^7e{ǩa{ѾxNq0jǨ܅jBjj=ZI@Km ƥ>rv/aUk0RGCm`:"̷UK8[PԧXz/p:*T,TUgKZ [gZ믍voBgtP "$?(;XL0ail5YKx /&܄BCPePF6ԆPjBMr\qRߪu[)zZᆊd6YJϪtI+QD|-[n[(g̔CF"I[ʈZ8m6lo&B$D[UeC~ş~Q~0a[ԩR?!Yo1xge7Jz>R v?ꝞxUy*Zx9XCE#کE?R2M= `'ٶH%ԜS57ɍ~GAxm=r֋nC"L\i?{&`{辂xUb⹉Dw:O%MtZY$ҏL1p~]rmpܙF,޲O]cbvôW7i*?Ce>A7$3VQR>blw@lR?%pgٲOzJf˞/y@{ɟd_HtŖܩ݂8Q 'b nO uMoWQ# 9Q䵾F5)b{:JG,Zig!a6 dKB1\+FF::i>Eu3i;$u6Q(6^zo̐Hm)n 4UF*Fo晁)[c,y5zJ5ι/kx~ ?gCP!:C?bL쪴U{IA=!r8#9=TԚZ4TZGh2L{^;*!JX!g,Jm/9O[.Lc~\#۞6FBE! )t糰?a6C!*pޚ9q@Y|ء$퇔ڝsfG0oNg#;s~Doʏog^㫚N:/ ~G6;qޡ<|߿ ocMpJI5&jjN$>OuI&TStqsO_Qφ0Ob8=a=)mH&Rm; V gsX5`0$*׬L5(mw1U8H~r6muAbىNxPzQ(n<6&TH8 B[Ӟ~e ܔ FˆTlS[|޶l(B`AY1"pR{%!ܨ9; (>Yxy4WaS786бJٗCp:E}"!#h&5r-fʼ)Vw {9\ >'E,"Yvq!R l(7C88.W灎21nщ9^Yzw~Q PLE-B-*I%jک9aţ%dqxs Mm5[T0\[2cTu%{Rp}.+ Y?Lh.|oj/p$\g8OixN]NyO;IFOiF{V H[ ;)1=.oyt[t1ѷq}qZ@Yh"d#*fmE!#~1,VHaPTA46u dgڍO OΆ a9S\nz7+Q/1Poi@kuȆ ؈%MMjc.>Hﮕ^Y-QS1ec*3R1ây0R^tpMPpت%Kɠh֫I79dWUu:VKũ8D$"EqQ\A]؅]mq\HLuC&[/̓WG:J#$9@ei0oi_͌gJ6%+F`qf0~f0'c/iJ?}6!ݑ^SF1sTѴB4Kdèԣdn1k D *IWTLKx߿ ߙ}{ac0]%MT3q6j lNme}̆:#2F8?WAŒ`|m T5|9PT[4E`U7šEgRwkم(@*4m7 0a `F4=V[̌L=1$MEI0Q\:^+3}EhcGO':0+ݛQ؅tf4RTQ>G^ KF22JP#<<%¸uHN py`CL6m0CDnF`_!= ܗp&Lnj@<4AF&VDDߋ J*6ueX*=rFŜ`^vNUmC^;x"[S<φagF&az#n9i8l@RN(*9t4fLfA‘HAөVMP9ܣ@(RE;*XphAl BZpD5$w'^UB&]e]dj4}3(>W`3~EiEK'3D  ߛL )qJ'ibZ襶ZFh+抹fm9 d6ۤB~)E`&h @9-D-Vyս E/0Ô2n(?:gȱ!1g?߄ID&#j(WMhQl.HGOTQ=_` os={fnJ1Ml;=eP&̇ w5#z ,+;#ԷC:rЮ}@{z "[$u-nAvTN}+ ]G1tTutL$䦉JBNt)yB&MBT,WJ̤B b[_C ׵ >~3Q.U4^@ng0~M_"Sd_nm B[V_'_'_>y}>KE}gܭ댋5O?vWr'45nykkc P>G%[/#+UW>v񫢝;tv͋iJQ1<[QNtz14J{$dH2:MND uFtpC _ŦԐpmy3S kv|OpPr:>{*p m.cTȶ]ю30{Z9k8 I+]HV4{ѮEpy,27쎩ٷ/Ə) ͯZ}S?jKBMz<5S7uSeЄ&G#6b#6@9c^]WVVE|Q__{#=y;mvhwiTm7'R !` M :B8}뒋#=i"*.]$o{s<5Oɽ,$H9wf߱ij43s%Hymj ZF1~܃naaa2L0=B#rƲkqohD:BGx=^Vj+Y,o׽ϽϽ/gҝc@A^SVm.c˖ewN z2J3{hFzQYZl>ehx6! izhޫZZ\Bk'\Kbݱ1p`X #9sfT^X=BE}#8m!v}'Z4ҹ1f U]W[|q*%V‡9FQߤέ-F̳X5,$k>W0XS(D].(Ҝ7#k%c{=tccTP*S8fv/VS}OBu },ծi_Ԅ27x#Ѿ Da-~֣|U٧vg^kHGUzuWw5W#HH?ؘ1act1]LGBDKT)z4}>M UCЌ52jyyCU:*+69' UZ;/.pua*x/}VfӕdYOvs1s٘2k,[ֲC3픷SN(g3wHb0%Ǜ^?“-,O'9Tȋymqqnb[:Óhk$/Ri}doD4S:.Їu c9TA v&SEoimI9:eo(V[zޅ]O[)C >b4ϵMcAפ,]۫.|#zͿgC2wۅ/."i-ɊؒPhKv?If8giB[ZLZ>x?O,.ܭ#F#or.kQ#dZrJɕ+7oHITDFagHTRRrvvև?;w(P#9JLu3;ʄ Yd*5;deqݞ,bSGXyśEav(7gS`k9εQ6Tp15pE* `n~۞g/WpM9}J<">ʱJǯsoh,)ϐyB\d&B,F _bE% bP'8M-Y"^G('i_ݫ/>hg\hƙ3og' zM^SVhMQ)Pt4wwwWgoޜ9wC,H 4OՖ" * ḋD h*[ZQ(C AH*`yQ@!LwTCxK\)- !h|8P8Qq<>R΃@)ٮ%qR[칀T|1?*>Hvfޕ OBRS};((Ȏ;LВ&.}[>ܡs:fDMCupq0Nz:t.eWWW,fύlhÕo7C⭒mgӅKiUC"zlkx̖U$E[naS(;P}!Y=A啼0T.cnM|˳&V#RϑBizC:PQ̣,CrΑJ`!%P69W1T05♶@cƯ {Hw4I9PèޭƓmVGD(,H#f&]щ_ǻs84c/JWsmm_ 1uuu%CFh#%qW&6b~J?y|b>ILHLHL? ~X"%>޳"=c?= qvC_x< A)WlZw6ȋvl@'^୔29$-M2@.wVQSH 10{ e }H iT.៴r(7 <Y-]RKy\~/e_>mX[>>>W)B(?-qt@𾿣hмyӟ?Oa҇I湝v۔@ q;zx2+I7 wָҴtbq@$rH4\& {^ͅL/;o MFUӶw@l4ۂe+OyWUm>w!UdhB Dڀ ((1iCD( *MH Ќ!a5v~'[}G}5sϺOZ^2rg؇q;!yr4#E :c<-dKg/]ڷ9v<CIP٢+g[(8ɸO~ѴIһZ\$uRQ p ,Q|s"qozj3&ykUluD(QQQvC0W_UQQXP{o؉r\(:99y ނஊ*x S0Sj<㒷;9OuQZv 5&jTzRGAIk,²W& DxTCgHτw' J)3!)zmC+JpJ䓄 `D$C XP`tM@ c-˾ӗJt!xH7~JE,Njz2-}w;ֻ|Wbe@Ԫ:7#WJ+W[[[<h ^i7@ $CLYEY.e˹ǹǹǻȻȻ:::bVŬYXX^Gc?^!x!Ft8>V#LT7/1ӫvUUz}zRRR<<hj@vTh 4PTIk%.;h6W!_L\R6{!a8}x1L?&-9Q+.&1hϦ][ ͍ţ\t9" 7+éχ7s4䒙c}2 SG3u‘|2XrfjϮihrBm|ύV*87]f.q p*گ˷Kt]-R^Wu>1U&~E}OnE"*6$&fJDgBT`eښ+ ZOHVֳۦF8Nq&_F/Ew/yooocpApAp7ߛwuuU{=vѮH'ſj<ߎX|1_z`~`~`~MAMAM[Nz+魤<%Oڏ+KKgf^3G+~oecPNOх7DtYJTFbҐU₝Ě'ֿcc>@y)Rk"/dԊBgizԎ ЛpfZ@CUHdkR}jIUG b߈B+:hR+V6Ҧ/wo/ Cˇ"d/5C];X7(;| )%3<5y/'J#sc\s3f̈]+ǕQJP ZA+_;? "؂-y9/7WUC_III'vrrB@IEg[l'pķIGG>qx]9+qE0PHAoFx7IH_E'0/mz"Jo"LC5labQG\g䦋,h$k4=c9_w DşuEQ+0H=&YRc`,U?R78%WT?S IIUZSL2F#n~zl>/SN [i_zIO>ٍnqӹ Ij36K+SۇΧO9ʏʏ׾ҾҾ  "%~~5Zud3rA\`46M-F+;NXXH)l;w@G.}'3::7$|.9%ÆS=i)ؐ0x=UWOK,>eXMeI~ЭS1'ȾT:ZTg̖)LŘp ,>1?j5"/ί^@X, V>PV1*~ʼΉn\7ls%xdXRw Gb$F<x"""[tEgo7o7o7g/g/g/UB~i tUb 9N 6GH?ٱcesssc7nݤ5j -ЂvJT78p$0,/^;UFƑTHA?e7>.86'B>+VeZYHm/S <D%oxF%kWBE]-5TW4  5#waO\#]JoǞ-wRnżӸSfj2N$Nfz^*{VVV9tx3jSԦMZȆlkΚ \؃=#b" 7IDAT5f 3JR4J X1Vu0`'cyRTG3:>~ASW?',9ҍhsHmsjGᲡISl>9z@/{+QٓћlKB(@wxOh!ƧIMySkbQ?z]LLmNjrzػe%?ዻkkVy&dqq?cq8a]5jtgdyrT.Q]:t<<<<#)")%/yԿ&w |bn111 !3dr\ޗ}ݍݍݍeˮIM5 1n_1ިyo_}Úx*R"O~ 6D_߉!;P14ђ+t 7jŽ>4X PSy| u !O}WK/$5zVԆjy^QzQg3יTO'///vm!JPD>\i4W%FQg-XK= Ow]TLB⌈eb1W8h?VSDLoetFvQieɣ5/d'ڳ8817ld6 )))4=W:|çSשoooYր5B,bs~،MHLpn87izZDHu:///888jsdi?˸˼Rs9 }S>k\wOZIVjf;^vxYTTTR, c|?9"(b2 y!/~ AsyncIterator[Scheduler]: scheduler = Scheduler(**PARAMS) yield scheduler await scheduler.close() @pytest.fixture async def make_scheduler() -> AsyncIterator[Callable[..., Awaitable[Scheduler]]]: schedulers = [] async def maker(**kwargs: Any) -> Scheduler: ret = Scheduler(**kwargs) schedulers.append(ret) return ret yield maker await asyncio.gather(*(s.close() for s in schedulers)) python-aiojobs_1.1.0.orig/tests/test_aiohttp.py0000644000000000000000000001033614322524771016662 0ustar00import asyncio from typing import Awaitable, Callable import pytest from aiohttp import ClientSession, web # isort: off from aiojobs.aiohttp import ( atomic, get_scheduler, get_scheduler_from_app, get_scheduler_from_request, setup as aiojobs_setup, spawn, ) # isort: on _Client = Callable[[web.Application], Awaitable[ClientSession]] async def test_plugin(aiohttp_client: _Client) -> None: job = None async def coro() -> None: await asyncio.sleep(10) async def handler(request: web.Request) -> web.Response: nonlocal job job = await spawn(request, coro()) assert not job.closed return web.Response() app = web.Application() app.router.add_get("/", handler) aiojobs_setup(app) client = await aiohttp_client(app) resp = await client.get("/") assert resp.status == 200 assert job is not None assert job.active await client.close() assert job.closed async def test_no_setup(aiohttp_client: _Client) -> None: async def handler(request: web.Request) -> web.Response: with pytest.raises(RuntimeError): get_scheduler(request) return web.Response() app = web.Application() app.router.add_get("/", handler) client = await aiohttp_client(app) resp = await client.get("/") assert resp.status == 200 async def test_atomic(aiohttp_client: _Client) -> None: @atomic async def handler(request: web.Request) -> web.Response: await asyncio.sleep(0) return web.Response() app = web.Application() app.router.add_get("/", handler) aiojobs_setup(app) client = await aiohttp_client(app) resp = await client.get("/") assert resp.status == 200 scheduler = get_scheduler_from_app(app) assert scheduler is not None assert scheduler.active_count == 0 assert scheduler.pending_count == 0 async def test_atomic_from_view(aiohttp_client: _Client) -> None: app = web.Application() class MyView(web.View): @atomic async def get(self) -> web.Response: return web.Response(text=self.request.method) app.router.add_route("*", "/", MyView) aiojobs_setup(app) client = await aiohttp_client(app) resp = await client.get("/") assert resp.status == 200 assert await resp.text() == "GET" scheduler = get_scheduler_from_app(app) assert scheduler is not None assert scheduler.active_count == 0 assert scheduler.pending_count == 0 async def test_nested_application(aiohttp_client: _Client) -> None: app = web.Application() aiojobs_setup(app) app2 = web.Application() class MyView(web.View): async def get(self) -> web.Response: assert get_scheduler_from_request(self.request) == get_scheduler_from_app( app ) return web.Response() app2.router.add_route("*", "/", MyView) app.add_subapp("/sub/", app2) client = await aiohttp_client(app) resp = await client.get("/sub/") assert resp.status == 200 async def test_nested_application_separate_scheduler(aiohttp_client: _Client) -> None: app = web.Application() aiojobs_setup(app) app2 = web.Application() aiojobs_setup(app2) class MyView(web.View): async def get(self) -> web.Response: assert get_scheduler_from_request(self.request) != get_scheduler_from_app( app ) assert get_scheduler_from_request(self.request) == get_scheduler_from_app( app2 ) return web.Response() app2.router.add_route("*", "/", MyView) app.add_subapp("/sub/", app2) client = await aiohttp_client(app) resp = await client.get("/sub/") assert resp.status == 200 async def test_nested_application_not_set(aiohttp_client: _Client) -> None: app = web.Application() app2 = web.Application() class MyView(web.View): async def get(self) -> web.Response: assert get_scheduler_from_request(self.request) is None return web.Response() app2.router.add_route("*", "/", MyView) app.add_subapp("/sub/", app2) client = await aiohttp_client(app) resp = await client.get("/sub/") assert resp.status == 200 python-aiojobs_1.1.0.orig/tests/test_job.py0000644000000000000000000001606414322571604015765 0ustar00import asyncio from contextlib import suppress from typing import Awaitable, Callable, NoReturn from unittest import mock import pytest from aiojobs._scheduler import Scheduler _MakeScheduler = Callable[..., Awaitable[Scheduler]] async def test_job_spawned(scheduler: Scheduler) -> None: async def coro() -> None: pass job = await scheduler.spawn(coro()) assert job.active assert not job.closed assert not job.pending assert "closed" not in repr(job) assert "pending" not in repr(job) assert repr(job).startswith("") async def test_job_awaited(scheduler: Scheduler) -> None: async def coro() -> None: pass job = await scheduler.spawn(coro()) await job.wait() assert not job.active assert job.closed assert not job.pending assert "closed" in repr(job) assert "pending" not in repr(job) async def test_job_closed(scheduler: Scheduler) -> None: async def coro() -> None: pass job = await scheduler.spawn(coro()) await job.close() assert not job.active assert job.closed assert not job.pending assert "closed" in repr(job) assert "pending" not in repr(job) async def test_job_pending(make_scheduler: _MakeScheduler) -> None: scheduler = await make_scheduler(limit=1) async def coro1() -> None: await asyncio.sleep(10) async def coro2() -> None: pass await scheduler.spawn(coro1()) job = await scheduler.spawn(coro2()) assert not job.active assert not job.closed assert job.pending assert "closed" not in repr(job) assert "pending" in repr(job) # Mangle a name for satisfy 'pending' not in repr check async def test_job_resume_after_p_e_nding(make_scheduler: _MakeScheduler) -> None: scheduler = await make_scheduler(limit=1) async def coro1() -> None: await asyncio.sleep(10) async def coro2() -> None: pass job1 = await scheduler.spawn(coro1()) job2 = await scheduler.spawn(coro2()) await job1.close() assert job2.active assert not job2.closed assert not job2.pending assert "closed" not in repr(job2) assert "pending" not in repr(job2) async def test_job_wait_result(make_scheduler: _MakeScheduler) -> None: handler = mock.Mock() scheduler = await make_scheduler(exception_handler=handler) async def coro() -> int: return 1 job = await scheduler.spawn(coro()) ret = await job.wait() assert ret == 1 assert not handler.called async def test_job_wait_exception(make_scheduler: _MakeScheduler) -> None: handler = mock.Mock() scheduler = await make_scheduler(exception_handler=handler) exc = RuntimeError() async def coro() -> NoReturn: raise exc job = await scheduler.spawn(coro()) with pytest.raises(RuntimeError) as ctx: await job.wait() assert ctx.value is exc assert not handler.called async def test_job_close_exception(make_scheduler: _MakeScheduler) -> None: handler = mock.Mock() scheduler = await make_scheduler(exception_handler=handler) exc = RuntimeError() fut: asyncio.Future[None] = asyncio.Future() async def coro() -> NoReturn: fut.set_result(None) raise exc job = await scheduler.spawn(coro()) await fut with pytest.raises(RuntimeError): await job.close() assert not handler.called async def test_job_close_timeout(make_scheduler: _MakeScheduler) -> None: handler = mock.Mock() scheduler = await make_scheduler(exception_handler=handler, close_timeout=0.01) fut1: asyncio.Future[None] = asyncio.Future() fut2: asyncio.Future[None] = asyncio.Future() async def coro() -> None: fut1.set_result(None) try: await asyncio.sleep(10) except asyncio.CancelledError: await fut2 job = await scheduler.spawn(coro()) await fut1 with pytest.raises(asyncio.TimeoutError): await job.close() assert not handler.called async def test_job_await_pending( make_scheduler: _MakeScheduler, event_loop: asyncio.AbstractEventLoop ) -> None: scheduler = await make_scheduler(limit=1) fut: asyncio.Future[None] = asyncio.Future() async def coro1() -> None: await fut async def coro2() -> int: return 1 await scheduler.spawn(coro1()) job = await scheduler.spawn(coro2()) event_loop.call_later(0.01, fut.set_result, None) ret = await job.wait() assert ret == 1 async def test_job_cancel_awaiting( make_scheduler: _MakeScheduler, event_loop: asyncio.AbstractEventLoop ) -> None: scheduler = await make_scheduler() fut = event_loop.create_future() async def f() -> None: await fut job = await scheduler.spawn(f()) task = asyncio.create_task(job.wait()) assert job.active, job await asyncio.sleep(0.05) assert job.active, job task.cancel() with suppress(asyncio.CancelledError): await task assert not fut.cancelled() fut.set_result(None) async def test_job_wait_closed(make_scheduler: _MakeScheduler) -> None: scheduler = await make_scheduler(limit=1) fut: asyncio.Future[None] = asyncio.Future() async def coro1() -> NoReturn: raise RuntimeError() async def coro2() -> None: fut.set_result(None) job = await scheduler.spawn(coro1()) await scheduler.spawn(coro2()) await fut with pytest.raises(RuntimeError): await job.wait() async def test_job_close_closed(make_scheduler: _MakeScheduler) -> None: scheduler = await make_scheduler(limit=1) fut: asyncio.Future[None] = asyncio.Future() async def coro1() -> NoReturn: raise RuntimeError() async def coro2() -> None: fut.set_result(None) job = await scheduler.spawn(coro1()) await scheduler.spawn(coro2()) await fut await job.close() async def test_job_await_closed(scheduler: Scheduler) -> None: async def coro() -> int: return 5 job = await scheduler.spawn(coro()) assert not job._closed # Let coro run. await asyncio.sleep(0) # Then let done callback run. await asyncio.sleep(0) assert job._closed # https://github.com/python/mypy/issues/11853 assert job._task.done() # type: ignore[unreachable] assert await job.wait() == 5 async def test_job_await_explicit_close(scheduler: Scheduler) -> None: async def coro() -> None: await asyncio.sleep(1) job = await scheduler.spawn(coro()) assert not job._closed # Ensure coro() task is started before close(). await asyncio.sleep(0) await job.close() assert job._closed assert job._task.done() # type: ignore[unreachable] with pytest.raises(asyncio.CancelledError): await job.wait() async def test_exception_handler_called_once(make_scheduler: _MakeScheduler) -> None: handler = mock.Mock() scheduler = await make_scheduler(exception_handler=handler) async def coro() -> NoReturn: raise Exception() await scheduler.spawn(coro()) await scheduler.close() handler.assert_called_once() python-aiojobs_1.1.0.orig/tests/test_scheduler.py0000644000000000000000000002736014322524771017175 0ustar00import asyncio from typing import Awaitable, Callable, List, NoReturn from unittest import mock import pytest from async_timeout import timeout from aiojobs import Scheduler _MakeScheduler = Callable[..., Awaitable[Scheduler]] def test_ctor(scheduler: Scheduler) -> None: assert len(scheduler) == 0 async def test_spawn(scheduler: Scheduler) -> None: async def coro() -> None: await asyncio.sleep(1) job = await scheduler.spawn(coro()) assert not job.closed assert len(scheduler) == 1 assert list(scheduler) == [job] assert job in scheduler async def test_run_retval(scheduler: Scheduler) -> None: async def coro() -> int: return 1 job = await scheduler.spawn(coro()) ret = await job.wait() assert ret == 1 assert job.closed assert len(scheduler) == 0 assert list(scheduler) == [] assert job not in scheduler async def test_exception_in_explicit_waiting(make_scheduler: _MakeScheduler) -> None: exc_handler = mock.Mock() scheduler = await make_scheduler(exception_handler=exc_handler) async def coro() -> NoReturn: await asyncio.sleep(0) raise RuntimeError() job = await scheduler.spawn(coro()) with pytest.raises(RuntimeError): await job.wait() assert job.closed assert len(scheduler) == 0 assert list(scheduler) == [] assert job not in scheduler assert not exc_handler.called async def test_exception_non_waited_job( make_scheduler: _MakeScheduler, event_loop: asyncio.AbstractEventLoop ) -> None: exc_handler = mock.Mock() scheduler = await make_scheduler(exception_handler=exc_handler) exc = RuntimeError() async def coro() -> NoReturn: await asyncio.sleep(0) raise exc await scheduler.spawn(coro()) assert len(scheduler) == 1 await asyncio.sleep(0.05) assert len(scheduler) == 0 expect = {"exception": exc, "job": mock.ANY, "message": "Job processing failed"} if event_loop.get_debug(): expect["source_traceback"] = mock.ANY exc_handler.assert_called_with(scheduler, expect) async def test_exception_on_close( make_scheduler: _MakeScheduler, event_loop: asyncio.AbstractEventLoop ) -> None: exc_handler = mock.Mock() scheduler = await make_scheduler(exception_handler=exc_handler) exc = RuntimeError() fut: asyncio.Future[None] = asyncio.Future() async def coro() -> NoReturn: fut.set_result(None) raise exc await scheduler.spawn(coro()) assert len(scheduler) == 1 await scheduler.close() assert len(scheduler) == 0 expect = {"exception": exc, "job": mock.ANY, "message": "Job processing failed"} if event_loop.get_debug(): expect["source_traceback"] = mock.ANY exc_handler.assert_called_with(scheduler, expect) async def test_close_timeout(make_scheduler: _MakeScheduler) -> None: s1 = await make_scheduler() assert s1.close_timeout == 0.1 s2 = await make_scheduler(close_timeout=1) assert s2.close_timeout == 1 async def test_scheduler_repr(scheduler: Scheduler) -> None: async def coro() -> None: await asyncio.sleep(1) assert repr(scheduler) == "" await scheduler.spawn(coro()) assert repr(scheduler) == "" await scheduler.close() assert repr(scheduler) == "" async def test_close_jobs(scheduler: Scheduler) -> None: async def coro() -> None: await asyncio.sleep(1) assert not scheduler.closed job = await scheduler.spawn(coro()) await scheduler.close() assert job.closed assert scheduler.closed assert len(scheduler) == 0 # type: ignore[unreachable] assert scheduler.active_count == 0 assert scheduler.pending_count == 0 async def test_exception_handler_api(make_scheduler: _MakeScheduler) -> None: s1 = await make_scheduler() assert s1.exception_handler is None handler = mock.Mock() s2 = await make_scheduler(exception_handler=handler) assert s2.exception_handler is handler with pytest.raises(TypeError): await make_scheduler(exception_handler=1) s3 = await make_scheduler(exception_handler=None) assert s3.exception_handler is None async def test_exception_handler_default( scheduler: Scheduler, event_loop: asyncio.AbstractEventLoop ) -> None: handler = mock.Mock() event_loop.set_exception_handler(handler) d = {"a": "b"} scheduler.call_exception_handler(d) handler.assert_called_with(event_loop, d) async def test_wait_with_timeout(scheduler: Scheduler) -> None: async def coro() -> None: await asyncio.sleep(1) job = await scheduler.spawn(coro()) with pytest.raises(asyncio.TimeoutError): await job.wait(timeout=0.01) assert job.closed assert len(scheduler) == 0 async def test_timeout_on_closing( make_scheduler: _MakeScheduler, event_loop: asyncio.AbstractEventLoop ) -> None: exc_handler = mock.Mock() scheduler = await make_scheduler(exception_handler=exc_handler, close_timeout=0.01) fut1: asyncio.Future[None] = asyncio.Future() fut2: asyncio.Future[None] = asyncio.Future() async def coro() -> None: try: await fut1 except asyncio.CancelledError: await fut2 job = await scheduler.spawn(coro()) await asyncio.sleep(0.001) await scheduler.close() assert job.closed assert fut1.cancelled() expect = {"message": "Job closing timed out", "job": job, "exception": mock.ANY} if event_loop.get_debug(): expect["source_traceback"] = mock.ANY exc_handler.assert_called_with(scheduler, expect) async def test_exception_on_closing( make_scheduler: _MakeScheduler, event_loop: asyncio.AbstractEventLoop ) -> None: exc_handler = mock.Mock() scheduler = await make_scheduler(exception_handler=exc_handler) fut: asyncio.Future[None] = asyncio.Future() exc = RuntimeError() async def coro() -> NoReturn: fut.set_result(None) raise exc job = await scheduler.spawn(coro()) await fut await scheduler.close() assert job.closed expect = {"message": "Job processing failed", "job": job, "exception": exc} if event_loop.get_debug(): expect["source_traceback"] = mock.ANY exc_handler.assert_called_with(scheduler, expect) async def test_limit(make_scheduler: _MakeScheduler) -> None: s1 = await make_scheduler() assert s1.limit == 100 s2 = await make_scheduler(limit=2) assert s2.limit == 2 async def test_pending_limit(make_scheduler: _MakeScheduler) -> None: s1 = await make_scheduler() assert s1.pending_limit == 10000 s2 = await make_scheduler(pending_limit=2) assert s2.pending_limit == 2 async def test_pending_queue_infinite(make_scheduler: _MakeScheduler) -> None: scheduler = await make_scheduler(limit=1) async def coro(fut: "asyncio.Future[None]") -> None: await fut fut1: "asyncio.Future[None]" = asyncio.Future() fut2: "asyncio.Future[None]" = asyncio.Future() fut3: "asyncio.Future[None]" = asyncio.Future() await scheduler.spawn(coro(fut1)) assert scheduler.pending_count == 0 await scheduler.spawn(coro(fut2)) assert scheduler.pending_count == 1 await scheduler.spawn(coro(fut3)) assert scheduler.pending_count == 2 async def test_pending_queue_limit_wait(make_scheduler: _MakeScheduler) -> None: scheduler = await make_scheduler(limit=1, pending_limit=1) async def coro(fut: "asyncio.Future[None]") -> None: await asyncio.sleep(0) await fut fut1: "asyncio.Future[None]" = asyncio.Future() fut2: "asyncio.Future[None]" = asyncio.Future() fut3: "asyncio.Future[None]" = asyncio.Future() await scheduler.spawn(coro(fut1)) assert scheduler.active_count == 1 assert scheduler.pending_count == 0 await scheduler.spawn(coro(fut2)) assert scheduler.active_count == 1 assert scheduler.pending_count == 1 with pytest.raises(asyncio.TimeoutError): # try to wait for 1 sec to add task to pending queue async with timeout(1): await scheduler.spawn(coro(fut3)) assert scheduler.active_count == 1 assert scheduler.pending_count == 1 async def test_scheduler_concurrency_pending_limit( make_scheduler: _MakeScheduler, ) -> None: scheduler = await make_scheduler(limit=1, pending_limit=1) async def coro(fut: "asyncio.Future[object]") -> None: await fut futures: List["asyncio.Future[object]"] = [asyncio.Future() for _ in range(3)] jobs = [] async def spawn() -> None: for fut in futures: jobs.append(await scheduler.spawn(coro(fut))) task = asyncio.create_task(spawn()) await asyncio.sleep(0) assert len(scheduler) == 2 assert scheduler.active_count == 1 assert scheduler.pending_count == 1 for fut in futures: fut.set_result(None) for job in jobs: await job.wait() await task assert len(scheduler) == 0 assert scheduler.active_count == 0 assert scheduler.pending_count == 0 assert all(job.closed for job in jobs) async def test_scheduler_concurrency_limit(make_scheduler: _MakeScheduler) -> None: scheduler = await make_scheduler(limit=1) async def coro(fut: "asyncio.Future[None]") -> None: await fut assert scheduler.active_count == 0 assert scheduler.pending_count == 0 fut1: "asyncio.Future[None]" = asyncio.Future() job1 = await scheduler.spawn(coro(fut1)) assert scheduler.active_count == 1 assert scheduler.pending_count == 0 assert job1.active fut2: "asyncio.Future[None]" = asyncio.Future() job2 = await scheduler.spawn(coro(fut2)) assert scheduler.active_count == 1 assert scheduler.pending_count == 1 assert job2.pending fut1.set_result(None) await job1.wait() assert scheduler.active_count == 1 assert scheduler.pending_count == 0 assert job1.closed assert job2.active fut2.set_result(None) await job2.wait() assert scheduler.active_count == 0 assert scheduler.pending_count == 0 assert job1.closed assert job2.closed async def test_resume_closed_task(make_scheduler: _MakeScheduler) -> None: scheduler = await make_scheduler(limit=1) async def coro(fut: "asyncio.Future[None]") -> None: await fut assert scheduler.active_count == 0 fut1: asyncio.Future[None] = asyncio.Future() job1 = await scheduler.spawn(coro(fut1)) assert scheduler.active_count == 1 fut2: asyncio.Future[None] = asyncio.Future() job2 = await scheduler.spawn(coro(fut2)) assert scheduler.active_count == 1 await job2.close() assert job2.closed assert not job2.pending fut1.set_result(None) await job1.wait() assert scheduler.active_count == 0 assert len(scheduler) == 0 async def test_concurrency_disabled(make_scheduler: _MakeScheduler) -> None: fut1: asyncio.Future[None] = asyncio.Future() fut2: asyncio.Future[None] = asyncio.Future() scheduler = await make_scheduler(limit=None) async def coro() -> None: fut1.set_result(None) await fut2 job = await scheduler.spawn(coro()) await fut1 assert scheduler.active_count == 1 fut2.set_result(None) await job.wait() assert scheduler.active_count == 0 async def test_run_after_close(scheduler: Scheduler) -> None: async def f() -> None: pass await scheduler.close() coro = f() with pytest.raises(RuntimeError): await scheduler.spawn(coro) with pytest.warns(RuntimeWarning): del coro def test_scheduler_must_be_created_within_running_loop() -> None: with pytest.raises(RuntimeError) as exc_info: Scheduler(close_timeout=0, limit=0, pending_limit=0, exception_handler=None) assert exc_info.match("no (current|running) event loop")