├── .github └── workflows │ └── test.yml ├── .gitignore ├── .readthedocs.yml ├── LICENSE ├── README.rst ├── dist.sh ├── distex ├── __init__.py ├── pool.py ├── poolmap.py ├── processor.py ├── serializer.py ├── server.py ├── slotpool.py ├── util.py ├── version.py └── worker.py ├── docs ├── Makefile ├── api.rst ├── conf.py ├── html │ ├── .nojekyll │ ├── _modules │ │ ├── concurrent │ │ │ └── futures │ │ │ │ └── _base.html │ │ ├── distex │ │ │ ├── pool.html │ │ │ ├── poolmap.html │ │ │ ├── processor.html │ │ │ ├── serializer.html │ │ │ ├── server.html │ │ │ ├── util.html │ │ │ └── worker.html │ │ └── index.html │ ├── _sources │ │ ├── api.rst.txt │ │ └── index.rst.txt │ ├── _static │ │ ├── ajax-loader.gif │ │ ├── alabaster.css │ │ ├── basic.css │ │ ├── classic.css │ │ ├── comment-bright.png │ │ ├── comment-close.png │ │ ├── comment.png │ │ ├── css │ │ │ ├── badge_only.css │ │ │ └── theme.css │ │ ├── custom.css │ │ ├── default.css │ │ ├── doctools.js │ │ ├── documentation_options.js │ │ ├── down-pressed.png │ │ ├── down.png │ │ ├── file.png │ │ ├── fonts │ │ │ ├── Lato │ │ │ │ ├── lato-bold.eot │ │ │ │ ├── lato-bold.ttf │ │ │ │ ├── lato-bold.woff │ │ │ │ ├── lato-bold.woff2 │ │ │ │ ├── lato-bolditalic.eot │ │ │ │ ├── lato-bolditalic.ttf │ │ │ │ ├── lato-bolditalic.woff │ │ │ │ ├── lato-bolditalic.woff2 │ │ │ │ ├── lato-italic.eot │ │ │ │ ├── lato-italic.ttf │ │ │ │ ├── lato-italic.woff │ │ │ │ ├── lato-italic.woff2 │ │ │ │ ├── lato-regular.eot │ │ │ │ ├── lato-regular.ttf │ │ │ │ ├── lato-regular.woff │ │ │ │ └── lato-regular.woff2 │ │ │ ├── RobotoSlab │ │ │ │ ├── roboto-slab-v7-bold.eot │ │ │ │ ├── roboto-slab-v7-bold.ttf │ │ │ │ ├── roboto-slab-v7-bold.woff │ │ │ │ ├── roboto-slab-v7-bold.woff2 │ │ │ │ ├── roboto-slab-v7-regular.eot │ │ │ │ ├── roboto-slab-v7-regular.ttf │ │ │ │ ├── roboto-slab-v7-regular.woff │ │ │ │ └── roboto-slab-v7-regular.woff2 │ │ │ ├── fontawesome-webfont.eot │ │ │ ├── fontawesome-webfont.svg │ │ │ ├── fontawesome-webfont.ttf │ │ │ ├── fontawesome-webfont.woff │ │ │ └── fontawesome-webfont.woff2 │ │ ├── jquery-3.1.0.js │ │ ├── jquery-3.2.1.js │ │ ├── jquery.js │ │ ├── js │ │ │ ├── modernizr.min.js │ │ │ └── theme.js │ │ ├── language_data.js │ │ ├── minus.png │ │ ├── plus.png │ │ ├── pygments.css │ │ ├── searchtools.js │ │ ├── sidebar.js │ │ ├── underscore-1.3.1.js │ │ ├── underscore.js │ │ ├── up-pressed.png │ │ ├── up.png │ │ └── websupport.js │ ├── api.html │ ├── genindex.html │ ├── index.html │ ├── objects.inv │ ├── py-modindex.html │ ├── search.html │ └── searchindex.js ├── index.rst ├── make.bat └── requirements.txt ├── mypy.ini ├── requirements.txt ├── setup.cfg ├── setup.py └── tests ├── bench.py └── pool_test.py /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: distex 2 | 3 | on: [ push, pull_request ] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | python-version: [ 3.8, 3.9, "3.10" ] 11 | 12 | steps: 13 | - uses: actions/checkout@v2 14 | 15 | - name: Set up Python ${{ matrix.python-version }} 16 | uses: actions/setup-python@v2 17 | with: 18 | python-version: ${{ matrix.python-version }} 19 | 20 | - name: Install dependencies 21 | run: | 22 | pip install flake8 mypy . 23 | 24 | - name: Flake8 static code analysis 25 | run: | 26 | flake8 distex 27 | 28 | - name: MyPy static code analysis 29 | run: | 30 | mypy -p distex 31 | 32 | 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | 103 | .project 104 | .pydevproject 105 | docs/doctrees/ 106 | .settings/ 107 | tests/test.py 108 | docs/html/.buildinfo 109 | .idea 110 | .vscode 111 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yml 2 | 3 | version: 2 4 | 5 | python: 6 | version: 3.7 7 | install: 8 | - method: pip 9 | path: . 10 | - requirements: requirements.txt 11 | - requirements: docs/requirements.txt 12 | 13 | # Build all formats 14 | formats: all 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2022, Ewald de Wit 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | |Build| |PyVersion| |Status| |PyPiVersion| |License| |Docs| 2 | 3 | Introduction 4 | ============ 5 | 6 | Distex offers a distributed process pool to utilize multiple CPUs or machines. 7 | It uses 8 | `asyncio `_ 9 | to efficiently manage the worker processes. 10 | 11 | Features: 12 | 13 | * Scales from 1 to 1000's of processors; 14 | * Can handle in the order of 50.000 small tasks per second; 15 | * Easy to use with SSH (secure shell) hosts; 16 | * Full async support; 17 | * Maps over unbounded iterables; 18 | * Compatible with 19 | `concurrent.futures.ProcessPool `_ 20 | (or PEP3148_). 21 | 22 | 23 | Installation 24 | ------------ 25 | 26 | :: 27 | 28 | pip3 install -U distex 29 | 30 | When using remote hosts then distex must be installed on those too. 31 | Make sure that the ``distex_proc`` script can be found in the path. 32 | 33 | For SSH hosts: Authentication should be done with SSH keys since there is 34 | no support for passwords. The remote installation can be tested with:: 35 | 36 | ssh distex_proc 37 | 38 | Dependencies: 39 | 40 | * Python_ version 3.6 or higher; 41 | * On Unix the ``uvloop`` package is recommended: ``pip3 install uvloop`` 42 | * SSH client and server (optional). 43 | 44 | Examples 45 | -------- 46 | 47 | A process pool can have local and remote workers. 48 | Here is a pool that uses 4 local workers: 49 | 50 | .. code-block:: python 51 | 52 | from distex import Pool 53 | 54 | def f(x): 55 | return x*x 56 | 57 | pool = Pool(4) 58 | for y in pool.map(f, range(100)): 59 | print(y) 60 | 61 | To create a pool that also uses 8 workers on host ``maxi``, using ssh: 62 | 63 | .. code-block:: python 64 | 65 | pool = Pool(4, 'ssh://maxi/8') 66 | 67 | To use a pool in combination with 68 | `eventkit `_: 69 | 70 | .. code-block:: python 71 | 72 | from distex import Pool 73 | import eventkit as ev 74 | import bz2 75 | 76 | pool = Pool() 77 | # await pool # un-comment in Jupyter 78 | data = [b'A' * 1000000] * 1000 79 | 80 | pipe = ev.Sequence(data).poolmap(pool, bz2.compress).map(len).mean().last() 81 | 82 | print(pipe.run()) # in Jupyter: print(await pipe) 83 | pool.shutdown() 84 | 85 | There is full support for every asynchronous construct imaginable: 86 | 87 | .. code-block:: python 88 | 89 | import asyncio 90 | from distex import Pool 91 | 92 | def init(): 93 | # pool initializer: set the start time for every worker 94 | import time 95 | import builtins 96 | builtins.t0 = time.time() 97 | 98 | async def timer(i=0): 99 | # async code running in the pool 100 | import time 101 | import asyncio 102 | await asyncio.sleep(1) 103 | return time.time() - t0 104 | 105 | async def ait(): 106 | # async iterator running on the user side 107 | for i in range(20): 108 | await asyncio.sleep(0.1) 109 | yield i 110 | 111 | async def main(): 112 | async with Pool(4, initializer=init, qsize=1) as pool: 113 | async for t in pool.map_async(timer, ait()): 114 | print(t) 115 | print(await pool.run_on_all_async(timer)) 116 | 117 | 118 | asyncio.run(main()) 119 | 120 | 121 | High level architecture 122 | ----------------------- 123 | 124 | Distex does not use remote 'task servers'. 125 | Instead it is done the other way around: A local 126 | server is started first; Then the local and remote workers are started 127 | and each of them will connect on its own back to the server. When all 128 | workers have connected then the pool is ready for duty. 129 | 130 | Each worker consists of a single-threaded process that is running 131 | an asyncio event loop. This loop is used both for communication and for 132 | running asynchronous tasks. Synchronous tasks are run in a blocking fashion. 133 | 134 | When using ssh, a remote (or 'reverse') tunnel is created from a remote Unix socket 135 | to the local Unix socket that the local server is listening on. 136 | Multiple workers on a remote machine will use the same Unix socket and 137 | share the same ssh tunnel. 138 | 139 | The plain ``ssh`` executable is used instead of much nicer solutions such 140 | as `AsyncSSH `_. This is to keep the 141 | CPU usage of encrypting/decrypting outside of the event loop and offload 142 | it to the ``ssh`` process(es). 143 | 144 | Documentation 145 | ------------- 146 | 147 | `Distex documentation `_ 148 | 149 | 150 | :author: Ewald de Wit 151 | 152 | .. _Python: http://www.python.org 153 | 154 | .. _ssh-keygen: https://linux.die.net/man/1/ssh-keygen 155 | 156 | .. _ssh-copy-id: https://linux.die.net/man/1/ssh-copy-id 157 | 158 | .. _PEP3148: https://www.python.org/dev/peps/pep-3148 159 | 160 | .. |PyPiVersion| image:: https://img.shields.io/pypi/v/distex.svg 161 | :alt: PyPi 162 | :target: https://pypi.python.org/pypi/distex 163 | 164 | .. |Build| image:: https://github.com/erdewit/distex/actions/workflows/test.yml/badge.svg?branch=master 165 | :alt: Build 166 | :target: https://github.com/erdewit/distex/actions 167 | 168 | .. |PyVersion| image:: https://img.shields.io/badge/python-3.6+-blue.svg 169 | :alt: 170 | 171 | .. |Status| image:: https://img.shields.io/badge/status-beta-green.svg 172 | :alt: 173 | 174 | .. |License| image:: https://img.shields.io/badge/license-BSD-blue.svg 175 | :alt: 176 | 177 | .. |Docs| image:: https://readthedocs.org/projects/distex/badge/?version=latest 178 | :alt: Documentation 179 | :target: https://distex.readthedocs.io/ 180 | -------------------------------------------------------------------------------- /dist.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | rm -Rf dist/* 3 | pushd docs 4 | make html 5 | popd 6 | python3 setup.py sdist bdist_wheel 7 | python3 -m twine upload dist/* 8 | -------------------------------------------------------------------------------- /distex/__init__.py: -------------------------------------------------------------------------------- 1 | """Execute tasks in a pool of local or remote worker processes.""" 2 | 3 | from .pool import HostSpec, LoopType, PickleType, Pool, RemoteException 4 | from .poolmap import PoolMap 5 | from .version import __version__, __version_info__ # noqa 6 | 7 | __all__ = [ 8 | 'Pool', 'RemoteException', 'HostSpec', 'PickleType', 'LoopType', 'PoolMap'] 9 | -------------------------------------------------------------------------------- /distex/poolmap.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | 3 | from eventkit import Event, Op 4 | 5 | 6 | class PoolMap(Op): 7 | """ 8 | Map a function using a distributed pool with 9 | `eventkit `_. 10 | 11 | Args: 12 | func: Function to map. If it returns an awaitable then the 13 | result will be awaited and returned. 14 | timeout: Timeout in seconds since map is started. 15 | chunksize: Source emits are chunked up to this size. 16 | A larger chunksize can greatly improve efficiency 17 | for small tasks. 18 | ordered: 19 | * ``True``: The order of results preserves the source order. 20 | * ``False``: Results are in order of completion. 21 | """ 22 | 23 | __slots__ = ('_pool', '_func', '_task', '_kwargs') 24 | 25 | def __init__( 26 | self, pool, func, timeout=None, chunksize=1, 27 | ordered=True, source=None): 28 | self._pool = pool 29 | self._func = func 30 | self._kwargs = dict( 31 | timeout=timeout, 32 | chunksize=chunksize, 33 | ordered=ordered, 34 | star=True) 35 | Op.__init__(self, source) 36 | 37 | def set_source(self, source): 38 | 39 | async def looper(): 40 | try: 41 | async for result in self._pool.map_async( 42 | self._func, 43 | source.aiter(tuples=True), 44 | **self._kwargs): 45 | self.emit(result) 46 | except Exception as error: 47 | self.error_event.emit(self, error) 48 | self._task = None 49 | self.set_done() 50 | 51 | self._task = asyncio.ensure_future(looper()) 52 | 53 | def __del__(self): 54 | if self._task: 55 | self._task.cancel() 56 | 57 | 58 | def poolmap( 59 | self, pool, func, 60 | timeout=None, chunksize=1, ordered=True): 61 | return PoolMap(pool, func, timeout, chunksize, ordered, self) 62 | 63 | 64 | poolmap.__doc__ = PoolMap.__doc__ 65 | Event.poolmap = poolmap 66 | -------------------------------------------------------------------------------- /distex/processor.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import asyncio 3 | import logging 4 | import signal 5 | 6 | from distex import util 7 | from distex.pool import LoopType, RemoteException 8 | from distex.serializer import ServerSerializer 9 | 10 | signal.signal(signal.SIGINT, signal.SIG_IGN) 11 | 12 | 13 | class Processor(asyncio.Protocol): 14 | """ 15 | Single process that works on tasks. 16 | """ 17 | 18 | def __init__(self, host, port, unix_path, func_pickle, data_pickle): 19 | self._host = host 20 | self._port = port 21 | self._unix_path = unix_path 22 | self._loop = asyncio.get_event_loop_policy().get_event_loop() 23 | self._data_q = asyncio.Queue() 24 | self._transport = None 25 | self._last_func = None 26 | self._serializer = ServerSerializer(func_pickle, data_pickle) 27 | self._worker_task = self._loop.create_task(self.worker()) 28 | self._logger = logging.getLogger('distex.Processor') 29 | self._loop.run_until_complete(self.create()) 30 | 31 | async def create(self): 32 | if self._unix_path: 33 | self._transport, _ = await self._loop.create_unix_connection( 34 | lambda: self, self._unix_path) 35 | else: 36 | self._transport, _ = await self._loop.create_connection( 37 | lambda: self, self._host, self._port) 38 | 39 | async def worker(self): 40 | while True: 41 | data = await self._data_q.get() 42 | self._serializer.add_data(data) 43 | while True: 44 | try: 45 | task = self._serializer.get_request() 46 | if not task: 47 | break 48 | func, args, kwargs, do_star, do_map = task 49 | if do_map: 50 | if do_star: 51 | result = [func(*a) for a in args] 52 | else: 53 | result = [func(a) for a in args] 54 | if result and hasattr(result[0], '__await__'): 55 | result = [await r for r in result] 56 | else: 57 | if do_star: 58 | result = func(*args, **kwargs) 59 | else: 60 | result = func(args) 61 | if hasattr(result, '__await__'): 62 | result = await result 63 | success = 1 64 | del func, args, kwargs 65 | except Exception as e: 66 | result = RemoteException(e) 67 | success = 0 68 | self._serializer.write_response( 69 | self._transport.write, success, result) 70 | del result 71 | 72 | def peername(self): 73 | if self._unix_path: 74 | return f'{self._unix_path}' 75 | else: 76 | return f'{self._host}:{self._port}' 77 | 78 | def connection_made(self, _transport): 79 | self._logger.info(f'Connected to {self.peername()}') 80 | 81 | def connection_lost(self, exc): 82 | self._worker_task.cancel() 83 | self._loop.stop() 84 | if exc: 85 | self._logger.error( 86 | f'Connection lost from {self.peername()}: {exc}') 87 | 88 | def data_received(self, data): 89 | self._data_q.put_nowait(data) 90 | 91 | 92 | def main(): 93 | parser = argparse.ArgumentParser( 94 | description='Run a single task processor', 95 | formatter_class=argparse.RawTextHelpFormatter) 96 | parser.add_argument( 97 | '--host', '-H', dest='host', type=str, help='connect to host') 98 | parser.add_argument( 99 | '--port', '-p', dest='port', type=int, help='port number') 100 | parser.add_argument( 101 | '--unix_path', '-u', dest='unix_path', type=str, 102 | help='connect to Unix domain socket') 103 | parser.add_argument( 104 | '--loop', '-l', dest='loop', default=0, type=int, 105 | help='0=default 1=asyncio 2=uvloop 3=proactor 4=quamash') 106 | parser.add_argument( 107 | '--func_pickle', '-f', dest='func_pickle', default=1, type=int, 108 | help='0=pickle 1=cloudpickle 2=dill') 109 | parser.add_argument( 110 | '--data_pickle', '-d', dest='data_pickle', default=0, type=int, 111 | help='0=pickle 1=cloudpickle 2=dill') 112 | args = parser.parse_args() 113 | if not args.port and not args.unix_path: 114 | print('distex installed OK') 115 | return 116 | 117 | if args.loop == LoopType.default: 118 | loop = util.get_loop() 119 | elif args.loop == LoopType.asyncio: 120 | loop = asyncio.get_event_loop_policy().get_event_loop() 121 | elif args.loop == LoopType.uvloop: 122 | import uvloop 123 | loop = uvloop.Loop() 124 | elif args.loop == LoopType.proactor: 125 | loop = asyncio.ProactorEventLoop() 126 | elif args.loop == LoopType.quamash: 127 | import quamash 128 | import PyQt5.Qt as qt 129 | qapp = qt.QApplication([]) # noqa 130 | loop = quamash.QEventLoop() 131 | asyncio.set_event_loop(loop) 132 | processor = Processor( # noqa 133 | args.host, args.port, args.unix_path, 134 | args.func_pickle, args.data_pickle) 135 | loop.run_forever() 136 | 137 | 138 | if __name__ == '__main__': 139 | main() 140 | -------------------------------------------------------------------------------- /distex/serializer.py: -------------------------------------------------------------------------------- 1 | """Serialization of tasks and data.""" 2 | 3 | import pickle 4 | import struct 5 | from enum import IntEnum 6 | 7 | import cloudpickle 8 | import dill 9 | 10 | REQ_HEADER_FMT = '!IQIBB' 11 | REQ_HEADER_SIZE = struct.calcsize(REQ_HEADER_FMT) 12 | RESP_HEADER_FMT = '!QB' 13 | RESP_HEADER_SIZE = struct.calcsize(RESP_HEADER_FMT) 14 | PICKLE_MODULES = [pickle, cloudpickle, dill] 15 | 16 | 17 | class SerializeError(Exception): 18 | pass 19 | 20 | 21 | class PickleType(IntEnum): 22 | pickle = 0 23 | cloudpickle = 1 24 | dill = 2 25 | 26 | 27 | class ClientSerializer: 28 | """Client-side serialization of requests and responses.""" 29 | 30 | __slots__ = ( 31 | 'data', 'func_pickle', 'data_pickle', 32 | 'data_dumps', 'data_loads', 'func_dumps', 'last_func') 33 | 34 | Func_cache = [None, None] 35 | 36 | def __init__(self, func_pickle, data_pickle): 37 | self.func_pickle = func_pickle 38 | self.data_pickle = data_pickle 39 | self.data_dumps = PICKLE_MODULES[data_pickle].dumps 40 | self.data_loads = PICKLE_MODULES[data_pickle].loads 41 | self.func_dumps = PICKLE_MODULES[func_pickle].dumps 42 | self.last_func = None 43 | self.data = bytearray() 44 | 45 | def add_data(self, data): 46 | self.data.extend(data) 47 | 48 | def write_request(self, write, task): 49 | func, args, kwargs, no_star, do_map = task 50 | if func is self.last_func: 51 | f = b'' 52 | else: 53 | cache = ClientSerializer.Func_cache 54 | if func is cache[0]: 55 | f = cache[1] 56 | else: 57 | f = self.func_dumps(func, -1) 58 | cache[:] = [func, f] 59 | self.last_func = func 60 | ar = self.data_dumps(args, -1) if args != () else b'' 61 | kw = self.data_dumps(kwargs, -1) if kwargs else b'' 62 | 63 | header = struct.pack( 64 | REQ_HEADER_FMT, len(f), len(ar), len(kw), no_star, do_map) 65 | write(header) 66 | if f: 67 | write(f) 68 | if ar: 69 | write(ar) 70 | if kw: 71 | write(kw) 72 | del ar, kw 73 | 74 | def get_responses(self): 75 | """Yield (success, result) tuples as long as there is data.""" 76 | data = self.data 77 | while data: 78 | sz = len(data) 79 | if sz < RESP_HEADER_SIZE: 80 | return 81 | header = data[:RESP_HEADER_SIZE] 82 | size, success = struct.unpack(RESP_HEADER_FMT, header) 83 | end = RESP_HEADER_SIZE + size 84 | if sz < end: 85 | return 86 | if size: 87 | payload = ( 88 | data if end < 4096 else 89 | memoryview(data))[RESP_HEADER_SIZE:end] 90 | result = self.data_loads(payload) 91 | del payload 92 | else: 93 | result = None 94 | del self.data[:end] 95 | yield (success, result) 96 | 97 | 98 | class ServerSerializer: 99 | """Server-side serialization of requests and responses.""" 100 | 101 | __slots__ = ('data', 'func_loads', 'data_loads', 'data_dumps', 'last_func') 102 | 103 | def __init__(self, func_pickle, data_pickle): 104 | self.func_loads = PICKLE_MODULES[func_pickle].loads 105 | self.data_loads = PICKLE_MODULES[data_pickle].loads 106 | self.data_dumps = PICKLE_MODULES[data_pickle].dumps 107 | self.last_func = None 108 | self.data = bytearray() 109 | 110 | def add_data(self, data): 111 | self.data.extend(data) 112 | 113 | def get_request(self): 114 | """Return the next request, or None is there is none""" 115 | data = self.data 116 | sz = len(data) 117 | if sz < REQ_HEADER_SIZE: 118 | return None 119 | header = data[:REQ_HEADER_SIZE] 120 | func_size, args_size, kwargs_size, no_star, do_map = \ 121 | struct.unpack(REQ_HEADER_FMT, header) 122 | func_end = REQ_HEADER_SIZE + func_size 123 | args_end = func_end + args_size 124 | end = args_end + kwargs_size 125 | if sz < end: 126 | return None 127 | f = data[REQ_HEADER_SIZE: func_end] 128 | a = data[func_end:args_end] 129 | k = data[args_end:end] 130 | del self.data[:end] 131 | if f: 132 | func = self.func_loads(f) 133 | self.last_func = func 134 | else: 135 | func = self.last_func 136 | args = self.data_loads(a) if a else () 137 | kwargs = self.data_loads(k) if k else {} 138 | del f, a, k 139 | return func, args, kwargs, no_star, do_map 140 | 141 | def write_response(self, write, success, result): 142 | if result is None: 143 | s = b'' 144 | else: 145 | try: 146 | s = self.data_dumps(result, -1) 147 | except Exception as e: 148 | s = self.data_dumps(SerializeError(str(e)), -1) 149 | success = 0 150 | header = struct.pack(RESP_HEADER_FMT, len(s), success) 151 | write(header) 152 | write(s) 153 | -------------------------------------------------------------------------------- /distex/server.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import asyncio 3 | import logging 4 | import os 5 | 6 | from . import util 7 | 8 | 9 | class Server: 10 | """ 11 | Serve requests from remote pools to spawn local processors. 12 | Each spawned processor will by itself connect back to the requesting pool. 13 | 14 | Use only in a trusted network environment. 15 | """ 16 | 17 | def __init__(self, host='0.0.0.0', port=util.DEFAULT_PORT): 18 | self._host = host 19 | self._port = port 20 | self._loop = asyncio.get_event_loop_policy().get_event_loop() 21 | self._server = None 22 | self._logger = logging.getLogger('distex.Server') 23 | self._loop.run_until_complete(self.create()) 24 | 25 | async def create(self): 26 | self._server = await asyncio.start_server( 27 | self.handle_request, self._host, self._port) 28 | self._logger.info(f'Serving on port {self._port}') 29 | 30 | async def handle_request(self, reader, writer): 31 | req_host, req_port = writer.get_extra_info('peername') 32 | peername = f'{req_host}:{req_port}' 33 | self._logger.info(f'Connection from {peername}') 34 | data = await reader.readline() 35 | nw, port, worker_loop, func_pickle, data_pickle = data.split() 36 | num_workers = int(nw) or os.cpu_count() 37 | self._logger.info( 38 | f'Starting up {num_workers} processors for {peername}') 39 | 40 | # start processors that will connect back to the remote server 41 | for _ in range(num_workers): 42 | task = asyncio.create_subprocess_exec( 43 | 'distex_proc', 44 | '-H', req_host, 45 | '-p', port, 46 | '-l', worker_loop, 47 | '-f', func_pickle, 48 | '-d', data_pickle, 49 | stdout=None, stderr=None) 50 | asyncio.ensure_future(task) 51 | 52 | writer.close() 53 | 54 | def stop(self): 55 | self._server.close() 56 | self._logger.info(f'Stopped serving from {self._port}') 57 | 58 | def run(self): 59 | try: 60 | self._loop.run_forever() 61 | except KeyboardInterrupt: 62 | self._server.close() 63 | self._loop.run_until_complete(self._server.wait_closed()) 64 | self._loop.close() 65 | 66 | 67 | if __name__ == '__main__': 68 | parser = argparse.ArgumentParser( 69 | formatter_class=argparse.RawTextHelpFormatter, 70 | description=( 71 | 'Run a process-spawning distex server. ' 72 | 'Use only in a trusted network environment.')) 73 | parser.add_argument( 74 | '--host', '-H', dest='host', default='0.0.0.0', type=str, 75 | help='local host to serve from') 76 | parser.add_argument( 77 | '--port', '-p', dest='port', default=util.DEFAULT_PORT, type=int, 78 | help='port number to serve from') 79 | args = parser.parse_args() 80 | 81 | util.logToConsole() 82 | 83 | server = Server(args.host, args.port) 84 | server.run() 85 | -------------------------------------------------------------------------------- /distex/slotpool.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from collections import deque 3 | 4 | 5 | class SlotPool: 6 | """ 7 | Pool that manages a limited number of contendable resource slots. 8 | """ 9 | 10 | __slots__ = ( 11 | 'num_free', 'capacity', '_slots', 12 | '_loop', '_get_waiters', '_slot_ready_waiter') 13 | 14 | def __init__(self): 15 | self.num_free = 0 16 | self.capacity = 0 17 | self._slots = deque() 18 | self._loop = asyncio.get_event_loop_policy().get_event_loop() 19 | self._get_waiters = deque() 20 | self._slot_ready_waiter = None 21 | 22 | def extend(self, slots): 23 | """Add new slots to the pool.""" 24 | if not slots: 25 | return 26 | slots_length = len(slots) 27 | self.capacity += slots_length 28 | self.num_free += slots_length 29 | self._slots.appendleft(slots[0]) 30 | self._slots.extend(slots[1:]) 31 | self._wake_up_next() 32 | 33 | def _wake_up_next(self): 34 | while self._get_waiters: 35 | waiter = self._get_waiters.popleft() 36 | if not waiter.done(): 37 | waiter.set_result(None) 38 | return 39 | 40 | async def get(self): 41 | """Await a free slot from the pool.""" 42 | while not self._slots: 43 | fut = self._loop.create_future() 44 | self._get_waiters.append(fut) 45 | try: 46 | await fut 47 | except Exception: 48 | fut.cancel() 49 | if self._slots and not fut.cancelled(): 50 | self._wake_up_next() 51 | raise 52 | self.num_free -= 1 53 | return self._slots.popleft() 54 | 55 | def put(self, slot, front=True): 56 | """Put the slot back in the pool again.""" 57 | self.num_free += 1 58 | if front: 59 | self._slots.appendleft(slot) 60 | else: 61 | self._slots.append(slot) 62 | self._wake_up_next() 63 | if self._slot_ready_waiter: 64 | self._slot_ready_waiter.set_result(None) 65 | self._slot_ready_waiter = None 66 | 67 | def slot_ready(self): 68 | """Awaitable signal when a slot has been put back with ``put``.""" 69 | if self._slot_ready_waiter is None: 70 | self._slot_ready_waiter = self._loop.create_future() 71 | return self._slot_ready_waiter 72 | -------------------------------------------------------------------------------- /distex/util.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import itertools 3 | import logging 4 | import socket 5 | import sys 6 | import uuid 7 | from contextlib import suppress 8 | 9 | DEFAULT_PORT = 8899 10 | 11 | 12 | async def zip_async(*sync_or_async_iterables): 13 | """ 14 | Asynchronous zip: Create iterator that yields the aggregated elements 15 | of the given synchronous or asynchronous iterables as tuples. 16 | """ 17 | nxts = [next_method(it) for it in sync_or_async_iterables] 18 | try: 19 | while True: 20 | r = [nxt() if is_sync else await nxt() for nxt, is_sync in nxts] 21 | yield tuple(r) 22 | except (StopIteration, StopAsyncIteration): 23 | pass 24 | 25 | 26 | def chunk(it, chunksize): 27 | """ 28 | Create iterator that chunks together the yielded values of the 29 | given iterable into tuples of ``chunksize`` long. 30 | The last tuple can be shorter if ``it`` is exhausted. 31 | """ 32 | while True: 33 | t = tuple(itertools.islice(it, chunksize)) 34 | if not t: 35 | break 36 | yield t 37 | 38 | 39 | async def chunk_async(ait, chunksize): 40 | """ 41 | Same as chunk but for asynchronous iterable. 42 | """ 43 | nxt = ait.__aiter__().__anext__ 44 | r = [] 45 | try: 46 | while True: 47 | for _ in range(chunksize): 48 | r.append(await nxt()) 49 | yield tuple(r) 50 | r = [] 51 | except (StopIteration, StopAsyncIteration): 52 | pass 53 | if r: 54 | yield tuple(r) 55 | 56 | 57 | def next_method(it): 58 | """ 59 | Get the method that yields next value from the given 60 | sync or async iterable. Returns (method, is_sync) tuple of 61 | the method and a boolean of whether the iterable is synchronous or not. 62 | """ 63 | if hasattr(it, '__next__'): 64 | nxt = it.__next__ 65 | is_sync = True 66 | elif hasattr(it, '__iter__'): 67 | nxt = it.__iter__().__next__ 68 | is_sync = True 69 | elif hasattr(it, '__anext__'): 70 | nxt = it.__anext__ 71 | is_sync = False 72 | elif hasattr(it, '__aiter__'): 73 | nxt = it.__aiter__().__anext__ 74 | is_sync = False 75 | else: 76 | raise ValueError(f'{it} is not iterable') 77 | return nxt, is_sync 78 | 79 | 80 | def get_loop(): 81 | """ 82 | Get optimal event loop for the platform. 83 | """ 84 | loop = None 85 | if sys.platform == 'win32': 86 | loop = asyncio.ProactorEventLoop() 87 | else: 88 | with suppress(ImportError): 89 | import uvloop 90 | loop = uvloop.Loop() 91 | return loop or asyncio.get_event_loop_policy().get_event_loop() 92 | 93 | 94 | def single_step(loop): 95 | """ 96 | Do a single step of the asyncio event loop. 97 | """ 98 | loop.call_soon(loop.stop) 99 | loop.run_forever() 100 | 101 | 102 | def get_random_port(): 103 | """ 104 | Get a random open port number on localhost. 105 | """ 106 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 107 | s.bind(('127.0.0.1', 0)) 108 | port = s.getsockname()[1] 109 | s.close() 110 | return port 111 | 112 | 113 | def get_temp_path(): 114 | """ 115 | Get a universally unique temporary filename. 116 | """ 117 | return f'/tmp/distex-{uuid.uuid1().hex}' 118 | 119 | 120 | def logToFile(path, level=logging.INFO): 121 | """ 122 | Create a log handler that logs to the given file. 123 | """ 124 | logger = logging.getLogger() 125 | logger.setLevel(level) 126 | formatter = logging.Formatter( 127 | '%(asctime)s %(name)s %(levelname)s %(message)s') 128 | handler = logging.FileHandler(path) 129 | handler.setFormatter(formatter) 130 | logger.addHandler(handler) 131 | 132 | 133 | def logToConsole(level=logging.INFO): 134 | """ 135 | Create a log handler that logs to the console. 136 | """ 137 | logger = logging.getLogger() 138 | logger.setLevel(level) 139 | formatter = logging.Formatter( 140 | '%(asctime)s %(name)s %(levelname)s %(message)s') 141 | handler = logging.StreamHandler() 142 | handler.setFormatter(formatter) 143 | logger.addHandler(handler) 144 | -------------------------------------------------------------------------------- /distex/version.py: -------------------------------------------------------------------------------- 1 | __version_info__ = (0, 7, 2) 2 | __version__ = '.'.join(str(v) for v in __version_info__) 3 | -------------------------------------------------------------------------------- /distex/worker.py: -------------------------------------------------------------------------------- 1 | """Proxy for a remote worker process.""" 2 | 3 | import asyncio 4 | import logging 5 | from collections import deque 6 | 7 | _logger = logging.getLogger('distex.Worker') 8 | 9 | 10 | class Worker(asyncio.Protocol): 11 | """ 12 | Worker that submits tasks to and gets results from a 13 | local or remote processor. 14 | """ 15 | 16 | def __init__(self, serializer): 17 | self.serializer = serializer 18 | self.loop = asyncio.get_event_loop_policy().get_event_loop() 19 | self.transport = None 20 | self.peername = '' 21 | self.disconnected = None 22 | self.futures = deque() 23 | self.tasks = deque() 24 | 25 | def __repr__(self): 26 | return f'' 27 | 28 | def run_task(self, task): 29 | """Send the task to the processor and return Future for the result.""" 30 | self.serializer.write_request(self.transport.write, task) 31 | future = self.loop.create_future() 32 | self.futures.append(future) 33 | self.tasks.append(task) 34 | return future 35 | 36 | def stop(self): 37 | """Close connection to the processor.""" 38 | if self.transport: 39 | self.transport.close() 40 | self.transport = None 41 | 42 | # protocol callbacks: 43 | 44 | def connection_made(self, transport): 45 | self.transport = transport 46 | hp = transport.get_extra_info('peername') 47 | if hp: 48 | host, port = hp 49 | self.peername = f'{host}:{port}' 50 | else: 51 | self.peername = 'Unix socket' 52 | _logger.info(f'Connection from {self.peername}') 53 | 54 | def connection_lost(self, exc): 55 | if exc: 56 | self.disconnected(self) 57 | _logger.error(f'Connection lost from {self.peername}: {exc}') 58 | self.transport = None 59 | 60 | def data_received(self, data): 61 | self.serializer.add_data(data) 62 | for resp in self.serializer.get_responses(): 63 | self.futures.popleft().set_result(resp) 64 | self.tasks.popleft() 65 | 66 | def eof_received(self): 67 | pass 68 | 69 | def pause_writing(self): 70 | pass 71 | 72 | def resume_writing(self): 73 | pass 74 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = python3 -msphinx 7 | SPHINXPROJ = distex 8 | SOURCEDIR = . 9 | BUILDDIR = . 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/api.rst: -------------------------------------------------------------------------------- 1 | .. _api: 2 | 3 | Distex documentation 4 | ==================== 5 | 6 | Release |release|. 7 | 8 | .. toctree:: 9 | :maxdepth: 3 10 | :caption: Modules: 11 | 12 | 13 | 14 | Pool 15 | ---- 16 | .. autoclass:: distex.pool.Pool 17 | 18 | PoolMap 19 | ------- 20 | .. autoclass:: distex.poolmap.PoolMap 21 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | extensions = [ 2 | 'sphinx.ext.autodoc', 3 | 'sphinx.ext.viewcode', 4 | 'sphinx.ext.napoleon', 5 | 'sphinx_autodoc_typehints', 6 | ] 7 | 8 | templates_path = ['_templates'] 9 | source_suffix = '.rst' 10 | master_doc = 'index' 11 | project = 'distex' 12 | copyright = '2020, Ewald de Wit' 13 | author = 'Ewald de Wit' 14 | 15 | __version__ = '' 16 | exec(open('../distex/version.py').read()) 17 | version = '.'.join(__version__.split('.')[:2]) 18 | release = __version__ 19 | 20 | language = None 21 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 22 | pygments_style = 'sphinx' 23 | todo_include_todos = False 24 | html_theme = 'sphinx_rtd_theme' 25 | html_theme_options = { 26 | 'canonical_url': 'https://distex.readthedocs.io', 27 | 'logo_only': False, 28 | 'display_version': True, 29 | 'prev_next_buttons_location': 'bottom', 30 | 'style_external_links': False, 31 | # Toc options 32 | 'collapse_navigation': True, 33 | 'sticky_navigation': True, 34 | 'navigation_depth': 4, 35 | 'includehidden': True, 36 | 'titles_only': False 37 | } 38 | github_url = 'https://github.com/erdewit/distex' 39 | 40 | autoclass_content = 'both' 41 | autodoc_member_order = "bysource" 42 | autodoc_default_flags = [ 43 | 'members', 44 | 'undoc-members', 45 | ] 46 | -------------------------------------------------------------------------------- /docs/html/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erdewit/distex/0d86d00f46c8cf2328c7674751a035408d46e865/docs/html/.nojekyll -------------------------------------------------------------------------------- /docs/html/_modules/distex/poolmap.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | distex.poolmap — distex 0.7.1 documentation 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 |
47 | 48 | 99 | 100 |
101 | 102 | 103 | 109 | 110 | 111 |
112 | 113 |
114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 |
132 | 133 |
    134 | 135 |
  • »
  • 136 | 137 |
  • Module code »
  • 138 | 139 |
  • distex.poolmap
  • 140 | 141 | 142 |
  • 143 | 144 |
  • 145 | 146 |
