Metadata-Version: 2.4
Name: requests-futures
Version: 1.1.0
Summary: Asynchronous Python HTTP for Humans.
Author-email: Ross McFarland <rwmcfa1@gmail.com>
License-Expression: Apache-2.0
Project-URL: Homepage, https://github.com/ross/requests-futures
Project-URL: Source, https://github.com/ross/requests-futures
Project-URL: Issues, https://github.com/ross/requests-futures/issues
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: Natural Language :: English
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Requires-Python: >=3.10
Description-Content-Type: text/x-rst
License-File: LICENSE
Requires-Dist: requests>=1.2.0
Provides-Extra: test
Requires-Dist: pytest>=6.2.5; extra == "test"
Requires-Dist: pytest-cov>=3.0.0; extra == "test"
Requires-Dist: pytest-httpbin>=2.0.0; extra == "test"
Provides-Extra: dev
Requires-Dist: pytest>=6.2.5; extra == "dev"
Requires-Dist: pytest-cov>=3.0.0; extra == "dev"
Requires-Dist: pytest-httpbin>=2.0.0; extra == "dev"
Requires-Dist: black<27.0.0,>=26.0.0; extra == "dev"
Requires-Dist: build>=0.7.0; extra == "dev"
Requires-Dist: changelet; extra == "dev"
Requires-Dist: isort>=5.11.4; extra == "dev"
Requires-Dist: proviso; extra == "dev"
Requires-Dist: pyflakes>=2.2.0; extra == "dev"
Requires-Dist: readme_renderer[rst]>=26.0; extra == "dev"
Requires-Dist: twine>=3.4.2; extra == "dev"
Provides-Extra: docs
Requires-Dist: sphinx>=8.1.0; extra == "docs"
Requires-Dist: sphinx-copybutton>=0.5.2; extra == "docs"
Requires-Dist: sphinx_rtd_theme; extra == "docs"
Dynamic: license-file

Asynchronous Python HTTP Requests for Humans
============================================

.. image:: https://readthedocs.org/projects/requests-futures/badge/?version=latest
        :target: https://requests-futures.readthedocs.io/en/latest/?badge=latest
        :alt: Documentation Status

Small add-on for the python requests_ http library that makes use of the
standard library's `concurrent.futures`_. Requires Python 3.10 or newer.

The additional API and changes are minimal and strives to avoid surprises.

The following synchronous code:

.. code-block:: python

    from requests import Session

    session = Session()
    # first requests starts and blocks until finished
    response_one = session.get('http://httpbin.org/get')
    # second request starts once first is finished
    response_two = session.get('http://httpbin.org/get?foo=bar')
    # both requests are complete
    print('response one status: {0}'.format(response_one.status_code))
    print(response_one.content)
    print('response two status: {0}'.format(response_two.status_code))
    print(response_two.content)

Can be translated to make use of futures, and thus be asynchronous by creating
a FuturesSession and catching the returned Future in place of Response. The
Response can be retrieved by calling the result method on the Future:

.. code-block:: python

    from requests_futures.sessions import FuturesSession

    session = FuturesSession()
    # first request is started in background
    future_one = session.get('http://httpbin.org/get')
    # second requests is started immediately
    future_two = session.get('http://httpbin.org/get?foo=bar')
    # wait for the first request to complete, if it hasn't already
    response_one = future_one.result()
    print('response one status: {0}'.format(response_one.status_code))
    print(response_one.content)
    # wait for the second request to complete, if it hasn't already
    response_two = future_two.result()
    print('response two status: {0}'.format(response_two.status_code))
    print(response_two.content)

By default a ThreadPoolExecutor is created with 8 workers. That default
executor is a single ``requests.Session`` shared across all of its worker
threads, and ``Session`` is not thread-safe: concurrently mutating shared
state on it, such as the cookie jar or ``session.headers``, from multiple
in-flight requests is a real hazard. See the `full thread-safety
discussion`_ in the docs for what to do instead.

That's it. The api of requests.Session is preserved without any modifications
beyond returning a Future rather than Response. As with all futures exceptions
are shifted (thrown) to the future.result() call so try/except blocks should be
moved there.

