Skip to content

Commit

Permalink
Merge pull request #117 from Colin-b/develop
Browse files Browse the repository at this point in the history
Release 0.26.0
  • Loading branch information
Colin-b authored Sep 18, 2023
2 parents 02f7f66 + c686de6 commit ad53e5f
Show file tree
Hide file tree
Showing 10 changed files with 562 additions and 164 deletions.
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.26.0] - 2023-09-18
### Added
- Added `proxy_url` parameter which allows matching on proxy URL.

## [0.25.0] - 2023-09-11
### Changed
- Requires [`httpx`](https://www.python-httpx.org)==0.25.\*
Expand Down Expand Up @@ -283,7 +287,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- First release, should be considered as unstable for now as design might change.

[Unreleased]: https:/Colin-b/pytest_httpx/compare/v0.25.0...HEAD
[Unreleased]: https:/Colin-b/pytest_httpx/compare/v0.26.0...HEAD
[0.26.0]: https:/Colin-b/pytest_httpx/compare/v0.25.0...v0.26.0
[0.25.0]: https:/Colin-b/pytest_httpx/compare/v0.24.0...v0.25.0
[0.24.0]: https:/Colin-b/pytest_httpx/compare/v0.23.1...v0.24.0
[0.23.1]: https:/Colin-b/pytest_httpx/compare/v0.23.0...v0.23.1
Expand Down
40 changes: 39 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<a href="https:/Colin-b/pytest_httpx/actions"><img alt="Build status" src="https:/Colin-b/pytest_httpx/workflows/Release/badge.svg"></a>
<a href="https:/Colin-b/pytest_httpx/actions"><img alt="Coverage" src="https://img.shields.io/badge/coverage-100%25-brightgreen"></a>
<a href="https:/psf/black"><img alt="Code style: black" src="https://img.shields.io/badge/code%20style-black-000000.svg"></a>
<a href="https:/Colin-b/pytest_httpx/actions"><img alt="Number of tests" src="https://img.shields.io/badge/tests-192 passed-blue"></a>
<a href="https:/Colin-b/pytest_httpx/actions"><img alt="Number of tests" src="https://img.shields.io/badge/tests-206 passed-blue"></a>
<a href="https://pypi.org/project/pytest-httpx/"><img alt="Number of downloads" src="https://img.shields.io/pypi/dm/pytest_httpx"></a>
</p>

Expand Down Expand Up @@ -146,6 +146,26 @@ def test_head(httpx_mock: HTTPXMock):

```

#### Matching on proxy URL

`proxy_url` parameter can either be a string, a python [re.Pattern](https://docs.python.org/3/library/re.html) instance or a [httpx.URL](https://www.python-httpx.org/api/#url) instance.

Matching is performed on the full proxy URL, query parameters included.

Order of parameters in the query string does not matter, however order of values do matter if the same parameter is provided more than once.

```python
import httpx
from pytest_httpx import HTTPXMock


def test_proxy_url(httpx_mock: HTTPXMock):
httpx_mock.add_response(proxy_url="http://test_proxy_url?a=1&b=2")

with httpx.Client(proxies={"https://": "http://test_proxy_url?a=2&b=1"}) as client:
response = client.get("https://test_url")
```

#### Matching on HTTP headers

Use `match_headers` parameter to specify the HTTP headers to reply to.
Expand Down Expand Up @@ -586,6 +606,8 @@ You can add criteria so that requests will be returned only in case of a more sp

Matching is performed on the full URL, query parameters included.

Order of parameters in the query string does not matter, however order of values do matter if the same parameter is provided more than once.

#### Matching on HTTP method

Use `method` parameter to specify the HTTP method (POST, PUT, DELETE, PATCH, HEAD) of the requests to retrieve.
Expand All @@ -594,6 +616,14 @@ Use `method` parameter to specify the HTTP method (POST, PUT, DELETE, PATCH, HEA

Matching is performed on equality.

#### Matching on proxy URL

`proxy_url` parameter can either be a string, a python [re.Pattern](https://docs.python.org/3/library/re.html) instance or a [httpx.URL](https://www.python-httpx.org/api/#url) instance.

Matching is performed on the full proxy URL, query parameters included.

Order of parameters in the query string does not matter, however order of values do matter if the same parameter is provided more than once.

#### Matching on HTTP headers

Use `match_headers` parameter to specify the HTTP headers executing the callback.
Expand All @@ -606,6 +636,14 @@ Use `match_content` parameter to specify the full HTTP body executing the callba

Matching is performed on equality.

##### Matching on HTTP JSON body

Use `match_json` parameter to specify the JSON decoded HTTP body executing the callback.

Matching is performed on equality. You can however use `unittest.mock.ANY` to do partial matching.

Note that `match_content` cannot be provided if `match_json` is also provided.

## Do not mock some requests

By default, `pytest-httpx` will mock every request.
Expand Down
4 changes: 2 additions & 2 deletions pytest_httpx/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def httpx_mock(
"_transport_for_url",
lambda self, url: real_sync_transport(self, url)
if url.host in non_mocked_hosts
else _PytestSyncTransport(mock),
else _PytestSyncTransport(real_sync_transport(self, url), mock),
)
# Mock asynchronous requests
real_async_transport = httpx.AsyncClient._transport_for_url
Expand All @@ -60,7 +60,7 @@ def httpx_mock(
"_transport_for_url",
lambda self, url: real_async_transport(self, url)
if url.host in non_mocked_hosts
else _PytestAsyncTransport(mock),
else _PytestAsyncTransport(real_async_transport(self, url), mock),
)
yield mock
mock.reset(assert_all_responses_were_requested)
34 changes: 34 additions & 0 deletions pytest_httpx/_httpx_internals.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import base64
from typing import (
Union,
Dict,
Expand All @@ -6,8 +7,10 @@
Iterable,
AsyncIterator,
Iterator,
Optional,
)

import httpcore
import httpx

# TODO Get rid of this internal import
Expand Down Expand Up @@ -36,3 +39,34 @@ async def __aiter__(self) -> AsyncIterator[bytes]:

AsyncIteratorByteStream.__init__(self, stream=Stream())
IteratorByteStream.__init__(self, stream=Stream())


def _to_httpx_url(url: httpcore.URL, headers: list[tuple[bytes, bytes]]) -> httpx.URL:
for name, value in headers:
if b"Proxy-Authorization" == name:
return httpx.URL(
scheme=url.scheme.decode(),
host=url.host.decode(),
port=url.port,
raw_path=url.target,
userinfo=base64.b64decode(value[6:]),
)

return httpx.URL(
scheme=url.scheme.decode(),
host=url.host.decode(),
port=url.port,
raw_path=url.target,
)


def _proxy_url(
real_transport: Union[httpx.BaseTransport, httpx.AsyncBaseTransport]
) -> Optional[httpx.URL]:
if isinstance(real_transport, httpx.HTTPTransport):
if isinstance(real_pool := real_transport._pool, httpcore.HTTPProxy):
return _to_httpx_url(real_pool._proxy_url, real_pool._proxy_headers)

if isinstance(real_transport, httpx.AsyncHTTPTransport):
if isinstance(real_pool := real_transport._pool, httpcore.AsyncHTTPProxy):
return _to_httpx_url(real_pool._proxy_url, real_pool._proxy_headers)
Loading

0 comments on commit ad53e5f

Please sign in to comment.