From: Tom Christie Date: Wed, 12 Jun 2019 14:02:16 +0000 (+0100) Subject: http3 (#86) X-Git-Tag: 0.5.0~19 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=c9747aa35731991de2965f23cc047b7d4ea33af9;p=thirdparty%2Fhttpx.git http3 (#86) * Start fleshing out documentation * Docs work * http3 * Update docs * Include lowercase status codes, for requests compat * Updating docs * Docs tweaks --- diff --git a/README.md b/README.md index c044c056..7abaa284 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@ -# HTTPCore +# HTTP3 - - Build Status + + Build Status - - Coverage + + Coverage - - Package version + + Package version ## Feature support @@ -41,8 +41,8 @@ Plus all the standard features of requests... Making a request: ```python ->>> import httpcore ->>> client = httpcore.Client() +>>> import http3 +>>> client = http3.Client() >>> response = client.get('https://example.com') >>> response.status_code @@ -57,8 +57,8 @@ Alternatively, async requests: **Note**: Use `ipython` to try this from the console, since it supports `await`. ```python ->>> import httpcore ->>> client = httpcore.AsyncClient() +>>> import http3 +>>> client = http3.AsyncClient() >>> response = await client.get('https://example.com') >>> response.status_code @@ -93,7 +93,7 @@ inspiration around the lower level networking details. *An HTTP client, with connection pooling, redirects, cookie persistence, etc.* ```python ->>> client = Client() +>>> client = http3.Client() >>> response = client.get('https://example.org') ``` @@ -140,7 +140,7 @@ inspiration around the lower level networking details. what gets sent over the wire.* ```python ->>> request = Request("GET", "https://example.org", headers={'host': 'example.org'}) +>>> request = http3.Request("GET", "https://example.org", headers={'host': 'example.org'}) >>> response = client.send(request) ``` diff --git a/docs/api.md b/docs/api.md new file mode 100644 index 00000000..40d90dd5 --- /dev/null +++ b/docs/api.md @@ -0,0 +1,146 @@ +# Developer Interface + +## Main Interface + +* `get(url, [params], [headers], [cookies], [auth], [stream], [allow_redirects], [verify], [cert], [timeout])` +* `options(url, [params], [headers], [cookies], [auth], [stream], [allow_redirects], [verify], [cert], [timeout])` +* `head(url, [params], [headers], [cookies], [auth], [stream], [allow_redirects], [verify], [cert], [timeout])` +* `post(url, [data], [json], [params], [headers], [cookies], [auth], [stream], [allow_redirects], [verify], [cert], [timeout])` +* `put(url, [data], [json], [params], [headers], [cookies], [auth], [stream], [allow_redirects], [verify], [cert], [timeout])` +* `patch(url, [data], [json], [params], [headers], [cookies], [auth], [stream], [allow_redirects], [verify], [cert], [timeout])` +* `delete(url, [data], [json], [params], [headers], [cookies], [auth], [stream], [allow_redirects], [verify], [cert], [timeout])` +* `request(method, url, [data], [params], [headers], [cookies], [auth], [stream], [allow_redirects], [verify], [cert], [timeout])` + +## `Client` + +*An HTTP client, with connection pooling, redirects, cookie persistence, etc.* + +```python +>>> client = http3.Client() +>>> response = client.get('https://example.org') +``` + +* `def __init__([auth], [cookies], [verify], [cert], [timeout], [pool_limits], [max_redirects], [dispatch])` +* `def .get(url, [params], [headers], [cookies], [auth], [stream], [allow_redirects], [verify], [cert], [timeout])` +* `def .options(url, [params], [headers], [cookies], [auth], [stream], [allow_redirects], [verify], [cert], [timeout])` +* `def .head(url, [params], [headers], [cookies], [auth], [stream], [allow_redirects], [verify], [cert], [timeout])` +* `def .post(url, [data], [json], [params], [headers], [cookies], [auth], [stream], [allow_redirects], [verify], [cert], [timeout])` +* `def .put(url, [data], [json], [params], [headers], [cookies], [auth], [stream], [allow_redirects], [verify], [cert], [timeout])` +* `def .patch(url, [data], [json], [params], [headers], [cookies], [auth], [stream], [allow_redirects], [verify], [cert], [timeout])` +* `def .delete(url, [data], [json], [params], [headers], [cookies], [auth], [stream], [allow_redirects], [verify], [cert], [timeout])` +* `def .request(method, url, [data], [params], [headers], [cookies], [auth], [stream], [allow_redirects], [verify], [cert], [timeout])` +* `def .send(request, [stream], [allow_redirects], [verify], [cert], [timeout])` +* `def .close()` + +## `Response` + +*An HTTP response.* + +* `def __init__(...)` +* `.status_code` - **int** *(Typically a `StatusCode` IntEnum.)* +* `.reason_phrase` - **str** +* `.protocol` - `"HTTP/2"` or `"HTTP/1.1"` +* `.url` - **URL** +* `.headers` - **Headers** +* `.content` - **bytes** +* `.text` - **str** +* `.encoding` - **str** +* `.is_redirect` - **bool** +* `.request` - **Request** +* `.cookies` - **Cookies** +* `.history` - **List[Response]** +* `def .raise_for_status()` - **None** +* `def .json()` - **Any** +* `def .read()` - **bytes** +* `def .stream()` - **bytes iterator** +* `def .raw()` - **bytes iterator** +* `def .close()` - **None** +* `def .next()` - **Response** + +## `Request` + +*An HTTP request. Can be constructed explicitly for more control over exactly +what gets sent over the wire.* + +```python +>>> request = http3.Request("GET", "https://example.org", headers={'host': 'example.org'}) +>>> response = client.send(request) +``` + +* `def __init__(method, url, [params], [data], [json], [headers], [cookies])` +* `.method` - **str** +* `.url` - **URL** +* `.content` - **byte** or **byte async iterator** +* `.headers` - **Headers** +* `.cookies` - **Cookies** + +## `URL` + +*A normalized, IDNA supporting URL.* + +```python +>>> url = URL("https://example.org/") +>>> url.host +'example.org' +``` + +* `def __init__(url, allow_relative=False, params=None)` +* `.scheme` - **str** +* `.authority` - **str** +* `.host` - **str** +* `.port` - **int** +* `.path` - **str** +* `.query` - **str** +* `.full_path` - **str** +* `.fragment` - **str** +* `.is_ssl` - **bool** +* `.origin` - **Origin** +* `.is_absolute_url` - **bool** +* `.is_relative_url` - **bool** +* `def .copy_with([scheme], [authority], [path], [query], [fragment])` - **URL** +* `def .resolve_with(url)` - **URL** + +## `Origin` + +*A normalized, IDNA supporting set of scheme/host/port info.* + +```python +>>> Origin('https://example.org') == Origin('HTTPS://EXAMPLE.ORG:443') +True +``` + +* `def __init__(url)` +* `.is_ssl` - **bool** +* `.host` - **str** +* `.port` - **int** + +## `Headers` + +*A case-insensitive multi-dict.* + +```python +>>> headers = Headers({'Content-Type': 'application/json'}) +>>> headers['content-type'] +'application/json' +``` + +* `def __init__(self, headers)` + +## `Cookies` + +*A dict-like cookie store.* + +```python +>>> cookies = Cookies() +>>> cookies.set("name", "value", domain="example.org") +``` + +* `def __init__(cookies: [dict, Cookies, CookieJar])` +* `.jar` - **CookieJar** +* `def extract_cookies(response)` +* `def set_cookie_header(request)` +* `def set(name, value, [domain], [path])` +* `def get(name, [domain], [path])` +* `def delete(name, [domain], [path])` +* `def clear([domain], [path])` +* *Standard mutable mapping interface* diff --git a/docs/async.md b/docs/async.md new file mode 100644 index 00000000..7822d35d --- /dev/null +++ b/docs/async.md @@ -0,0 +1,60 @@ +# Async Client + +HTTP3 offers a standard synchronous API by default, but also gives you +the option of an async client if you need it. + +Async is a concurrency model that is far more efficient than multi-threading, +and can provide significant performance benefits and enable the use of +long-lived network connections such as WebSockets. + +If you're working with an async web framework such as Sanic, Starlette, FastAPI, +Responder or Bocadillo, then you'll also want to use an async client for sending +outgoing HTTP requests. + +## Making Async requests + +To make asynchronous requests, you'll need an `AsyncClient`. + +```python +>>> client = http3.AsyncClient() +>>> r = await client.get('https://www.example.com/') +``` + +## API Differences + +If you're using streaming responses then there are a few bits of API that +use async methods: + +```python +>>> client = http3.AsyncClient() +>>> r = await client.get('https://www.example.com/', stream=True) +>>> try: +>>> async for chunk in r.stream(): +>>> ... +>>> finally: +>>> await r.close() +``` + +The async response methods are: + +* `.read()` +* `.stream()` +* `.raw()` +* `.close()` + +If you're making parallel requests, then you'll also need to use an async API: + +```python +>>> client = http3.AsyncClient() +>>> async with client.parallel() as parallel: +>>> pending_one = parallel.get('https://example.com/1') +>>> pending_two = parallel.get('https://example.com/2') +>>> response_one = await pending_one.get_response() +>>> response_two = await pending_two.get_response() +``` + +The async parallel methods are: + +* `.parallel()` *Used as an "async with" context manager.* +* `.get_response()` +* `.next_response()` diff --git a/docs/compatibility.md b/docs/compatibility.md new file mode 100644 index 00000000..34d9e5af --- /dev/null +++ b/docs/compatibility.md @@ -0,0 +1,7 @@ +# Requests Compatibility Guide + +HTTP3 aims to be compatible with the `requests` API wherever possible. + +This documentation outlines places where the API differs... + +**TODO** diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 00000000..41c8ee90 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,88 @@ +# HTTP3 + + + Build Status + + + Coverage + + + Package version + + +HTTP3 is a next-generation HTTP client for Python. + +!!! warning + This project should be considered as an "alpha" release. It is substantially + API complete, but there are still some areas that need more work. + +--- + +Let's get started... + +```python +>>> import http3 +>>> r = http3.get('https://www.example.org/') +>>> r.status_code + +>>> r.protocol +'HTTP/2' +>>> r.headers['content-type'] +'text/html; charset=UTF-8' +>>> r.text +'\n\n\nExample Domain...' +``` + +## Features + +HTTP3 builds on the well-established usability of `requests`, and gives you: + +* A requests-compatible API. +* HTTP/2 and HTTP/1.1 support. +* Support for issuing HTTP requests in parallel. +* Standard synchronous interface, but with `async`/`await` support if you need it. +* Strict timeouts everywhere. +* Fully type annotated. +* 100% test coverage. + +Plus all the standard features of `requests`... + +* International Domains and URLs +* Keep-Alive & Connection Pooling +* Sessions with Cookie Persistence +* Browser-style SSL Verification +* Basic/Digest Authentication *Digest is still TODO* +* Elegant Key/Value Cookies +* Automatic Decompression +* Automatic Content Decoding +* Unicode Response Bodies +* Multipart File Uploads *TODO* +* HTTP(S) Proxy Support *TODO* +* Connection Timeouts +* Streaming Downloads +* .netrc Support *TODO* +* Chunked Requests + +## Documentation + +For a run-through of all the basics, head over to the [QuickStart](quickstart.md). + +For more advanced topics, see the [Parallel Requests](parallel.md) or [Async Client](async.md) documentation. + +The [Developer Interface](api.md) provides a comprehensive API reference. + +## Dependencies + +The HTTP3 project relies on these excellent libraries: + +* `h2` - HTTP/2 support. +* `h11` - HTTP/1.1 support. +* `certifi` - SSL certificates. +* `chardet` - Fallback auto-detection for response encoding. +* `idna` - Internationalized domain name support. +* `rfc3986` - URL parsing & normalization. +* `brotlipy` - Decoding for "brotli" compressed responses. *(Optional)* + +A huge amount of credit is due to `requests` for the API layout that +much of this work follows, as well as to `urllib3` for plenty of design +inspiration around the lower level networking details. diff --git a/docs/parallel.md b/docs/parallel.md new file mode 100644 index 00000000..66d84905 --- /dev/null +++ b/docs/parallel.md @@ -0,0 +1,66 @@ +# Parallel Requests + +HTTP3 allows you to make HTTP requests in parallel in a highly efficient way, +using async under the hood, while still presenting a standard threaded interface. + +This has the huge benefit of allowing you to efficiently make parallel HTTP +requests without having to switch out to using async all the way through. + +## Making Parallel Requests + +Let's make two outgoing HTTP requests in parallel: + +```python +>>> with http3.parallel() as parallel: +>>> pending_one = parallel.get('https://example.com/1') +>>> pending_two = parallel.get('https://example.com/2') +>>> response_one = pending_one.get_response() +>>> response_two = pending_two.get_response() +``` + +If we're making lots of outgoing requests, we might not want to deal with the +responses sequentially, but rather deal with each response that comes back +as soon as it's available: + +```python +>>> with http3.parallel() as parallel: +>>> for counter in range(1, 10): +>>> parallel.get(f'https://example.com/{counter}') +>>> while parallel.has_pending_responses: +>>> r = parallel.next_response() +``` + +## Exceptions and Cancellations + +The style of using `parallel` blocks ensures that you'll always have well +defined exception and cancellation behaviours. Request exceptions are only ever +raised when calling either `get_response` or `next_response`, and any pending +requests are cancelled on exiting the block. + +## Parallel requests with a Client + +You can also call `parallel()` from a client instance, which allows you to +control the authentication or dispatch behaviour for all requests within the +block. + +```python +>>> client = http3.Client() +>>> with client.parallel() as parallel: +>>> ... +``` + +## Async parallel requests + +If you're working within an async framework, then you'll want to use a fully +async API for making requests. + +```python +>>> client = http3.AsyncClient() +>>> async with client.parallel() as parallel: +>>> pending_one = await parallel.get('https://example.com/1') +>>> pending_two = await parallel.get('https://example.com/2') +>>> response_one = await pending_one.get_response() +>>> response_two = await pending_two.get_response() +``` + +See [the Async Client documentation](async.md) for more details. diff --git a/docs/quickstart.md b/docs/quickstart.md new file mode 100644 index 00000000..07587255 --- /dev/null +++ b/docs/quickstart.md @@ -0,0 +1,270 @@ +# QuickStart + +!!! note + This page closely follows the layout of the `requests` QuickStart documentation. + The `http3` library is designed to be API compatible with `requests` wherever + possible. + +First start by importing HTTP3: + +``` +>>> import http3 +``` + +Now, let’s try to get a webpage. For this example, let’s get GitHub’s public timeline: + +```python +>>> r = http3.get('https://api.github.com/events') +``` + +Similarly, to make an HTTP POST request: + +```python +>>> r = http3.post('https://httpbin.org/post', data={'key': 'value'}) +``` + +The PUT, DELETE, HEAD, and OPTIONS requests all follow the same style: + +```python +>>> r = http3.put('https://httpbin.org/put', data={'key': 'value'}) +>>> r = http3.delete('https://httpbin.org/delete') +>>> r = http3.head('https://httpbin.org/get') +>>> r = http3.options('https://httpbin.org/get') +``` + +## Passing Parameters in URLs + +To include URL query parameters in the request, use the `params` keyword: + +```python +>>> params = {'key1': 'value1', 'key2': 'value2'} +>>> r = http3.get('https://httpbin.org/get', params=params) +``` + +To see how the values get encoding into the URL string, we can inspect the +resulting URL that was used to make the request: + +```python +>>> r.url +URL('https://httpbin.org/get?key2=value2&key1=value1') +``` + +You can also pass a list of items as a value: + +```python +>>> params = {'key1': 'value1', 'key2': ['value2', 'value3']} +>>> r = http3.get('https://httpbin.org/get', params=params) +>>> r.url +URL('https://httpbin.org/get?key1=value1&key2=value2&key2=value3') +``` + +## Response Content + +HTTP3 will automatically handle decoding the response content into unicode text. + +```python +>>> r = http3.get('https://www.example.org/') +>>> r.text +'\n\n\nExample Domain...' +``` + +You can inspect what encoding has been used to decode the response. + +```python +>>> r.encoding +'UTF-8' +``` + +If you need to override the standard behavior and explicitly set the encoding to +use, then you can do that too. + +```python +>>> r.encoding = 'ISO-8859-1' +``` + +## Binary Response Content + +The response content can also be accessed as bytes, for non-text responses: + +```python +>>> r.content +b'\n\n\nExample Domain...' +``` + +Any `gzip` and `deflate` HTTP response encodings will automatically +be decoded for you. If `brotlipy` is installed, then the `brotli` response +encoding will also be supported. + +For example, to create an image from binary data returned by a request, you can use the following code: + +```python +>>> from PIL import Image +>>> from io import BytesIO +>>> i = Image.open(BytesIO(r.content)) +``` + +## JSON Response Content + +Often Web API responses will be encoded as JSON. + +```python +>>> r = http3.get('https://api.github.com/events') +>>> r.json() +[{u'repository': {u'open_issues': 0, u'url': 'https://github.com/...' ... }}] +``` + +## Custom Headers + +To include additional headers in the outgoing request, use the `headers` keyword argument: + +```python +>>> url = 'http://httpbin.org/headers' +>>> headers = {'user-agent': 'my-app/0.0.1'} +>>> r = http3.get(url, headers=headers) +``` + +## Sending Form Encoded Data + +Some types of HTTP requests, such as `POST` and `PUT` requests, can include data +in the request body. One common way of including that is as form encoded data, +which is used for HTML forms. + +```python +>>> data = {'key1': 'value1', 'key2': 'value2'} +>>> r = http3.post("https://httpbin.org/post", data=data) +>>> print(r.text) +{ + ... + "form": { + "key2": "value2", + "key1": "value1" + }, + ... +} +``` + +Form encoded data can also include multiple values form a given key. + +```python +>>> data = {'key1': ['value1', 'value2']} +>>> r = http3.post("https://httpbin.org/post", data=data) +>>> print(r.text) +{ + ... + "form": { + "key1": [ + "value1", + "value2" + ] + }, + ... +} +``` + +## Sending JSON Encoded Data + +Form encoded data is okay if all you need is simple key-value data structure. +For more complicated data structures you'll often want to use JSON encoding instead. + +```python +>>> data = {'integer': 123, 'boolean': True, 'list': ['a', 'b', 'c']} +>>> r = http3.post("https://httpbin.org/post", json=data) +>>> print(r.text) +{ + ... + "json": { + "boolean": true, + "integer": 123, + "list": [ + "a", + "b", + "c" + ] + }, + ... +} +``` + +## Sending Binary Request Data + +For other encodings you should use either a `bytes` type, or a generator +that yields `bytes`. + +You'll probably also want to set a custom `Content-Type` header when uploading +binary data. + +## Response Status Codes + +We can inspect the HTTP status code of the response: + +```python +>>> r = http3.get('https://httpbin.org/get') +>>> r.status_code + +``` + +The status code is an integer enum, meaning that the Python representation gives +use some descriptive information, but the value itself can be used as a regular integer. + +```python +>>> r.status_code == 200 +True +``` + +HTTP3 also includes an easy shortcut for accessing status codes by their text phrase. + +```python +>>> r.status_code == requests.codes.OK +True +``` + +We can raise an exception for any Client or Server error responses (4xx or 5xx status codes): + +```python +>>> not_found = http3.get('https://httpbin.org/status/404') +>>> not_found.status_code + +>>> not_found.raise_for_status() +Traceback (most recent call last): + File "/Users/tomchristie/GitHub/encode/httpcore/http3/models.py", line 776, in raise_for_status + raise HttpError(message) +http3.exceptions.HttpError: 404 Not Found +``` + +Any successful response codes will simply return `None` rather than raising an exception. + +``` python +>>> r.raise_for_status() +``` + +## Response Headers + +The response headers are available as a dictionary-like interface. + +```python +>>> r.headers +Headers({ + 'content-encoding': 'gzip', + 'transfer-encoding': 'chunked', + 'connection': 'close', + 'server': 'nginx/1.0.4', + 'x-runtime': '148ms', + 'etag': '"e1ca502697e5c9317743dc078f67693f"', + 'content-type': 'application/json' +}) +``` + +The `Headers` data type is case-insensitive, so you can use any capitalization. + +```python +>>> r.headers['Content-Type'] +'application/json' + +>>> r.headers.get('content-type') +'application/json' +``` + +Multiple values for a single response header are represented as a single comma separated +value, as per [RFC 7230](https://tools.ietf.org/html/rfc7230#section-3.2): + +> A recipient MAY combine multiple header fields with the same field name into one “field-name: field-value” pair, without changing the semantics of the message, by appending each subsequent field value to the combined field value in order, separated by a comma. diff --git a/httpcore/__init__.py b/http3/__init__.py similarity index 97% rename from httpcore/__init__.py rename to http3/__init__.py index 8f9cb07c..79dab8b4 100644 --- a/httpcore/__init__.py +++ b/http3/__init__.py @@ -49,4 +49,4 @@ from .models import ( ) from .status_codes import StatusCode, codes -__version__ = "0.4.0" +__version__ = "0.0.1" diff --git a/httpcore/api.py b/http3/api.py similarity index 100% rename from httpcore/api.py rename to http3/api.py diff --git a/httpcore/auth.py b/http3/auth.py similarity index 100% rename from httpcore/auth.py rename to http3/auth.py diff --git a/httpcore/client.py b/http3/client.py similarity index 100% rename from httpcore/client.py rename to http3/client.py diff --git a/httpcore/concurrency.py b/http3/concurrency.py similarity index 100% rename from httpcore/concurrency.py rename to http3/concurrency.py diff --git a/httpcore/config.py b/http3/config.py similarity index 100% rename from httpcore/config.py rename to http3/config.py diff --git a/httpcore/decoders.py b/http3/decoders.py similarity index 89% rename from httpcore/decoders.py rename to http3/decoders.py index 7f3ef0db..b352de25 100644 --- a/httpcore/decoders.py +++ b/http3/decoders.py @@ -6,7 +6,7 @@ See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding import typing import zlib -import httpcore.exceptions +from .exceptions import DecodingError try: import brotli @@ -48,13 +48,13 @@ class DeflateDecoder(Decoder): try: return self.decompressor.decompress(data) except zlib.error as exc: - raise httpcore.exceptions.DecodingError from exc + raise DecodingError from exc def flush(self) -> bytes: try: return self.decompressor.flush() except zlib.error as exc: # pragma: nocover - raise httpcore.exceptions.DecodingError from exc + raise DecodingError from exc class GZipDecoder(Decoder): @@ -71,13 +71,13 @@ class GZipDecoder(Decoder): try: return self.decompressor.decompress(data) except zlib.error as exc: - raise httpcore.exceptions.DecodingError from exc + raise DecodingError from exc def flush(self) -> bytes: try: return self.decompressor.flush() except zlib.error as exc: # pragma: nocover - raise httpcore.exceptions.DecodingError from exc + raise DecodingError from exc class BrotliDecoder(Decoder): @@ -97,14 +97,14 @@ class BrotliDecoder(Decoder): try: return self.decompressor.decompress(data) except brotli.Error as exc: - raise httpcore.exceptions.DecodingError from exc + raise DecodingError from exc def flush(self) -> bytes: try: self.decompressor.finish() return b"" except brotli.Error as exc: # pragma: nocover - raise httpcore.exceptions.DecodingError from exc + raise DecodingError from exc class MultiDecoder(Decoder): diff --git a/httpcore/dispatch/__init__.py b/http3/dispatch/__init__.py similarity index 100% rename from httpcore/dispatch/__init__.py rename to http3/dispatch/__init__.py diff --git a/httpcore/dispatch/connection.py b/http3/dispatch/connection.py similarity index 100% rename from httpcore/dispatch/connection.py rename to http3/dispatch/connection.py diff --git a/httpcore/dispatch/connection_pool.py b/http3/dispatch/connection_pool.py similarity index 100% rename from httpcore/dispatch/connection_pool.py rename to http3/dispatch/connection_pool.py diff --git a/httpcore/dispatch/http11.py b/http3/dispatch/http11.py similarity index 100% rename from httpcore/dispatch/http11.py rename to http3/dispatch/http11.py diff --git a/httpcore/dispatch/http2.py b/http3/dispatch/http2.py similarity index 100% rename from httpcore/dispatch/http2.py rename to http3/dispatch/http2.py diff --git a/httpcore/dispatch/threaded.py b/http3/dispatch/threaded.py similarity index 100% rename from httpcore/dispatch/threaded.py rename to http3/dispatch/threaded.py diff --git a/httpcore/exceptions.py b/http3/exceptions.py similarity index 100% rename from httpcore/exceptions.py rename to http3/exceptions.py diff --git a/httpcore/interfaces.py b/http3/interfaces.py similarity index 100% rename from httpcore/interfaces.py rename to http3/interfaces.py diff --git a/httpcore/models.py b/http3/models.py similarity index 99% rename from httpcore/models.py rename to http3/models.py index eb610801..6b22674b 100644 --- a/httpcore/models.py +++ b/http3/models.py @@ -509,7 +509,7 @@ class BaseRequest: has_accept_encoding = "accept-encoding" in self.headers if not has_user_agent: - auto_headers.append((b"user-agent", b"httpcore")) + auto_headers.append((b"user-agent", b"http3")) if not has_accept: auto_headers.append((b"accept", b"*/*")) if not has_content_length: diff --git a/httpcore/status_codes.py b/http3/status_codes.py similarity index 97% rename from httpcore/status_codes.py rename to http3/status_codes.py index b839dcfe..eb5e7ceb 100644 --- a/httpcore/status_codes.py +++ b/http3/status_codes.py @@ -127,3 +127,7 @@ class StatusCode(IntEnum): codes = StatusCode + +# Include lower-case styles for `requests` compatability. +for code in codes: + setattr(codes, code._name_.lower(), int(code)) diff --git a/httpcore/utils.py b/http3/utils.py similarity index 100% rename from httpcore/utils.py rename to http3/utils.py diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 00000000..2e80f344 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,21 @@ +site_name: HTTP3 +site_description: The next generation HTTP client. + +theme: + name: 'material' + +repo_name: encode/http3 +repo_url: https://github.com/encode/http3 +edit_uri: "" + +nav: + - Introduction: 'index.md' + - QuickStart: 'quickstart.md' + - Parallel Requests: 'parallel.md' + - Async Client: 'async.md' + - Requests Compatibility: 'compatibility.md' + - Developer Interface: 'api.md' + +markdown_extensions: + - admonition + - codehilite diff --git a/scripts/clean b/scripts/clean index 3a639f5e..bd133afe 100755 --- a/scripts/clean +++ b/scripts/clean @@ -9,6 +9,6 @@ fi if [ -d 'htmlcov' ] ; then rm -r htmlcov fi -if [ -d 'httpcore.egg-info' ] ; then - rm -r httpcore.egg-info +if [ -d 'http3.egg-info' ] ; then + rm -r http3.egg-info fi diff --git a/scripts/lint b/scripts/lint index 3ce97239..89e1e93a 100755 --- a/scripts/lint +++ b/scripts/lint @@ -7,9 +7,9 @@ fi set -x -${PREFIX}autoflake --in-place --recursive httpcore tests -${PREFIX}black httpcore tests -${PREFIX}isort --multi-line=3 --trailing-comma --force-grid-wrap=0 --combine-as --line-width 88 --recursive --apply httpcore tests -${PREFIX}mypy httpcore --ignore-missing-imports --disallow-untyped-defs +${PREFIX}autoflake --in-place --recursive http3 tests +${PREFIX}black http3 tests +${PREFIX}isort --multi-line=3 --trailing-comma --force-grid-wrap=0 --combine-as --line-width 88 --recursive --apply http3 tests +${PREFIX}mypy http3 --ignore-missing-imports --disallow-untyped-defs scripts/clean diff --git a/scripts/publish b/scripts/publish index 73ccae76..9ec45d54 100755 --- a/scripts/publish +++ b/scripts/publish @@ -1,6 +1,6 @@ #!/bin/sh -e -export PACKAGE="httpcore" +export PACKAGE="http3" export VERSION=`cat ${PACKAGE}/__init__.py | grep __version__ | sed "s/__version__ = //" | sed "s/'//g"` export PREFIX="" if [ -d 'venv' ] ; then diff --git a/scripts/test b/scripts/test index 63ad08a6..fe000e2f 100755 --- a/scripts/test +++ b/scripts/test @@ -1,6 +1,6 @@ #!/bin/sh -e -export PACKAGE="httpcore" +export PACKAGE="http3" export PREFIX="" if [ -d 'venv' ] ; then export PREFIX="venv/bin/" diff --git a/setup.py b/setup.py index 77e581cb..69fcde0a 100644 --- a/setup.py +++ b/setup.py @@ -35,17 +35,17 @@ def get_packages(package): setup( - name="httpcore", + name="http3", python_requires=">=3.6", - version=get_version("httpcore"), - url="https://github.com/encode/httpcore", + version=get_version("http3"), + url="https://github.com/encode/http3", license="BSD", - description="...", + description="The next generation HTTP client.", long_description=get_long_description(), long_description_content_type="text/markdown", author="Tom Christie", author_email="tom@tomchristie.com", - packages=get_packages("httpcore"), + packages=get_packages("http3"), data_files=[("", ["LICENSE.md"])], install_requires=[ "certifi", diff --git a/tests/client/test_async_client.py b/tests/client/test_async_client.py index 01f45853..bf7fe573 100644 --- a/tests/client/test_async_client.py +++ b/tests/client/test_async_client.py @@ -1,12 +1,12 @@ import pytest -import httpcore +import http3 @pytest.mark.asyncio async def test_get(server): url = "http://127.0.0.1:8000/" - async with httpcore.AsyncClient() as client: + async with http3.AsyncClient() as client: response = await client.get(url) assert response.status_code == 200 assert response.text == "Hello, world!" @@ -18,7 +18,7 @@ async def test_get(server): @pytest.mark.asyncio async def test_post(server): url = "http://127.0.0.1:8000/" - async with httpcore.AsyncClient() as client: + async with http3.AsyncClient() as client: response = await client.post(url, data=b"Hello, world!") assert response.status_code == 200 @@ -26,14 +26,14 @@ async def test_post(server): @pytest.mark.asyncio async def test_post_json(server): url = "http://127.0.0.1:8000/" - async with httpcore.AsyncClient() as client: + async with http3.AsyncClient() as client: response = await client.post(url, json={"text": "Hello, world!"}) assert response.status_code == 200 @pytest.mark.asyncio async def test_stream_response(server): - async with httpcore.AsyncClient() as client: + async with http3.AsyncClient() as client: response = await client.request("GET", "http://127.0.0.1:8000/", stream=True) assert response.status_code == 200 body = await response.read() @@ -43,10 +43,10 @@ async def test_stream_response(server): @pytest.mark.asyncio async def test_access_content_stream_response(server): - async with httpcore.AsyncClient() as client: + async with http3.AsyncClient() as client: response = await client.request("GET", "http://127.0.0.1:8000/", stream=True) assert response.status_code == 200 - with pytest.raises(httpcore.ResponseNotRead): + with pytest.raises(http3.ResponseNotRead): response.content @@ -56,7 +56,7 @@ async def test_stream_request(server): yield b"Hello, " yield b"world!" - async with httpcore.AsyncClient() as client: + async with http3.AsyncClient() as client: response = await client.request( "POST", "http://127.0.0.1:8000/", data=hello_world() ) @@ -65,14 +65,14 @@ async def test_stream_request(server): @pytest.mark.asyncio async def test_raise_for_status(server): - async with httpcore.AsyncClient() as client: + async with http3.AsyncClient() as client: for status_code in (200, 400, 404, 500, 505): response = await client.request( "GET", f"http://127.0.0.1:8000/status/{status_code}" ) if 400 <= status_code < 600: - with pytest.raises(httpcore.exceptions.HttpError): + with pytest.raises(http3.exceptions.HttpError): response.raise_for_status() else: assert response.raise_for_status() is None @@ -81,7 +81,7 @@ async def test_raise_for_status(server): @pytest.mark.asyncio async def test_options(server): url = "http://127.0.0.1:8000/" - async with httpcore.AsyncClient() as client: + async with http3.AsyncClient() as client: response = await client.options(url) assert response.status_code == 200 assert response.text == "Hello, world!" @@ -90,7 +90,7 @@ async def test_options(server): @pytest.mark.asyncio async def test_head(server): url = "http://127.0.0.1:8000/" - async with httpcore.AsyncClient() as client: + async with http3.AsyncClient() as client: response = await client.head(url) assert response.status_code == 200 assert response.text == "" @@ -99,7 +99,7 @@ async def test_head(server): @pytest.mark.asyncio async def test_put(server): url = "http://127.0.0.1:8000/" - async with httpcore.AsyncClient() as client: + async with http3.AsyncClient() as client: response = await client.put(url, data=b"Hello, world!") assert response.status_code == 200 @@ -107,7 +107,7 @@ async def test_put(server): @pytest.mark.asyncio async def test_patch(server): url = "http://127.0.0.1:8000/" - async with httpcore.AsyncClient() as client: + async with http3.AsyncClient() as client: response = await client.patch(url, data=b"Hello, world!") assert response.status_code == 200 @@ -115,7 +115,7 @@ async def test_patch(server): @pytest.mark.asyncio async def test_delete(server): url = "http://127.0.0.1:8000/" - async with httpcore.AsyncClient() as client: + async with http3.AsyncClient() as client: response = await client.delete(url) assert response.status_code == 200 assert response.text == "Hello, world!" @@ -127,7 +127,7 @@ async def test_100_continue(server): headers = {"Expect": "100-continue"} data = b"Echo request body" - async with httpcore.AsyncClient() as client: + async with http3.AsyncClient() as client: response = await client.post(url, headers=headers, data=data) assert response.status_code == 200 diff --git a/tests/client/test_auth.py b/tests/client/test_auth.py index 17993383..597aae77 100644 --- a/tests/client/test_auth.py +++ b/tests/client/test_auth.py @@ -2,7 +2,7 @@ import json import pytest -from httpcore import ( +from http3 import ( URL, AsyncDispatcher, AsyncRequest, diff --git a/tests/client/test_client.py b/tests/client/test_client.py index f3663f69..d8e8f4a0 100644 --- a/tests/client/test_client.py +++ b/tests/client/test_client.py @@ -3,7 +3,7 @@ import functools import pytest -import httpcore +import http3 def threadpool(func): @@ -26,15 +26,15 @@ def threadpool(func): @threadpool def test_get(server): url = "http://127.0.0.1:8000/" - with httpcore.Client() as http: + with http3.Client() as http: response = http.get(url) assert response.status_code == 200 - assert response.url == httpcore.URL(url) + assert response.url == http3.URL(url) assert response.content == b"Hello, world!" assert response.text == "Hello, world!" assert response.protocol == "HTTP/1.1" assert response.encoding == "iso-8859-1" - assert response.request.url == httpcore.URL(url) + assert response.request.url == http3.URL(url) assert response.headers assert response.is_redirect is False assert repr(response) == "" @@ -42,7 +42,7 @@ def test_get(server): @threadpool def test_post(server): - with httpcore.Client() as http: + with http3.Client() as http: response = http.post("http://127.0.0.1:8000/", data=b"Hello, world!") assert response.status_code == 200 assert response.reason_phrase == "OK" @@ -50,7 +50,7 @@ def test_post(server): @threadpool def test_post_json(server): - with httpcore.Client() as http: + with http3.Client() as http: response = http.post("http://127.0.0.1:8000/", json={"text": "Hello, world!"}) assert response.status_code == 200 assert response.reason_phrase == "OK" @@ -58,7 +58,7 @@ def test_post_json(server): @threadpool def test_stream_response(server): - with httpcore.Client() as http: + with http3.Client() as http: response = http.get("http://127.0.0.1:8000/", stream=True) assert response.status_code == 200 content = response.read() @@ -67,7 +67,7 @@ def test_stream_response(server): @threadpool def test_stream_iterator(server): - with httpcore.Client() as http: + with http3.Client() as http: response = http.get("http://127.0.0.1:8000/", stream=True) assert response.status_code == 200 body = b"" @@ -78,7 +78,7 @@ def test_stream_iterator(server): @threadpool def test_raw_iterator(server): - with httpcore.Client() as http: + with http3.Client() as http: response = http.get("http://127.0.0.1:8000/", stream=True) assert response.status_code == 200 body = b"" @@ -90,14 +90,14 @@ def test_raw_iterator(server): @threadpool def test_raise_for_status(server): - with httpcore.Client() as client: + with http3.Client() as client: for status_code in (200, 400, 404, 500, 505): response = client.request( "GET", "http://127.0.0.1:8000/status/{}".format(status_code) ) if 400 <= status_code < 600: - with pytest.raises(httpcore.exceptions.HttpError): + with pytest.raises(http3.exceptions.HttpError): response.raise_for_status() else: assert response.raise_for_status() is None @@ -105,7 +105,7 @@ def test_raise_for_status(server): @threadpool def test_options(server): - with httpcore.Client() as http: + with http3.Client() as http: response = http.options("http://127.0.0.1:8000/") assert response.status_code == 200 assert response.reason_phrase == "OK" @@ -113,7 +113,7 @@ def test_options(server): @threadpool def test_head(server): - with httpcore.Client() as http: + with http3.Client() as http: response = http.head("http://127.0.0.1:8000/") assert response.status_code == 200 assert response.reason_phrase == "OK" @@ -121,7 +121,7 @@ def test_head(server): @threadpool def test_put(server): - with httpcore.Client() as http: + with http3.Client() as http: response = http.put("http://127.0.0.1:8000/", data=b"Hello, world!") assert response.status_code == 200 assert response.reason_phrase == "OK" @@ -129,7 +129,7 @@ def test_put(server): @threadpool def test_patch(server): - with httpcore.Client() as http: + with http3.Client() as http: response = http.patch("http://127.0.0.1:8000/", data=b"Hello, world!") assert response.status_code == 200 assert response.reason_phrase == "OK" @@ -137,7 +137,7 @@ def test_patch(server): @threadpool def test_delete(server): - with httpcore.Client() as http: + with http3.Client() as http: response = http.delete("http://127.0.0.1:8000/") assert response.status_code == 200 assert response.reason_phrase == "OK" diff --git a/tests/client/test_cookies.py b/tests/client/test_cookies.py index 5cbb3809..f4f7ceb6 100644 --- a/tests/client/test_cookies.py +++ b/tests/client/test_cookies.py @@ -3,7 +3,7 @@ from http.cookiejar import Cookie, CookieJar import pytest -from httpcore import ( +from http3 import ( URL, AsyncDispatcher, AsyncRequest, diff --git a/tests/client/test_redirects.py b/tests/client/test_redirects.py index 3f516897..01c0471c 100644 --- a/tests/client/test_redirects.py +++ b/tests/client/test_redirects.py @@ -3,7 +3,7 @@ from urllib.parse import parse_qs import pytest -from httpcore import ( +from http3 import ( URL, AsyncClient, AsyncDispatcher, diff --git a/tests/dispatch/test_connection_pools.py b/tests/dispatch/test_connection_pools.py index b8049c70..00e0a7e4 100644 --- a/tests/dispatch/test_connection_pools.py +++ b/tests/dispatch/test_connection_pools.py @@ -1,6 +1,6 @@ import pytest -import httpcore +import http3 @pytest.mark.asyncio @@ -8,7 +8,7 @@ async def test_keepalive_connections(server): """ Connections should default to staying in a keep-alive state. """ - async with httpcore.ConnectionPool() as http: + async with http3.ConnectionPool() as http: response = await http.request("GET", "http://127.0.0.1:8000/") await response.read() assert len(http.active_connections) == 0 @@ -25,7 +25,7 @@ async def test_differing_connection_keys(server): """ Connnections to differing connection keys should result in multiple connections. """ - async with httpcore.ConnectionPool() as http: + async with http3.ConnectionPool() as http: response = await http.request("GET", "http://127.0.0.1:8000/") await response.read() assert len(http.active_connections) == 0 @@ -42,9 +42,9 @@ async def test_soft_limit(server): """ The soft_limit config should limit the maximum number of keep-alive connections. """ - pool_limits = httpcore.PoolLimits(soft_limit=1) + pool_limits = http3.PoolLimits(soft_limit=1) - async with httpcore.ConnectionPool(pool_limits=pool_limits) as http: + async with http3.ConnectionPool(pool_limits=pool_limits) as http: response = await http.request("GET", "http://127.0.0.1:8000/") await response.read() assert len(http.active_connections) == 0 @@ -61,7 +61,7 @@ async def test_streaming_response_holds_connection(server): """ A streaming request should hold the connection open until the response is read. """ - async with httpcore.ConnectionPool() as http: + async with http3.ConnectionPool() as http: response = await http.request("GET", "http://127.0.0.1:8000/") assert len(http.active_connections) == 1 assert len(http.keepalive_connections) == 0 @@ -77,7 +77,7 @@ async def test_multiple_concurrent_connections(server): """ Multiple conncurrent requests should open multiple conncurrent connections. """ - async with httpcore.ConnectionPool() as http: + async with http3.ConnectionPool() as http: response_a = await http.request("GET", "http://127.0.0.1:8000/") assert len(http.active_connections) == 1 assert len(http.keepalive_connections) == 0 @@ -101,7 +101,7 @@ async def test_close_connections(server): Using a `Connection: close` header should close the connection. """ headers = [(b"connection", b"close")] - async with httpcore.ConnectionPool() as http: + async with http3.ConnectionPool() as http: response = await http.request("GET", "http://127.0.0.1:8000/", headers=headers) await response.read() assert len(http.active_connections) == 0 @@ -113,7 +113,7 @@ async def test_standard_response_close(server): """ A standard close should keep the connection open. """ - async with httpcore.ConnectionPool() as http: + async with http3.ConnectionPool() as http: response = await http.request("GET", "http://127.0.0.1:8000/") await response.read() await response.close() @@ -126,7 +126,7 @@ async def test_premature_response_close(server): """ A premature close should close the connection. """ - async with httpcore.ConnectionPool() as http: + async with http3.ConnectionPool() as http: response = await http.request("GET", "http://127.0.0.1:8000/") await response.close() assert len(http.active_connections) == 0 diff --git a/tests/dispatch/test_connections.py b/tests/dispatch/test_connections.py index 4b267f4f..639ed917 100644 --- a/tests/dispatch/test_connections.py +++ b/tests/dispatch/test_connections.py @@ -1,6 +1,6 @@ import pytest -from httpcore import HTTPConnection, Request, SSLConfig +from http3 import HTTPConnection, Request, SSLConfig @pytest.mark.asyncio diff --git a/tests/dispatch/test_http2.py b/tests/dispatch/test_http2.py index e19bb1cf..b87e528a 100644 --- a/tests/dispatch/test_http2.py +++ b/tests/dispatch/test_http2.py @@ -2,7 +2,7 @@ import json import pytest -from httpcore import Client, Response +from http3 import Client, Response from .utils import MockHTTP2Backend diff --git a/tests/dispatch/test_threaded.py b/tests/dispatch/test_threaded.py index d177dbba..04a9a2e6 100644 --- a/tests/dispatch/test_threaded.py +++ b/tests/dispatch/test_threaded.py @@ -2,7 +2,7 @@ import json import pytest -from httpcore import ( +from http3 import ( CertTypes, Client, Dispatcher, diff --git a/tests/dispatch/utils.py b/tests/dispatch/utils.py index 651fddf8..5a0203e1 100644 --- a/tests/dispatch/utils.py +++ b/tests/dispatch/utils.py @@ -5,7 +5,7 @@ import h2.config import h2.connection import h2.events -from httpcore import ( +from http3 import ( AsyncioBackend, BaseReader, BaseWriter, diff --git a/tests/models/test_cookies.py b/tests/models/test_cookies.py index 705245d6..57b1412c 100644 --- a/tests/models/test_cookies.py +++ b/tests/models/test_cookies.py @@ -1,6 +1,6 @@ import pytest -from httpcore import CookieConflict, Cookies +from http3 import CookieConflict, Cookies def test_cookies(): diff --git a/tests/models/test_headers.py b/tests/models/test_headers.py index ffcde8e1..c6b755d1 100644 --- a/tests/models/test_headers.py +++ b/tests/models/test_headers.py @@ -1,8 +1,8 @@ -import httpcore +import http3 def test_headers(): - h = httpcore.Headers([("a", "123"), ("a", "456"), ("b", "789")]) + h = http3.Headers([("a", "123"), ("a", "456"), ("b", "789")]) assert "a" in h assert "A" in h assert "b" in h @@ -18,10 +18,10 @@ def test_headers(): assert list(h) == ["a", "a", "b"] assert dict(h) == {"a": "123, 456", "b": "789"} assert repr(h) == "Headers([('a', '123'), ('a', '456'), ('b', '789')])" - assert h == httpcore.Headers([("a", "123"), ("b", "789"), ("a", "456")]) + assert h == http3.Headers([("a", "123"), ("b", "789"), ("a", "456")]) assert h != [("a", "123"), ("A", "456"), ("b", "789")] - h = httpcore.Headers({"a": "123", "b": "789"}) + h = http3.Headers({"a": "123", "b": "789"}) assert h["A"] == "123" assert h["B"] == "789" assert h.raw == [(b"a", b"123"), (b"b", b"789")] @@ -29,7 +29,7 @@ def test_headers(): def test_header_mutations(): - h = httpcore.Headers() + h = http3.Headers() assert dict(h) == {} h["a"] = "1" assert dict(h) == {"a": "1"} @@ -45,31 +45,31 @@ def test_header_mutations(): def test_copy_headers(): - headers = httpcore.Headers({"custom": "example"}) - headers_copy = httpcore.Headers(headers) + headers = http3.Headers({"custom": "example"}) + headers_copy = http3.Headers(headers) assert headers == headers_copy def test_headers_insert_retains_ordering(): - headers = httpcore.Headers({"a": "a", "b": "b", "c": "c"}) + headers = http3.Headers({"a": "a", "b": "b", "c": "c"}) headers["b"] = "123" assert list(headers.values()) == ["a", "123", "c"] def test_headers_insert_appends_if_new(): - headers = httpcore.Headers({"a": "a", "b": "b", "c": "c"}) + headers = http3.Headers({"a": "a", "b": "b", "c": "c"}) headers["d"] = "123" assert list(headers.values()) == ["a", "b", "c", "123"] def test_headers_insert_removes_all_existing(): - headers = httpcore.Headers([("a", "123"), ("a", "456")]) + headers = http3.Headers([("a", "123"), ("a", "456")]) headers["a"] = "789" assert dict(headers) == {"a": "789"} def test_headers_delete_removes_all_existing(): - headers = httpcore.Headers([("a", "123"), ("a", "456")]) + headers = http3.Headers([("a", "123"), ("a", "456")]) del headers["a"] assert dict(headers) == {} @@ -78,7 +78,7 @@ def test_headers_dict_repr(): """ Headers should display with a dict repr by default. """ - headers = httpcore.Headers({"custom": "example"}) + headers = http3.Headers({"custom": "example"}) assert repr(headers) == "Headers({'custom': 'example'})" @@ -86,7 +86,7 @@ def test_headers_encoding_in_repr(): """ Headers should display an encoding in the repr if required. """ - headers = httpcore.Headers({b"custom": "example ☃".encode("utf-8")}) + headers = http3.Headers({b"custom": "example ☃".encode("utf-8")}) assert repr(headers) == "Headers({'custom': 'example ☃'}, encoding='utf-8')" @@ -94,7 +94,7 @@ def test_headers_list_repr(): """ Headers should display with a list repr if they include multiple identical keys. """ - headers = httpcore.Headers([("custom", "example 1"), ("custom", "example 2")]) + headers = http3.Headers([("custom", "example 1"), ("custom", "example 2")]) assert ( repr(headers) == "Headers([('custom', 'example 1'), ('custom', 'example 2')])" ) @@ -105,7 +105,7 @@ def test_headers_decode_ascii(): Headers should decode as ascii by default. """ raw_headers = [(b"Custom", b"Example")] - headers = httpcore.Headers(raw_headers) + headers = http3.Headers(raw_headers) assert dict(headers) == {"custom": "Example"} assert headers.encoding == "ascii" @@ -115,7 +115,7 @@ def test_headers_decode_utf_8(): Headers containing non-ascii codepoints should default to decoding as utf-8. """ raw_headers = [(b"Custom", "Code point: ☃".encode("utf-8"))] - headers = httpcore.Headers(raw_headers) + headers = http3.Headers(raw_headers) assert dict(headers) == {"custom": "Code point: ☃"} assert headers.encoding == "utf-8" @@ -125,7 +125,7 @@ def test_headers_decode_iso_8859_1(): Headers containing non-UTF-8 codepoints should default to decoding as iso-8859-1. """ raw_headers = [(b"Custom", "Code point: ÿ".encode("iso-8859-1"))] - headers = httpcore.Headers(raw_headers) + headers = http3.Headers(raw_headers) assert dict(headers) == {"custom": "Code point: ÿ"} assert headers.encoding == "iso-8859-1" @@ -136,7 +136,7 @@ def test_headers_decode_explicit_encoding(): particular decoding. """ raw_headers = [(b"Custom", "Code point: ☃".encode("utf-8"))] - headers = httpcore.Headers(raw_headers) + headers = http3.Headers(raw_headers) headers.encoding = "iso-8859-1" assert dict(headers) == {"custom": "Code point: â\x98\x83"} assert headers.encoding == "iso-8859-1" @@ -146,8 +146,8 @@ def test_multiple_headers(): """ Most headers should split by commas for `getlist`, except 'Set-Cookie'. """ - h = httpcore.Headers([("set-cookie", "a, b"), ("set-cookie", "c")]) + h = http3.Headers([("set-cookie", "a, b"), ("set-cookie", "c")]) h.getlist("Set-Cookie") == ["a, b", "b"] - h = httpcore.Headers([("vary", "a, b"), ("vary", "c")]) + h = http3.Headers([("vary", "a, b"), ("vary", "c")]) h.getlist("Vary") == ["a", "b", "c"] diff --git a/tests/models/test_queryparams.py b/tests/models/test_queryparams.py index 90e4a4b7..369d43ce 100644 --- a/tests/models/test_queryparams.py +++ b/tests/models/test_queryparams.py @@ -1,4 +1,4 @@ -from httpcore import QueryParams +from http3 import QueryParams def test_queryparams(): diff --git a/tests/models/test_requests.py b/tests/models/test_requests.py index 79cbba36..b5126bc7 100644 --- a/tests/models/test_requests.py +++ b/tests/models/test_requests.py @@ -1,32 +1,32 @@ import pytest -import httpcore +import http3 def test_request_repr(): - request = httpcore.Request("GET", "http://example.org") + request = http3.Request("GET", "http://example.org") assert repr(request) == "" def test_no_content(): - request = httpcore.Request("GET", "http://example.org") + request = http3.Request("GET", "http://example.org") assert "Content-Length" not in request.headers def test_content_length_header(): - request = httpcore.Request("POST", "http://example.org", data=b"test 123") + request = http3.Request("POST", "http://example.org", data=b"test 123") assert request.headers["Content-Length"] == "8" def test_url_encoded_data(): - for RequestClass in (httpcore.Request, httpcore.AsyncRequest): + for RequestClass in (http3.Request, http3.AsyncRequest): request = RequestClass("POST", "http://example.org", data={"test": "123"}) assert request.headers["Content-Type"] == "application/x-www-form-urlencoded" assert request.content == b"test=123" def test_json_encoded_data(): - for RequestClass in (httpcore.Request, httpcore.AsyncRequest): + for RequestClass in (http3.Request, http3.AsyncRequest): request = RequestClass("POST", "http://example.org", json={"test": 123}) assert request.headers["Content-Type"] == "application/json" assert request.content == b'{"test": 123}' @@ -38,7 +38,7 @@ def test_transfer_encoding_header(): data = streaming_body(b"test 123") - request = httpcore.Request("POST", "http://example.org", data=data) + request = http3.Request("POST", "http://example.org", data=data) assert "Content-Length" not in request.headers assert request.headers["Transfer-Encoding"] == "chunked" @@ -46,14 +46,14 @@ def test_transfer_encoding_header(): def test_override_host_header(): headers = {"host": "1.2.3.4:80"} - request = httpcore.Request("GET", "http://example.org", headers=headers) + request = http3.Request("GET", "http://example.org", headers=headers) assert request.headers["Host"] == "1.2.3.4:80" def test_override_accept_encoding_header(): headers = {"Accept-Encoding": "identity"} - request = httpcore.Request("GET", "http://example.org", headers=headers) + request = http3.Request("GET", "http://example.org", headers=headers) assert request.headers["Accept-Encoding"] == "identity" @@ -64,30 +64,30 @@ def test_override_content_length_header(): data = streaming_body(b"test 123") headers = {"Content-Length": "8"} - request = httpcore.Request("POST", "http://example.org", data=data, headers=headers) + request = http3.Request("POST", "http://example.org", data=data, headers=headers) assert request.headers["Content-Length"] == "8" def test_url(): url = "http://example.org" - request = httpcore.Request("GET", url) + request = http3.Request("GET", url) assert request.url.scheme == "http" assert request.url.port == 80 assert request.url.full_path == "/" url = "https://example.org/abc?foo=bar" - request = httpcore.Request("GET", url) + request = http3.Request("GET", url) assert request.url.scheme == "https" assert request.url.port == 443 assert request.url.full_path == "/abc?foo=bar" def test_invalid_urls(): - with pytest.raises(httpcore.InvalidURL): - httpcore.Request("GET", "example.org") + with pytest.raises(http3.InvalidURL): + http3.Request("GET", "example.org") - with pytest.raises(httpcore.InvalidURL): - httpcore.Request("GET", "invalid://example.org") + with pytest.raises(http3.InvalidURL): + http3.Request("GET", "invalid://example.org") - with pytest.raises(httpcore.InvalidURL): - httpcore.Request("GET", "http:///foo") + with pytest.raises(http3.InvalidURL): + http3.Request("GET", "http:///foo") diff --git a/tests/models/test_responses.py b/tests/models/test_responses.py index f2d080ff..140d7f34 100644 --- a/tests/models/test_responses.py +++ b/tests/models/test_responses.py @@ -1,6 +1,6 @@ import pytest -import httpcore +import http3 def streaming_body(): @@ -14,14 +14,14 @@ async def async_streaming_body(): def test_response(): - response = httpcore.Response(200, content=b"Hello, world!") + response = http3.Response(200, content=b"Hello, world!") assert response.status_code == 200 assert response.reason_phrase == "OK" assert response.text == "Hello, world!" def test_response_repr(): - response = httpcore.Response(200, content=b"Hello, world!") + response = http3.Response(200, content=b"Hello, world!") assert repr(response) == "" @@ -31,7 +31,7 @@ def test_response_content_type_encoding(): """ headers = {"Content-Type": "text-plain; charset=latin-1"} content = "Latin 1: ÿ".encode("latin-1") - response = httpcore.Response(200, content=content, headers=headers) + response = http3.Response(200, content=content, headers=headers) assert response.text == "Latin 1: ÿ" assert response.encoding == "latin-1" @@ -41,7 +41,7 @@ def test_response_autodetect_encoding(): Autodetect encoding if there is no charset info in a Content-Type header. """ content = "おはようございます。".encode("EUC-JP") - response = httpcore.Response(200, content=content) + response = http3.Response(200, content=content) assert response.text == "おはようございます。" assert response.encoding == "EUC-JP" @@ -52,7 +52,7 @@ def test_response_fallback_to_autodetect(): """ headers = {"Content-Type": "text-plain; charset=invalid-codec-name"} content = "おはようございます。".encode("EUC-JP") - response = httpcore.Response(200, content=content, headers=headers) + response = http3.Response(200, content=content, headers=headers) assert response.text == "おはようございます。" assert response.encoding == "EUC-JP" @@ -64,7 +64,7 @@ def test_response_default_text_encoding(): """ content = b"Hello, world!" headers = {"Content-Type": "text/plain"} - response = httpcore.Response(200, content=content, headers=headers) + response = http3.Response(200, content=content, headers=headers) assert response.status_code == 200 assert response.encoding == "iso-8859-1" assert response.text == "Hello, world!" @@ -74,7 +74,7 @@ def test_response_default_encoding(): """ Default to utf-8 if all else fails. """ - response = httpcore.Response(200, content=b"") + response = http3.Response(200, content=b"") assert response.text == "" assert response.encoding == "utf-8" @@ -84,7 +84,7 @@ def test_response_non_text_encoding(): Default to apparent encoding for non-text content-type headers. """ headers = {"Content-Type": "image/png"} - response = httpcore.Response(200, content=b"xyz", headers=headers) + response = http3.Response(200, content=b"xyz", headers=headers) assert response.text == "xyz" assert response.encoding == "ascii" @@ -93,7 +93,7 @@ def test_response_set_explicit_encoding(): headers = { "Content-Type": "text-plain; charset=utf-8" } # Deliberately incorrect charset - response = httpcore.Response( + response = http3.Response( 200, content="Latin 1: ÿ".encode("latin-1"), headers=headers ) response.encoding = "latin-1" @@ -102,7 +102,7 @@ def test_response_set_explicit_encoding(): def test_response_force_encoding(): - response = httpcore.Response(200, content="Snowman: ☃".encode("utf-8")) + response = http3.Response(200, content="Snowman: ☃".encode("utf-8")) response.encoding = "iso-8859-1" assert response.status_code == 200 assert response.reason_phrase == "OK" @@ -111,7 +111,7 @@ def test_response_force_encoding(): def test_read_response(): - response = httpcore.Response(200, content=b"Hello, world!") + response = http3.Response(200, content=b"Hello, world!") assert response.status_code == 200 assert response.text == "Hello, world!" @@ -126,7 +126,7 @@ def test_read_response(): def test_raw_interface(): - response = httpcore.Response(200, content=b"Hello, world!") + response = http3.Response(200, content=b"Hello, world!") raw = b"" for part in response.raw(): @@ -135,7 +135,7 @@ def test_raw_interface(): def test_stream_interface(): - response = httpcore.Response(200, content=b"Hello, world!") + response = http3.Response(200, content=b"Hello, world!") content = b"" for part in response.stream(): @@ -145,7 +145,7 @@ def test_stream_interface(): @pytest.mark.asyncio async def test_async_stream_interface(): - response = httpcore.AsyncResponse(200, content=b"Hello, world!") + response = http3.AsyncResponse(200, content=b"Hello, world!") content = b"" async for part in response.stream(): @@ -154,7 +154,7 @@ async def test_async_stream_interface(): def test_stream_interface_after_read(): - response = httpcore.Response(200, content=b"Hello, world!") + response = http3.Response(200, content=b"Hello, world!") response.read() @@ -166,7 +166,7 @@ def test_stream_interface_after_read(): @pytest.mark.asyncio async def test_async_stream_interface_after_read(): - response = httpcore.AsyncResponse(200, content=b"Hello, world!") + response = http3.AsyncResponse(200, content=b"Hello, world!") await response.read() @@ -177,7 +177,7 @@ async def test_async_stream_interface_after_read(): def test_streaming_response(): - response = httpcore.Response(200, content=streaming_body()) + response = http3.Response(200, content=streaming_body()) assert response.status_code == 200 assert not response.is_closed @@ -191,7 +191,7 @@ def test_streaming_response(): @pytest.mark.asyncio async def test_async_streaming_response(): - response = httpcore.AsyncResponse(200, content=async_streaming_body()) + response = http3.AsyncResponse(200, content=async_streaming_body()) assert response.status_code == 200 assert not response.is_closed @@ -204,49 +204,49 @@ async def test_async_streaming_response(): def test_cannot_read_after_stream_consumed(): - response = httpcore.Response(200, content=streaming_body()) + response = http3.Response(200, content=streaming_body()) content = b"" for part in response.stream(): content += part - with pytest.raises(httpcore.StreamConsumed): + with pytest.raises(http3.StreamConsumed): response.read() @pytest.mark.asyncio async def test_async_cannot_read_after_stream_consumed(): - response = httpcore.AsyncResponse(200, content=async_streaming_body()) + response = http3.AsyncResponse(200, content=async_streaming_body()) content = b"" async for part in response.stream(): content += part - with pytest.raises(httpcore.StreamConsumed): + with pytest.raises(http3.StreamConsumed): await response.read() def test_cannot_read_after_response_closed(): - response = httpcore.Response(200, content=streaming_body()) + response = http3.Response(200, content=streaming_body()) response.close() - with pytest.raises(httpcore.ResponseClosed): + with pytest.raises(http3.ResponseClosed): response.read() @pytest.mark.asyncio async def test_async_cannot_read_after_response_closed(): - response = httpcore.AsyncResponse(200, content=async_streaming_body()) + response = http3.AsyncResponse(200, content=async_streaming_body()) await response.close() - with pytest.raises(httpcore.ResponseClosed): + with pytest.raises(http3.ResponseClosed): await response.read() def test_unknown_status_code(): - response = httpcore.Response(600) + response = http3.Response(600) assert response.status_code == 600 assert response.reason_phrase == "" assert response.text == "" diff --git a/tests/models/test_url.py b/tests/models/test_url.py index 2a45cb3b..365809bd 100644 --- a/tests/models/test_url.py +++ b/tests/models/test_url.py @@ -1,4 +1,4 @@ -from httpcore import URL +from http3 import URL def test_idna_url(): diff --git a/tests/test_api.py b/tests/test_api.py index 1247a416..b417a817 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -3,7 +3,7 @@ import functools import pytest -import httpcore +import http3 def threadpool(func): @@ -25,7 +25,7 @@ def threadpool(func): @threadpool def test_get(server): - response = httpcore.get("http://127.0.0.1:8000/") + response = http3.get("http://127.0.0.1:8000/") assert response.status_code == 200 assert response.reason_phrase == "OK" assert response.text == "Hello, world!" @@ -33,7 +33,7 @@ def test_get(server): @threadpool def test_post(server): - response = httpcore.post("http://127.0.0.1:8000/", data=b"Hello, world!") + response = http3.post("http://127.0.0.1:8000/", data=b"Hello, world!") assert response.status_code == 200 assert response.reason_phrase == "OK" @@ -45,41 +45,41 @@ def test_post_byte_iterator(server): yield b", " yield b"world!" - response = httpcore.post("http://127.0.0.1:8000/", data=data()) + response = http3.post("http://127.0.0.1:8000/", data=data()) assert response.status_code == 200 assert response.reason_phrase == "OK" @threadpool def test_options(server): - response = httpcore.options("http://127.0.0.1:8000/") + response = http3.options("http://127.0.0.1:8000/") assert response.status_code == 200 assert response.reason_phrase == "OK" @threadpool def test_head(server): - response = httpcore.head("http://127.0.0.1:8000/") + response = http3.head("http://127.0.0.1:8000/") assert response.status_code == 200 assert response.reason_phrase == "OK" @threadpool def test_put(server): - response = httpcore.put("http://127.0.0.1:8000/", data=b"Hello, world!") + response = http3.put("http://127.0.0.1:8000/", data=b"Hello, world!") assert response.status_code == 200 assert response.reason_phrase == "OK" @threadpool def test_patch(server): - response = httpcore.patch("http://127.0.0.1:8000/", data=b"Hello, world!") + response = http3.patch("http://127.0.0.1:8000/", data=b"Hello, world!") assert response.status_code == 200 assert response.reason_phrase == "OK" @threadpool def test_delete(server): - response = httpcore.delete("http://127.0.0.1:8000/") + response = http3.delete("http://127.0.0.1:8000/") assert response.status_code == 200 assert response.reason_phrase == "OK" diff --git a/tests/test_config.py b/tests/test_config.py index 4ee6d6e7..a77f2189 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -3,34 +3,34 @@ import ssl import pytest -import httpcore +import http3 @pytest.mark.asyncio async def test_load_ssl_config(): - ssl_config = httpcore.SSLConfig() + ssl_config = http3.SSLConfig() context = await ssl_config.load_ssl_context() assert context.verify_mode == ssl.VerifyMode.CERT_REQUIRED @pytest.mark.asyncio async def test_load_ssl_config_verify_non_existing_path(): - ssl_config = httpcore.SSLConfig(verify="/path/to/nowhere") + ssl_config = http3.SSLConfig(verify="/path/to/nowhere") with pytest.raises(IOError): await ssl_config.load_ssl_context() @pytest.mark.asyncio async def test_load_ssl_config_verify_existing_file(): - ssl_config = httpcore.SSLConfig(verify=httpcore.config.DEFAULT_CA_BUNDLE_PATH) + ssl_config = http3.SSLConfig(verify=http3.config.DEFAULT_CA_BUNDLE_PATH) context = await ssl_config.load_ssl_context() assert context.verify_mode == ssl.VerifyMode.CERT_REQUIRED @pytest.mark.asyncio async def test_load_ssl_config_verify_directory(): - path = os.path.dirname(httpcore.config.DEFAULT_CA_BUNDLE_PATH) - ssl_config = httpcore.SSLConfig(verify=path) + path = os.path.dirname(http3.config.DEFAULT_CA_BUNDLE_PATH) + ssl_config = http3.SSLConfig(verify=path) context = await ssl_config.load_ssl_context() assert context.verify_mode == ssl.VerifyMode.CERT_REQUIRED @@ -38,7 +38,7 @@ async def test_load_ssl_config_verify_directory(): @pytest.mark.asyncio async def test_load_ssl_config_cert_and_key(cert_and_key_paths): cert_path, key_path = cert_and_key_paths - ssl_config = httpcore.SSLConfig(cert=(cert_path, key_path)) + ssl_config = http3.SSLConfig(cert=(cert_path, key_path)) context = await ssl_config.load_ssl_context() assert context.verify_mode == ssl.VerifyMode.CERT_REQUIRED @@ -46,28 +46,28 @@ async def test_load_ssl_config_cert_and_key(cert_and_key_paths): @pytest.mark.asyncio async def test_load_ssl_config_cert_without_key_raises(cert_and_key_paths): cert_path, _ = cert_and_key_paths - ssl_config = httpcore.SSLConfig(cert=cert_path) + ssl_config = http3.SSLConfig(cert=cert_path) with pytest.raises(ssl.SSLError): await ssl_config.load_ssl_context() @pytest.mark.asyncio async def test_load_ssl_config_no_verify(verify=False): - ssl_config = httpcore.SSLConfig(verify=False) + ssl_config = http3.SSLConfig(verify=False) context = await ssl_config.load_ssl_context() assert context.verify_mode == ssl.VerifyMode.CERT_NONE def test_ssl_repr(): - ssl = httpcore.SSLConfig(verify=False) + ssl = http3.SSLConfig(verify=False) assert repr(ssl) == "SSLConfig(cert=None, verify=False)" def test_timeout_repr(): - timeout = httpcore.TimeoutConfig(timeout=5.0) + timeout = http3.TimeoutConfig(timeout=5.0) assert repr(timeout) == "TimeoutConfig(timeout=5.0)" - timeout = httpcore.TimeoutConfig(read_timeout=5.0) + timeout = http3.TimeoutConfig(read_timeout=5.0) assert ( repr(timeout) == "TimeoutConfig(connect_timeout=None, read_timeout=5.0, write_timeout=None)" @@ -75,32 +75,32 @@ def test_timeout_repr(): def test_limits_repr(): - limits = httpcore.PoolLimits(hard_limit=100) + limits = http3.PoolLimits(hard_limit=100) assert ( repr(limits) == "PoolLimits(soft_limit=None, hard_limit=100, pool_timeout=None)" ) def test_ssl_eq(): - ssl = httpcore.SSLConfig(verify=False) - assert ssl == httpcore.SSLConfig(verify=False) + ssl = http3.SSLConfig(verify=False) + assert ssl == http3.SSLConfig(verify=False) def test_timeout_eq(): - timeout = httpcore.TimeoutConfig(timeout=5.0) - assert timeout == httpcore.TimeoutConfig(timeout=5.0) + timeout = http3.TimeoutConfig(timeout=5.0) + assert timeout == http3.TimeoutConfig(timeout=5.0) def test_limits_eq(): - limits = httpcore.PoolLimits(hard_limit=100) - assert limits == httpcore.PoolLimits(hard_limit=100) + limits = http3.PoolLimits(hard_limit=100) + assert limits == http3.PoolLimits(hard_limit=100) def test_timeout_from_tuple(): - timeout = httpcore.TimeoutConfig(timeout=(5.0, 5.0, 5.0)) - assert timeout == httpcore.TimeoutConfig(timeout=5.0) + timeout = http3.TimeoutConfig(timeout=(5.0, 5.0, 5.0)) + assert timeout == http3.TimeoutConfig(timeout=5.0) def test_timeout_from_config_instance(): - timeout = httpcore.TimeoutConfig(timeout=(5.0)) - assert httpcore.TimeoutConfig(timeout) == httpcore.TimeoutConfig(timeout=5.0) + timeout = http3.TimeoutConfig(timeout=(5.0)) + assert http3.TimeoutConfig(timeout) == http3.TimeoutConfig(timeout=5.0) diff --git a/tests/test_decoders.py b/tests/test_decoders.py index ac795ca9..2e217909 100644 --- a/tests/test_decoders.py +++ b/tests/test_decoders.py @@ -3,7 +3,7 @@ import zlib import brotli import pytest -import httpcore +import http3 def test_deflate(): @@ -12,7 +12,7 @@ def test_deflate(): compressed_body = compressor.compress(body) + compressor.flush() headers = [(b"Content-Encoding", b"deflate")] - response = httpcore.Response(200, headers=headers, content=compressed_body) + response = http3.Response(200, headers=headers, content=compressed_body) assert response.content == body @@ -22,7 +22,7 @@ def test_gzip(): compressed_body = compressor.compress(body) + compressor.flush() headers = [(b"Content-Encoding", b"gzip")] - response = httpcore.Response(200, headers=headers, content=compressed_body) + response = http3.Response(200, headers=headers, content=compressed_body) assert response.content == body @@ -31,7 +31,7 @@ def test_brotli(): compressed_body = brotli.compress(body) headers = [(b"Content-Encoding", b"br")] - response = httpcore.Response(200, headers=headers, content=compressed_body) + response = http3.Response(200, headers=headers, content=compressed_body) assert response.content == body @@ -47,7 +47,7 @@ def test_multi(): ) headers = [(b"Content-Encoding", b"deflate, gzip")] - response = httpcore.Response(200, headers=headers, content=compressed_body) + response = http3.Response(200, headers=headers, content=compressed_body) assert response.content == body @@ -56,11 +56,11 @@ def test_multi_with_identity(): compressed_body = brotli.compress(body) headers = [(b"Content-Encoding", b"br, identity")] - response = httpcore.Response(200, headers=headers, content=compressed_body) + response = http3.Response(200, headers=headers, content=compressed_body) assert response.content == body headers = [(b"Content-Encoding", b"identity, br")] - response = httpcore.Response(200, headers=headers, content=compressed_body) + response = http3.Response(200, headers=headers, content=compressed_body) assert response.content == body @@ -73,7 +73,7 @@ def test_streaming(): yield compressor.flush() headers = [(b"Content-Encoding", b"gzip")] - response = httpcore.Response(200, headers=headers, content=compress(body)) + response = http3.Response(200, headers=headers, content=compress(body)) assert not hasattr(response, "body") assert response.read() == body @@ -83,6 +83,6 @@ def test_decoding_errors(header_value): headers = [(b"Content-Encoding", header_value)] body = b"test 123" compressed_body = brotli.compress(body)[3:] - with pytest.raises(httpcore.exceptions.DecodingError): - response = httpcore.Response(200, headers=headers, content=compressed_body) + with pytest.raises(http3.exceptions.DecodingError): + response = http3.Response(200, headers=headers, content=compressed_body) response.content diff --git a/tests/test_timeouts.py b/tests/test_timeouts.py index f1206fa8..bfced702 100644 --- a/tests/test_timeouts.py +++ b/tests/test_timeouts.py @@ -1,6 +1,6 @@ import pytest -from httpcore import ( +from http3 import ( AsyncClient, ConnectTimeout, PoolLimits,