Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pure ByteStream interface #133

Merged
merged 5 commits into from
Aug 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@ The `AsyncConnectionPool` class is a concrete implementation of `AsyncHTTPTransp
::: httpcore.AsyncConnectionPool
:docstring:


The `PlainByteStream` and `AsyncIteratorByteStream` classes are concrete implementations of `AsyncByteStream`.

::: httpcore.PlainByteStream
:docstring:

::: httpcore.AsyncIteratorByteStream
:docstring:

---

## Sync API Overview
Expand All @@ -37,3 +46,11 @@ The `SyncConnectionPool` class is a concrete implementation of `SyncHTTPTranspor

::: httpcore.SyncConnectionPool
:docstring:

The `PlainByteStream` and `IteratorByteStream` classes are concrete implementations of `SyncByteStream`.

::: httpcore.PlainByteStream
:docstring:

::: httpcore.IteratorByteStream
:docstring:
4 changes: 4 additions & 0 deletions httpcore/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from ._async.base import AsyncByteStream, AsyncHTTPTransport
from ._async.connection_pool import AsyncConnectionPool
from ._async.http_proxy import AsyncHTTPProxy
from ._bytestreams import PlainByteStream, AsyncIteratorByteStream, IteratorByteStream
from ._exceptions import (
CloseError,
ConnectError,
Expand Down Expand Up @@ -44,5 +45,8 @@
"LocalProtocolError",
"RemoteProtocolError",
"UnsupportedProtocol",
"AsyncIteratorByteStream",
"IteratorByteStream",
"PlainByteStream",
]
__version__ = "0.9.1"
24 changes: 4 additions & 20 deletions httpcore/_async/base.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import enum
from types import TracebackType
from typing import AsyncIterator, Callable, List, Tuple, Type
from typing import AsyncIterator, List, Tuple, Type

from .._types import URL, Headers, TimeoutDict

Expand Down Expand Up @@ -37,36 +37,20 @@ class AsyncByteStream:
The base interface for request and response bodies.