Tying extra information to the request/response
===============================================

The most common piece of information needed is the URL of the request. This can
be accessed without any extra steps using the `request` property of the
response object.

.. code-block:: python

    from concurrent.futures import as_completed
    from pprint import pprint
    from requests_futures.sessions import FuturesSession

    session = FuturesSession()

    futures=[session.get(f'http://httpbin.org/get?{i}') for i in range(3)]

    for future in as_completed(futures):
        resp = future.result()
        pprint({
            'url': resp.request.url,
            'content': resp.json(),
        })

There are situations in which you may want to tie additional information to a
request/response. There are a number of ways to go about this, the simplest is
to attach additional information to the future object itself.

.. code-block:: python

    from concurrent.futures import as_completed
    from pprint import pprint
    from requests_futures.sessions import FuturesSession

    session = FuturesSession()

    futures=[]
    for i in range(3):
        future = session.get('http://httpbin.org/get')
        future.i = i
        futures.append(future)

    for future in as_completed(futures):
        resp = future.result()
        pprint({
            'i': future.i,
            'content': resp.json(),
        })

Canceling queued requests (a.k.a cleaning up after yourself)
============================================================

If you know that you won't be needing any additional responses from futures that
haven't yet resolved, it's a good idea to cancel those requests. You can do this
by using the session as a context manager:

.. code-block:: python

    from requests_futures.sessions import FuturesSession
    with FuturesSession(max_workers=1) as session:
        future = session.get('https://httpbin.org/get')
        future2 = session.get('https://httpbin.org/delay/10')
        future3 = session.get('https://httpbin.org/delay/10')
        response = future.result()

Exiting the ``with`` block calls ``close()``, which shuts down the session's
own executor with ``cancel_futures=True``: every queued request that hasn't
started running yet is cancelled immediately, not merely skipped one at a
time. Here, with ``max_workers=1``, ``future`` is already done by the time the
block exits, but neither ``future2`` nor ``future3`` has started, so both are
cancelled together, saving the time and resources their requests would
otherwise have used. A request that is already running when ``close()`` runs
is left to finish.

Using ProcessPoolExecutor
=========================

A ``ProcessPoolExecutor`` can be supplied in place of a ``ThreadPoolExecutor``
to run requests in separate processes instead of threads, which is useful
when per-request memory usage is high enough that cycling the interpreter is
needed to release it back to the OS.

Everything submitted to a process pool must be picklable, which means any
``FuturesSession`` subclass used this way must be importable at module scope
(not defined inline, e.g. in a function or ``__main__`` script body). If
something in the request isn't picklable, ``FuturesSession`` raises a
``RuntimeError`` pointing back to this documentation rather than letting the
pickling failure surface on its own.

.. code-block:: python

    from concurrent.futures import ProcessPoolExecutor
    from requests_futures.sessions import FuturesSession

    session = FuturesSession(executor=ProcessPoolExecutor(max_workers=10))
    # ... use as before

See the `full ProcessPoolExecutor guide`_ in the docs for a module-global
callback example and more on the pickling requirements.

Documentation
=============

Full documentation, including everything below, lives at
https://requests-futures.readthedocs.io/:

* ``as_completed`` with a timeout
* error handling across the future boundary (submit-time vs. ``result()``-time)
* retries via a mounted ``HTTPAdapter`` and urllib3's ``Retry``
* sizing ``max_workers`` against the connection pool, including with a
  supplied ``session=``
* streaming responses (``stream=True``) and why it interacts badly with
  background work
* sharing one executor across several sessions, and what ``close()`` does in
  that configuration
* ``hooks``, the recommended replacement for the deprecated
  ``background_callback``
* the full ``ProcessPoolExecutor`` guide
* the full thread-safety discussion

Installation
============

    pip install requests-futures

.. _`requests`: https://github.com/kennethreitz/requests
.. _`concurrent.futures`: http://docs.python.org/dev/library/concurrent.futures.html
.. _`full thread-safety discussion`: https://requests-futures.readthedocs.io/en/latest/usage.html
.. _`full ProcessPoolExecutor guide`: https://requests-futures.readthedocs.io/en/latest/usage.html