147 | 148 | 149 |
150 |
151 |
152 |
153 | 154 |

Source code for distex.poolmap

155 | import asyncio
156 | 
157 | from eventkit import Event, Op
158 | 
159 | 
160 | 
[docs]class PoolMap(Op): 161 | """ 162 | Map a function using a distributed pool with 163 | `eventkit <https://github.com/erdewit/eventkit>`_. 164 | 165 | Args: 166 | func: Function to map. If it returns an awaitable then the 167 | result will be awaited and returned. 168 | timeout: Timeout in seconds since map is started. 169 | chunksize: Source emits are chunked up to this size. 170 | A larger chunksize can greatly improve efficiency 171 | for small tasks. 172 | ordered: 173 | * ``True``: The order of results preserves the source order. 174 | * ``False``: Results are in order of completion. 175 | """ 176 | 177 | __slots__ = ('_pool', '_func', '_task', '_kwargs') 178 | 179 | def __init__( 180 | self, pool, func, timeout=None, chunksize=1, 181 | ordered=True, source=None): 182 | self._pool = pool 183 | self._func = func 184 | self._kwargs = dict( 185 | timeout=timeout, 186 | chunksize=chunksize, 187 | ordered=ordered, 188 | star=True) 189 | Op.__init__(self, source) 190 | 191 | def set_source(self, source): 192 | 193 | async def looper(): 194 | try: 195 | async for result in self._pool.map_async( 196 | self._func, 197 | source.aiter(tuples=True), 198 | **self._kwargs): 199 | self.emit(result) 200 | except Exception as error: 201 | self.error_event.emit(self, error) 202 | self._task = None 203 | self.set_done() 204 | 205 | self._task = asyncio.ensure_future(looper()) 206 | 207 | def __del__(self): 208 | if self._task: 209 | self._task.cancel()
210 | 211 | 212 | def poolmap( 213 | self, pool, func, 214 | timeout=None, chunksize=1, ordered=True): 215 | return PoolMap(pool, func, timeout, chunksize, ordered, self) 216 | 217 | 218 | poolmap.__doc__ = PoolMap.__doc__ 219 | Event.poolmap = poolmap 220 |
221 | 222 |
223 | 224 |
225 |
226 | 227 | 228 |
229 | 230 |
231 |