Concrete implementations should subclass this class, and implement
the `\\__aiter__` method, and optionally the `close` method.
the `\\__aiter__` method, and optionally the `aclose` method.
"""

def __init__(
self,
content: bytes = b"",
aiterator: AsyncIterator[bytes] = None,
aclose_func: Callable = None,
) -> None:
assert aiterator is None or not content
self.content = content
self.aiterator = aiterator
self.aclose_func = aclose_func

async def __aiter__(self) -> AsyncIterator[bytes]:
"""
Yield bytes representing the request or response body.
"""
if self.aiterator is None:
yield self.content
else:
async for chunk in self.aiterator:
yield chunk
yield b"" # pragma: nocover

async def aclose(self) -> None:
"""
Must be called by the client to indicate that the stream has been closed.
"""
if self.aclose_func is not None:
await self.aclose_func()
pass # pragma: nocover


class AsyncHTTPTransport:
Expand Down
7 changes: 4 additions & 3 deletions httpcore/_async/http11.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import h11

from .._backends.auto import AsyncSocketStream
from .._bytestreams import PlainByteStream, AsyncIteratorByteStream
from .._exceptions import RemoteProtocolError, LocalProtocolError, map_exceptions
from .._types import URL, Headers, TimeoutDict
from .._utils import get_logger
Expand Down Expand Up @@ -57,7 +58,7 @@ async def request(
timeout: TimeoutDict = None,
) -> Tuple[bytes, int, bytes, List[Tuple[bytes, bytes]], AsyncByteStream]:
headers = [] if headers is None else headers
stream = AsyncByteStream() if stream is None else stream
stream = PlainByteStream(b"") if stream is None else stream
timeout = {} if timeout is None else timeout

self.state = ConnectionState.ACTIVE
Expand All @@ -70,11 +71,11 @@ async def request(
reason_phrase,
headers,
) = await self._receive_response(timeout)
stream = AsyncByteStream(
response_stream = AsyncIteratorByteStream(
aiterator=self._receive_response_data(timeout),
aclose_func=self._response_closed,
)
return (http_version, status_code, reason_phrase, headers, stream)
return (http_version, status_code, reason_phrase, headers, response_stream)

async def start_tls(
self, hostname: bytes, timeout: TimeoutDict = None
Expand Down
7 changes: 4 additions & 3 deletions httpcore/_async/http2.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from h2.settings import SettingCodes, Settings

from .._backends.auto import AsyncLock, AsyncSemaphore, AsyncSocketStream, AutoBackend
from .._bytestreams import PlainByteStream, AsyncIteratorByteStream
from .._exceptions import PoolTimeout, RemoteProtocolError
from .._types import URL, Headers, TimeoutDict
from .._utils import get_logger
Expand Down Expand Up @@ -282,7 +283,7 @@ async def request(
timeout: TimeoutDict = None,
) -> Tuple[bytes, int, bytes, List[Tuple[bytes, bytes]], AsyncByteStream]:
headers = [] if headers is None else [(k.lower(), v) for (k, v) in headers]
stream = AsyncByteStream() if stream is None else stream
stream = PlainByteStream(b"") if stream is None else stream
timeout = {} if timeout is None else timeout

# Send the request.
Expand All @@ -298,11 +299,11 @@ async def request(
# Receive the response.
status_code, headers = await self.receive_response(timeout)
reason_phrase = get_reason_phrase(status_code)
stream = AsyncByteStream(
response_stream = AsyncIteratorByteStream(
aiterator=self.body_iter(timeout), aclose_func=self._response_closed
)

return (b"HTTP/2", status_code, reason_phrase, headers, stream)
return (b"HTTP/2", status_code, reason_phrase, headers, response_stream)

async def send_headers(
self,
Expand Down
77 changes: 77 additions & 0 deletions httpcore/_bytestreams.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
from typing import AsyncIterator, Iterator, Callable
from ._async.base import AsyncByteStream
from ._sync.base import SyncByteStream


class PlainByteStream(AsyncByteStream, SyncByteStream):
"""
A concrete implementation for either sync or async byte streams.
Just handles a plain byte string as the content of the stream.

```
stream = httpcore.PlainByteStream(b"123")
```
"""

def __init__(self, content: bytes) -> None:
self._content = content

def __iter__(self) -> Iterator[bytes]:
yield self._content

async def __aiter__(self) -> AsyncIterator[bytes]:
yield self._content


class IteratorByteStream(SyncByteStream):
"""
A concrete implementation for sync byte streams.
Handles a byte iterator as the content of the stream.

```
def generate_content():
...

stream = httpcore.IteratorByteStream(generate_content())
```
"""

def __init__(self, iterator: Iterator[bytes], close_func: Callable = None) -> None:
self._iterator = iterator
self._close_func = close_func

def __iter__(self) -> Iterator[bytes]:
for chunk in self._iterator:
yield chunk

def close(self) -> None:
if self._close_func is not None:
self._close_func()


class AsyncIteratorByteStream(AsyncByteStream):
"""
A concrete implementation for async byte streams.
Handles an async byte iterator as the content of the stream.

```
async def generate_content():
...

stream = httpcore.AsyncIteratorByteStream(generate_content())
```
"""

def __init__(
self, aiterator: AsyncIterator[bytes], aclose_func: Callable = None
) -> None:
self._aiterator = aiterator
self._aclose_func = aclose_func

async def __aiter__(self) -> AsyncIterator[bytes]:
async for chunk in self._aiterator:
yield chunk

async def aclose(self) -> None:
if self._aclose_func is not None:
await self._aclose_func()
22 changes: 3 additions & 19 deletions httpcore/_sync/base.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import enum
from types import TracebackType
from typing import Iterator, Callable, List, Tuple, Type
from typing import Iterator, List, Tuple, Type

from .._types import URL, Headers, TimeoutDict

Expand Down Expand Up @@ -40,33 +40,17 @@ class SyncByteStream:
the `\\__iter__` method, and optionally the `close` method.
"""

