<jupyter_start><jupyter_text>Basic<jupyter_code>@asyncio.coroutine
def quote_simple(url='http://localhost:5566/quote/uniform', slow=False):
    r = yield from aiohttp.request(
        'GET', url, params={'slow': True} if slow else {}
    )
    if r.status != 200:
        logger.error('Unsuccessful response [Status: %s (%d)]' 
                     % (r.reason, r.status))
        r.close(force=True)
        return None
    quote_json = yield from r.json()
    return quote_json['quote']
loop = asyncio.get_event_loop()<jupyter_output>[37m00:18:40.617 D[0m [36m[asyncio __init__][0m Using selector: EpollSelector[0m<jupyter_text>To run a simple asyncio corountine.<jupyter_code>coro = quote_simple()
quote = loop.run_until_complete(coro)
quote<jupyter_output><empty_output><jupyter_text>Internally asyncio wraps it with [`asyncio.Task`].So the following works equivalently.[`asyncio.Task`]:  https://docs.python.org/3.4/library/asyncio-task.htmlasyncio.Task<jupyter_code>task = asyncio.Task(quote_simple())
quote = loop.run_until_complete(task)
quote<jupyter_output><empty_output><jupyter_text>However, `coro` is `corountine`, and `task` is `Task` (subclass of [`Future`]).One can use `asyncio.ensure_future` to make sure having a Future obj returned.[`Future`]: https://docs.python.org/3.4/library/asyncio-task.htmlasyncio.Future<jupyter_code>type(coro), type(task)<jupyter_output><empty_output><jupyter_text>Passing wrong URL gives error<jupyter_code>quote = loop.run_until_complete(
    quote_simple(url='http://localhost:5566/quote/uniform?part=100')
)<jupyter_output>[01;31m00:18:40.649 E[0m [36m[bench_rest_api quote_simple][0m Unsuccessful response [Status: Bad Request (400)][0m<jupyter_text>Multiple Concurrent Requests<jupyter_code>@asyncio.coroutine
def quote_many_naive(num_quotes=1):
    coroutines = [
        quote_simple(slow=True) for i in range(num_quotes)
    ]
    quotes = yield from (asyncio.gather(*coroutines))
    return quotes
%%time
quotes = loop.run_until_complete(quote_many_naive(2000))<jupyter_output>CPU times: user 2.05 s, sys: 240 ms, total: 2.29 s
Wall time: 4.56 s<jupyter_text>This is not helping since we open 2000 connections at a time. It is slower than expected.  Limiting connection pool sizeRef on [official site](http://aiohttp.readthedocs.org/en/latest/client.htmllimiting-connection-pool-size).<jupyter_code>@asyncio.coroutine
def quote(conn, url='http://localhost:5566/quote/uniform', slow=False):
    r = yield from aiohttp.request(
        'GET', url, params={'slow': True} if slow else {},
        connector=conn
    )
    if r.status != 200:
        logger.error('Unsuccessful response [Status: %s (%d)]' 
                     % (r.reason, r.status))
        r.close(force=True)
        return None
    quote_json = yield from r.json()
    r.close(force=True)
    return quote_json['quote']

@asyncio.coroutine
def quote_many(num_quotes=1, conn_limit=20):
    conn = aiohttp.TCPConnector(keepalive_timeout=1, force_close=True, limit=conn_limit)
    coroutines = [
        quote(conn) for i in range(num_quotes)
    ]
    quotes = yield from (asyncio.gather(*coroutines))
    return quotes
%%time
quotes = loop.run_until_complete(quote_many(2000, conn_limit=100))<jupyter_output>CPU times: user 1.88 s, sys: 196 ms, total: 2.08 s
Wall time: 3.79 s<jupyter_text>I don't know why, but using its internal connection limit is slow. But we can implement one ourselves.  Custom connection limit using semaphoreUse [`asyncio.Semaphore`] acting as a lock.[`asyncio.Semaphore`]: https://docs.python.org/3.4/library/asyncio-sync.htmlasyncio.Semaphore<jupyter_code>def quote_with_lock(semaphore, url='http://localhost:5566/quote/uniform'):
    with (yield from semaphore):
        r = yield from aiohttp.request('GET', url)
        if r.status != 200:
            logger.error('Unsuccessful response [Status: %s (%d)]' 
                         % (r.reason, r.status))
            r.close(force=True)
            return None
        quote_json = yield from r.json()
    r.close(force=True)
    return quote_json['quote']

@asyncio.coroutine
def quote_many(num_quotes=1, conn_limit=20):
    semaphore = asyncio.Semaphore(conn_limit)
    coroutines = [
        quote_with_lock(semaphore) for i in range(num_quotes)
    ]
    quotes = yield from (asyncio.gather(*coroutines))
    return quotes
%%time
quotes = loop.run_until_complete(quote_many(2000, conn_limit=100))<jupyter_output>CPU times: user 1.96 s, sys: 212 ms, total: 2.17 s
Wall time: 1.97 s<jupyter_text>Add Progressbar If you don't care the original of coroutines<jupyter_code>@asyncio.coroutine
def quote_many(num_quotes=1, conn_limit=20, progress=None, step=10):
    if progress is None:
        progress = widgets.IntProgress()
        progress.max = num_quotes // step
        ipydisplay(progress)
    semaphore = asyncio.Semaphore(conn_limit)
    coroutines = [
        quote_with_lock(semaphore) for i in range(num_quotes)
    ]
    # quotes = yield from (asyncio.gather(*coroutines))
    quotes = []
    for ith, coro in enumerate(asyncio.as_completed(coroutines), 1):
        if ith % step == 0:
            progress.value += 1
        q = yield from coro
        quotes.append(q)
    return quotes
%%time
quotes = loop.run_until_complete(quote_many(2000, conn_limit=100, step=1))<jupyter_output>CPU times: user 2.61 s, sys: 324 ms, total: 2.93 s
Wall time: 2.67 s<jupyter_text>For fast response, progress bar introduces considerable latency. Try modify the step higher.<jupyter_code>%%time
quotes = loop.run_until_complete(quote_many(2000, conn_limit=100, step=20))<jupyter_output>CPU times: user 1.94 s, sys: 204 ms, total: 2.14 s
Wall time: 1.96 s<jupyter_text>Original order matters... go eat yourself.<jupyter_code>@asyncio.coroutine
def quote_many(num_quotes=1, conn_limit=20, progress=None, step=10):
    if progress is None:
        progress = widgets.IntProgress()
        progress.max = num_quotes // step
        ipydisplay(progress)

    # create the lock
    semaphore = asyncio.Semaphore(conn_limit)

    finished_task_count = 0
    def progress_adder(fut):
        nonlocal finished_task_count
        finished_task_count += 1
        if finished_task_count % step == 0:
            progress.value += 1
    
    # wrap coroutines as Tasks
    futures = []
    for i in range(num_quotes):
        task = asyncio.Task(quote_with_lock(semaphore))
        task.add_done_callback(progress_adder)
        futures.append(task)
    
    quotes = yield from (asyncio.gather(*futures))
    return quotes
%%time
quotes = loop.run_until_complete(quote_many(2000, conn_limit=100, step=1))
%%time
quotes = loop.run_until_complete(quote_many(2000, conn_limit=100, step=20))<jupyter_output>CPU times: user 1.88 s, sys: 264 ms, total: 2.15 s
Wall time: 1.98 s<jupyter_text>Alternative way<jupyter_code>@asyncio.coroutine
def quote_many(num_quotes=1, conn_limit=20, progress=None, step=10):
    if progress is None:
        progress = widgets.IntProgress()
        progress.max = num_quotes // step
        ipydisplay(progress)
    
    semaphore = asyncio.Semaphore(conn_limit)
    
    # wrap coroutines with future
    # For Python 3.4.4+, asyncio.ensure_future(...)
    # will wrap coro as Task and keep input the same 
    # if it is already Future.
    futures = [
        asyncio.ensure_future(quote_with_lock(semaphore))
        for i in range(num_quotes)
    ]

    for ith, coro in enumerate(asyncio.as_completed(futures), 1):
        if ith % step == 0:
            progress.value += 1
        yield from coro
        
    quotes = [fut.result() for fut in futures]
    return quotes
%%time
quotes = loop.run_until_complete(quote_many(2000, conn_limit=100, step=20))<jupyter_output>CPU times: user 1.91 s, sys: 276 ms, total: 2.19 s
Wall time: 2.02 s
{'fraction_non_alphanumeric': 0.11729903536977491, 'fraction_numerical': 0.03292604501607717, 'mean_word_length': 3.8630393996247654, 'pattern_counts': {'":': 0, '<': 40, '<?xml version=': 0, '>': 40, 'https://': 3, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '12', 'dir': 'jupyter-structured-clean-dedup', 'id': '514277', 'license': 'MIT', 'n_tokens_mistral': 2843, 'n_tokens_neox': 2677, 'n_words': 605, 'path': '1_demo.ipynb', 'repo_name': 'ccwang002/play_aiohttp', 'size': '7775'}