Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Requests in Asyncio - Keyword Arguments

I'm using asyncio with the requests module to make an asynchronous HTTP request.

I can make a GET request like this:

@asyncio.coroutine
def do_checks():
    loop = asyncio.get_event_loop()
    req = loop.run_in_executor(None, requests.get, 'https://api.github.com/user')
    resp = yield from req
    print(resp.status_code)
loop = asyncio.get_event_loop()
loop.run_until_complete(do_checks())

However, I need to do support Basic HTTP Auth (described here) in the request.

According to the documentation, url and auth are both named parameters for requests.get().

But, if I run this (note the addition of url='' and auth = ''):

@asyncio.coroutine
def do_checks():
    loop = asyncio.get_event_loop()
    req = loop.run_in_executor(None, requests.get, url='https://api.github.com/user', auth=HTTPBasicAuth('user', 'pass'))
    resp = yield from req
    print(resp.status_code)
loop = asyncio.get_event_loop()
loop.run_until_complete(do_checks())

I get this error:

TypeError: run_in_executor() got an unexpected keyword argument 'url'

In the prototype for asyncio.run_in_executor(), additional arguments are supported:

BaseEventLoop.run_in_executor(executor, callback, *args)

requests.get() clearly supports named parameters (get, auth, etc.). What's wrong?

like image 233
okoboko Avatar asked May 30 '14 04:05

okoboko


People also ask

What is asyncio in Python?

The essence of asyncio is that it allows the program to continue executing other instructions while waiting for specific processes to finish (e.g., a request to an API). In this tutorial, you will see how to use asyncio for accelerating a program that makes multiple requests to an API.

Can I use asyncio with requests?

@ospider You are right, here is the question: stackoverflow.com/questions/56523043/… Requests does not currently support asyncio and there are no plans to provide such support. It's likely that you could implement a custom "Transport Adapter" (as discussed here) that knows how to use asyncio.

Why do we need asynchronous HTTP requests?

Making a single asynchronous HTTP request is great because we can let the event loop work on other tasks instead of blocking the entire thread while waiting for a response. But this functionality truly shines when trying to make a larger number of requests.

What is an example of asynchronicity in Python?

HTTP requests are a classic example of something that is well-suited to asynchronicity because they involve waiting for a response from a server, during which time it would be convenient and efficient to have other code running. Make sure to have your Python environment setup before we get started.


2 Answers

This is actually a design decision in asyncio. From the docstring of asyncio/base_events.py:

"""Base implementation of event loop.

The event loop can be broken up into a multiplexer (the part
responsible for notifying us of IO events) and the event loop proper,
which wraps a multiplexer with functionality for scheduling callbacks,
immediately or at a given time in the future.

Whenever a public API takes a callback, subsequent positional
arguments will be passed to the callback if/when it is called.  This
avoids the proliferation of trivial lambdas implementing closures.
Keyword arguments for the callback are not supported; this is a
conscious design decision, leaving the door open for keyword arguments
to modify the meaning of the API call itself.
"""

Note the last sentence there.

The asyncio PEP notes this as well, and recommends a lambda to work around it:

This convention specifically does not support keyword arguments. Keyword arguments are used to pass optional extra information about the callback. This allows graceful evolution of the API without having to worry about whether a keyword might be significant to a callee somewhere. If you have a callback that must be called with a keyword argument, you can use a lambda. For example:

loop.call_soon(lambda: foo('abc', repeat=42))

like image 75
dano Avatar answered Oct 16 '22 08:10

dano


Two ways to do that. Create a wrapper function, or just use a session to provide the auth.

Using a session:

@asyncio.coroutine
def do_checks():
    loop = asyncio.get_event_loop()
    session = requests.Session()
    session.auth = HTTPBasicAuth('user', 'pass')
    req = loop.run_in_executor(None, session.get, 'https://api.github.com/user')
    resp = yield from req
    print(resp.status_code)

Writing a wrapper function (note that I'm using def for clarity here, but that a lambda would obviously work too):

@asyncio.coroutine
def do_checks():
    def do_req():
        return requests.get('https://api.github.com/user', auth=HTTPBasicAuth('user', 'pass'))
    loop = asyncio.get_event_loop()
    req = loop.run_in_executor(None, do_req)
    resp = yield from req
    print(resp.status_code)
like image 29
Thomas Orozco Avatar answered Oct 16 '22 07:10

Thomas Orozco