232 | 233 | © Copyright 2020, Ewald de Wit 234 | 235 |

236 |
237 | 238 | 239 | 240 | Built with Sphinx using a 241 | 242 | theme 243 | 244 | provided by Read the Docs. 245 | 246 |
247 | 248 |
249 |
250 | 251 |
252 | 253 |
254 | 255 | 256 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | -------------------------------------------------------------------------------- /docs/html/_modules/distex/util.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | distex.util — distex 0.5.1 documentation 9 | 10 | 11 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 |
35 | 36 | 72 |
73 |
74 |
75 | 76 |

Source code for distex.util

 77 | import sys
 78 | import asyncio
 79 | import socket
 80 | import logging
 81 | import uuid
 82 | import collections.abc
 83 | from contextlib import suppress
 84 | 
 85 | 
 86 | 
[docs]async def sync_iter(sync_or_async_iterable): 87 | """ 88 | Return normal iterator from either normal or async iterable. 89 | """ 90 | 91 | def _aiter(): 92 | async for v in sync_or_async_iterable: 93 | yield v 94 | 95 | if isinstance(sync_or_async_iterable, collections.abc.AsyncIterable): 96 | it = _aiter() 97 | else: 98 | it = iter(sync_or_async_iterable) 99 | return it
100 | 101 | 102 |
[docs]async def sync_zip(*sync_or_async_iterables): 103 | """ 104 | Return normal iterator from the zipped normal or async iterables. 105 | """ 106 | return zip(*[await sync_iter(it) for it in sync_or_async_iterables])
107 | 108 | 109 |
[docs]def get_loop(): 110 | """ 111 | Get optimal event loop for the platform. 112 | """ 113 | loop = None 114 | if sys.platform == 'win32': 115 | loop = asyncio.ProactorEventLoop() 116 | else: 117 | with suppress(ImportError): 118 | import uvloop 119 | loop = uvloop.Loop() 120 | return loop or asyncio.get_event_loop()
121 | 122 | 123 |
[docs]def single_step(loop): 124 | """ 125 | Do a single step of the asyncio event loop. 126 | """ 127 | loop.call_soon(loop.stop) 128 | loop.run_forever()
129 | 130 | 131 |
[docs]def get_random_port(): 132 | """ 133 | Get a random open port number on localhost. 134 | """ 135 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 136 | s.bind(('127.0.0.1', 0)) 137 | port = s.getsockname()[1] 138 | s.close() 139 | return port
140 | 141 | 142 |
[docs]def get_temp_path(): 143 | """ 144 | Get a universally unique temporary filename. 145 | """ 146 | return f'/tmp/distex-{uuid.uuid1().hex}'
147 | 148 | 149 |
[docs]def logToFile(path, level=logging.INFO): 150 | """ 151 | Create a log handler that logs to the given file. 152 | """ 153 | logger = logging.getLogger() 154 | logger.setLevel(level) 155 | formatter = logging.Formatter( 156 | '%(asctime)s %(name)s %(levelname)s %(message)s') 157 | handler = logging.FileHandler(path) 158 | handler.setFormatter(formatter) 159 | logger.addHandler(handler)
160 | 161 | 162 |
[docs]def logToConsole(level=logging.INFO): 163 | """ 164 | Create a log handler that logs to the console. 165 | """ 166 | logger = logging.getLogger() 167 | logger.setLevel(level) 168 | formatter = logging.Formatter( 169 | '%(asctime)s %(name)s %(levelname)s %(message)s') 170 | handler = logging.StreamHandler() 171 | handler.setFormatter(formatter) 172 | logger.addHandler(handler)
173 |
174 | 175 |
176 |
177 |
178 |
179 |
180 | 188 | 189 | 190 | 191 | 192 | 193 | -------------------------------------------------------------------------------- /docs/html/_modules/distex/worker.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | distex.worker — distex 0.5.1 documentation 9 | 10 | 11 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 |
35 | 36 | 72 |
73 |
74 |
75 | 76 |

Source code for distex.worker

 77 | import logging
 78 | from collections import deque
 79 | 
 80 | _logger = logging.getLogger('distex.Worker')
 81 | 
 82 | 
 83 | 
[docs]class Worker: 84 | """ 85 | Worker that submits tasks to and gets results from a 86 | local or remote processor. 87 | 88 | Implements asyncio.Protocol. 89 | """ 90 | 91 | __slots__ = ('futures', 'tasks', 'loop', 'disconnected', 92 | 'peername', 'serializer', 'transport') 93 | 94 | def __init__(self, serializer, loop): 95 | self.serializer = serializer 96 | self.loop = loop 97 | self.transport = None 98 | self.peername = None 99 | self.disconnected = None 100 | self.futures = deque() 101 | self.tasks = deque() 102 | 103 | def __repr__(self): 104 | return f'<Worker {self.peername}>' 105 | 106 |
[docs] def run_task(self, task): 107 | """ 108 | Send the task to the processor and return Future for the result. 109 | """ 110 | future = self.loop.create_future() 111 | self.futures.append(future) 112 | self.tasks.append(task) 113 | req = self.serializer.create_request(task) 114 | self.transport.write(req) 115 | return future
116 | 117 |
[docs] def stop(self): 118 | """ 119 | Close connection to the processor. 120 | """ 121 | self.transport.close() 122 | self.transport = None
123 | 124 | # protocol callbacks: 125 | 126 |
[docs] def connection_made(self, transport): 127 | self.transport = transport 128 | hp = transport.get_extra_info('peername') 129 | if hp: 130 | host, port = hp 131 | self.peername = f'{host}:{port}' 132 | else: 133 | self.peername = 'Unix socket' 134 | _logger.info(f'Connection from {self.peername}')
135 | 136 |
[docs] def connection_lost(self, exc): 137 | if exc: 138 | self.disconnected(self) 139 | _logger.error(f'Connection lost from {self.peername}: {exc}') 140 | self.transport = None
141 | 142 |
[docs] def data_received(self, data): 143 | for resp in self.serializer.get_responses(data): 144 | self.futures.popleft().set_result(resp) 145 | self.tasks.popleft()
146 | 147 |
[docs] def eof_received(self): 148 | pass
149 | 150 |
[docs] def pause_writing(self): 151 | pass
152 | 153 |
[docs] def resume_writing(self): 154 | pass
155 |
156 | 157 |
158 |
159 |
160 |
161 |
162 | 170 | 171 | 172 | 173 | 174 | 175 | -------------------------------------------------------------------------------- /docs/html/_modules/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Overview: module code — distex 0.7.1 documentation 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 |
47 | 48 | 99 | 100 |
101 | 102 | 103 | 109 | 110 | 111 |
112 | 113 |
114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 |
132 | 133 |
    134 | 135 |
  • »
  • 136 | 137 |
  • Overview: module code
  • 138 | 139 | 140 |
  • 141 | 142 |
  • 143 | 144 |
145 | 146 | 147 |
148 |
149 |
150 |
151 | 152 |

All modules for which code is available

153 | 156 | 157 |
158 | 159 |
160 |
161 | 162 | 163 |
164 | 165 |
166 |

167 | 168 | © Copyright 2020, Ewald de Wit 169 | 170 |

