diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index 9e9d59e..e9eaaa2 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -11,24 +11,14 @@ jobs:
build:
name: Build & Publish
runs-on: ubuntu-latest
-
steps:
- - uses: actions/checkout@v2
- - name: Set up Python
- uses: actions/setup-python@v2
- with:
- python-version: 3.7
- - name: Pip cache
- uses: actions/cache@v1
+ - uses: actions/checkout@v3
+ uses: actions/setup-python@v4
with:
- path: ~/.cache/pip
- key: ${{ runner.os }}-pip
- restore-keys: |
- ${{ runner.os }}-pip
+ python-version: "3.10"
+ - run: pip install nox
- name: Build
- run: |
- pip install --upgrade nox
- nox -s docs
+ run: nox -N -s docs
- name: Publish
if: github.repository_owner == 'lundberg'
run: |
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 9055f35..f902069 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -1,12 +1,10 @@
name: test
on:
- pull_request:
- branches:
- - master
push:
branches:
- master
+ pull_request:
env:
FORCE_COLOR: 1
@@ -15,51 +13,52 @@ jobs:
test:
name: Test Python ${{ matrix.python-version }}
runs-on: ubuntu-latest
- needs: lint
strategy:
- max-parallel: 5
+ max-parallel: 4
matrix:
python-version: ["3.7", "3.8", "3.9", "3.10"]
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v2
+ uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- - name: Pip cache
- uses: actions/cache@v1
- with:
- path: ~/.cache/pip
- key: ${{ runner.os }}-pip
- restore-keys: |
- ${{ runner.os }}-pip
+ - run: pip install nox
- name: Test
- run: |
- pip install --upgrade nox
- nox -s test-${{ matrix.python-version }}
- - name: Upload report
- uses: codecov/codecov-action@v1
+ run: nox -N -s test-${{ matrix.python-version }} -- -v
+ - name: Upload coverage report
+ uses: codecov/codecov-action@v3
with:
- token: ${{secrets.CODECOV_TOKEN}}
name: Python ${{ matrix.python-version }}
+ files: ./coverage.xml
+ fail_ci_if_error: true
lint:
name: Check Linting
+ uses: less-action/reusables/.github/workflows/pre-commit.yaml@v6
+ with:
+ python-version: "3.10"
+
+ check-types:
+ name: Check Typing
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
- - name: Set up Python
- uses: actions/setup-python@v2
- with:
- python-version: 3.7
- - name: Pip cache
- uses: actions/cache@v1
- with:
- path: ~/.cache/pip
- key: ${{ runner.os }}-pip
- restore-keys: |
- ${{ runner.os }}-pip
- - name: Lint
- run: |
- pip install --upgrade nox
- nox -k "check + docs"
+ - uses: actions/checkout@v3
+ - uses: actions/setup-python@v4
+ with:
+ python-version: "3.7"
+ - run: pip install nox
+ - name: Run mypy
+ run: nox -N -s mypy
+
+ check-docs:
+ name: Check Docs
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - uses: actions/setup-python@v4
+ with:
+ python-version: "3.10"
+ - run: pip install nox
+ - name: Run mypy
+ run: nox -N -s docs
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 0000000..77debb5
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,98 @@
+default_language_version:
+ python: python3.10
+repos:
+ - repo: https://github.com/pre-commit/pre-commit-hooks
+ rev: v4.3.0
+ hooks:
+ - id: check-added-large-files
+ - id: check-case-conflict
+ - id: check-merge-conflict
+ - id: check-toml
+ - id: end-of-file-fixer
+ - id: trailing-whitespace
+ - id: debug-statements
+ - id: detect-private-key
+ - repo: https://github.com/asottile/pyupgrade
+ rev: v2.37.3
+ hooks:
+ - id: pyupgrade
+ args:
+ - --py37-plus
+ - --keep-runtime-typing
+ - repo: https://github.com/myint/autoflake
+ rev: v1.5.3
+ hooks:
+ - id: autoflake
+ args:
+ - --in-place
+ - --remove-all-unused-imports
+ - --ignore-init-module-imports
+ - repo: https://github.com/pycqa/isort
+ rev: 5.10.1
+ hooks:
+ - id: isort
+ - repo: https://github.com/psf/black
+ rev: 22.8.0
+ hooks:
+ - id: black
+ - repo: https://github.com/PyCQA/flake8
+ rev: 5.0.4
+ hooks:
+ - id: flake8
+ additional_dependencies:
+ - flake8-bugbear
+ - flake8-comprehensions
+ - flake8-tidy-imports
+ - flake8-print
+ - flake8-pytest-style
+ - flake8-datetimez
+ - repo: https://github.com/sirosen/check-jsonschema
+ rev: 0.18.2
+ hooks:
+ - id: check-github-workflows
+ - repo: https://github.com/asottile/yesqa
+ rev: v1.4.0
+ hooks:
+ - id: yesqa
+ additional_dependencies:
+ - flake8-bugbear
+ - flake8-comprehensions
+ - flake8-tidy-imports
+ - flake8-print
+ - flake8-pytest-style
+ - flake8-datetimez
+ - repo: https://github.com/pre-commit/mirrors-prettier
+ rev: "v2.7.1"
+ hooks:
+ - id: prettier
+ alias: format-markdown
+ types: [markdown]
+ args:
+ - --parser=markdown
+ - --print-width=88
+ - --prose-wrap=always
+ - repo: https://github.com/mgedmin/check-manifest
+ rev: "0.48"
+ hooks:
+ - id: check-manifest
+ args: ["--no-build-isolation"]
+
+exclude: |
+ (?x)(
+ /(
+ \.eggs
+ | \.git
+ | \.hg
+ | \.mypy_cache
+ | \.pytest_cache
+ | \.nox
+ | \.tox
+ | \.venv
+ | _build
+ | buck-out
+ | build
+ | dist
+ )/
+ | docs
+ | LICENSE\.md
+ )
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 34dbde9..b4cf1fe 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,22 +1,26 @@
# Changelog
+
All notable changes to this project will be documented in this file.
-The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
-and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and
+this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.20.0] - 2022-09-16
### Changed
-- Type Router.__getitem__ to not return optional routes, thanks @flaeppe (#216)
+
+- Type `Router.__getitem__` to not return optional routes, thanks @flaeppe (#216)
- Change `Call.response` to raise instead of returning optional response (#217)
- Change `CallList.last` to raise instead of return optional call (#217)
- Type `M()` to not return optional pattern, by introducing a `Noop` pattern (#217)
- Type `Route.pattern` to not be optional (#217)
### Fixed
+
- Correct type hints for side effects (#217)
### Added
+
- Runs `mypy` on both tests and respx (#217)
- Added nox test session for python 3.11 (#217)
- Added `Call.has_response` helper, now that `.response` raises (#217)
@@ -24,6 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [0.19.3] - 2022-09-14
### Fixed
+
- Fix typing for Route modulos arg
- Respect patterns with empty value when using equal lookup (#206)
- Use pytest asyncio auto mode (#212)
@@ -31,17 +36,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Wrap pytest function correctly, i.e. don't hide real function name (#213)
### Changed
-- Enable mypy strict_optional (#201)
+- Enable mypy strict_optional (#201)
## [0.19.2] - 2022-02-03
### Fixed
+
- Better cleanup before building egg, thanks @nebularazer (#198)
## [0.19.1] - 2022-01-10
### Fixed
+
- Allow first path segments containing colons, thanks @hannseman. (#192)
- Fix license classifier, thanks @shadchin (#195)
- Fix typos, thanks @kianmeng (#194)
@@ -49,140 +56,178 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [0.19.0] - 2021-11-15
### Fixed
+
- Support HTTPX 0.21.0. (#189)
- Use Session.notify when chaining nox sessions, thanks @flaeppe. (#188)
-- Add overloads to MockRouter.__call__, thanks @flaeppe. (#187)
+- Add overloads to `MockRouter.__call__`, thanks @flaeppe. (#187)
- Enhance AND pattern evaluation to fail fast. (#185)
- Fix CallList assertion error message. (#178)
### Changed
+
- Prevent method and url as lookups in HTTP method helpers, thanks @flaeppe. (#183)
- Fail pattern match when JSON path not found. (#184)
## [0.18.2] - 2021-10-22
### Fixed
+
- Include extensions when instantiating request in HTTPCoreMocker. (#176)
## [0.18.1] - 2021-10-20
### Fixed
+
- Respect ordered param values. (#172)
### Changed
+
- Raise custom error types for assertion checks. (#174)
## [0.18.0] - 2021-10-14
### Fixed
+
- Downgrade `HTTPX` requirement to 0.20.0. (#170)
### Added
-- Add support for matching param with *ANY* value. (#167)
+
+- Add support for matching param with _ANY_ value. (#167)
## [0.18.0b0] - 2021-09-15
### Changed
+
- Deprecate RESPX MockTransport in favour of HTTPX MockTransport. (#152)
### Fixed
+
- Support `HTTPX` 1.0.0b0. (#164)
- Allow tuples as params to align with httpx, thanks @shelbylsmith. (#151)
- Fix xfail marked tests. (#153)
- Only publish docs for upstream repo, thanks @hugovk. (#161)
### Added
+
- Add optional route arg to side effects. (#158)
## [0.17.1] - 2021-06-05
+
### Added
+
- Implement support for async side effects in router. (#147)
- Support mocking responses using asgi/wsgi apps. (#146)
- Added pytest fixture and configuration marker. (#150)
### Fixed
+
- Typo in import from examples.md, thanks @shelbylsmith. (#148)
- Fix pass-through test case. (#149)
## [0.17.0] - 2021-04-27
+
### Changed
+
- Require `HTTPX` 0.18.0 and implement the new transport API. (PR #142)
- Removed ASGI and WSGI transports from httpcore patch list. (PR #131)
- Don't pre-read mocked async response streams. (PR #136)
### Fixed
+
- Fixed syntax highlighting in docs, thanks @florimondmanca. (PR #134)
- Type check `route.return_value`, thanks @tzing. (PR #133)
- Fixed a typo in the docs, thanks @lewoudar. (PR #139)
### Added
+
- Added support for adding/removing patch targets. (PR #131)
- Added test session for python 3.10. (PR #140)
- Added RESPX Mock Swallowtail to README. (PR #128)
## [0.16.3] - 2020-12-14
+
### Fixed
+
- Fixed decorator `respx_mock` kwarg, mistreated as a `pytest` fixture. (PR #117)
- Fixed `JSON` pattern sometimes causing a `JSONDecodeError`. (PR #124)
### Added
+
- Snapshot and rollback of routes' pattern and name. (PR #120)
- Internally extracted a `RouteList` from `Router`. (PR #120)
- Auto registration of `Mocker` implementations and their `using` name. (PR #121)
- Added `HTTPXMocker`, optionally patching `HTTPX`. (PR #122)
### Changed
+
- Protected a routes' pattern to be modified. (PR #120)
## [0.16.2] - 2020-11-26
+
### Added
+
- Easier support for using HTTPX MockTransport. (PR #118)
- Support mixed case for `method__in` and `scheme__in` pattern lookups. (PR #113)
### Fixed
+
- Handle missing path in URL pattern (PR #113)
### Changed
+
- Refactored internal mocking vs `MockTransport`. (PR #112)
### Removed
+
- Dropped raw request support when parsing patterns (PR #113)
## [0.16.1] - 2020-11-16
+
### Added
+
- Extended `url` pattern with support for `HTTPX` proxy url format. (PR #110)
- Extended `host` pattern with support for regex lookup. (PR #110)
- Added `respx.request(...)`. (PR #111)
### Changed
+
- Deprecated old `MockTransport` in favour of `respx.mock(...)`. (PR #109)
- Wrapping actual `MockTransport` in `MockRouter`, instead of extending. (PR #109)
- Extracted a `HTTPXMock`, for transport patching, from `MockRouter`. (PR #109)
## [0.16.0] - 2020-11-13
+
One year since first release, yay!
### Removed
+
- Dropped all deprecated APIs and models, see `0.15.0` Changed section. (PR #105)
### Added
+
- Added support for content, data and json patterns. (PR #106)
- Automatic pattern registration when subclassing Pattern. (PR #108)
### Fixed
+
- Multiple snapshots to support nested mock routers. (PR #107)
## [0.15.1] - 2020-11-10
+
### Added
+
- Snapshot routes and mocks when starting router, rollback when stopping. (PR #102)
- Added support for base_url combined with pattern lookups. (PR #103)
- Added support for patterns/lookups to the HTTP method helpers. (PR #104)
### Fixed
+
- Fix to not clear routes added outside mock context when stopping router. (PR #102)
## [0.15.0] - 2020-11-09
+
### Added
+
- Added `respx.route(...)` with enhanced request pattern matching. (PR #96)
- Added support for AND/OR when request pattern matching. (PR #96)
- Added support for adding responses to a route using % operator. (PR #96)
@@ -196,6 +241,7 @@ One year since first release, yay!
- Introduced Route `.return_value` and `.side_effect` setters. (PR #101)
### Changed
+
- Deprecated mixing of request pattern and response details in all API's. (PR #96)
- Deprecated passing http method as arg in `respx.add` in favour of `method=`. (PR #96)
- Deprecated `alias=...` in favour of `name=...` when adding routes. (PR #96)
@@ -209,12 +255,15 @@ One year since first release, yay!
- Stacked responses no longer keeps and repeats last response. (PR #101)
### Removed
+
- Removed support for regex `base_url`. (PR #96)
- Dropped support for `async` side effects (callbacks). (PR #97)
- Dropped support for mixing side effect (callback) and response details. (PR #97)
## [0.14.0] - 2020-10-15
+
### Added
+
- Added `text`, `html` and `json` content shorthands to ResponseTemplate. (PR #82)
- Added `text`, `html` and `json` content shorthands to high level API. (PR #93)
- Added support to set `http_version` for a mocked response. (PR #82)
@@ -225,159 +274,223 @@ One year since first release, yay!
- Added Python 3.9 to supported versions and test suite, thanks @jairhenrique. (PR #89)
### Changed
+
- `ResponseTemplate.content` as proper getter, i.e. no resolve/encode to bytes. (PR #82)
- Enhanced headers by using HTTPX Response when encoding raw responses. (PR #82)
- Deprecated `respx.stats` in favour of `respx.calls`, thanks @SlavaSkvortsov. (PR #92)
### Fixed
+
- Recorded requests in call stats are pre-read like the responses. (PR #86)
- Postponed request decoding for enhanced performance. (PR #91)
- Lazy call history for enhanced performance, thanks @SlavaSkvortsov. (PR #92)
### Removed
+
- Removed auto setting the `Content-Type: text/plain` header. (PR #82)
## [0.13.0] - 2020-09-30
+
### Fixed
+
- Fixed support for `HTTPX` 0.15. (PR #77)
### Added
+
- Added global `respx.pop` api, thanks @paulineribeyre. (PR #72)
### Removed
+
- Dropped deprecated `HTTPXMock` in favour of `MockTransport`.
- Dropped deprecated `respx.request` in favour of `respx.add`.
- Removed `HTTPX` max version requirement in setup.py.
## [0.12.1] - 2020-08-21
+
### Fixed
+
- Fixed non-iterable pass-through responses. (PR #68)
## [0.12.0] - 2020-08-17
+
### Changed
+
- Dropped no longer needed `asynctest` dependency, in favour of built-in mock. (PR #69)
## [0.11.3] - 2020-08-13
+
### Fixed
+
- Fixed support for `HTTPX` 0.14.0. (PR #45)
## [0.11.2] - 2020-06-25
+
### Added
+
- Added support for pop'ing a request pattern by alias, thanks @radeklat. (PR #60)
## [0.11.1] - 2020-06-01
+
### Fixed
+
- Fixed mocking `HTTPX` clients instantiated with proxies. (PR #58)
- Fixed matching URL patterns with missing path. (PR #59)
## [0.11.0] - 2020-05-29
+
### Fixed
+
- Fixed support for `HTTPX` 0.13. (PR #57)
### Added
+
- Added support for mocking out `HTTP Core`.
- Added support for using mock transports with `HTTPX` clients without patching.
- Include LICENSE.md in source distribution, thanks @synapticarbors.
### Changed
+
- Renamed passed mock to decorated functions from `httpx_mock` to `respx_mock`.
- Renamed `HTTPXMock` to `MockTransport`, but kept a deprecated `HTTPXMock` subclass.
- Deprecated `respx.request()` in favour of `respx.add()`.
## [0.10.1] - 2020-03-11
+
### Fixed
+
- Fixed support for `HTTPX` 0.12.0. (PR #45)
## [0.10.0] - 2020-01-30
+
### Changed
+
- Refactored high level and internal api for better editor autocompletion. (PR #44)
## [0.9.0] - 2020-01-22
+
### Fixed
+
- Fixed usage of nested or parallel mock instances. (PR #39)
## [0.8.3] - 2020-01-10
+
### Fixed
+
- Fixed support for `HTTPX` 0.11.0 sync api. (PR #38)
## [0.8.2] - 2020-01-07
+
### Fixed
+
- Renamed refactored httpx internals. (PR #37)
## [0.8.1] - 2019-12-09
+
### Added
+
- Added support for configuring patterns `base_url`. (PR #34)
- Added manifest and `py.typed` files.
### Fixed
+
- Fixed support for `HTTPX` 0.9.3 refactorizations. (PR #35)
## [0.8] - 2019-11-27
+
### Added
+
- Added documentation built with `mkdocs`. (PR #30)
### Changed
+
- Dropped sync support and now requires `HTTPX` version 0.8+. (PR #32)
- Renamed `respx.mock` module to `respx.api`. (PR #29)
- Refactored tests- and checks-runner to `nox`. (PR #31)
## [0.7.4] - 2019-11-24
+
### Added
+
- Allowing assertions to be configured through decorator and context manager. (PR #28)
## [0.7.3] - 2019-11-21
+
### Added
+
- Allows `mock` decorator to be used as sync or async context manager. (PR #27)
## [0.7.2] - 2019-11-21
+
### Added
+
- Added `stats` to high level API and patterns, along with `call_count`. (PR #25)
### Fixed
+
- Allowing headers to be modified within a pattern match callback. (PR #26)
## [0.7.1] - 2019-11-20
+
### Fixed
+
- Fixed responses in call stats when using synchronous `HTTPX` client. (PR #23)
## [0.7] - 2019-11-19
+
### Added
+
- Added support for `pass_through` patterns. (PR #20)
- Added `assert_all_mocked` feature and setting. (PR #21)
### Changed
+
- Requires all `HTTPX` requests to be mocked.
## [0.6] - 2019-11-18
+
### Changed
+
- Renamed `activate` decorator to `mock`. (PR #15)
## [0.5] - 2019-11-18
+
### Added
+
- Added `assert_all_called` feature and setting. (PR #14)
### Changed
+
- Clears call stats when exiting decorator.
## [0.4] - 2019-11-16
+
### Changed
+
- Renamed python package to `respx`. (PR #12)
- Renamed `add()` to `request()` and added HTTP method shorthands. (PR #13)
## [0.3.1] - 2019-11-16
+
### Changed
+
- Renamed PyPI package to `respx`.
## [0.3] - 2019-11-15
+
### Added
+
- Exposes `responsex` high level API along with a `activate` decorator. (PR #5)
- Added support for custom pattern match callback function. (PR #7)
- Added support for repeated patterns. (PR #8)
## [0.2] - 2019-11-14
+
### Added
+
- Added support for any `HTTPX` concurrency backend.
## [0.1] - 2019-11-13
+
### Added
+
- Initial POC.
diff --git a/MANIFEST.in b/MANIFEST.in
index 01d2dd3..118fd71 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,3 +1,10 @@
+recursive-exclude .github *
+recursive-exclude docs *
+recursive-exclude tests *
+exclude *.yaml
+exclude *.xml
+exclude flake.*
+exclude noxfile.py
include README.md
include CHANGELOG.md
include LICENSE.md
diff --git a/Makefile b/Makefile
deleted file mode 100644
index 77703ce..0000000
--- a/Makefile
+++ /dev/null
@@ -1,18 +0,0 @@
-.PHONY: test
-test:
- nox
-
-.PHONY: clean
-clean:
- rm -rf build dist respx.egg-info
-
-.PHONY: build
-build: clean
- python -m pip install --upgrade pip
- python -m pip install --upgrade wheel
- python setup.py sdist bdist_wheel
-
-.PHONY: release
-release: build
- python -m pip install --upgrade twine
- python -m twine upload dist/*
diff --git a/README.md b/README.md
index f68efd8..d8e874a 100644
--- a/README.md
+++ b/README.md
@@ -7,19 +7,28 @@
---
-[![tests](https://img.shields.io/github/workflow/status/lundberg/respx/test?label=tests&logo=github&logoColor=white&style=for-the-badge)](https://github.com/lundberg/respx/actions/workflows/test.yml) [![codecov](https://img.shields.io/codecov/c/github/lundberg/respx?logo=codecov&logoColor=white&style=for-the-badge)](https://codecov.io/gh/lundberg/respx) [![PyPi Version](https://img.shields.io/pypi/v/respx?logo=pypi&logoColor=white&style=for-the-badge)](https://pypi.org/project/respx/) [![Python Versions](https://img.shields.io/pypi/pyversions/respx?logo=python&logoColor=white&style=for-the-badge)](https://pypi.org/project/respx/)
+[![tests](https://img.shields.io/github/workflow/status/lundberg/respx/test?label=tests&logo=github&logoColor=white&style=for-the-badge)](https://github.com/lundberg/respx/actions/workflows/test.yml)
+[![codecov](https://img.shields.io/codecov/c/github/lundberg/respx?logo=codecov&logoColor=white&style=for-the-badge)](https://codecov.io/gh/lundberg/respx)
+[![PyPi Version](https://img.shields.io/pypi/v/respx?logo=pypi&logoColor=white&style=for-the-badge)](https://pypi.org/project/respx/)
+[![Python Versions](https://img.shields.io/pypi/pyversions/respx?logo=python&logoColor=white&style=for-the-badge)](https://pypi.org/project/respx/)
## Documentation
-Full documentation is available at [lundberg.github.io/respx](https://lundberg.github.io/respx/)
+Full documentation is available at
+[lundberg.github.io/respx](https://lundberg.github.io/respx/)
## QuickStart
-RESPX is a simple, *yet powerful*, utility for mocking out the [HTTPX](https://www.python-httpx.org/), *and [HTTP Core](https://www.encode.io/httpcore/)*, libraries.
+RESPX is a simple, _yet powerful_, utility for mocking out the
+[HTTPX](https://www.python-httpx.org/), _and
+[HTTP Core](https://www.encode.io/httpcore/)_, libraries.
-Start by [patching](https://lundberg.github.io/respx/guide/#mock-httpx) `HTTPX`, using `respx.mock`, then add request [routes](https://lundberg.github.io/respx/guide/#routing-requests) to mock [responses](https://lundberg.github.io/respx/guide/#mocking-responses).
+Start by [patching](https://lundberg.github.io/respx/guide/#mock-httpx) `HTTPX`, using
+`respx.mock`, then add request
+[routes](https://lundberg.github.io/respx/guide/#routing-requests) to mock
+[responses](https://lundberg.github.io/respx/guide/#mocking-responses).
-``` python
+```python
import httpx
import respx
@@ -34,14 +43,16 @@ def test_example():
assert response.status_code == 204
```
-> Read the [User Guide](https://lundberg.github.io/respx/guide/) for a complete walk-through.
-
+> Read the [User Guide](https://lundberg.github.io/respx/guide/) for a complete
+> walk-through.
### pytest + httpx
-For a neater `pytest` experience, RESPX includes a `respx_mock` *fixture* for easy `HTTPX` mocking, along with an optional `respx` *marker* to fine-tune the mock [settings](https://lundberg.github.io/respx/api/#configuration).
+For a neater `pytest` experience, RESPX includes a `respx_mock` _fixture_ for easy
+`HTTPX` mocking, along with an optional `respx` _marker_ to fine-tune the mock
+[settings](https://lundberg.github.io/respx/api/#configuration).
-``` python
+```python
import httpx
import pytest
@@ -59,14 +70,14 @@ def test_with_marker(respx_mock):
assert response.status_code == 204
```
-
## Installation
Install with pip:
-``` console
+```console
$ pip install respx
```
-Requires Python 3.7+ and HTTPX 0.21+.
-See [Changelog](https://github.com/lundberg/respx/blob/master/CHANGELOG.md) for older HTTPX compatibility.
+Requires Python 3.7+ and HTTPX 0.21+. See
+[Changelog](https://github.com/lundberg/respx/blob/master/CHANGELOG.md) for older HTTPX
+compatibility.
diff --git a/Taskfile.yaml b/Taskfile.yaml
new file mode 100644
index 0000000..b08ea34
--- /dev/null
+++ b/Taskfile.yaml
@@ -0,0 +1,64 @@
+version: "3"
+
+tasks:
+ default:
+ cmds: [task: all]
+
+ all:
+ desc: Run test suite, mypy & linting
+ label: all -- [nox options]
+ silent: true
+ deps: [tools]
+ cmds:
+ - .venv/bin/nox -k "test + mypy" {{.CLI_ARGS | default "-R"}}
+ - task: lint
+
+ test:
+ desc: Run single test suite
+ label: test -- [pytest options]
+ silent: true
+ deps: [tools]
+ cmds: [".venv/bin/nox -R -s test-3.10 -- {{.CLI_ARGS}}"]
+
+ mypy:
+ desc: Statically type check python files
+ silent: true
+ deps: [tools]
+ cmds: [.venv/bin/nox -R -s mypy]
+
+ lint:
+ desc: Lint project files
+ silent: true
+ deps: [tools]
+ cmds: [.venv/bin/pre-commit run --all-files]
+
+ docs:
+ desc: Start docs server, in watch mode
+ silent: true
+ deps: [tools]
+ cmds: [.venv/bin/nox -R -s docs -- serve]
+
+ reset:
+ desc: Delete environment and artifacts
+ silent: true
+ cmds:
+ - echo Deleting environment and artifacts ...
+ - rm -rf \
+ .venv .nox .mypy_cache .pytest_cache respx.egg-info .coverage coverage.xml
+
+ tools:
+ internal: true
+ silent: true
+ run: once
+ deps: [venv]
+ cmds: [.venv/bin/python -m pip install nox pre-commit]
+ status:
+ - test -f .venv/bin/nox
+ - test -f .venv/bin/pre-commit
+
+ venv:
+ internal: true
+ silent: true
+ run: once
+ cmds: [python -m venv --copies --upgrade-deps .venv > /dev/null]
+ status: [test -d .venv]
diff --git a/mkdocs.yml b/mkdocs.yaml
similarity index 100%
rename from mkdocs.yml
rename to mkdocs.yaml
diff --git a/noxfile.py b/noxfile.py
index 4041dc1..249c1f8 100644
--- a/noxfile.py
+++ b/noxfile.py
@@ -2,11 +2,7 @@
nox.options.stop_on_first_error = True
nox.options.reuse_existing_virtualenvs = True
-nox.options.keywords = "test + check"
-
-source_files = ("respx", "tests", "setup.py", "noxfile.py")
-lint_requirements = ("flake8", "black", "isort")
-docs_requirements = ("mkdocs", "mkdocs-material", "mkautodoc>=0.1.0")
+nox.options.keywords = "test + mypy"
@nox.session(python=["3.7", "3.8", "3.9", "3.10", "3.11"])
@@ -15,47 +11,23 @@ def test(session):
session.install("--upgrade", *deps)
session.install("-e", ".")
- options = session.posargs
- if "-k" in options or "-x" in options:
- options.append("--no-cov")
+ if any(option in session.posargs for option in ("-k", "-x")):
+ session.posargs.append("--no-cov")
- session.run("pytest", "-v", *options)
+ session.run("pytest", *session.posargs)
@nox.session(python="3.7")
-def check(session):
- session.install("--upgrade", "flake8-bugbear", "mypy", *lint_requirements)
+def mypy(session):
+ session.install("--upgrade", "mypy")
session.install("-e", ".")
-
- session.run("black", "--check", "--diff", "--target-version=py37", *source_files)
- session.run("isort", "--check", "--diff", "--project=respx", *source_files)
- session.run("flake8", *source_files)
session.run("mypy")
-@nox.session
-def lint(session):
- session.install("--upgrade", "autoflake", *lint_requirements)
-
- session.run("autoflake", "--in-place", "--recursive", *source_files)
- session.run("isort", "--project=respx", *source_files)
- session.run("black", "--target-version=py37", *source_files)
-
- session.notify("check")
-
-
-@nox.session
+@nox.session(python="3.10")
def docs(session):
- session.install("--upgrade", *docs_requirements)
+ deps = ["mkdocs", "mkdocs-material", "mkautodoc>=0.1.0"]
+ session.install("--upgrade", *deps)
session.install("-e", ".")
-
args = session.posargs if session.posargs else ["build"]
session.run("mkdocs", *args)
-
-
-@nox.session(reuse_venv=True)
-def watch(session):
- session.install("--upgrade", *docs_requirements)
- session.install("-e", ".")
-
- session.run("mkdocs", "serve")
diff --git a/respx/plugin.py b/respx/plugin.py
index 9c39660..e5c78c6 100644
--- a/respx/plugin.py
+++ b/respx/plugin.py
@@ -16,7 +16,7 @@ def pytest_configure(config):
)
-@pytest.fixture
+@pytest.fixture()
def respx_mock(request):
respx_marker = request.node.get_closest_marker("respx")
diff --git a/tests/conftest.py b/tests/conftest.py
index 47b3fcb..766680b 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -7,13 +7,13 @@
pytest_plugins = ["pytester"]
-@pytest.fixture
+@pytest.fixture()
async def client():
async with httpx.AsyncClient() as client:
yield client
-@pytest.fixture
+@pytest.fixture()
async def my_mock():
async with respx.mock(
base_url="https://httpx.mock", using="httpcore"
diff --git a/tests/test_api.py b/tests/test_api.py
index 4a91442..8fcbb23 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -73,7 +73,7 @@ async def test_http_methods(client):
@pytest.mark.parametrize(
- "url,pattern",
+ ("url", "pattern"),
[
("https://foo.bar", "https://foo.bar"),
("https://foo.bar/baz/", None),
@@ -134,7 +134,7 @@ async def test_status_code(client):
@pytest.mark.parametrize(
- "headers,content_type,expected",
+ ("headers", "content_type", "expected"),
[
({"X-Foo": "bar"}, None, {"X-Foo": "bar"}),
(
@@ -161,7 +161,7 @@ async def test_headers(client, headers, content_type, expected):
@pytest.mark.parametrize(
- "content,expected",
+ ("content", "expected"),
[
(b"eldr\xc3\xa4v", "eldräv"),
("äpple", "äpple"),
@@ -178,7 +178,7 @@ async def test_text_encoding(client, content, expected):
@pytest.mark.parametrize(
- "key,value,expected_content_type",
+ ("key", "value", "expected_content_type"),
[
("content", b"foobar", None),
("content", "foobar", None),
@@ -186,7 +186,6 @@ async def test_text_encoding(client, content, expected):
("json", {"foo": "bar"}, "application/json"),
("text", "foobar", "text/plain; charset=utf-8"),
("html", "foobar", "text/html; charset=utf-8"),
- ("json", {"foo": "bar"}, "application/json"),
],
)
async def test_content_variants(client, key, value, expected_content_type):
@@ -207,7 +206,7 @@ async def test_content_variants(client, key, value, expected_content_type):
@pytest.mark.parametrize(
- "content,headers,expected_headers",
+ ("content", "headers", "expected_headers"),
[
(
{"foo": "bar"},
@@ -345,23 +344,24 @@ def callback(request, name):
)
assert response.text == "hello lundberg"
+ respx_mock.get("https://ham.spam/").mock(
+ side_effect=lambda req: "invalid" # type: ignore[arg-type,return-value]
+ )
+
+ def _callback(request):
+ raise httpcore.NetworkError()
+
+ respx_mock.get("https://egg.plant").mock(side_effect=_callback)
+
with pytest.raises(TypeError):
- respx_mock.get("https://ham.spam/").mock(
- side_effect=lambda req: "invalid" # type: ignore[arg-type,return-value]
- )
await client.get("https://ham.spam/")
with pytest.raises(httpx.NetworkError):
-
- def _callback(request):
- raise httpcore.NetworkError()
-
- respx_mock.get("https://egg.plant").mock(side_effect=_callback)
await client.get("https://egg.plant/")
@pytest.mark.parametrize(
- "using,route,expected",
+ ("using", "route", "expected"),
[
("httpcore", Route(url="https://example.org/").pass_through(), True),
("httpx", Route(url="https://example.org/").pass_through(), True),
@@ -417,7 +417,7 @@ def content(request, page):
@pytest.mark.parametrize(
- "method_str, client_method_attr",
+ ("method_str", "client_method_attr"),
[
("DELETE", "delete"),
("delete", "delete"),
@@ -459,7 +459,7 @@ def test_pop():
@respx.mock
@pytest.mark.parametrize(
- "url,params,call_url,call_params",
+ ("url", "params", "call_url", "call_params"),
[
("https://foo/", "foo=bar", "https://foo/", "foo=bar"),
("https://foo/", b"foo=bar", "https://foo/", b"foo=bar"),
@@ -485,7 +485,7 @@ async def test_params_match(client, url, params, call_url, call_params):
@pytest.mark.parametrize(
- "base,url",
+ ("base", "url"),
[
(None, "https://foo.bar/baz/"),
("", "https://foo.bar/baz/"),
@@ -515,7 +515,7 @@ def test_add():
with pytest.raises(TypeError):
respx.add(route, status_code=418) # type: ignore[call-arg]
- with pytest.raises(ValueError):
+ with pytest.raises(ValueError, match="Invalid route"):
respx.add("GET") # type: ignore[arg-type]
with pytest.raises(NotImplementedError):
diff --git a/tests/test_mock.py b/tests/test_mock.py
index 5cb2b7b..b804e07 100644
--- a/tests/test_mock.py
+++ b/tests/test_mock.py
@@ -403,7 +403,7 @@ async def test_start_stop(client):
@pytest.mark.parametrize(
- "assert_all_called,do_post,raises",
+ ("assert_all_called", "do_post", "raises"),
[
(True, False, pytest.raises(AllCalledAssertionError)),
(True, True, does_not_raise()),
@@ -426,7 +426,7 @@ async def test_assert_all_called(client, assert_all_called, do_post, raises):
@pytest.mark.parametrize(
- "assert_all_mocked,raises",
+ ("assert_all_mocked", "raises"),
[(True, pytest.raises(AllMockedAssertionError)), (False, does_not_raise())],
)
async def test_assert_all_mocked(client, assert_all_mocked, raises):
@@ -680,7 +680,7 @@ def baz():
@pytest.mark.parametrize(
- "url,port",
+ ("url", "port"),
[
("https://foo.bar/", None),
("https://foo.bar:443/", 443),
diff --git a/tests/test_patterns.py b/tests/test_patterns.py
index f846654..b0bc5b9 100644
--- a/tests/test_patterns.py
+++ b/tests/test_patterns.py
@@ -36,7 +36,7 @@ def test_bitwise_and():
@pytest.mark.parametrize(
- "method,url,expected",
+ ("method", "url", "expected"),
[
("GET", "https://foo.bar/", True),
("GET", "https://foo.bar/baz/", False),
@@ -80,7 +80,7 @@ def test_noop_pattern():
@pytest.mark.parametrize(
- "kwargs,url,expected",
+ ("kwargs", "url", "expected"),
[
({"params__eq": {}}, "https://foo.bar/", True),
({"params__eq": {}}, "https://foo.bar/?x=y", False),
@@ -93,7 +93,7 @@ def test_m_pattern(kwargs, url, expected):
@pytest.mark.parametrize(
- "lookup,value,expected",
+ ("lookup", "value", "expected"),
[
(Lookup.EQUAL, "GET", True),
(Lookup.EQUAL, "get", True),
@@ -108,7 +108,7 @@ def test_method_pattern(lookup, value, expected):
@pytest.mark.parametrize(
- "lookup,headers,request_headers,expected",
+ ("lookup", "headers", "request_headers", "expected"),
[
(Lookup.CONTAINS, {"X-Foo": "bar"}, {"x-foo": "bar"}, True),
(Lookup.CONTAINS, {"content-type": "text/plain"}, "", False),
@@ -126,7 +126,7 @@ def test_headers_pattern_hash():
@pytest.mark.parametrize(
- "lookup,cookies,request_cookies,expected",
+ ("lookup", "cookies", "request_cookies", "expected"),
[
(Lookup.CONTAINS, {"foo": "bar"}, {"ham": "spam", "foo": "bar"}, True),
(Lookup.CONTAINS, {"foo": "bar"}, {"ham": "spam"}, False),
@@ -149,7 +149,7 @@ def test_cookies_pattern__hash():
@pytest.mark.parametrize(
- "lookup,scheme,expected",
+ ("lookup", "scheme", "expected"),
[
(Lookup.EQUAL, "https", True),
(Lookup.EQUAL, "HTTPS", True),
@@ -163,7 +163,7 @@ def test_scheme_pattern(lookup, scheme, expected):
@pytest.mark.parametrize(
- "lookup,host,expected",
+ ("lookup", "host", "expected"),
[
(Lookup.EQUAL, "foo.bar", True),
(Lookup.EQUAL, "ham.spam", False),
@@ -176,7 +176,7 @@ def test_host_pattern(lookup, host, expected):
@pytest.mark.parametrize(
- "lookup,port,url,expected",
+ ("lookup", "port", "url", "expected"),
[
(Lookup.EQUAL, 443, "https://foo.bar/", True),
(Lookup.EQUAL, 80, "https://foo.bar/", False),
@@ -224,7 +224,7 @@ def test_path_pattern():
@pytest.mark.parametrize(
- "lookup,params,url,expected",
+ ("lookup", "params", "url", "expected"),
[
(Lookup.CONTAINS, "", "https://foo.bar/", True),
(Lookup.CONTAINS, "x=1", "https://foo.bar/?x=1", True),
@@ -264,7 +264,7 @@ def test_params_pattern_hash():
@pytest.mark.parametrize(
- "lookup,value,context,url,expected",
+ ("lookup", "value", "context", "url", "expected"),
[
(Lookup.REGEX, r"https?://a.b/(?P\w+)/", {"c": "c"}, "http://a.b/c/", True),
(Lookup.REGEX, re.compile(r"^https://a.b/.+$"), {}, "https://a.b/c/", True),
@@ -297,7 +297,7 @@ def test_url_pattern_hash():
@pytest.mark.parametrize(
- "lookup,content,expected",
+ ("lookup", "content", "expected"),
[
(Lookup.EQUAL, b"foobar", True),
(Lookup.EQUAL, "foobar", True),
@@ -310,7 +310,7 @@ def test_content_pattern(lookup, content, expected):
@pytest.mark.parametrize(
- "lookup,data,expected",
+ ("lookup", "data", "expected"),
[
(Lookup.EQUAL, {"foo": "bar", "ham": "spam"}, True),
],
@@ -322,7 +322,7 @@ def test_data_pattern(lookup, data, expected):
@pytest.mark.parametrize(
- "lookup,value,json,expected",
+ ("lookup", "value", "json", "expected"),
[
(
Lookup.EQUAL,
@@ -349,12 +349,6 @@ def test_data_pattern(lookup, data, expected):
False,
),
(Lookup.EQUAL, "json-string", "json-string", True),
- (
- Lookup.EQUAL,
- {"foo": "bar", "ham": "spam"},
- {"ham": "spam", "foo": "bar"},
- True,
- ),
],
)
def test_json_pattern(lookup, value, json, expected):
@@ -364,7 +358,7 @@ def test_json_pattern(lookup, value, json, expected):
@pytest.mark.parametrize(
- "json,path,value,expected",
+ ("json", "path", "value", "expected"),
[
({"foo": {"bar": "baz"}}, "foo__bar", "baz", True),
({"x": {"z": 2, "y": 1}}, "x", {"y": 1, "z": 2}, True),
diff --git a/tests/test_remote.py b/tests/test_remote.py
index a12c229..c6a58bd 100644
--- a/tests/test_remote.py
+++ b/tests/test_remote.py
@@ -10,7 +10,7 @@
@pytest.mark.parametrize(
- "using,client_lib,call_count",
+ ("using", "client_lib", "call_count"),
[
("httpcore", "httpx", 2), # TODO: AsyncConnectionPool + AsyncHTTPConnection
("httpx", "httpx", 1),
diff --git a/tests/test_router.py b/tests/test_router.py
index cb6ad01..fc8541b 100644
--- a/tests/test_router.py
+++ b/tests/test_router.py
@@ -38,7 +38,7 @@ async def test_empty_router__auto_mocked():
@pytest.mark.parametrize(
- "args,kwargs,expected",
+ ("args", "kwargs", "expected"),
[
((Method("GET"), Host("foo.bar")), dict(), True),
(tuple(), dict(method="GET", host="foo.bar"), True),
@@ -90,7 +90,7 @@ def test_pass_through():
@pytest.mark.parametrize(
- "url,lookups,expected",
+ ("url", "lookups", "expected"),
[
("https://foo.bar/api/baz/", {"url": "/baz/"}, True),
("https://foo.bar/api/baz/", {"path__regex": r"^/(?P\w+)/$"}, True),
@@ -116,7 +116,7 @@ def test_base_url(url, lookups, expected):
@pytest.mark.parametrize(
- "lookups,url,expected",
+ ("lookups", "url", "expected"),
[
({"url": "//foo.bar/baz/"}, "https://foo.bar/baz/", True),
({"url": "all"}, "https://foo.bar/baz/", True),
@@ -266,8 +266,8 @@ def test_side_effect_list():
assert response.status_code == 201
assert response.request == request
+ request = httpx.Request("GET", "https://foo.bar")
with pytest.raises(StopIteration):
- request = httpx.Request("GET", "https://foo.bar")
router.handler(request)
route.side_effect = None
diff --git a/tests/test_transports.py b/tests/test_transports.py
index 4be1223..3c53bb1 100644
--- a/tests/test_transports.py
+++ b/tests/test_transports.py
@@ -57,7 +57,7 @@ async def test_transport_assertions():
transport = MockTransport(router=router)
assert len(w) == 1
- with pytest.raises(AllCalledAssertionError):
+ with pytest.raises(AllCalledAssertionError, match="not called"): # noqa [PT012]
async with httpx.AsyncClient(transport=transport) as client:
response = await client.get(url)
assert response.status_code == 404