Skip to content

Commit

Permalink
❇️ Add AsyncResponse to properly handle streams with AsyncSession (#64)
Browse files Browse the repository at this point in the history
  • Loading branch information
Ousret authored Jan 11, 2024
1 parent f173bdc commit 26e8b12
Show file tree
Hide file tree
Showing 12 changed files with 829 additions and 23 deletions.
9 changes: 9 additions & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
Release History
===============

3.4.2 (2024-01-11)
------------------

**Fixed**
- Connection information kept targeting its original copy, thus always keeping the latest timings inside while expecting the historical ones.

**Added**
- `AsyncSession` now returns a `AsyncResponse` when `stream` is set to True in order to handle properly streams in an async context.

3.4.1 (2024-01-07)
------------------

Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,16 @@ True
>>> r.conn_info.established_latency
datetime.timedelta(microseconds=38)
```
or using async/await! <small>you'll need to enclose the code within proper async function, see the docs for more.</small>
```python
import niquests
>>> s = niquests.AsyncSession(resolver="doh+google://")
>>> r = await s.get('https://pie.dev/basic-auth/user/pass', auth=('user', 'pass'), stream=True)
>>> r
<Response HTTP/3 [200]>
>>> await r.json()
{'authenticated': True, ...}
```

Niquests allows you to send HTTP requests extremely easily. There’s no need to manually add query strings to your URLs, or to form-encode your `PUT` & `POST` data — but nowadays, just use the `json` method!

Expand Down
2 changes: 2 additions & 0 deletions docs/user/advanced.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1276,6 +1276,8 @@ over QUIC.

.. warning:: You cannot specify another hostname for security reasons.

.. note:: Using a custom DNS resolver can solve the problem as we can probe the HTTPS record for the given hostname and connect directly using HTTP/3 over QUIC.

Increase the default Alt-Svc cache size
---------------------------------------

Expand Down
85 changes: 85 additions & 0 deletions docs/user/quickstart.rst
Original file line number Diff line number Diff line change
Expand Up @@ -787,6 +787,91 @@ Look at this basic sample::
asyncio.run(main())


.. warning:: Combining AsyncSession with ``multiplexed=True`` and passing ``stream=True`` produces ``AsyncResponse``, make sure to call ``await session.gather()`` before trying to access directly the lazy instance of response.

AsyncResponse for streams
-------------------------

Delaying the content consumption in an async context can be easily achieved using::

import niquests
import asyncio

async def main() -> None:

async with niquests.AsyncSession() as s:
r = await s.get("https://pie.dev/get", stream=True)

async for chunk in await r.iter_content(16):
print(chunk)


if __name__ == "__main__":

asyncio.run(main())

Or simply by doing::

import niquests
import asyncio

async def main() -> None:

async with niquests.AsyncSession() as s:
r = await s.get("https://pie.dev/get", stream=True)
payload = await r.json()

if __name__ == "__main__":

asyncio.run(main())

When you specify ``stream=True`` within a ``AsyncSession``, the returned object will be of type ``AsyncResponse``.
So that the following methods and properties will be coroutines (aka. awaitable):

- iter_content(...)
- iter_lines(...)
- content
- json(...)
- text(...)

When enabling multiplexing while in an async context, you will have to issue a call to ``await s.gather()``
to avoid blocking your event loop.

Here is a basic example of how you would do it::

import niquests
import asyncio


async def main() -> None:

responses = []

async with niquests.AsyncSession(multiplexed=True) as s:
responses.append(
await s.get("https://pie.dev/get", stream=True)
)
responses.append(
await s.get("https://pie.dev/get", stream=True)
)

print(responses)

await s.gather()

print(responses)

for response in responses:
async for chunk in await response.iter_content(16):
print(chunk)


if __name__ == "__main__":

asyncio.run(main())

.. warning:: Accessing a lazy ``AsyncResponse`` without a call to ``s.gather()`` will raise a warning.

DNS Resolution
--------------

Expand Down
3 changes: 2 additions & 1 deletion src/niquests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@
__url__,
__version__,
)
from ._async import AsyncSession
from ._async import AsyncSession, AsyncResponse
from .api import delete, get, head, options, patch, post, put, request
from .exceptions import (
ConnectionError,
Expand Down Expand Up @@ -131,4 +131,5 @@
"Session",
"codes",
"AsyncSession",
"AsyncResponse",
)
4 changes: 2 additions & 2 deletions src/niquests/__version__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
__url__: str = "https://niquests.readthedocs.io"

__version__: str
__version__ = "3.4.1"
__version__ = "3.4.2"

__build__: int = 0x030401
__build__: int = 0x030402
__author__: str = "Kenneth Reitz"
__author_email__: str = "me@kennethreitz.org"
__license__: str = "Apache-2.0"
Expand Down
Loading

0 comments on commit 26e8b12

Please sign in to comment.