def __init__(
self,
content: bytes = b"",
iterator: Iterator[bytes] = None,
close_func: Callable = None,
) -> None:
assert iterator is None or not content
self.content = content
self.iterator = iterator
self.close_func = close_func

def __iter__(self) -> Iterator[bytes]:
"""
Yield bytes representing the request or response body.
"""
if self.iterator is None:
yield self.content
else:
for chunk in self.iterator:
yield chunk
yield b"" # pragma: nocover

def close(self) -> None:
"""
Must be called by the client to indicate that the stream has been closed.
"""
if self.close_func is not None:
self.close_func()
pass # pragma: nocover


class SyncHTTPTransport:
Expand Down
7 changes: 4 additions & 3 deletions httpcore/_sync/http11.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import h11

from .._backends.auto import SyncSocketStream
from .._bytestreams import PlainByteStream, IteratorByteStream
from .._exceptions import RemoteProtocolError, LocalProtocolError, map_exceptions
from .._types import URL, Headers, TimeoutDict
from .._utils import get_logger
Expand Down Expand Up @@ -57,7 +58,7 @@ def request(
timeout: TimeoutDict = None,
) -> Tuple[bytes, int, bytes, List[Tuple[bytes, bytes]], SyncByteStream]:
headers = [] if headers is None else headers
stream = SyncByteStream() if stream is None else stream
stream = PlainByteStream(b"") if stream is None else stream
timeout = {} if timeout is None else timeout

self.state = ConnectionState.ACTIVE
Expand All @@ -70,11 +71,11 @@ def request(
reason_phrase,
headers,
) = self._receive_response(timeout)
stream = SyncByteStream(
response_stream = IteratorByteStream(
iterator=self._receive_response_data(timeout),
close_func=self._response_closed,
)
return (http_version, status_code, reason_phrase, headers, stream)
return (http_version, status_code, reason_phrase, headers, response_stream)

def start_tls(
self, hostname: bytes, timeout: TimeoutDict = None
Expand Down
7 changes: 4 additions & 3 deletions httpcore/_sync/http2.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from h2.settings import SettingCodes, Settings

from .._backends.auto import SyncLock, SyncSemaphore, SyncSocketStream, SyncBackend
from .._bytestreams import PlainByteStream, IteratorByteStream
from .._exceptions import PoolTimeout, RemoteProtocolError
from .._types import URL, Headers, TimeoutDict
from .._utils import get_logger
Expand Down Expand Up @@ -282,7 +283,7 @@ def request(
timeout: TimeoutDict = None,
) -> Tuple[bytes, int, bytes, List[Tuple[bytes, bytes]], SyncByteStream]:
headers = [] if headers is None else [(k.lower(), v) for (k, v) in headers]
stream = SyncByteStream() if stream is None else stream
stream = PlainByteStream(b"") if stream is None else stream
timeout = {} if timeout is None else timeout

# Send the request.
Expand All @@ -298,11 +299,11 @@ def request(
# Receive the response.
status_code, headers = self.receive_response(timeout)
reason_phrase = get_reason_phrase(status_code)
stream = SyncByteStream(
response_stream = IteratorByteStream(
iterator=self.body_iter(timeout), close_func=self._response_closed
)

return (b"HTTP/2", status_code, reason_phrase, headers, stream)
return (b"HTTP/2", status_code, reason_phrase, headers, response_stream)

def send_headers(
self,
Expand Down
1 change: 1 addition & 0 deletions unasync.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import sys

SUBS = [
('AsyncIteratorByteStream', 'IteratorByteStream'),
('AsyncIterator', 'Iterator'),
('AutoBackend', 'SyncBackend'),
('Async([A-Z][A-Za-z0-9_]*)', r'Sync\2'),
Expand Down