171 |
172 | 173 | 174 | 175 | Built with Sphinx using a 176 | 177 | theme 178 | 179 | provided by Read the Docs. 180 | 181 |
182 | 183 |
184 |
185 | 186 |
187 | 188 |
189 | 190 | 191 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | -------------------------------------------------------------------------------- /docs/html/_sources/api.rst.txt: -------------------------------------------------------------------------------- 1 | .. _api: 2 | 3 | Distex documentation 4 | ==================== 5 | 6 | Release |release|. 7 | 8 | .. toctree:: 9 | :maxdepth: 3 10 | :caption: Modules: 11 | 12 | 13 | 14 | Pool 15 | ---- 16 | .. autoclass:: distex.pool.Pool 17 | 18 | PoolMap 19 | ------- 20 | .. autoclass:: distex.poolmap.PoolMap 21 | -------------------------------------------------------------------------------- /docs/html/_sources/index.rst.txt: -------------------------------------------------------------------------------- 1 | .. toctree:: 2 | :maxdepth: 3 3 | 4 | api 5 | 6 | 7 | .. include:: ../README.rst 8 | 9 | -------------------------------------------------------------------------------- /docs/html/_static/ajax-loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erdewit/distex/0d86d00f46c8cf2328c7674751a035408d46e865/docs/html/_static/ajax-loader.gif -------------------------------------------------------------------------------- /docs/html/_static/alabaster.css: -------------------------------------------------------------------------------- 1 | @import url("basic.css"); 2 | 3 | /* -- page layout ----------------------------------------------------------- */ 4 | 5 | body { 6 | font-family: Georgia, serif; 7 | font-size: 17px; 8 | background-color: #fff; 9 | color: #000; 10 | margin: 0; 11 | padding: 0; 12 | } 13 | 14 | 15 | div.document { 16 | width: 65em; 17 | margin: 30px auto 0 auto; 18 | } 19 | 20 | div.documentwrapper { 21 | float: left; 22 | width: 100%; 23 | } 24 | 25 | div.bodywrapper { 26 | margin: 0 0 0 220px; 27 | } 28 | 29 | div.sphinxsidebar { 30 | width: 220px; 31 | font-size: 14px; 32 | line-height: 1.5; 33 | } 34 | 35 | hr { 36 | border: 1px solid #B1B4B6; 37 | } 38 | 39 | div.body { 40 | background-color: #fff; 41 | color: #3E4349; 42 | padding: 0 30px 0 30px; 43 | } 44 | 45 | div.body > .section { 46 | text-align: left; 47 | } 48 | 49 | div.footer { 50 | width: 65em; 51 | margin: 20px auto 30px auto; 52 | font-size: 14px; 53 | color: #888; 54 | text-align: right; 55 | } 56 | 57 | div.footer a { 58 | color: #888; 59 | } 60 | 61 | p.caption { 62 | font-family: inherit; 63 | font-size: inherit; 64 | } 65 | 66 | 67 | div.relations { 68 | display: none; 69 | } 70 | 71 | 72 | div.sphinxsidebar a { 73 | color: #266; 74 | text-decoration: none; 75 | border-bottom: 1px dotted #999; 76 | } 77 | 78 | div.sphinxsidebar a:hover { 79 | border-bottom: 1px solid #999; 80 | } 81 | 82 | div.sphinxsidebarwrapper { 83 | padding: 18px 10px; 84 | } 85 | 86 | div.sphinxsidebarwrapper p.logo { 87 | padding: 0; 88 | margin: -10px 0 0 0px; 89 | text-align: center; 90 | } 91 | 92 | div.sphinxsidebarwrapper h1.logo { 93 | margin-top: -10px; 94 | text-align: center; 95 | margin-bottom: 5px; 96 | text-align: left; 97 | } 98 | 99 | div.sphinxsidebarwrapper h1.logo-name { 100 | margin-top: 0px; 101 | } 102 | 103 | div.sphinxsidebarwrapper p.blurb { 104 | margin-top: 0; 105 | font-style: normal; 106 | } 107 | 108 | div.sphinxsidebar h3, 109 | div.sphinxsidebar h4 { 110 | font-family: Georgia, serif; 111 | color: #444; 112 | font-size: 24px; 113 | font-weight: normal; 114 | margin: 0 0 5px 0; 115 | padding: 0; 116 | } 117 | 118 | div.sphinxsidebar h4 { 119 | font-size: 20px; 120 | } 121 | 122 | div.sphinxsidebar h3 a { 123 | color: #266; 124 | } 125 | 126 | div.sphinxsidebar p.logo a, 127 | div.sphinxsidebar h3 a, 128 | div.sphinxsidebar p.logo a:hover, 129 | div.sphinxsidebar h3 a:hover { 130 | border: none; 131 | } 132 | 133 | div.sphinxsidebar p { 134 | color: #555; 135 | margin: 10px 0; 136 | } 137 | 138 | div.sphinxsidebar ul { 139 | margin: 10px 0; 140 | padding: 0; 141 | color: #000; 142 | } 143 | 144 | div.sphinxsidebar ul li.toctree-l1 > a { 145 | font-size: 120%; 146 | } 147 | 148 | div.sphinxsidebar ul li.toctree-l2 > a { 149 | font-size: 110%; 150 | } 151 | 152 | div.sphinxsidebar input { 153 | border: 1px solid #CCC; 154 | font-family: Georgia, serif; 155 | font-size: 1em; 156 | } 157 | 158 | div.sphinxsidebar hr { 159 | border: none; 160 | height: 1px; 161 | color: #AAA; 162 | background: #AAA; 163 | 164 | text-align: left; 165 | margin-left: 0; 166 | width: 50%; 167 | } 168 | 169 | /* -- body styles ----------------------------------------------------------- */ 170 | 171 | a { 172 | color: #266; 173 | text-decoration: underline; 174 | } 175 | 176 | a:hover { 177 | color: #6D4100; 178 | text-decoration: underline; 179 | } 180 | 181 | div.body h1, 182 | div.body h2, 183 | div.body h3, 184 | div.body h4, 185 | div.body h5, 186 | div.body h6 { 187 | font-family: Georgia, serif; 188 | font-weight: normal; 189 | margin: 30px 0px 10px 0px; 190 | padding: 0; 191 | } 192 | 193 | div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; } 194 | div.body h2 { font-size: 180%; } 195 | div.body h3 { font-size: 150%; } 196 | div.body h4 { font-size: 130%; } 197 | div.body h5 { font-size: 100%; } 198 | div.body h6 { font-size: 100%; } 199 | 200 | a.headerlink { 201 | color: #DDD; 202 | padding: 0 4px; 203 | text-decoration: none; 204 | } 205 | 206 | a.headerlink:hover { 207 | color: #444; 208 | background: #EAEAEA; 209 | } 210 | 211 | div.body p, div.body dd, div.body li { 212 | line-height: 1.4em; 213 | } 214 | 215 | div.admonition { 216 | margin: 20px 0px; 217 | padding: 10px 30px; 218 | background-color: #EEE; 219 | border: 1px solid #CCC; 220 | } 221 | 222 | div.admonition tt.xref, div.admonition code.xref, div.admonition a tt { 223 | background-color: #FBFBFB; 224 | border-bottom: 1px solid #fafafa; 225 | } 226 | 227 | div.admonition p.admonition-title { 228 | font-family: Georgia, serif; 229 | font-weight: normal; 230 | font-size: 24px; 231 | margin: 0 0 10px 0; 232 | padding: 0; 233 | line-height: 1; 234 | } 235 | 236 | div.admonition p.last { 237 | margin-bottom: 0; 238 | } 239 | 240 | div.highlight { 241 | background-color: #fff; 242 | } 243 | 244 | dt:target, .highlight { 245 | background: #FAF3E8; 246 | } 247 | 248 | div.warning { 249 | background-color: #FCC; 250 | border: 1px solid #FAA; 251 | } 252 | 253 | div.danger { 254 | background-color: #FCC; 255 | border: 1px solid #FAA; 256 | -moz-box-shadow: 2px 2px 4px #D52C2C; 257 | -webkit-box-shadow: 2px 2px 4px #D52C2C; 258 | box-shadow: 2px 2px 4px #D52C2C; 259 | } 260 | 261 | div.error { 262 | background-color: #FCC; 263 | border: 1px solid #FAA; 264 | -moz-box-shadow: 2px 2px 4px #D52C2C; 265 | -webkit-box-shadow: 2px 2px 4px #D52C2C; 266 | box-shadow: 2px 2px 4px #D52C2C; 267 | } 268 | 269 | div.caution { 270 | background-color: #FCC; 271 | border: 1px solid #FAA; 272 | } 273 | 274 | div.attention { 275 | background-color: #FCC; 276 | border: 1px solid #FAA; 277 | } 278 | 279 | div.important { 280 | background-color: #EEE; 281 | border: 1px solid #CCC; 282 | } 283 | 284 | div.note { 285 | background-color: #EEE; 286 | border: 1px solid #CCC; 287 | } 288 | 289 | div.tip { 290 | background-color: #EEE; 291 | border: 1px solid #CCC; 292 | } 293 | 294 | div.hint { 295 | background-color: #EEE; 296 | border: 1px solid #CCC; 297 | } 298 | 299 | div.seealso { 300 | background-color: #EEE; 301 | border: 1px solid #CCC; 302 | } 303 | 304 | div.topic { 305 | background-color: #EEE; 306 | } 307 | 308 | p.admonition-title { 309 | display: inline; 310 | } 311 | 312 | p.admonition-title:after { 313 | content: ":"; 314 | } 315 | 316 | pre, tt, code { 317 | font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; 318 | font-size: 0.9em; 319 | } 320 | 321 | .hll { 322 | background-color: #FFC; 323 | margin: 0 -12px; 324 | padding: 0 12px; 325 | display: block; 326 | } 327 | 328 | img.screenshot { 329 | } 330 | 331 | tt.descname, tt.descclassname, code.descname, code.descclassname { 332 | font-size: 0.95em; 333 | } 334 | 335 | tt.descname, code.descname { 336 | padding-right: 0.08em; 337 | } 338 | 339 | img.screenshot { 340 | -moz-box-shadow: 2px 2px 4px #EEE; 341 | -webkit-box-shadow: 2px 2px 4px #EEE; 342 | box-shadow: 2px 2px 4px #EEE; 343 | } 344 | 345 | table.docutils { 346 | border: 1px solid #888; 347 | -moz-box-shadow: 2px 2px 4px #EEE; 348 | -webkit-box-shadow: 2px 2px 4px #EEE; 349 | box-shadow: 2px 2px 4px #EEE; 350 | } 351 | 352 | table.docutils td, table.docutils th { 353 | border: 1px solid #888; 354 | padding: 0.25em 0.7em; 355 | } 356 | 357 | table.field-list, table.footnote { 358 | border: none; 359 | -moz-box-shadow: none; 360 | -webkit-box-shadow: none; 361 | box-shadow: none; 362 | } 363 | 364 | table.footnote { 365 | margin: 15px 0; 366 | width: 100%; 367 | border: 1px solid #EEE; 368 | background: #FDFDFD; 369 | font-size: 0.9em; 370 | } 371 | 372 | table.footnote + table.footnote { 373 | margin-top: -15px; 374 | border-top: none; 375 | } 376 | 377 | table.field-list th { 378 | padding: 0 0.8em 0 0; 379 | } 380 | 381 | table.field-list td { 382 | padding: 0; 383 | } 384 | 385 | table.field-list p { 386 | margin-bottom: 0.8em; 387 | } 388 | 389 | /* Cloned from 390 | * https://github.com/sphinx-doc/sphinx/commit/ef60dbfce09286b20b7385333d63a60321784e68 391 | */ 392 | .field-name { 393 | -moz-hyphens: manual; 394 | -ms-hyphens: manual; 395 | -webkit-hyphens: manual; 396 | hyphens: manual; 397 | } 398 | 399 | table.footnote td.label { 400 | width: .1px; 401 | padding: 0.3em 0 0.3em 0.5em; 402 | } 403 | 404 | table.footnote td { 405 | padding: 0.3em 0.5em; 406 | } 407 | 408 | dl { 409 | margin: 0; 410 | padding: 0; 411 | } 412 | 413 | dl dd { 414 | margin-left: 30px; 415 | } 416 | 417 | blockquote { 418 | margin: 0 0 0 30px; 419 | padding: 0; 420 | } 421 | 422 | ul, ol { 423 | /* Matches the 30px from the narrow-screen "li > ul" selector below */ 424 | margin: 10px 0 10px 30px; 425 | padding: 0; 426 | } 427 | 428 | pre { 429 | background: #EEE; 430 | padding: 7px 30px; 431 | margin: 15px 0px; 432 | line-height: 1.3em; 433 | } 434 | 435 | div.viewcode-block:target { 436 | background: #ffd; 437 | } 438 | 439 | dl pre, blockquote pre, li pre { 440 | margin-left: 0; 441 | padding-left: 30px; 442 | } 443 | 444 | tt, code { 445 | background-color: #ecf0f3; 446 | color: #222; 447 | /* padding: 1px 2px; */ 448 | } 449 | 450 | tt.xref, code.xref, a tt { 451 | background-color: #FBFBFB; 452 | border-bottom: 1px solid #fff; 453 | } 454 | 455 | a.reference { 456 | text-decoration: none; 457 | border-bottom: 1px dotted #266; 458 | } 459 | 460 | /* Don't put an underline on images */ 461 | a.image-reference, a.image-reference:hover { 462 | border-bottom: none; 463 | } 464 | 465 | a.reference:hover { 466 | border-bottom: 1px solid #6D4100; 467 | } 468 | 469 | a.footnote-reference { 470 | text-decoration: none; 471 | font-size: 0.7em; 472 | vertical-align: top; 473 | border-bottom: 1px dotted #266; 474 | } 475 | 476 | a.footnote-reference:hover { 477 | border-bottom: 1px solid #6D4100; 478 | } 479 | 480 | a:hover tt, a:hover code { 481 | background: #EEE; 482 | } 483 | 484 | 485 | @media screen and (max-width: 870px) { 486 | 487 | div.sphinxsidebar { 488 | display: none; 489 | } 490 | 491 | div.document { 492 | width: 100%; 493 | 494 | } 495 | 496 | div.documentwrapper { 497 | margin-left: 0; 498 | margin-top: 0; 499 | margin-right: 0; 500 | margin-bottom: 0; 501 | } 502 | 503 | div.bodywrapper { 504 | margin-top: 0; 505 | margin-right: 0; 506 | margin-bottom: 0; 507 | margin-left: 0; 508 | } 509 | 510 | ul { 511 | margin-left: 0; 512 | } 513 | 514 | li > ul { 515 | /* Matches the 30px from the "ul, ol" selector above */ 516 | margin-left: 30px; 517 | } 518 | 519 | .document { 520 | width: auto; 521 | } 522 | 523 | .footer { 524 | width: auto; 525 | } 526 | 527 | .bodywrapper { 528 | margin: 0; 529 | } 530 | 531 | .footer { 532 | width: auto; 533 | } 534 | 535 | .github { 536 | display: none; 537 | } 538 | 539 | 540 | 541 | } 542 | 543 | 544 | 545 | @media screen and (max-width: 875px) { 546 | 547 | body { 548 | margin: 0; 549 | padding: 20px 30px; 550 | } 551 | 552 | div.documentwrapper { 553 | float: none; 554 | background: #fff; 555 | } 556 | 557 | div.sphinxsidebar { 558 | display: block; 559 | float: none; 560 | width: 102.5%; 561 | margin: -20px -30px 20px -30px; 562 | padding: 10px 20px; 563 | background: #333; 564 | color: #FFF; 565 | } 566 | 567 | div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p, 568 | div.sphinxsidebar h3 a { 569 | color: #fff; 570 | } 571 | 572 | div.sphinxsidebar a { 573 | color: #AAA; 574 | } 575 | 576 | div.sphinxsidebar p.logo { 577 | display: none; 578 | } 579 | 580 | div.document { 581 | width: 100%; 582 | margin: 0; 583 | } 584 | 585 | div.footer { 586 | display: none; 587 | } 588 | 589 | div.bodywrapper { 590 | margin: 0; 591 | } 592 | 593 | div.body { 594 | min-height: 0; 595 | padding: 0; 596 | } 597 | 598 | .rtd_doc_footer { 599 | display: none; 600 | } 601 | 602 | .document { 603 | width: auto; 604 | } 605 | 606 | .footer { 607 | width: auto; 608 | } 609 | 610 | .footer { 611 | width: auto; 612 | } 613 | 614 | .github { 615 | display: none; 616 | } 617 | } 618 | @media screen and (min-width: 876px) { 619 | div.sphinxsidebar { 620 | position: fixed; 621 | margin-left: 0; 622 | } 623 | } 624 | 625 | 626 | /* misc. */ 627 | 628 | .revsys-inline { 629 | display: none!important; 630 | } 631 | 632 | /* Make nested-list/multi-paragraph items look better in Releases changelog 633 | * pages. Without this, docutils' magical list fuckery causes inconsistent 634 | * formatting between different release sub-lists. 635 | */ 636 | div#changelog > div.section > ul > li > p:only-child { 637 | margin-bottom: 0; 638 | } 639 | 640 | /* Hide fugly table cell borders in ..bibliography:: directive output */ 641 | table.docutils.citation, table.docutils.citation td, table.docutils.citation th { 642 | border: none; 643 | /* Below needed in some edge cases; if not applied, bottom shadows appear */ 644 | -moz-box-shadow: none; 645 | -webkit-box-shadow: none; 646 | box-shadow: none; 647 | } 648 | 649 | 650 | /* relbar */ 651 | 652 | .related { 653 | line-height: 30px; 654 | width: 100%; 655 | font-size: 0.9rem; 656 | } 657 | 658 | .related.top { 659 | border-bottom: 1px solid #EEE; 660 | margin-bottom: 20px; 661 | } 662 | 663 | .related.bottom { 664 | border-top: 1px solid #EEE; 665 | } 666 | 667 | .related ul { 668 | padding: 0; 669 | margin: 0; 670 | list-style: none; 671 | } 672 | 673 | .related li { 674 | display: inline; 675 | } 676 | 677 | nav#rellinks { 678 | float: right; 679 | } 680 | 681 | nav#rellinks li+li:before { 682 | content: "|"; 683 | } 684 | 685 | nav#breadcrumbs li+li:before { 686 | content: "\00BB"; 687 | } 688 | 689 | /* Hide certain items when printing */ 690 | @media print { 691 | div.related { 692 | display: none; 693 | } 694 | } -------------------------------------------------------------------------------- /docs/html/_static/classic.css: -------------------------------------------------------------------------------- 1 | /* 2 | * classic.css_t 3 | * ~~~~~~~~~~~~~ 4 | * 5 | * Sphinx stylesheet -- classic theme. 6 | * 7 | * :copyright: Copyright 2007-2017 by the Sphinx team, see AUTHORS. 8 | * :license: BSD, see LICENSE for details. 9 | * 10 | */ 11 | 12 | @import url("basic.css"); 13 | 14 | /* -- page layout ----------------------------------------------------------- */ 15 | 16 | body { 17 | font-family: sans-serif; 18 | font-size: 100%; 19 | background-color: #11303d; 20 | color: #000; 21 | margin: 0; 22 | padding: 0; 23 | } 24 | 25 | div.document { 26 | background-color: #1c4e63; 27 | } 28 | 29 | div.documentwrapper { 30 | float: left; 31 | width: 100%; 32 | } 33 | 34 | div.bodywrapper { 35 | margin: 0 0 0 230px; 36 | } 37 | 38 | div.body { 39 | background-color: #ffffff; 40 | color: #000000; 41 | padding: 0 20px 30px 20px; 42 | } 43 | 44 | div.footer { 45 | color: #ffffff; 46 | width: 100%; 47 | padding: 9px 0 9px 0; 48 | text-align: center; 49 | font-size: 75%; 50 | } 51 | 52 | div.footer a { 53 | color: #ffffff; 54 | text-decoration: underline; 55 | } 56 | 57 | div.related { 58 | background-color: #133f52; 59 | line-height: 30px; 60 | color: #ffffff; 61 | } 62 | 63 | div.related a { 64 | color: #ffffff; 65 | } 66 | 67 | div.sphinxsidebar { 68 | } 69 | 70 | div.sphinxsidebar h3 { 71 | font-family: 'Trebuchet MS', sans-serif; 72 | color: #ffffff; 73 | font-size: 1.4em; 74 | font-weight: normal; 75 | margin: 0; 76 | padding: 0; 77 | } 78 | 79 | div.sphinxsidebar h3 a { 80 | color: #ffffff; 81 | } 82 | 83 | div.sphinxsidebar h4 { 84 | font-family: 'Trebuchet MS', sans-serif; 85 | color: #ffffff; 86 | font-size: 1.3em; 87 | font-weight: normal; 88 | margin: 5px 0 0 0; 89 | padding: 0; 90 | } 91 | 92 | div.sphinxsidebar p { 93 | color: #ffffff; 94 | } 95 | 96 | div.sphinxsidebar p.topless { 97 | margin: 5px 10px 10px 10px; 98 | } 99 | 100 | div.sphinxsidebar ul { 101 | margin: 10px; 102 | padding: 0; 103 | color: #ffffff; 104 | } 105 | 106 | div.sphinxsidebar a { 107 | color: #98dbcc; 108 | } 109 | 110 | div.sphinxsidebar input { 111 | border: 1px solid #98dbcc; 112 | font-family: sans-serif; 113 | font-size: 1em; 114 | } 115 | 116 | 117 | 118 | /* -- hyperlink styles ------------------------------------------------------ */ 119 | 120 | a { 121 | color: #355f7c; 122 | text-decoration: none; 123 | } 124 | 125 | a:visited { 126 | color: #355f7c; 127 | text-decoration: none; 128 | } 129 | 130 | a:hover { 131 | text-decoration: underline; 132 | } 133 | 134 | 135 | 136 | /* -- body styles ----------------------------------------------------------- */ 137 | 138 | div.body h1, 139 | div.body h2, 140 | div.body h3, 141 | div.body h4, 142 | div.body h5, 143 | div.body h6 { 144 | font-family: 'Trebuchet MS', sans-serif; 145 | background-color: #f2f2f2; 146 | font-weight: normal; 147 | color: #20435c; 148 | border-bottom: 1px solid #ccc; 149 | margin: 20px -20px 10px -20px; 150 | padding: 3px 0 3px 10px; 151 | } 152 | 153 | div.body h1 { margin-top: 0; font-size: 200%; } 154 | div.body h2 { font-size: 160%; } 155 | div.body h3 { font-size: 140%; } 156 | div.body h4 { font-size: 120%; } 157 | div.body h5 { font-size: 110%; } 158 | div.body h6 { font-size: 100%; } 159 | 160 | a.headerlink { 161 | color: #c60f0f; 162 | font-size: 0.8em; 163 | padding: 0 4px 0 4px; 164 | text-decoration: none; 165 | } 166 | 167 | a.headerlink:hover { 168 | background-color: #c60f0f; 169 | color: white; 170 | } 171 | 172 | div.body p, div.body dd, div.body li, div.body blockquote { 173 | text-align: justify; 174 | line-height: 130%; 175 | } 176 | 177 | div.admonition p.admonition-title + p { 178 | display: inline; 179 | } 180 | 181 | div.admonition p { 182 | margin-bottom: 5px; 183 | } 184 | 185 | div.admonition pre { 186 | margin-bottom: 5px; 187 | } 188 | 189 | div.admonition ul, div.admonition ol { 190 | margin-bottom: 5px; 191 | } 192 | 193 | div.note { 194 | background-color: #eee; 195 | border: 1px solid #ccc; 196 | } 197 | 198 | div.seealso { 199 | background-color: #ffc; 200 | border: 1px solid #ff6; 201 | } 202 | 203 | div.topic { 204 | background-color: #eee; 205 | } 206 | 207 | div.warning { 208 | background-color: #ffe4e4; 209 | border: 1px solid #f66; 210 | } 211 | 212 | p.admonition-title { 213 | display: inline; 214 | } 215 | 216 | p.admonition-title:after { 217 | content: ":"; 218 | } 219 | 220 | pre { 221 | padding: 5px; 222 | background-color: #eeffcc; 223 | color: #333333; 224 | line-height: 120%; 225 | border: 1px solid #ac9; 226 | border-left: none; 227 | border-right: none; 228 | } 229 | 230 | code { 231 | background-color: #ecf0f3; 232 | padding: 0 1px 0 1px; 233 | font-size: 0.95em; 234 | } 235 | 236 | th { 237 | background-color: #ede; 238 | } 239 | 240 | .warning code { 241 | background: #efc2c2; 242 | } 243 | 244 | .note code { 245 | background: #d6d6d6; 246 | } 247 | 248 | .viewcode-back { 249 | font-family: sans-serif; 250 | } 251 | 252 | div.viewcode-block:target { 253 | background-color: #f4debf; 254 | border-top: 1px solid #ac9; 255 | border-bottom: 1px solid #ac9; 256 | } 257 | 258 | div.code-block-caption { 259 | color: #efefef; 260 | background-color: #1c4e63; 261 | } -------------------------------------------------------------------------------- /docs/html/_static/comment-bright.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erdewit/distex/0d86d00f46c8cf2328c7674751a035408d46e865/docs/html/_static/comment-bright.png -------------------------------------------------------------------------------- /docs/html/_static/comment-close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erdewit/distex/0d86d00f46c8cf2328c7674751a035408d46e865/docs/html/_static/comment-close.png -------------------------------------------------------------------------------- /docs/html/_static/comment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erdewit/distex/0d86d00f46c8cf2328c7674751a035408d46e865/docs/html/_static/comment.png -------------------------------------------------------------------------------- /docs/html/_static/css/badge_only.css: -------------------------------------------------------------------------------- 1 | .fa:before{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#FontAwesome) format("svg")}.fa:before{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1}.fa:before,a .fa{text-decoration:inherit}.fa:before,a .fa,li .fa{display:inline-block}li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-caret-down:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before,.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before,.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before,.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60}.rst-versions .rst-current-version:after{clear:both;content:"";display:block}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}} -------------------------------------------------------------------------------- /docs/html/_static/custom.css: -------------------------------------------------------------------------------- 1 | /* This file intentionally left blank. */ 2 | -------------------------------------------------------------------------------- /docs/html/_static/default.css: -------------------------------------------------------------------------------- 1 | @import url("classic.css"); 2 | -------------------------------------------------------------------------------- /docs/html/_static/doctools.js: -------------------------------------------------------------------------------- 1 | /* 2 | * doctools.js 3 | * ~~~~~~~~~~~ 4 | * 5 | * Sphinx JavaScript utilities for all documentation. 6 | * 7 | * :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. 8 | * :license: BSD, see LICENSE for details. 9 | * 10 | */ 11 | 12 | /** 13 | * select a different prefix for underscore 14 | */ 15 | $u = _.noConflict(); 16 | 17 | /** 18 | * make the code below compatible with browsers without 19 | * an installed firebug like debugger 20 | if (!window.console || !console.firebug) { 21 | var names = ["log", "debug", "info", "warn", "error", "assert", "dir", 22 | "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", 23 | "profile", "profileEnd"]; 24 | window.console = {}; 25 | for (var i = 0; i < names.length; ++i) 26 | window.console[names[i]] = function() {}; 27 | } 28 | */ 29 | 30 | /** 31 | * small helper function to urldecode strings 32 | */ 33 | jQuery.urldecode = function(x) { 34 | return decodeURIComponent(x).replace(/\+/g, ' '); 35 | }; 36 | 37 | /** 38 | * small helper function to urlencode strings 39 | */ 40 | jQuery.urlencode = encodeURIComponent; 41 | 42 | /** 43 | * This function returns the parsed url parameters of the 44 | * current request. Multiple values per key are supported, 45 | * it will always return arrays of strings for the value parts. 46 | */ 47 | jQuery.getQueryParameters = function(s) { 48 | if (typeof s === 'undefined') 49 | s = document.location.search; 50 | var parts = s.substr(s.indexOf('?') + 1).split('&'); 51 | var result = {}; 52 | for (var i = 0; i < parts.length; i++) { 53 | var tmp = parts[i].split('=', 2); 54 | var key = jQuery.urldecode(tmp[0]); 55 | var value = jQuery.urldecode(tmp[1]); 56 | if (key in result) 57 | result[key].push(value); 58 | else 59 | result[key] = [value]; 60 | } 61 | return result; 62 | }; 63 | 64 | /** 65 | * highlight a given string on a jquery object by wrapping it in 66 | * span elements with the given class name. 67 | */ 68 | jQuery.fn.highlightText = function(text, className) { 69 | function highlight(node, addItems) { 70 | if (node.nodeType === 3) { 71 | var val = node.nodeValue; 72 | var pos = val.toLowerCase().indexOf(text); 73 | if (pos >= 0 && 74 | !jQuery(node.parentNode).hasClass(className) && 75 | !jQuery(node.parentNode).hasClass("nohighlight")) { 76 | var span; 77 | var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); 78 | if (isInSVG) { 79 | span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); 80 | } else { 81 | span = document.createElement("span"); 82 | span.className = className; 83 | } 84 | span.appendChild(document.createTextNode(val.substr(pos, text.length))); 85 | node.parentNode.insertBefore(span, node.parentNode.insertBefore( 86 | document.createTextNode(val.substr(pos + text.length)), 87 | node.nextSibling)); 88 | node.nodeValue = val.substr(0, pos); 89 | if (isInSVG) { 90 | var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); 91 | var bbox = node.parentElement.getBBox(); 92 | rect.x.baseVal.value = bbox.x; 93 | rect.y.baseVal.value = bbox.y; 94 | rect.width.baseVal.value = bbox.width; 95 | rect.height.baseVal.value = bbox.height; 96 | rect.setAttribute('class', className); 97 | addItems.push({ 98 | "parent": node.parentNode, 99 | "target": rect}); 100 | } 101 | } 102 | } 103 | else if (!jQuery(node).is("button, select, textarea")) { 104 | jQuery.each(node.childNodes, function() { 105 | highlight(this, addItems); 106 | }); 107 | } 108 | } 109 | var addItems = []; 110 | var result = this.each(function() { 111 | highlight(this, addItems); 112 | }); 113 | for (var i = 0; i < addItems.length; ++i) { 114 | jQuery(addItems[i].parent).before(addItems[i].target); 115 | } 116 | return result; 117 | }; 118 | 119 | /* 120 | * backward compatibility for jQuery.browser 121 | * This will be supported until firefox bug is fixed. 122 | */ 123 | if (!jQuery.browser) { 124 | jQuery.uaMatch = function(ua) { 125 | ua = ua.toLowerCase(); 126 | 127 | var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || 128 | /(webkit)[ \/]([\w.]+)/.exec(ua) || 129 | /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || 130 | /(msie) ([\w.]+)/.exec(ua) || 131 | ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || 132 | []; 133 | 134 | return { 135 | browser: match[ 1 ] || "", 136 | version: match[ 2 ] || "0" 137 | }; 138 | }; 139 | jQuery.browser = {}; 140 | jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; 141 | } 142 | 143 | /** 144 | * Small JavaScript module for the documentation. 145 | */ 146 | var Documentation = { 147 | 148 | init : function() { 149 | this.fixFirefoxAnchorBug(); 150 | this.highlightSearchWords(); 151 | this.initIndexTable(); 152 | if (DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) { 153 | this.initOnKeyListeners(); 154 | } 155 | }, 156 | 157 | /** 158 | * i18n support 159 | */ 160 | TRANSLATIONS : {}, 161 | PLURAL_EXPR : function(n) { return n === 1 ? 0 : 1; }, 162 | LOCALE : 'unknown', 163 | 164 | // gettext and ngettext don't access this so that the functions 165 | // can safely bound to a different name (_ = Documentation.gettext) 166 | gettext : function(string) { 167 | var translated = Documentation.TRANSLATIONS[string]; 168 | if (typeof translated === 'undefined') 169 | return string; 170 | return (typeof translated === 'string') ? translated : translated[0]; 171 | }, 172 | 173 | ngettext : function(singular, plural, n) { 174 | var translated = Documentation.TRANSLATIONS[singular]; 175 | if (typeof translated === 'undefined') 176 | return (n == 1) ? singular : plural; 177 | return translated[Documentation.PLURALEXPR(n)]; 178 | }, 179 | 180 | addTranslations : function(catalog) { 181 | for (var key in catalog.messages) 182 | this.TRANSLATIONS[key] = catalog.messages[key]; 183 | this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')'); 184 | this.LOCALE = catalog.locale; 185 | }, 186 | 187 | /** 188 | * add context elements like header anchor links 189 | */ 190 | addContextElements : function() { 191 | $('div[id] > :header:first').each(function() { 192 | $('\u00B6'). 193 | attr('href', '#' + this.id). 194 | attr('title', _('Permalink to this headline')). 195 | appendTo(this); 196 | }); 197 | $('dt[id]').each(function() { 198 | $('\u00B6'). 199 | attr('href', '#' + this.id). 200 | attr('title', _('Permalink to this definition')). 201 | appendTo(this); 202 | }); 203 | }, 204 | 205 | /** 206 | * workaround a firefox stupidity 207 | * see: https://bugzilla.mozilla.org/show_bug.cgi?id=645075 208 | */ 209 | fixFirefoxAnchorBug : function() { 210 | if (document.location.hash && $.browser.mozilla) 211 | window.setTimeout(function() { 212 | document.location.href += ''; 213 | }, 10); 214 | }, 215 | 216 | /** 217 | * highlight the search words provided in the url in the text 218 | */ 219 | highlightSearchWords : function() { 220 | var params = $.getQueryParameters(); 221 | var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; 222 | if (terms.length) { 223 | var body = $('div.body'); 224 | if (!body.length) { 225 | body = $('body'); 226 | } 227 | window.setTimeout(function() { 228 | $.each(terms, function() { 229 | body.highlightText(this.toLowerCase(), 'highlighted'); 230 | }); 231 | }, 10); 232 | $('') 234 | .appendTo($('#searchbox')); 235 | } 236 | }, 237 | 238 | /** 239 | * init the domain index toggle buttons 240 | */ 241 | initIndexTable : function() { 242 | var togglers = $('img.toggler').click(function() { 243 | var src = $(this).attr('src'); 244 | var idnum = $(this).attr('id').substr(7); 245 | $('tr.cg-' + idnum).toggle(); 246 | if (src.substr(-9) === 'minus.png') 247 | $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); 248 | else 249 | $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); 250 | }).css('display', ''); 251 | if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { 252 | togglers.click(); 253 | } 254 | }, 255 | 256 | /** 257 | * helper function to hide the search marks again 258 | */ 259 | hideSearchWords : function() { 260 | $('#searchbox .highlight-link').fadeOut(300); 261 | $('span.highlighted').removeClass('highlighted'); 262 | }, 263 | 264 | /** 265 | * make the url absolute 266 | */ 267 | makeURL : function(relativeURL) { 268 | return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; 269 | }, 270 | 271 | /** 272 | * get the current relative url 273 | */ 274 | getCurrentURL : function() { 275 | var path = document.location.pathname; 276 | var parts = path.split(/\//); 277 | $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { 278 | if (this === '..') 279 | parts.pop(); 280 | }); 281 | var url = parts.join('/'); 282 | return path.substring(url.lastIndexOf('/') + 1, path.length - 1); 283 | }, 284 | 285 | initOnKeyListeners: function() { 286 | $(document).keydown(function(event) { 287 | var activeElementType = document.activeElement.tagName; 288 | // don't navigate when in search box or textarea 289 | if (activeElementType !== 'TEXTAREA' && activeElementType !== 'INPUT' && activeElementType !== 'SELECT' 290 | && !event.altKey && !event.ctrlKey && !event.metaKey && !event.shiftKey) { 291 | switch (event.keyCode) { 292 | case 37: // left 293 | var prevHref = $('link[rel="prev"]').prop('href'); 294 | if (prevHref) { 295 | window.location.href = prevHref; 296 | return false; 297 | } 298 | case 39: // right 299 | var nextHref = $('link[rel="next"]').prop('href'); 300 | if (nextHref) { 301 | window.location.href = nextHref; 302 | return false; 303 | } 304 | } 305 | } 306 | }); 307 | } 308 | }; 309 | 310 | // quick alias for translations 311 | _ = Documentation.gettext; 312 | 313 | $(document).ready(function() { 314 | Documentation.init(); 315 | }); 316 | -------------------------------------------------------------------------------- /docs/html/_static/documentation_options.js: -------------------------------------------------------------------------------- 1 | var DOCUMENTATION_OPTIONS = { 2 | URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), 3 | VERSION: '0.7.1', 4 | LANGUAGE: 'None', 5 | COLLAPSE_INDEX: false, 6 | BUILDER: 'html', 7 | FILE_SUFFIX: '.html', 8 | LINK_SUFFIX: '.html', 9 | HAS_SOURCE: true, 10 | SOURCELINK_SUFFIX: '.txt', 11 | NAVIGATION_WITH_KEYS: false 12 | }; -------------------------------------------------------------------------------- /docs/html/_static/down-pressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erdewit/distex/0d86d00f46c8cf2328c7674751a035408d46e865/docs/html/_static/down-pressed.png -------------------------------------------------------------------------------- /docs/html/_static/down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erdewit/distex/0d86d00f46c8cf2328c7674751a035408d46e865/docs/html/_static/down.png -------------------------------------------------------------------------------- /docs/html/_static/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erdewit/distex/0d86d00f46c8cf2328c7674751a035408d46e865/docs/html/_static/file.png -------------------------------------------------------------------------------- /docs/html/_static/fonts/Lato/lato-bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erdewit/distex/0d86d00f46c8cf2328c7674751a035408d46e865/docs/html/_static/fonts/Lato/lato-bold.eot -------------------------------------------------------------------------------- /docs/html/_static/fonts/Lato/lato-bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erdewit/distex/0d86d00f46c8cf2328c7674751a035408d46e865/docs/html/_static/fonts/Lato/lato-bold.ttf -------------------------------------------------------------------------------- /docs/html/_static/fonts/Lato/lato-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erdewit/distex/0d86d00f46c8cf2328c7674751a035408d46e865/docs/html/_static/fonts/Lato/lato-bold.woff -------------------------------------------------------------------------------- /docs/html/_static/fonts/Lato/lato-bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erdewit/distex/0d86d00f46c8cf2328c7674751a035408d46e865/docs/html/_static/fonts/Lato/lato-bold.woff2 -------------------------------------------------------------------------------- /docs/html/_static/fonts/Lato/lato-bolditalic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erdewit/distex/0d86d00f46c8cf2328c7674751a035408d46e865/docs/html/_static/fonts/Lato/lato-bolditalic.eot -------------------------------------------------------------------------------- /docs/html/_static/fonts/Lato/lato-bolditalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erdewit/distex/0d86d00f46c8cf2328c7674751a035408d46e865/docs/html/_static/fonts/Lato/lato-bolditalic.ttf -------------------------------------------------------------------------------- /docs/html/_static/fonts/Lato/lato-bolditalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erdewit/distex/0d86d00f46c8cf2328c7674751a035408d46e865/docs/html/_static/fonts/Lato/lato-bolditalic.woff -------------------------------------------------------------------------------- /docs/html/_static/fonts/Lato/lato-bolditalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erdewit/distex/0d86d00f46c8cf2328c7674751a035408d46e865/docs/html/_static/fonts/Lato/lato-bolditalic.woff2 -------------------------------------------------------------------------------- /docs/html/_static/fonts/Lato/lato-italic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erdewit/distex/0d86d00f46c8cf2328c7674751a035408d46e865/docs/html/_static/fonts/Lato/lato-italic.eot -------------------------------------------------------------------------------- /docs/html/_static/fonts/Lato/lato-italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erdewit/distex/0d86d00f46c8cf2328c7674751a035408d46e865/docs/html/_static/fonts/Lato/lato-italic.ttf -------------------------------------------------------------------------------- /docs/html/_static/fonts/Lato/lato-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erdewit/distex/0d86d00f46c8cf2328c7674751a035408d46e865/docs/html/_static/fonts/Lato/lato-italic.woff -------------------------------------------------------------------------------- /docs/html/_static/fonts/Lato/lato-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erdewit/distex/0d86d00f46c8cf2328c7674751a035408d46e865/docs/html/_static/fonts/Lato/lato-italic.woff2 -------------------------------------------------------------------------------- /docs/html/_static/fonts/Lato/lato-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erdewit/distex/0d86d00f46c8cf2328c7674751a035408d46e865/docs/html/_static/fonts/Lato/lato-regular.eot -------------------------------------------------------------------------------- /docs/html/_static/fonts/Lato/lato-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erdewit/distex/0d86d00f46c8cf2328c7674751a035408d46e865/docs/html/_static/fonts/Lato/lato-regular.ttf -------------------------------------------------------------------------------- /docs/html/_static/fonts/Lato/lato-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erdewit/distex/0d86d00f46c8cf2328c7674751a035408d46e865/docs/html/_static/fonts/Lato/lato-regular.woff -------------------------------------------------------------------------------- /docs/html/_static/fonts/Lato/lato-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erdewit/distex/0d86d00f46c8cf2328c7674751a035408d46e865/docs/html/_static/fonts/Lato/lato-regular.woff2 -------------------------------------------------------------------------------- /docs/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erdewit/distex/0d86d00f46c8cf2328c7674751a035408d46e865/docs/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot -------------------------------------------------------------------------------- /docs/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erdewit/distex/0d86d00f46c8cf2328c7674751a035408d46e865/docs/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf -------------------------------------------------------------------------------- /docs/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erdewit/distex/0d86d00f46c8cf2328c7674751a035408d46e865/docs/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff -------------------------------------------------------------------------------- /docs/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erdewit/distex/0d86d00f46c8cf2328c7674751a035408d46e865/docs/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 -------------------------------------------------------------------------------- /docs/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erdewit/distex/0d86d00f46c8cf2328c7674751a035408d46e865/docs/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot -------------------------------------------------------------------------------- /docs/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erdewit/distex/0d86d00f46c8cf2328c7674751a035408d46e865/docs/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf -------------------------------------------------------------------------------- /docs/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erdewit/distex/0d86d00f46c8cf2328c7674751a035408d46e865/docs/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff -------------------------------------------------------------------------------- /docs/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erdewit/distex/0d86d00f46c8cf2328c7674751a035408d46e865/docs/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 -------------------------------------------------------------------------------- /docs/html/_static/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erdewit/distex/0d86d00f46c8cf2328c7674751a035408d46e865/docs/html/_static/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /docs/html/_static/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erdewit/distex/0d86d00f46c8cf2328c7674751a035408d46e865/docs/html/_static/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /docs/html/_static/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erdewit/distex/0d86d00f46c8cf2328c7674751a035408d46e865/docs/html/_static/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /docs/html/_static/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erdewit/distex/0d86d00f46c8cf2328c7674751a035408d46e865/docs/html/_static/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /docs/html/_static/js/modernizr.min.js: -------------------------------------------------------------------------------- 1 | /* Modernizr 2.6.2 (Custom Build) | MIT & BSD 2 | * Build: http://modernizr.com/download/#-fontface-backgroundsize-borderimage-borderradius-boxshadow-flexbox-hsla-multiplebgs-opacity-rgba-textshadow-cssanimations-csscolumns-generatedcontent-cssgradients-cssreflections-csstransforms-csstransforms3d-csstransitions-applicationcache-canvas-canvastext-draganddrop-hashchange-history-audio-video-indexeddb-input-inputtypes-localstorage-postmessage-sessionstorage-websockets-websqldatabase-webworkers-geolocation-inlinesvg-smil-svg-svgclippaths-touch-webgl-shiv-mq-cssclasses-addtest-prefixed-teststyles-testprop-testallprops-hasevent-prefixes-domprefixes-load 3 | */ 4 | ;window.Modernizr=function(a,b,c){function D(a){j.cssText=a}function E(a,b){return D(n.join(a+";")+(b||""))}function F(a,b){return typeof a===b}function G(a,b){return!!~(""+a).indexOf(b)}function H(a,b){for(var d in a){var e=a[d];if(!G(e,"-")&&j[e]!==c)return b=="pfx"?e:!0}return!1}function I(a,b,d){for(var e in a){var f=b[a[e]];if(f!==c)return d===!1?a[e]:F(f,"function")?f.bind(d||b):f}return!1}function J(a,b,c){var d=a.charAt(0).toUpperCase()+a.slice(1),e=(a+" "+p.join(d+" ")+d).split(" ");return F(b,"string")||F(b,"undefined")?H(e,b):(e=(a+" "+q.join(d+" ")+d).split(" "),I(e,b,c))}function K(){e.input=function(c){for(var d=0,e=c.length;d',a,""].join(""),l.id=h,(m?l:n).innerHTML+=f,n.appendChild(l),m||(n.style.background="",n.style.overflow="hidden",k=g.style.overflow,g.style.overflow="hidden",g.appendChild(n)),i=c(l,a),m?l.parentNode.removeChild(l):(n.parentNode.removeChild(n),g.style.overflow=k),!!i},z=function(b){var c=a.matchMedia||a.msMatchMedia;if(c)return c(b).matches;var d;return y("@media "+b+" { #"+h+" { position: absolute; } }",function(b){d=(a.getComputedStyle?getComputedStyle(b,null):b.currentStyle)["position"]=="absolute"}),d},A=function(){function d(d,e){e=e||b.createElement(a[d]||"div"),d="on"+d;var f=d in e;return f||(e.setAttribute||(e=b.createElement("div")),e.setAttribute&&e.removeAttribute&&(e.setAttribute(d,""),f=F(e[d],"function"),F(e[d],"undefined")||(e[d]=c),e.removeAttribute(d))),e=null,f}var a={select:"input",change:"input",submit:"form",reset:"form",error:"img",load:"img",abort:"img"};return d}(),B={}.hasOwnProperty,C;!F(B,"undefined")&&!F(B.call,"undefined")?C=function(a,b){return B.call(a,b)}:C=function(a,b){return b in a&&F(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=w.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(w.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(w.call(arguments)))};return e}),s.flexbox=function(){return J("flexWrap")},s.canvas=function(){var a=b.createElement("canvas");return!!a.getContext&&!!a.getContext("2d")},s.canvastext=function(){return!!e.canvas&&!!F(b.createElement("canvas").getContext("2d").fillText,"function")},s.webgl=function(){return!!a.WebGLRenderingContext},s.touch=function(){var c;return"ontouchstart"in a||a.DocumentTouch&&b instanceof DocumentTouch?c=!0:y(["@media (",n.join("touch-enabled),("),h,")","{#modernizr{top:9px;position:absolute}}"].join(""),function(a){c=a.offsetTop===9}),c},s.geolocation=function(){return"geolocation"in navigator},s.postmessage=function(){return!!a.postMessage},s.websqldatabase=function(){return!!a.openDatabase},s.indexedDB=function(){return!!J("indexedDB",a)},s.hashchange=function(){return A("hashchange",a)&&(b.documentMode===c||b.documentMode>7)},s.history=function(){return!!a.history&&!!history.pushState},s.draganddrop=function(){var a=b.createElement("div");return"draggable"in a||"ondragstart"in a&&"ondrop"in a},s.websockets=function(){return"WebSocket"in a||"MozWebSocket"in a},s.rgba=function(){return D("background-color:rgba(150,255,150,.5)"),G(j.backgroundColor,"rgba")},s.hsla=function(){return D("background-color:hsla(120,40%,100%,.5)"),G(j.backgroundColor,"rgba")||G(j.backgroundColor,"hsla")},s.multiplebgs=function(){return D("background:url(https://),url(https://),red url(https://)"),/(url\s*\(.*?){3}/.test(j.background)},s.backgroundsize=function(){return J("backgroundSize")},s.borderimage=function(){return J("borderImage")},s.borderradius=function(){return J("borderRadius")},s.boxshadow=function(){return J("boxShadow")},s.textshadow=function(){return b.createElement("div").style.textShadow===""},s.opacity=function(){return E("opacity:.55"),/^0.55$/.test(j.opacity)},s.cssanimations=function(){return J("animationName")},s.csscolumns=function(){return J("columnCount")},s.cssgradients=function(){var a="background-image:",b="gradient(linear,left top,right bottom,from(#9f9),to(white));",c="linear-gradient(left top,#9f9, white);";return D((a+"-webkit- ".split(" ").join(b+a)+n.join(c+a)).slice(0,-a.length)),G(j.backgroundImage,"gradient")},s.cssreflections=function(){return J("boxReflect")},s.csstransforms=function(){return!!J("transform")},s.csstransforms3d=function(){var a=!!J("perspective");return a&&"webkitPerspective"in g.style&&y("@media (transform-3d),(-webkit-transform-3d){#modernizr{left:9px;position:absolute;height:3px;}}",function(b,c){a=b.offsetLeft===9&&b.offsetHeight===3}),a},s.csstransitions=function(){return J("transition")},s.fontface=function(){var a;return y('@font-face {font-family:"font";src:url("https://")}',function(c,d){var e=b.getElementById("smodernizr"),f=e.sheet||e.styleSheet,g=f?f.cssRules&&f.cssRules[0]?f.cssRules[0].cssText:f.cssText||"":"";a=/src/i.test(g)&&g.indexOf(d.split(" ")[0])===0}),a},s.generatedcontent=function(){var a;return y(["#",h,"{font:0/0 a}#",h,':after{content:"',l,'";visibility:hidden;font:3px/1 a}'].join(""),function(b){a=b.offsetHeight>=3}),a},s.video=function(){var a=b.createElement("video"),c=!1;try{if(c=!!a.canPlayType)c=new Boolean(c),c.ogg=a.canPlayType('video/ogg; codecs="theora"').replace(/^no$/,""),c.h264=a.canPlayType('video/mp4; codecs="avc1.42E01E"').replace(/^no$/,""),c.webm=a.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/^no$/,"")}catch(d){}return c},s.audio=function(){var a=b.createElement("audio"),c=!1;try{if(c=!!a.canPlayType)c=new Boolean(c),c.ogg=a.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,""),c.mp3=a.canPlayType("audio/mpeg;").replace(/^no$/,""),c.wav=a.canPlayType('audio/wav; codecs="1"').replace(/^no$/,""),c.m4a=(a.canPlayType("audio/x-m4a;")||a.canPlayType("audio/aac;")).replace(/^no$/,"")}catch(d){}return c},s.localstorage=function(){try{return localStorage.setItem(h,h),localStorage.removeItem(h),!0}catch(a){return!1}},s.sessionstorage=function(){try{return sessionStorage.setItem(h,h),sessionStorage.removeItem(h),!0}catch(a){return!1}},s.webworkers=function(){return!!a.Worker},s.applicationcache=function(){return!!a.applicationCache},s.svg=function(){return!!b.createElementNS&&!!b.createElementNS(r.svg,"svg").createSVGRect},s.inlinesvg=function(){var a=b.createElement("div");return a.innerHTML="",(a.firstChild&&a.firstChild.namespaceURI)==r.svg},s.smil=function(){return!!b.createElementNS&&/SVGAnimate/.test(m.call(b.createElementNS(r.svg,"animate")))},s.svgclippaths=function(){return!!b.createElementNS&&/SVGClipPath/.test(m.call(b.createElementNS(r.svg,"clipPath")))};for(var L in s)C(s,L)&&(x=L.toLowerCase(),e[x]=s[L](),v.push((e[x]?"":"no-")+x));return e.input||K(),e.addTest=function(a,b){if(typeof a=="object")for(var d in a)C(a,d)&&e.addTest(d,a[d]);else{a=a.toLowerCase();if(e[a]!==c)return e;b=typeof b=="function"?b():b,typeof f!="undefined"&&f&&(g.className+=" "+(b?"":"no-")+a),e[a]=b}return e},D(""),i=k=null,function(a,b){function k(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function l(){var a=r.elements;return typeof a=="string"?a.split(" "):a}function m(a){var b=i[a[g]];return b||(b={},h++,a[g]=h,i[h]=b),b}function n(a,c,f){c||(c=b);if(j)return c.createElement(a);f||(f=m(c));var g;return f.cache[a]?g=f.cache[a].cloneNode():e.test(a)?g=(f.cache[a]=f.createElem(a)).cloneNode():g=f.createElem(a),g.canHaveChildren&&!d.test(a)?f.frag.appendChild(g):g}function o(a,c){a||(a=b);if(j)return a.createDocumentFragment();c=c||m(a);var d=c.frag.cloneNode(),e=0,f=l(),g=f.length;for(;e",f="hidden"in a,j=a.childNodes.length==1||function(){b.createElement("a");var a=b.createDocumentFragment();return typeof a.cloneNode=="undefined"||typeof a.createDocumentFragment=="undefined"||typeof a.createElement=="undefined"}()}catch(c){f=!0,j=!0}})();var r={elements:c.elements||"abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video",shivCSS:c.shivCSS!==!1,supportsUnknownElements:j,shivMethods:c.shivMethods!==!1,type:"default",shivDocument:q,createElement:n,createDocumentFragment:o};a.html5=r,q(b)}(this,b),e._version=d,e._prefixes=n,e._domPrefixes=q,e._cssomPrefixes=p,e.mq=z,e.hasEvent=A,e.testProp=function(a){return H([a])},e.testAllProps=J,e.testStyles=y,e.prefixed=function(a,b,c){return b?J(a,b,c):J(a,"pfx")},g.className=g.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(f?" js "+v.join(" "):""),e}(this,this.document),function(a,b,c){function d(a){return"[object Function]"==o.call(a)}function e(a){return"string"==typeof a}function f(){}function g(a){return!a||"loaded"==a||"complete"==a||"uninitialized"==a}function h(){var a=p.shift();q=1,a?a.t?m(function(){("c"==a.t?B.injectCss:B.injectJs)(a.s,0,a.a,a.x,a.e,1)},0):(a(),h()):q=0}function i(a,c,d,e,f,i,j){function k(b){if(!o&&g(l.readyState)&&(u.r=o=1,!q&&h(),l.onload=l.onreadystatechange=null,b)){"img"!=a&&m(function(){t.removeChild(l)},50);for(var d in y[c])y[c].hasOwnProperty(d)&&y[c][d].onload()}}var j=j||B.errorTimeout,l=b.createElement(a),o=0,r=0,u={t:d,s:c,e:f,a:i,x:j};1===y[c]&&(r=1,y[c]=[]),"object"==a?l.data=c:(l.src=c,l.type=a),l.width=l.height="0",l.onerror=l.onload=l.onreadystatechange=function(){k.call(this,r)},p.splice(e,0,u),"img"!=a&&(r||2===y[c]?(t.insertBefore(l,s?null:n),m(k,j)):y[c].push(l))}function j(a,b,c,d,f){return q=0,b=b||"j",e(a)?i("c"==b?v:u,a,b,this.i++,c,d,f):(p.splice(this.i++,0,a),1==p.length&&h()),this}function k(){var a=B;return a.loader={load:j,i:0},a}var l=b.documentElement,m=a.setTimeout,n=b.getElementsByTagName("script")[0],o={}.toString,p=[],q=0,r="MozAppearance"in l.style,s=r&&!!b.createRange().compareNode,t=s?l:n.parentNode,l=a.opera&&"[object Opera]"==o.call(a.opera),l=!!b.attachEvent&&!l,u=r?"object":l?"script":"img",v=l?"script":u,w=Array.isArray||function(a){return"[object Array]"==o.call(a)},x=[],y={},z={timeout:function(a,b){return b.length&&(a.timeout=b[0]),a}},A,B;B=function(a){function b(a){var a=a.split("!"),b=x.length,c=a.pop(),d=a.length,c={url:c,origUrl:c,prefixes:a},e,f,g;for(f=0;f"),n("table.docutils.footnote").wrap("
"),n("table.docutils.citation").wrap("
"),n(".wy-menu-vertical ul").not(".simple").siblings("a").each((function(){var t=n(this);expand=n(''),expand.on("click",(function(n){return e.toggleCurrent(t),n.stopPropagation(),!1})),t.prepend(expand)}))},reset:function(){var n=encodeURI(window.location.hash)||"#";try{var e=$(".wy-menu-vertical"),t=e.find('[href="'+n+'"]');if(0===t.length){var i=$('.document [id="'+n.substring(1)+'"]').closest("div.section");0===(t=e.find('[href="#'+i.attr("id")+'"]')).length&&(t=e.find('[href="#"]'))}t.length>0&&($(".wy-menu-vertical .current").removeClass("current"),t.addClass("current"),t.closest("li.toctree-l1").addClass("current"),t.closest("li.toctree-l1").parent().addClass("current"),t.closest("li.toctree-l1").addClass("current"),t.closest("li.toctree-l2").addClass("current"),t.closest("li.toctree-l3").addClass("current"),t.closest("li.toctree-l4").addClass("current"),t.closest("li.toctree-l5").addClass("current"),t[0].scrollIntoView())}catch(n){console.log("Error expanding nav for anchor",n)}},onScroll:function(){this.winScroll=!1;var n=this.win.scrollTop(),e=n+this.winHeight,t=this.navBar.scrollTop()+(n-this.winPosition);n<0||e>this.docHeight||(this.navBar.scrollTop(t),this.winPosition=n)},onResize:function(){this.winResize=!1,this.winHeight=this.win.height(),this.docHeight=$(document).height()},hashChange:function(){this.linkScroll=!0,this.win.one("hashchange",(function(){this.linkScroll=!1}))},toggleCurrent:function(n){var e=n.closest("li");e.siblings("li.current").removeClass("current"),e.siblings().find("li.current").removeClass("current"),e.find("> ul li.current").removeClass("current"),e.toggleClass("current")}},"undefined"!=typeof window&&(window.SphinxRtdTheme={Navigation:n.exports.ThemeNav,StickyNav:n.exports.ThemeNav}),function(){for(var n=0,e=["ms","moz","webkit","o"],t=0;t0 62 | var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 63 | var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 64 | var s_v = "^(" + C + ")?" + v; // vowel in stem 65 | 66 | this.stemWord = function (w) { 67 | var stem; 68 | var suffix; 69 | var firstch; 70 | var origword = w; 71 | 72 | if (w.length < 3) 73 | return w; 74 | 75 | var re; 76 | var re2; 77 | var re3; 78 | var re4; 79 | 80 | firstch = w.substr(0,1); 81 | if (firstch == "y") 82 | w = firstch.toUpperCase() + w.substr(1); 83 | 84 | // Step 1a 85 | re = /^(.+?)(ss|i)es$/; 86 | re2 = /^(.+?)([^s])s$/; 87 | 88 | if (re.test(w)) 89 | w = w.replace(re,"$1$2"); 90 | else if (re2.test(w)) 91 | w = w.replace(re2,"$1$2"); 92 | 93 | // Step 1b 94 | re = /^(.+?)eed$/; 95 | re2 = /^(.+?)(ed|ing)$/; 96 | if (re.test(w)) { 97 | var fp = re.exec(w); 98 | re = new RegExp(mgr0); 99 | if (re.test(fp[1])) { 100 | re = /.$/; 101 | w = w.replace(re,""); 102 | } 103 | } 104 | else if (re2.test(w)) { 105 | var fp = re2.exec(w); 106 | stem = fp[1]; 107 | re2 = new RegExp(s_v); 108 | if (re2.test(stem)) { 109 | w = stem; 110 | re2 = /(at|bl|iz)$/; 111 | re3 = new RegExp("([^aeiouylsz])\\1$"); 112 | re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); 113 | if (re2.test(w)) 114 | w = w + "e"; 115 | else if (re3.test(w)) { 116 | re = /.$/; 117 | w = w.replace(re,""); 118 | } 119 | else if (re4.test(w)) 120 | w = w + "e"; 121 | } 122 | } 123 | 124 | // Step 1c 125 | re = /^(.+?)y$/; 126 | if (re.test(w)) { 127 | var fp = re.exec(w); 128 | stem = fp[1]; 129 | re = new RegExp(s_v); 130 | if (re.test(stem)) 131 | w = stem + "i"; 132 | } 133 | 134 | // Step 2 135 | re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; 136 | if (re.test(w)) { 137 | var fp = re.exec(w); 138 | stem = fp[1]; 139 | suffix = fp[2]; 140 | re = new RegExp(mgr0); 141 | if (re.test(stem)) 142 | w = stem + step2list[suffix]; 143 | } 144 | 145 | // Step 3 146 | re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; 147 | if (re.test(w)) { 148 | var fp = re.exec(w); 149 | stem = fp[1]; 150 | suffix = fp[2]; 151 | re = new RegExp(mgr0); 152 | if (re.test(stem)) 153 | w = stem + step3list[suffix]; 154 | } 155 | 156 | // Step 4 157 | re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; 158 | re2 = /^(.+?)(s|t)(ion)$/; 159 | if (re.test(w)) { 160 | var fp = re.exec(w); 161 | stem = fp[1]; 162 | re = new RegExp(mgr1); 163 | if (re.test(stem)) 164 | w = stem; 165 | } 166 | else if (re2.test(w)) { 167 | var fp = re2.exec(w); 168 | stem = fp[1] + fp[2]; 169 | re2 = new RegExp(mgr1); 170 | if (re2.test(stem)) 171 | w = stem; 172 | } 173 | 174 | // Step 5 175 | re = /^(.+?)e$/; 176 | if (re.test(w)) { 177 | var fp = re.exec(w); 178 | stem = fp[1]; 179 | re = new RegExp(mgr1); 180 | re2 = new RegExp(meq1); 181 | re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); 182 | if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) 183 | w = stem; 184 | } 185 | re = /ll$/; 186 | re2 = new RegExp(mgr1); 187 | if (re.test(w) && re2.test(w)) { 188 | re = /.$/; 189 | w = w.replace(re,""); 190 | } 191 | 192 | // and turn initial Y back to y 193 | if (firstch == "y") 194 | w = firstch.toLowerCase() + w.substr(1); 195 | return w; 196 | } 197 | } 198 | 199 | 200 | 201 | 202 | 203 | var splitChars = (function() { 204 | var result = {}; 205 | var singles = [96, 180, 187, 191, 215, 247, 749, 885, 903, 907, 909, 930, 1014, 1648, 206 | 1748, 1809, 2416, 2473, 2481, 2526, 2601, 2609, 2612, 2615, 2653, 2702, 207 | 2706, 2729, 2737, 2740, 2857, 2865, 2868, 2910, 2928, 2948, 2961, 2971, 208 | 2973, 3085, 3089, 3113, 3124, 3213, 3217, 3241, 3252, 3295, 3341, 3345, 209 | 3369, 3506, 3516, 3633, 3715, 3721, 3736, 3744, 3748, 3750, 3756, 3761, 210 | 3781, 3912, 4239, 4347, 4681, 4695, 4697, 4745, 4785, 4799, 4801, 4823, 211 | 4881, 5760, 5901, 5997, 6313, 7405, 8024, 8026, 8028, 8030, 8117, 8125, 212 | 8133, 8181, 8468, 8485, 8487, 8489, 8494, 8527, 11311, 11359, 11687, 11695, 213 | 11703, 11711, 11719, 11727, 11735, 12448, 12539, 43010, 43014, 43019, 43587, 214 | 43696, 43713, 64286, 64297, 64311, 64317, 64319, 64322, 64325, 65141]; 215 | var i, j, start, end; 216 | for (i = 0; i < singles.length; i++) { 217 | result[singles[i]] = true; 218 | } 219 | var ranges = [[0, 47], [58, 64], [91, 94], [123, 169], [171, 177], [182, 184], [706, 709], 220 | [722, 735], [741, 747], [751, 879], [888, 889], [894, 901], [1154, 1161], 221 | [1318, 1328], [1367, 1368], [1370, 1376], [1416, 1487], [1515, 1519], [1523, 1568], 222 | [1611, 1631], [1642, 1645], [1750, 1764], [1767, 1773], [1789, 1790], [1792, 1807], 223 | [1840, 1868], [1958, 1968], [1970, 1983], [2027, 2035], [2038, 2041], [2043, 2047], 224 | [2070, 2073], [2075, 2083], [2085, 2087], [2089, 2307], [2362, 2364], [2366, 2383], 225 | [2385, 2391], [2402, 2405], [2419, 2424], [2432, 2436], [2445, 2446], [2449, 2450], 226 | [2483, 2485], [2490, 2492], [2494, 2509], [2511, 2523], [2530, 2533], [2546, 2547], 227 | [2554, 2564], [2571, 2574], [2577, 2578], [2618, 2648], [2655, 2661], [2672, 2673], 228 | [2677, 2692], [2746, 2748], [2750, 2767], [2769, 2783], [2786, 2789], [2800, 2820], 229 | [2829, 2830], [2833, 2834], [2874, 2876], [2878, 2907], [2914, 2917], [2930, 2946], 230 | [2955, 2957], [2966, 2968], [2976, 2978], [2981, 2983], [2987, 2989], [3002, 3023], 231 | [3025, 3045], [3059, 3076], [3130, 3132], [3134, 3159], [3162, 3167], [3170, 3173], 232 | [3184, 3191], [3199, 3204], [3258, 3260], [3262, 3293], [3298, 3301], [3312, 3332], 233 | [3386, 3388], [3390, 3423], [3426, 3429], [3446, 3449], [3456, 3460], [3479, 3481], 234 | [3518, 3519], [3527, 3584], [3636, 3647], [3655, 3663], [3674, 3712], [3717, 3718], 235 | [3723, 3724], [3726, 3731], [3752, 3753], [3764, 3772], [3774, 3775], [3783, 3791], 236 | [3802, 3803], [3806, 3839], [3841, 3871], [3892, 3903], [3949, 3975], [3980, 4095], 237 | [4139, 4158], [4170, 4175], [4182, 4185], [4190, 4192], [4194, 4196], [4199, 4205], 238 | [4209, 4212], [4226, 4237], [4250, 4255], [4294, 4303], [4349, 4351], [4686, 4687], 239 | [4702, 4703], [4750, 4751], [4790, 4791], [4806, 4807], [4886, 4887], [4955, 4968], 240 | [4989, 4991], [5008, 5023], [5109, 5120], [5741, 5742], [5787, 5791], [5867, 5869], 241 | [5873, 5887], [5906, 5919], [5938, 5951], [5970, 5983], [6001, 6015], [6068, 6102], 242 | [6104, 6107], [6109, 6111], [6122, 6127], [6138, 6159], [6170, 6175], [6264, 6271], 243 | [6315, 6319], [6390, 6399], [6429, 6469], [6510, 6511], [6517, 6527], [6572, 6592], 244 | [6600, 6607], [6619, 6655], [6679, 6687], [6741, 6783], [6794, 6799], [6810, 6822], 245 | [6824, 6916], [6964, 6980], [6988, 6991], [7002, 7042], [7073, 7085], [7098, 7167], 246 | [7204, 7231], [7242, 7244], [7294, 7400], [7410, 7423], [7616, 7679], [7958, 7959], 247 | [7966, 7967], [8006, 8007], [8014, 8015], [8062, 8063], [8127, 8129], [8141, 8143], 248 | [8148, 8149], [8156, 8159], [8173, 8177], [8189, 8303], [8306, 8307], [8314, 8318], 249 | [8330, 8335], [8341, 8449], [8451, 8454], [8456, 8457], [8470, 8472], [8478, 8483], 250 | [8506, 8507], [8512, 8516], [8522, 8525], [8586, 9311], [9372, 9449], [9472, 10101], 251 | [10132, 11263], [11493, 11498], [11503, 11516], [11518, 11519], [11558, 11567], 252 | [11622, 11630], [11632, 11647], [11671, 11679], [11743, 11822], [11824, 12292], 253 | [12296, 12320], [12330, 12336], [12342, 12343], [12349, 12352], [12439, 12444], 254 | [12544, 12548], [12590, 12592], [12687, 12689], [12694, 12703], [12728, 12783], 255 | [12800, 12831], [12842, 12880], [12896, 12927], [12938, 12976], [12992, 13311], 256 | [19894, 19967], [40908, 40959], [42125, 42191], [42238, 42239], [42509, 42511], 257 | [42540, 42559], [42592, 42593], [42607, 42622], [42648, 42655], [42736, 42774], 258 | [42784, 42785], [42889, 42890], [42893, 43002], [43043, 43055], [43062, 43071], 259 | [43124, 43137], [43188, 43215], [43226, 43249], [43256, 43258], [43260, 43263], 260 | [43302, 43311], [43335, 43359], [43389, 43395], [43443, 43470], [43482, 43519], 261 | [43561, 43583], [43596, 43599], [43610, 43615], [43639, 43641], [43643, 43647], 262 | [43698, 43700], [43703, 43704], [43710, 43711], [43715, 43738], [43742, 43967], 263 | [44003, 44015], [44026, 44031], [55204, 55215], [55239, 55242], [55292, 55295], 264 | [57344, 63743], [64046, 64047], [64110, 64111], [64218, 64255], [64263, 64274], 265 | [64280, 64284], [64434, 64466], [64830, 64847], [64912, 64913], [64968, 65007], 266 | [65020, 65135], [65277, 65295], [65306, 65312], [65339, 65344], [65371, 65381], 267 | [65471, 65473], [65480, 65481], [65488, 65489], [65496, 65497]]; 268 | for (i = 0; i < ranges.length; i++) { 269 | start = ranges[i][0]; 270 | end = ranges[i][1]; 271 | for (j = start; j <= end; j++) { 272 | result[j] = true; 273 | } 274 | } 275 | return result; 276 | })(); 277 | 278 | function splitQuery(query) { 279 | var result = []; 280 | var start = -1; 281 | for (var i = 0; i < query.length; i++) { 282 | if (splitChars[query.charCodeAt(i)]) { 283 | if (start !== -1) { 284 | result.push(query.slice(start, i)); 285 | start = -1; 286 | } 287 | } else if (start === -1) { 288 | start = i; 289 | } 290 | } 291 | if (start !== -1) { 292 | result.push(query.slice(start)); 293 | } 294 | return result; 295 | } 296 | 297 | 298 | -------------------------------------------------------------------------------- /docs/html/_static/minus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erdewit/distex/0d86d00f46c8cf2328c7674751a035408d46e865/docs/html/_static/minus.png -------------------------------------------------------------------------------- /docs/html/_static/plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erdewit/distex/0d86d00f46c8cf2328c7674751a035408d46e865/docs/html/_static/plus.png -------------------------------------------------------------------------------- /docs/html/_static/pygments.css: -------------------------------------------------------------------------------- 1 | .highlight .hll { background-color: #ffffcc } 2 | .highlight { background: #eeffcc; } 3 | .highlight .c { color: #408090; font-style: italic } /* Comment */ 4 | .highlight .err { border: 1px solid #FF0000 } /* Error */ 5 | .highlight .k { color: #007020; font-weight: bold } /* Keyword */ 6 | .highlight .o { color: #666666 } /* Operator */ 7 | .highlight .ch { color: #408090; font-style: italic } /* Comment.Hashbang */ 8 | .highlight .cm { color: #408090; font-style: italic } /* Comment.Multiline */ 9 | .highlight .cp { color: #007020 } /* Comment.Preproc */ 10 | .highlight .cpf { color: #408090; font-style: italic } /* Comment.PreprocFile */ 11 | .highlight .c1 { color: #408090; font-style: italic } /* Comment.Single */ 12 | .highlight .cs { color: #408090; background-color: #fff0f0 } /* Comment.Special */ 13 | .highlight .gd { color: #A00000 } /* Generic.Deleted */ 14 | .highlight .ge { font-style: italic } /* Generic.Emph */ 15 | .highlight .gr { color: #FF0000 } /* Generic.Error */ 16 | .highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 17 | .highlight .gi { color: #00A000 } /* Generic.Inserted */ 18 | .highlight .go { color: #333333 } /* Generic.Output */ 19 | .highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */ 20 | .highlight .gs { font-weight: bold } /* Generic.Strong */ 21 | .highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 22 | .highlight .gt { color: #0044DD } /* Generic.Traceback */ 23 | .highlight .kc { color: #007020; font-weight: bold } /* Keyword.Constant */ 24 | .highlight .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */ 25 | .highlight .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */ 26 | .highlight .kp { color: #007020 } /* Keyword.Pseudo */ 27 | .highlight .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */ 28 | .highlight .kt { color: #902000 } /* Keyword.Type */ 29 | .highlight .m { color: #208050 } /* Literal.Number */ 30 | .highlight .s { color: #4070a0 } /* Literal.String */ 31 | .highlight .na { color: #4070a0 } /* Name.Attribute */ 32 | .highlight .nb { color: #007020 } /* Name.Builtin */ 33 | .highlight .nc { color: #0e84b5; font-weight: bold } /* Name.Class */ 34 | .highlight .no { color: #60add5 } /* Name.Constant */ 35 | .highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */ 36 | .highlight .ni { color: #d55537; font-weight: bold } /* Name.Entity */ 37 | .highlight .ne { color: #007020 } /* Name.Exception */ 38 | .highlight .nf { color: #06287e } /* Name.Function */ 39 | .highlight .nl { color: #002070; font-weight: bold } /* Name.Label */ 40 | .highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */ 41 | .highlight .nt { color: #062873; font-weight: bold } /* Name.Tag */ 42 | .highlight .nv { color: #bb60d5 } /* Name.Variable */ 43 | .highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */ 44 | .highlight .w { color: #bbbbbb } /* Text.Whitespace */ 45 | .highlight .mb { color: #208050 } /* Literal.Number.Bin */ 46 | .highlight .mf { color: #208050 } /* Literal.Number.Float */ 47 | .highlight .mh { color: #208050 } /* Literal.Number.Hex */ 48 | .highlight .mi { color: #208050 } /* Literal.Number.Integer */ 49 | .highlight .mo { color: #208050 } /* Literal.Number.Oct */ 50 | .highlight .sa { color: #4070a0 } /* Literal.String.Affix */ 51 | .highlight .sb { color: #4070a0 } /* Literal.String.Backtick */ 52 | .highlight .sc { color: #4070a0 } /* Literal.String.Char */ 53 | .highlight .dl { color: #4070a0 } /* Literal.String.Delimiter */ 54 | .highlight .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */ 55 | .highlight .s2 { color: #4070a0 } /* Literal.String.Double */ 56 | .highlight .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */ 57 | .highlight .sh { color: #4070a0 } /* Literal.String.Heredoc */ 58 | .highlight .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */ 59 | .highlight .sx { color: #c65d09 } /* Literal.String.Other */ 60 | .highlight .sr { color: #235388 } /* Literal.String.Regex */ 61 | .highlight .s1 { color: #4070a0 } /* Literal.String.Single */ 62 | .highlight .ss { color: #517918 } /* Literal.String.Symbol */ 63 | .highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */ 64 | .highlight .fm { color: #06287e } /* Name.Function.Magic */ 65 | .highlight .vc { color: #bb60d5 } /* Name.Variable.Class */ 66 | .highlight .vg { color: #bb60d5 } /* Name.Variable.Global */ 67 | .highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */ 68 | .highlight .vm { color: #bb60d5 } /* Name.Variable.Magic */ 69 | .highlight .il { color: #208050 } /* Literal.Number.Integer.Long */ -------------------------------------------------------------------------------- /docs/html/_static/sidebar.js: -------------------------------------------------------------------------------- 1 | /* 2 | * sidebar.js 3 | * ~~~~~~~~~~ 4 | * 5 | * This script makes the Sphinx sidebar collapsible. 6 | * 7 | * .sphinxsidebar contains .sphinxsidebarwrapper. This script adds 8 | * in .sphixsidebar, after .sphinxsidebarwrapper, the #sidebarbutton 9 | * used to collapse and expand the sidebar. 10 | * 11 | * When the sidebar is collapsed the .sphinxsidebarwrapper is hidden 12 | * and the width of the sidebar and the margin-left of the document 13 | * are decreased. When the sidebar is expanded the opposite happens. 14 | * This script saves a per-browser/per-session cookie used to 15 | * remember the position of the sidebar among the pages. 16 | * Once the browser is closed the cookie is deleted and the position 17 | * reset to the default (expanded). 18 | * 19 | * :copyright: Copyright 2007-2017 by the Sphinx team, see AUTHORS. 20 | * :license: BSD, see LICENSE for details. 21 | * 22 | */ 23 | 24 | $(function() { 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | // global elements used by the functions. 34 | // the 'sidebarbutton' element is defined as global after its 35 | // creation, in the add_sidebar_button function 36 | var bodywrapper = $('.bodywrapper'); 37 | var sidebar = $('.sphinxsidebar'); 38 | var sidebarwrapper = $('.sphinxsidebarwrapper'); 39 | 40 | // for some reason, the document has no sidebar; do not run into errors 41 | if (!sidebar.length) return; 42 | 43 | // original margin-left of the bodywrapper and width of the sidebar 44 | // with the sidebar expanded 45 | var bw_margin_expanded = bodywrapper.css('margin-left'); 46 | var ssb_width_expanded = sidebar.width(); 47 | 48 | // margin-left of the bodywrapper and width of the sidebar 49 | // with the sidebar collapsed 50 | var bw_margin_collapsed = '.8em'; 51 | var ssb_width_collapsed = '.8em'; 52 | 53 | // colors used by the current theme 54 | var dark_color = $('.related').css('background-color'); 55 | var light_color = $('.document').css('background-color'); 56 | 57 | function sidebar_is_collapsed() { 58 | return sidebarwrapper.is(':not(:visible)'); 59 | } 60 | 61 | function toggle_sidebar() { 62 | if (sidebar_is_collapsed()) 63 | expand_sidebar(); 64 | else 65 | collapse_sidebar(); 66 | } 67 | 68 | function collapse_sidebar() { 69 | sidebarwrapper.hide(); 70 | sidebar.css('width', ssb_width_collapsed); 71 | bodywrapper.css('margin-left', bw_margin_collapsed); 72 | sidebarbutton.css({ 73 | 'margin-left': '0', 74 | 'height': bodywrapper.height() 75 | }); 76 | sidebarbutton.find('span').text('»'); 77 | sidebarbutton.attr('title', _('Expand sidebar')); 78 | document.cookie = 'sidebar=collapsed'; 79 | } 80 | 81 | function expand_sidebar() { 82 | bodywrapper.css('margin-left', bw_margin_expanded); 83 | sidebar.css('width', ssb_width_expanded); 84 | sidebarwrapper.show(); 85 | sidebarbutton.css({ 86 | 'margin-left': ssb_width_expanded-12, 87 | 'height': bodywrapper.height() 88 | }); 89 | sidebarbutton.find('span').text('«'); 90 | sidebarbutton.attr('title', _('Collapse sidebar')); 91 | document.cookie = 'sidebar=expanded'; 92 | } 93 | 94 | function add_sidebar_button() { 95 | sidebarwrapper.css({ 96 | 'float': 'left', 97 | 'margin-right': '0', 98 | 'width': ssb_width_expanded - 28 99 | }); 100 | // create the button 101 | sidebar.append( 102 | '
«
' 103 | ); 104 | var sidebarbutton = $('#sidebarbutton'); 105 | light_color = sidebarbutton.css('background-color'); 106 | // find the height of the viewport to center the '<<' in the page 107 | var viewport_height; 108 | if (window.innerHeight) 109 | viewport_height = window.innerHeight; 110 | else 111 | viewport_height = $(window).height(); 112 | sidebarbutton.find('span').css({ 113 | 'display': 'block', 114 | 'margin-top': (viewport_height - sidebar.position().top - 20) / 2 115 | }); 116 | 117 | sidebarbutton.click(toggle_sidebar); 118 | sidebarbutton.attr('title', _('Collapse sidebar')); 119 | sidebarbutton.css({ 120 | 'color': '#FFFFFF', 121 | 'border-left': '1px solid ' + dark_color, 122 | 'font-size': '1.2em', 123 | 'cursor': 'pointer', 124 | 'height': bodywrapper.height(), 125 | 'padding-top': '1px', 126 | 'margin-left': ssb_width_expanded - 12 127 | }); 128 | 129 | sidebarbutton.hover( 130 | function () { 131 | $(this).css('background-color', dark_color); 132 | }, 133 | function () { 134 | $(this).css('background-color', light_color); 135 | } 136 | ); 137 | } 138 | 139 | function set_position_from_cookie() { 140 | if (!document.cookie) 141 | return; 142 | var items = document.cookie.split(';'); 143 | for(var k=0; k2;a== 12 | null&&(a=[]);if(y&&a.reduce===y)return e&&(c=b.bind(c,e)),f?a.reduce(c,d):a.reduce(c);j(a,function(a,b,i){f?d=c.call(e,d,a,b,i):(d=a,f=true)});if(!f)throw new TypeError("Reduce of empty array with no initial value");return d};b.reduceRight=b.foldr=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(z&&a.reduceRight===z)return e&&(c=b.bind(c,e)),f?a.reduceRight(c,d):a.reduceRight(c);var g=b.toArray(a).reverse();e&&!f&&(c=b.bind(c,e));return f?b.reduce(g,c,d,e):b.reduce(g,c)};b.find=b.detect= 13 | function(a,c,b){var e;E(a,function(a,g,h){if(c.call(b,a,g,h))return e=a,true});return e};b.filter=b.select=function(a,c,b){var e=[];if(a==null)return e;if(A&&a.filter===A)return a.filter(c,b);j(a,function(a,g,h){c.call(b,a,g,h)&&(e[e.length]=a)});return e};b.reject=function(a,c,b){var e=[];if(a==null)return e;j(a,function(a,g,h){c.call(b,a,g,h)||(e[e.length]=a)});return e};b.every=b.all=function(a,c,b){var e=true;if(a==null)return e;if(B&&a.every===B)return a.every(c,b);j(a,function(a,g,h){if(!(e= 14 | e&&c.call(b,a,g,h)))return n});return e};var E=b.some=b.any=function(a,c,d){c||(c=b.identity);var e=false;if(a==null)return e;if(C&&a.some===C)return a.some(c,d);j(a,function(a,b,h){if(e||(e=c.call(d,a,b,h)))return n});return!!e};b.include=b.contains=function(a,c){var b=false;if(a==null)return b;return p&&a.indexOf===p?a.indexOf(c)!=-1:b=E(a,function(a){return a===c})};b.invoke=function(a,c){var d=i.call(arguments,2);return b.map(a,function(a){return(b.isFunction(c)?c||a:a[c]).apply(a,d)})};b.pluck= 15 | function(a,c){return b.map(a,function(a){return a[c]})};b.max=function(a,c,d){if(!c&&b.isArray(a))return Math.max.apply(Math,a);if(!c&&b.isEmpty(a))return-Infinity;var e={computed:-Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b>=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);if(!c&&b.isEmpty(a))return Infinity;var e={computed:Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;bd?1:0}),"value")};b.groupBy=function(a,c){var d={},e=b.isFunction(c)?c:function(a){return a[c]};j(a,function(a,b){var c=e(a,b);(d[c]||(d[c]=[])).push(a)});return d};b.sortedIndex=function(a, 17 | c,d){d||(d=b.identity);for(var e=0,f=a.length;e>1;d(a[g])=0})})};b.difference=function(a){var c=b.flatten(i.call(arguments,1));return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a=i.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e=0;d--)b=[a[d].apply(this,b)];return b[0]}}; 24 | b.after=function(a,b){return a<=0?b():function(){if(--a<1)return b.apply(this,arguments)}};b.keys=J||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var c=[],d;for(d in a)b.has(a,d)&&(c[c.length]=d);return c};b.values=function(a){return b.map(a,b.identity)};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&&c.push(d);return c.sort()};b.extend=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]=b[d]});return a};b.defaults=function(a){j(i.call(arguments, 25 | 1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return!b.isObject(a)?a:b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,b){return q(a,b,[])};b.isEmpty=function(a){if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(b.has(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=o||function(a){return l.call(a)=="[object Array]"};b.isObject=function(a){return a===Object(a)}; 26 | b.isArguments=function(a){return l.call(a)=="[object Arguments]"};if(!b.isArguments(arguments))b.isArguments=function(a){return!(!a||!b.has(a,"callee"))};b.isFunction=function(a){return l.call(a)=="[object Function]"};b.isString=function(a){return l.call(a)=="[object String]"};b.isNumber=function(a){return l.call(a)=="[object Number]"};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===true||a===false||l.call(a)=="[object Boolean]"};b.isDate=function(a){return l.call(a)=="[object Date]"}; 27 | b.isRegExp=function(a){return l.call(a)=="[object RegExp]"};b.isNull=function(a){return a===null};b.isUndefined=function(a){return a===void 0};b.has=function(a,b){return I.call(a,b)};b.noConflict=function(){r._=G;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=0;e/g,">").replace(/"/g,""").replace(/'/g,"'").replace(/\//g,"/")};b.mixin=function(a){j(b.functions(a), 28 | function(c){K(c,b[c]=a[c])})};var L=0;b.uniqueId=function(a){var b=L++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var t=/.^/,u=function(a){return a.replace(/\\\\/g,"\\").replace(/\\'/g,"'")};b.template=function(a,c){var d=b.templateSettings,d="var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push('"+a.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(d.escape||t,function(a,b){return"',_.escape("+ 29 | u(b)+"),'"}).replace(d.interpolate||t,function(a,b){return"',"+u(b)+",'"}).replace(d.evaluate||t,function(a,b){return"');"+u(b).replace(/[\r\n\t]/g," ")+";__p.push('"}).replace(/\r/g,"\\r").replace(/\n/g,"\\n").replace(/\t/g,"\\t")+"');}return __p.join('');",e=new Function("obj","_",d);return c?e(c,b):function(a){return e.call(this,a,b)}};b.chain=function(a){return b(a).chain()};var m=function(a){this._wrapped=a};b.prototype=m.prototype;var v=function(a,c){return c?b(a).chain():a},K=function(a,c){m.prototype[a]= 30 | function(){var a=i.call(arguments);H.call(a,this._wrapped);return v(c.apply(b,a),this._chain)}};b.mixin(b);j("pop,push,reverse,shift,sort,splice,unshift".split(","),function(a){var b=k[a];m.prototype[a]=function(){var d=this._wrapped;b.apply(d,arguments);var e=d.length;(a=="shift"||a=="splice")&&e===0&&delete d[0];return v(d,this._chain)}});j(["concat","join","slice"],function(a){var b=k[a];m.prototype[a]=function(){return v(b.apply(this._wrapped,arguments),this._chain)}});m.prototype.chain=function(){this._chain= 31 | true;return this};m.prototype.value=function(){return this._wrapped}}).call(this); 32 | -------------------------------------------------------------------------------- /docs/html/_static/up-pressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erdewit/distex/0d86d00f46c8cf2328c7674751a035408d46e865/docs/html/_static/up-pressed.png -------------------------------------------------------------------------------- /docs/html/_static/up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erdewit/distex/0d86d00f46c8cf2328c7674751a035408d46e865/docs/html/_static/up.png -------------------------------------------------------------------------------- /docs/html/genindex.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Index — distex 0.7.1 documentation 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 |
47 | 48 | 99 | 100 |
101 | 102 | 103 | 109 | 110 | 111 |
112 | 113 |
114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 |
132 | 133 |
    134 | 135 |
  • »
  • 136 | 137 |
  • Index
  • 138 | 139 | 140 |
  • 141 | 142 | 143 | 144 |
  • 145 | 146 |
147 | 148 | 149 |
150 |
151 |
152 |
153 | 154 | 155 |

Index

156 | 157 |
158 | P 159 | 160 |
161 |

P

162 | 163 | 167 | 171 |
172 | 173 | 174 | 175 |
176 | 177 |
178 |
179 | 180 | 181 |
182 | 183 |
184 |

185 | 186 | © Copyright 2020, Ewald de Wit 187 | 188 |

189 |
190 | 191 | 192 | 193 | Built with Sphinx using a 194 | 195 | theme 196 | 197 | provided by Read the Docs. 198 | 199 |
200 | 201 |
202 |
203 | 204 |
205 | 206 |
207 | 208 | 209 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | -------------------------------------------------------------------------------- /docs/html/objects.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erdewit/distex/0d86d00f46c8cf2328c7674751a035408d46e865/docs/html/objects.inv -------------------------------------------------------------------------------- /docs/html/py-modindex.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Python Module Index — distex 0.6.0 documentation 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 |
46 | 47 | 48 | 97 | 98 |
99 | 100 | 101 | 107 | 108 | 109 |
110 | 111 |
112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 |
130 | 131 |
    132 | 133 |
  • Docs »
  • 134 | 135 |
  • Python Module Index
  • 136 | 137 | 138 |
  • 139 | 140 |
  • 141 | 142 |
143 | 144 | 145 |
146 |
147 |
148 |
149 | 150 | 151 |

Python Module Index

152 | 153 |
154 | d 155 |
156 | 157 | 158 | 159 | 161 | 162 | 164 | 167 | 168 | 169 | 172 |
 
160 | d
165 | distex 166 |
    170 | distex.pool 171 |
173 | 174 | 175 |
176 | 177 |
178 |
179 | 180 | 181 |
182 | 183 |
184 |

185 | © Copyright 2019, Ewald de Wit 186 | 187 |

188 |
189 | Built with Sphinx using a theme provided by Read the Docs. 190 | 191 |
192 | 193 |
194 |
195 | 196 |
197 | 198 |
199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 222 | 223 | 224 | -------------------------------------------------------------------------------- /docs/html/search.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Search — distex 0.7.1 documentation 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 |
49 | 50 | 101 | 102 |
103 | 104 | 105 | 111 | 112 | 113 |
114 | 115 |
116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 |
134 | 135 |
    136 | 137 |
  • »
  • 138 | 139 |
  • Search
  • 140 | 141 | 142 |
  • 143 | 144 | 145 | 146 |
  • 147 | 148 |
149 | 150 | 151 |
152 |
153 |
154 |
155 | 156 | 163 | 164 | 165 |
166 | 167 |
168 | 169 |
170 | 171 |
172 |
173 | 174 | 175 |
176 | 177 |
178 |

179 | 180 | © Copyright 2020, Ewald de Wit 181 | 182 |

183 |
184 | 185 | 186 | 187 | Built with Sphinx using a 188 | 189 | theme 190 | 191 | provided by Read the Docs. 192 | 193 |
194 | 195 |
196 |
197 | 198 |
199 | 200 |
201 | 202 | 203 | 208 | 209 | 210 | 211 | 212 | 213 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | -------------------------------------------------------------------------------- /docs/html/searchindex.js: -------------------------------------------------------------------------------- 1 | Search.setIndex({docnames:["api","index"],envversion:{"sphinx.domains.c":2,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":3,"sphinx.domains.index":1,"sphinx.domains.javascript":2,"sphinx.domains.math":2,"sphinx.domains.python":2,"sphinx.domains.rst":2,"sphinx.domains.std":1,"sphinx.ext.viewcode":1,sphinx:56},filenames:["api.rst","index.rst"],objects:{"distex.pool":{Pool:[0,0,1,""]},"distex.poolmap":{PoolMap:[0,0,1,""]}},objnames:{"0":["py","class","Python class"]},objtypes:{"0":"py:class"},terms:{"000":1,"100":1,"1000":1,"1000000":1,"10022":0,"class":0,"default":0,"function":0,"import":1,"int":0,"return":[0,1],"true":0,For:1,The:[0,1],Then:1,There:1,With:0,address:0,ait:1,all:[0,1],also:1,ani:0,argument:0,around:1,async:1,asynchron:1,asyncio:[0,1],asyncssh:1,authent:1,author:1,avail:0,await:[0,1],back:1,block:1,bool:0,both:1,builtin:1,bz2:1,callabl:0,can:[0,1],chunk:0,chunksiz:0,client:1,cloudpickl:0,code:1,com:1,combin:[0,1],comment:1,commun:1,compat:1,complet:0,compress:1,concurr:[0,1],connect:1,consist:1,construct:1,copi:0,cpu:[0,1],creat:[0,1],data:[0,1],data_pickl:0,decrypt:1,def:1,depend:1,desir:0,dill:0,distex:1,distex_proc:[0,1],distribut:[0,1],doe:1,done:1,duti:1,each:1,easi:1,effici:[0,1],emit:0,encrypt:1,environ:0,event:[0,1],eventkit:[0,1],everi:1,ewald:1,execut:1,executor:0,fals:0,fashion:1,featur:1,first:[0,1],format:0,found:1,from:[0,1],full:1,func:0,func_pickl:0,futur:[0,1],get_event_loop:1,give:0,gmail:1,greatli:0,handl:1,has:0,have:[0,1],here:1,higher:1,host:[0,1],hostnam:0,imagin:1,implement:0,improv:0,increas:0,init:1,initarg:0,initi:[0,1],instal:0,instead:[0,1],interfac:0,involv:0,iter:1,its:1,jupyt:1,keep:1,kei:[0,1],keygen:0,larger:0,last:1,lazy_cr:0,len:1,list:0,listen:[0,1],local:[0,1],localhost:0,localport:0,loop:[0,1],looptyp:0,machin:1,main:1,make:1,manag:1,map:[0,1],map_async:1,maxi:[0,1],mean:1,mini:0,much:1,multipl:1,must:[0,1],necessari:0,network:0,nicer:1,non:0,none:0,num_work:0,number:0,offer:1,offload:1,one:0,onli:0,open:0,option:1,order:[0,1],other:1,outsid:1,over:1,own:1,packag:1,paramet:0,password:[0,1],path:[0,1],pend:0,pep3148:1,per:[0,1],pickl:0,pickletyp:0,pip3:1,pipe:1,place:0,plain:1,pool:1,poolmap:1,port:0,portnumb:0,possibl:0,preserv:0,print:1,proactor:0,process:[0,1],processor:1,processpool:1,processpoolexecutor:0,pyqt:0,python3:0,python:1,qsize:[0,1],quamash:0,queue:0,random:0,rang:1,readi:1,recommend:1,releas:0,remot:[0,1],result:0,revers:1,run:[0,1],run_on_all_async:1,run_until_complet:1,same:1,scale:1,script:[0,1],second:[0,1],secur:1,selector:0,sequenc:1,serial:0,server:[0,1],set:[0,1],share:1,shell:1,should:1,shutdown:1,side:1,sinc:[0,1],singl:1,size:0,sleep:1,small:[0,1],socket:1,solut:1,sourc:0,spawn:0,specif:0,specifi:0,ssh:[0,1],standard:0,start:[0,1],str:0,string:0,submit:0,support:1,sure:1,synchron:1,task:[0,1],tcp:0,test:[0,1],them:1,thi:[0,1],those:1,thread:1,throughput:0,time:1,timeout:0,timer:1,too:1,trust:0,tunnel:1,tupl:0,unbound:1,unix:[0,1],unpack:0,until:0,usag:1,use:[0,1],used:[0,1],user:1,usernam:0,uses:1,using:[0,1],util:1,uvloop:[0,1],version:1,wai:1,when:[0,1],window:0,wit:1,worker:[0,1],worker_loop:0,yield:1},titles:["Distex documentation","Introduction"],titleterms:{architectur:1,distex:0,document:[0,1],exampl:1,high:1,instal:1,introduct:1,level:1,pool:0,poolmap:0}}) -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. toctree:: 2 | :maxdepth: 3 3 | 4 | api 5 | 6 | 7 | .. include:: ../README.rst 8 | 9 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=python3 -msphinx 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | set SPHINXPROJ=distex 13 | 14 | if "%1" == "" goto help 15 | 16 | %SPHINXBUILD% >NUL 2>NUL 17 | if errorlevel 9009 ( 18 | echo. 19 | echo.The Sphinx module was not found. Make sure you have Sphinx installed, 20 | echo.then set the SPHINXBUILD environment variable to point to the full 21 | echo.path of the 'sphinx-build' executable. Alternatively you may add the 22 | echo.Sphinx directory to PATH. 23 | echo. 24 | echo.If you don't have Sphinx installed, grab it from 25 | echo.http://sphinx-doc.org/ 26 | exit /b 1 27 | ) 28 | 29 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 30 | goto end 31 | 32 | :help 33 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 34 | 35 | :end 36 | popd 37 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinxcontrib-napoleon 2 | sphinx-autodoc-typehints 3 | -------------------------------------------------------------------------------- /mypy.ini: -------------------------------------------------------------------------------- 1 | [mypy] 2 | ignore_missing_imports = True 3 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | dill 2 | cloudpickle 3 | eventkit 4 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | ignore = D100,D101,D102,D103,D105,D107,D200,D205,D400,D401,W503,I201 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import codecs 4 | from setuptools import setup 5 | 6 | if sys.version_info < (3, 6, 0): 7 | raise RuntimeError("Python 3.6 or higher required") 8 | 9 | here = os.path.abspath(os.path.dirname(__file__)) 10 | with codecs.open(os.path.join(here, 'README.rst'), encoding='utf-8') as f: 11 | long_description = f.read() 12 | 13 | __version__ = '' 14 | exec(open(os.path.join(here, 'distex', 'version.py')).read()) 15 | 16 | setup( 17 | name='distex', 18 | version=__version__, 19 | description='Async distributed process pool using asyncio', 20 | long_description=long_description, 21 | url='https://github.com/erdewit/distex', 22 | author='Ewald R. de Wit', 23 | author_email='ewald.de.wit@gmail.com', 24 | license='BSD', 25 | classifiers=[ 26 | 'Development Status :: 4 - Beta', 27 | 'Intended Audience :: Science/Research', 28 | 'Intended Audience :: Developers', 29 | 'License :: OSI Approved :: BSD License', 30 | 'Programming Language :: Python :: 3.6', 31 | 'Programming Language :: Python :: 3.7', 32 | 'Programming Language :: Python :: 3.8', 33 | 'Programming Language :: Python :: 3.9', 34 | 'Programming Language :: Python :: 3.10', 35 | 'Programming Language :: Python :: 3.11', 36 | 'Programming Language :: Python :: 3 :: Only', 37 | ], 38 | keywords=( 39 | 'python asyncio parallel distributed computing process ' 40 | 'pool task queue'), 41 | packages=['distex'], 42 | entry_points={ 43 | 'console_scripts': [ 44 | 'distex_proc=distex.processor:main' 45 | ] 46 | }, 47 | install_requires=['dill', 'cloudpickle', 'eventkit'], 48 | ) 49 | -------------------------------------------------------------------------------- /tests/bench.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import time 3 | import cProfile 4 | import pstats 5 | import asyncio 6 | import traceback 7 | 8 | # import uvloop 9 | # uvloop.install() 10 | 11 | from distex import Pool, util, PickleType 12 | 13 | sys.excepthook = traceback.print_exception 14 | loop = util.get_loop() 15 | asyncio.set_event_loop(loop) 16 | # util.logToConsole(logging.DEBUG) 17 | # loop.set_debug(True) 18 | 19 | loop = asyncio.get_event_loop_policy().get_event_loop() 20 | REPS = 100000 21 | 22 | 23 | def f(x): 24 | import math 25 | for _ in range(10000): 26 | x += math.sin(x) 27 | return x 28 | 29 | 30 | def g(x): 31 | return x + 2 32 | 33 | 34 | def main(): 35 | 36 | async def run(): 37 | result = await asyncio.gather( 38 | *[pool.run_async(g, i) for i in range(REPS)]) 39 | print(result) 40 | 41 | async def map_async(): 42 | async for result in pool.map_async( 43 | g, range(REPS), 44 | chunksize=1, timeout=None): 45 | pass 46 | print(result) 47 | # pool.shutdown() 48 | 49 | pool = Pool() 50 | if 1: 51 | loop.run_until_complete(map_async()) 52 | elif 1: 53 | loop.run_until_complete(run()) 54 | else: 55 | for r in pool.map(g, range(REPS)): 56 | pass 57 | print(r) 58 | # pool.shutdown() 59 | 60 | 61 | if 1: 62 | t0 = time.time() 63 | main() 64 | print(REPS / (time.time() - t0)) 65 | else: 66 | profPath = '.distex.prof' 67 | cProfile.run('main()', profPath) 68 | stats = pstats.Stats(profPath) 69 | stats.strip_dirs() 70 | stats.sort_stats('time') 71 | stats.print_stats() 72 | # profile.print_stats() 73 | -------------------------------------------------------------------------------- /tests/pool_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import itertools 3 | import asyncio 4 | import warnings 5 | 6 | from distex import Pool, util 7 | 8 | loop = util.get_loop() 9 | asyncio.set_event_loop(loop) 10 | # loop.set_debug(True) 11 | 12 | 13 | def exception_handler(loop, context): 14 | print('Exception:', context) 15 | 16 | 17 | loop.set_exception_handler(exception_handler) 18 | 19 | 20 | def f(x): 21 | return x * 2 22 | 23 | 24 | def g(x, y, z): 25 | return x * y - 4 * z 26 | 27 | 28 | def exc(x): 29 | raise RuntimeError(x) 30 | 31 | 32 | async def ait(it): 33 | for x in it: 34 | await asyncio.sleep(0.001) 35 | yield x 36 | 37 | 38 | class PoolTest(unittest.TestCase): 39 | 40 | @classmethod 41 | def setUpClass(cls): 42 | warnings.simplefilter("always") 43 | 44 | cls.pool = Pool(4, lazy_create=True) 45 | cls.reps = 100 46 | cls.x = [i for i in range(cls.reps)] 47 | cls.y = [i * i for i in range(cls.reps)] 48 | cls.z = [i * 2 for i in range(cls.reps)] 49 | cls.xyz = list(zip(cls.x, cls.y, cls.z)) 50 | 51 | @classmethod 52 | def tearDownCls(cls): 53 | cls.pool.shutdown() 54 | 55 | def assertNoWarnings(self, w): 56 | self.assertEqual( 57 | len(w), 0, msg='\n'.join(str(warning.message) for warning in w)) 58 | 59 | def test_run_coro(self): 60 | with warnings.catch_warnings(record=True) as w: 61 | 62 | async def add(a, b): 63 | import asyncio 64 | await asyncio.sleep(0.001) 65 | return a + b 66 | 67 | expected = 8 68 | actual = self.pool.run(add, 3, 5) 69 | self.assertEqual(actual, expected) 70 | 71 | self.assertNoWarnings(w) 72 | 73 | def test_run_async(self): 74 | 75 | async def coro(): 76 | tasks = [ 77 | self.pool.run_async( 78 | g, self.x[i], self.y[i], z=self.z[i]) 79 | for i in range(self.reps)] 80 | return await asyncio.gather(*tasks) 81 | 82 | with warnings.catch_warnings(record=True) as w: 83 | expected = list(map(g, self.x, self.y, self.z)) 84 | actual = loop.run_until_complete(coro()) 85 | self.assertEqual(actual, expected) 86 | 87 | self.assertNoWarnings(w) 88 | 89 | def test_run_async_with_exception(self): 90 | with warnings.catch_warnings(record=True) as w: 91 | with self.assertRaises(RuntimeError): 92 | loop.run_until_complete( 93 | self.pool.run_async(exc, 'Deliberatly thrown')) 94 | 95 | self.assertNoWarnings(w) 96 | 97 | def test_run_on_all_async(self): 98 | 99 | def getpid(): 100 | import os 101 | return os.getpid() 102 | 103 | with warnings.catch_warnings(record=True) as w: 104 | interference = self.pool.map( # noqa 105 | f, 2 * self.x[:self.pool.total_workers() + 1]) 106 | pids = loop.run_until_complete(self.pool.run_on_all_async(getpid)) 107 | self.assertEqual(self.pool.total_workers(), len(set(pids))) 108 | 109 | self.assertNoWarnings(w) 110 | 111 | def test_submit(self): 112 | with warnings.catch_warnings(record=True) as w: 113 | expected = g(10, 9, z=8) 114 | f = self.pool.submit(g, 10, 9, z=8) 115 | f2 = asyncio.wrap_future(f, loop=loop) 116 | actual = loop.run_until_complete(f2) 117 | self.assertEqual(actual, expected) 118 | 119 | self.assertNoWarnings(w) 120 | 121 | def test_submit_with_exception(self): 122 | with warnings.catch_warnings(record=True) as w: 123 | f = self.pool.submit(exc, 'Okay then') 124 | f2 = asyncio.wrap_future(f, loop=loop) 125 | with self.assertRaises(RuntimeError): 126 | loop.run_until_complete(f2) 127 | 128 | self.assertNoWarnings(w) 129 | 130 | def test_ordered_map_1_arg(self): 131 | with warnings.catch_warnings(record=True) as w: 132 | expected = list(map(f, self.x)) 133 | actual = list(self.pool.map(f, self.x)) 134 | self.assertSequenceEqual(actual, expected) 135 | 136 | self.assertNoWarnings(w) 137 | 138 | def test_ordered_map_3_arg(self): 139 | with warnings.catch_warnings(record=True) as w: 140 | expected = list(map(g, self.x, self.y, self.z)) 141 | actual = list(self.pool.map(g, self.x, self.y, self.z)) 142 | self.assertEqual(actual, expected) 143 | 144 | self.assertNoWarnings(w) 145 | 146 | def test_ordered_starmap(self): 147 | with warnings.catch_warnings(record=True) as w: 148 | expected = list(itertools.starmap(g, self.xyz)) 149 | actual = list(self.pool.map(g, self.xyz, star=True)) 150 | self.assertEqual(actual, expected) 151 | 152 | self.assertNoWarnings(w) 153 | 154 | def test_ordered_map_1_arg_chunked(self): 155 | with warnings.catch_warnings(record=True) as w: 156 | expected = list(map(f, self.x)) 157 | actual = list(self.pool.map(f, self.x, chunksize=7)) 158 | self.assertEqual(actual, expected) 159 | 160 | self.assertNoWarnings(w) 161 | 162 | def test_ordered_map_3_arg_chunked(self): 163 | with warnings.catch_warnings(record=True) as w: 164 | expected = list(map(g, self.x, self.y, self.z)) 165 | actual = list( 166 | self.pool.map(g, self.x, self.y, self.z, chunksize=10)) 167 | self.assertEqual(actual, expected) 168 | 169 | self.assertNoWarnings(w) 170 | 171 | def test_ordered_map_async_3_arg(self): 172 | 173 | async def coro(): 174 | return [v async for v in self.pool.map_async(g, 175 | self.x, self.y, self.z)] 176 | 177 | with warnings.catch_warnings(record=True) as w: 178 | expected = list(map(g, self.x, self.y, self.z)) 179 | actual = loop.run_until_complete(coro()) 180 | self.assertEqual(actual, expected) 181 | 182 | self.assertNoWarnings(w) 183 | 184 | def test_ordered_astarmap(self): 185 | 186 | async def coro(): 187 | return [v async for v in self.pool.map_async(g, 188 | self.xyz, star=True)] 189 | 190 | with warnings.catch_warnings(record=True) as w: 191 | expected = list(itertools.starmap(g, self.xyz)) 192 | actual = loop.run_until_complete(coro()) 193 | self.assertEqual(actual, expected) 194 | 195 | self.assertNoWarnings(w) 196 | 197 | def test_ordered_astarmap_async_iterator(self): 198 | 199 | async def coro(): 200 | return [v async for v in self.pool.map_async(g, 201 | ait(self.xyz), star=True)] 202 | 203 | with warnings.catch_warnings(record=True) as w: 204 | expected = list(itertools.starmap(g, self.xyz)) 205 | actual = loop.run_until_complete(coro()) 206 | self.assertEqual(actual, expected) 207 | 208 | self.assertNoWarnings(w) 209 | 210 | def test_ordered_map_sync_async_iterators(self): 211 | with warnings.catch_warnings(record=True) as w: 212 | expected = list(itertools.starmap(g, self.xyz)) 213 | actual = list( 214 | self.pool.map(g, ait(self.x), ait(self.y), self.z)) 215 | self.assertEqual(actual, expected) 216 | 217 | self.assertNoWarnings(w) 218 | 219 | def test_ordered_map_async__sync_async_iterators_chunked(self): 220 | 221 | async def coro(): 222 | return [v async for v in self.pool.map_async(g, 223 | ait(self.x), ait(self.y), self.z, chunksize=10)] 224 | 225 | with warnings.catch_warnings(record=True) as w: 226 | expected = list(itertools.starmap(g, self.xyz)) 227 | actual = loop.run_until_complete(coro()) 228 | self.assertEqual(actual, expected) 229 | 230 | self.assertNoWarnings(w) 231 | 232 | def test_executor(self): 233 | with warnings.catch_warnings(record=True) as w: 234 | expected = g(1, 2, 3) 235 | actual = loop.run_until_complete( 236 | loop.run_in_executor(self.pool, g, 1, 2, 3)) 237 | self.assertEqual(actual, expected) 238 | 239 | self.assertNoWarnings(w) 240 | 241 | 242 | if __name__ == '__main__': 243 | unittest.main() 244 | --------------------------------------------------------------------------------