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