mirror of
https://github.com/kevin1024/vcrpy.git
synced 2025-12-09 01:03:24 +00:00
Merge branch 'master' of github.com:kevin1024/vcrpy into fix-resource-warning-2
This commit is contained in:
2
.github/workflows/codespell.yml
vendored
2
.github/workflows/codespell.yml
vendored
@@ -17,6 +17,6 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
- name: Codespell
|
||||
uses: codespell-project/actions-codespell@v2
|
||||
|
||||
8
.github/workflows/docs.yml
vendored
8
.github/workflows/docs.yml
vendored
@@ -10,11 +10,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.11"
|
||||
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Install build dependencies
|
||||
run: pip install -r docs/requirements.txt
|
||||
- name: Rendering HTML documentation
|
||||
|
||||
6
.github/workflows/main.yml
vendored
6
.github/workflows/main.yml
vendored
@@ -13,13 +13,13 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version: ["3.8", "3.9", "3.10", "3.11", "pypy-3.8"]
|
||||
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "pypy-3.8", "pypy-3.9", "pypy-3.10"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3.5.2
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v4
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
|
||||
62
.github/workflows/pre-commit-detect-outdated.yml
vendored
Normal file
62
.github/workflows/pre-commit-detect-outdated.yml
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
# Copyright (c) 2023 Sebastian Pipping <sebastian@pipping.org>
|
||||
# Licensed under the MIT license
|
||||
|
||||
name: Detect outdated pre-commit hooks
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 16 * * 5' # Every Friday 4pm
|
||||
|
||||
# NOTE: This will drop all permissions from GITHUB_TOKEN except metadata read,
|
||||
# and then (re)add the ones listed below:
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
pre_commit_detect_outdated:
|
||||
name: Detect outdated pre-commit hooks
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python 3.12
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.12
|
||||
|
||||
- name: Install pre-commit
|
||||
run: |-
|
||||
pip install \
|
||||
--disable-pip-version-check \
|
||||
--no-warn-script-location \
|
||||
--user \
|
||||
pre-commit
|
||||
echo "PATH=${HOME}/.local/bin:${PATH}" >> "${GITHUB_ENV}"
|
||||
|
||||
- name: Check for outdated hooks
|
||||
run: |-
|
||||
pre-commit autoupdate
|
||||
git diff -- .pre-commit-config.yaml
|
||||
|
||||
- name: Create pull request from changes (if any)
|
||||
id: create-pull-request
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
with:
|
||||
author: 'pre-commit <pre-commit@tools.invalid>'
|
||||
base: master
|
||||
body: |-
|
||||
For your consideration.
|
||||
|
||||
:warning: Please **CLOSE AND RE-OPEN** this pull request so that [further workflow runs get triggered](https://github.com/peter-evans/create-pull-request/blob/main/docs/concepts-guidelines.md#triggering-further-workflow-runs) for this pull request.
|
||||
branch: precommit-autoupdate
|
||||
commit-message: "pre-commit: Autoupdate"
|
||||
delete-branch: true
|
||||
draft: true
|
||||
labels: enhancement
|
||||
title: "pre-commit: Autoupdate"
|
||||
|
||||
- name: Log pull request URL
|
||||
if: "${{ steps.create-pull-request.outputs.pull-request-url }}"
|
||||
run: |
|
||||
echo "Pull request URL is: ${{ steps.create-pull-request.outputs.pull-request-url }}"
|
||||
20
.github/workflows/pre-commit.yml
vendored
Normal file
20
.github/workflows/pre-commit.yml
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
# Copyright (c) 2023 Sebastian Pipping <sebastian@pipping.org>
|
||||
# Licensed under the MIT license
|
||||
|
||||
name: Run pre-commit
|
||||
|
||||
on:
|
||||
- pull_request
|
||||
- push
|
||||
- workflow_dispatch
|
||||
|
||||
jobs:
|
||||
pre-commit:
|
||||
name: Run pre-commit
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.12
|
||||
- uses: pre-commit/action@v3.0.0
|
||||
17
.pre-commit-config.yaml
Normal file
17
.pre-commit-config.yaml
Normal file
@@ -0,0 +1,17 @@
|
||||
# Copyright (c) 2023 Sebastian Pipping <sebastian@pipping.org>
|
||||
# Licensed under the MIT license
|
||||
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.1.7
|
||||
hooks:
|
||||
- id: ruff
|
||||
args: ["--show-source"]
|
||||
- id: ruff-format
|
||||
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.4.0
|
||||
hooks:
|
||||
- id: check-merge-conflict
|
||||
- id: end-of-file-fixer
|
||||
- id: trailing-whitespace
|
||||
@@ -9,7 +9,7 @@ version: 2
|
||||
build:
|
||||
os: ubuntu-22.04
|
||||
tools:
|
||||
python: "3.11"
|
||||
python: "3.12"
|
||||
|
||||
# Build documentation in the docs/ directory with Sphinx
|
||||
sphinx:
|
||||
@@ -20,3 +20,5 @@ sphinx:
|
||||
python:
|
||||
install:
|
||||
- requirements: docs/requirements.txt
|
||||
- method: pip
|
||||
path: .
|
||||
|
||||
@@ -4,7 +4,7 @@ VCR.py 📼
|
||||
###########
|
||||
|
||||
|
||||
|PyPI| |Python versions| |Build Status| |CodeCov| |Gitter| |CodeStyleBlack|
|
||||
|PyPI| |Python versions| |Build Status| |CodeCov| |Gitter|
|
||||
|
||||
----
|
||||
|
||||
@@ -70,6 +70,3 @@ more details
|
||||
.. |CodeCov| image:: https://codecov.io/gh/kevin1024/vcrpy/branch/master/graph/badge.svg
|
||||
:target: https://codecov.io/gh/kevin1024/vcrpy
|
||||
:alt: Code Coverage Status
|
||||
.. |CodeStyleBlack| image:: https://img.shields.io/badge/code%20style-black-000000.svg
|
||||
:target: https://github.com/psf/black
|
||||
:alt: Code Style: black
|
||||
|
||||
2
docs/_static/vcr.svg
vendored
2
docs/_static/vcr.svg
vendored
@@ -24,4 +24,4 @@
|
||||
<stop offset="1" stop-color="#27DDA6"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 6.2 KiB |
@@ -16,7 +16,7 @@ a nice addition. Here's an example:
|
||||
with vcr.use_cassette('fixtures/vcr_cassettes/synopsis.yaml') as cass:
|
||||
response = urllib2.urlopen('http://www.zombo.com/').read()
|
||||
# cass should have 1 request inside it
|
||||
assert len(cass) == 1
|
||||
assert len(cass) == 1
|
||||
# the request uri should have been http://www.zombo.com/
|
||||
assert cass.requests[0].uri == 'http://www.zombo.com/'
|
||||
|
||||
@@ -208,7 +208,7 @@ So these two calls are the same:
|
||||
|
||||
# original (still works)
|
||||
vcr = VCR(filter_headers=['authorization'])
|
||||
|
||||
|
||||
# new
|
||||
vcr = VCR(filter_headers=[('authorization', None)])
|
||||
|
||||
@@ -218,7 +218,7 @@ Here are two examples of the new functionality:
|
||||
|
||||
# replace with a static value (most common)
|
||||
vcr = VCR(filter_headers=[('authorization', 'XXXXXX')])
|
||||
|
||||
|
||||
# replace with a callable, for example when testing
|
||||
# lots of different kinds of authorization.
|
||||
def replace_auth(key, value, request):
|
||||
@@ -286,7 +286,7 @@ sensitive data from the response body:
|
||||
before_record_response=scrub_string(settings.USERNAME, 'username'),
|
||||
)
|
||||
with my_vcr.use_cassette('test.yml'):
|
||||
# your http code here
|
||||
# your http code here
|
||||
|
||||
|
||||
Decode compressed response
|
||||
|
||||
@@ -8,21 +8,22 @@ For a full list of triaged issues, bugs and PRs and what release they are target
|
||||
All help in providing PRs to close out bug issues is appreciated. Even if that is providing a repo that fully replicates issues. We have very generous contributors that have added these to bug issues which meant another contributor picked up the bug and closed it out.
|
||||
|
||||
- 5.1.0
|
||||
- Use ruff for linting (instead of current flake8/isort/pyflakes) - thanks @jairhenrique
|
||||
- Enable rule B (flake8-bugbear) on ruff - thanks @jairhenrique
|
||||
- Configure read the docs V2 - thanks @jairhenrique
|
||||
- Fix typo in docs - thanks @quasimik
|
||||
- Make json.loads of Python >=3.6 decode bytes by itself - thanks @hartwork
|
||||
- Fix body matcher for chunked requests (fixes #734) - thanks @hartwork
|
||||
- Fix query param filter for aiohttp (fixes #517) - thanks @hartwork and @salomvary
|
||||
- Remove unnecessary dependency on six. - thanks @charettes
|
||||
- build(deps): update sphinx requirement from <7 to <8 - thanks @jairhenrique
|
||||
- Add action to validate docs - thanks @jairhenrique
|
||||
- Add editorconfig file - thanks @jairhenrique
|
||||
- Drop iscoroutinefunction fallback function for unsupported python thanks @jairhenrique
|
||||
- Use ruff for linting (instead of current flake8/isort/pyflakes) - thanks @jairhenrique
|
||||
- Enable rule B (flake8-bugbear) on ruff - thanks @jairhenrique
|
||||
- Configure read the docs V2 - thanks @jairhenrique
|
||||
- Fix typo in docs - thanks @quasimik
|
||||
- Make json.loads of Python >=3.6 decode bytes by itself - thanks @hartwork
|
||||
- Fix body matcher for chunked requests (fixes #734) - thanks @hartwork
|
||||
- Fix query param filter for aiohttp (fixes #517) - thanks @hartwork and @salomvary
|
||||
- Remove unnecessary dependency on six. - thanks @charettes
|
||||
- build(deps): update sphinx requirement from <7 to <8 - thanks @jairhenrique
|
||||
- Add action to validate docs - thanks @jairhenrique
|
||||
- Add editorconfig file - thanks @jairhenrique
|
||||
- Drop iscoroutinefunction fallback function for unsupported python thanks @jairhenrique
|
||||
- 5.0.0
|
||||
- BREAKING CHANGE: Drop support for Python 3.7. 3.7 is EOL as of 6/27/23 Thanks @jairhenrique
|
||||
- BREAKING CHANGE: Custom Cassette persisters no longer catch ValueError. If you have implemented a custom persister (has anyone implemented a custom persister? Let us know!) then you will need to throw a CassetteNotFoundError when unable to find a cassette. See #681 for discussion and reason for this change. Thanks @amosjyng for the PR and the review from @hartwork
|
||||
|
||||
- 4.4.0
|
||||
- HUGE thanks to @hartwork for all the work done on this release!
|
||||
- Bring vcr/unittest in to vcrpy as a full feature of vcr instead of a separate library. Big thanks to @hartwork for doing this and to @agriffis for originally creating the library
|
||||
@@ -70,8 +71,8 @@ All help in providing PRs to close out bug issues is appreciated. Even if that i
|
||||
- Bugfix: Fix test suite by switching to mockbin (thanks @jairhenrique)
|
||||
- 4.0.2
|
||||
- Fix mock imports as reported in #504 by @llybin. Thank you.
|
||||
- 4.0.1
|
||||
- Fix logo alignment for PyPI
|
||||
- 4.0.1
|
||||
- Fix logo alignment for PyPI
|
||||
- 4.0.0
|
||||
- Remove Python2 support (@hugovk)
|
||||
- Add Python 3.8 TravisCI support (@neozenith)
|
||||
@@ -83,7 +84,7 @@ All help in providing PRs to close out bug issues is appreciated. Even if that i
|
||||
- Add support for `request_info` on mocked responses in aiohttp stub #495 (@nickdirienzo)
|
||||
- doc: fixed variable name (a -> cass) in an example for rewind #492 (@yarikoptic)
|
||||
|
||||
- 2.1.1
|
||||
- 2.1.1
|
||||
- Format code with black (@neozenith)
|
||||
- Use latest pypy3 in Travis (@hugovk)
|
||||
- Improve documentation about custom matchers (@gward)
|
||||
@@ -91,7 +92,7 @@ All help in providing PRs to close out bug issues is appreciated. Even if that i
|
||||
- Add `pytest-recording` to the documentation as an alternative Pytest plugin (@Stranger6667)
|
||||
- Fix yarl and python3.5 version issue (@neozenith)
|
||||
- Fix header matcher for boto3 - fixes #474 (@simahawk)
|
||||
- 2.1.0
|
||||
- 2.1.0
|
||||
- Add a `rewind` method to reset a cassette (thanks @khamidou)
|
||||
- New error message with more details on why the cassette failed to play a request (thanks @arthurHamon2, @neozenith)
|
||||
- Handle connect tunnel URI (thanks @jeking3)
|
||||
@@ -103,9 +104,9 @@ All help in providing PRs to close out bug issues is appreciated. Even if that i
|
||||
- Fix bugs on aiohttp integration (thanks @graingert, @steinnes, @stj, @lamenezes, @lmazuel)
|
||||
- Fix Biopython incompatibility (thanks @rishab121)
|
||||
- Fix Boto3 integration (thanks @1oglop1, @arthurHamon2)
|
||||
- 2.0.1
|
||||
- 2.0.1
|
||||
- Fix bug when using vcrpy with python 3.4
|
||||
- 2.0.0
|
||||
- 2.0.0
|
||||
- Support python 3.7 (fix httplib2 and urllib2, thanks @felixonmars)
|
||||
- [#356] Fixes `before_record_response` so the original response isn't changed (thanks @kgraves)
|
||||
- Fix requests stub when using proxy (thanks @samuelfekete @daneoshiga)
|
||||
@@ -115,56 +116,56 @@ All help in providing PRs to close out bug issues is appreciated. Even if that i
|
||||
- Improve docs (thanks @adamchainz)
|
||||
|
||||
|
||||
- 1.13.0
|
||||
- 1.13.0
|
||||
- Fix support to latest aiohttp version (3.3.2). Fix content-type bug in aiohttp stub. Save URL with query params properly when using aiohttp.
|
||||
- 1.12.0
|
||||
- 1.12.0
|
||||
- Fix support to latest aiohttp version (3.2.1), Adapted setup to PEP508, Support binary responses on aiohttp, Dropped support for EOL python versions (2.6 and 3.3)
|
||||
- 1.11.1
|
||||
- 1.11.1
|
||||
- Fix compatibility with newest requests and urllib3 releases
|
||||
- 1.11.0
|
||||
- 1.11.0
|
||||
- Allow injection of persistence methods + bugfixes (thanks @j-funk and @IvanMalison),
|
||||
- Support python 3.6 + CI tests (thanks @derekbekoe and @graingert),
|
||||
- Support pytest-asyncio coroutines (thanks @graingert)
|
||||
- 1.10.5
|
||||
- 1.10.5
|
||||
- Added a fix to httplib2 (thanks @carlosds730), Fix an issue with
|
||||
- aiohttp (thanks @madninja), Add missing requirement yarl (thanks @lamenezes),
|
||||
- Remove duplicate mock triple (thanks @FooBarQuaxx)
|
||||
- 1.10.4
|
||||
- 1.10.4
|
||||
- Fix an issue with asyncio aiohttp (thanks @madninja)
|
||||
- 1.10.3
|
||||
- 1.10.3
|
||||
- Fix some issues with asyncio and params (thanks @anovikov1984 and @lamenezes)
|
||||
- Fix some issues with cassette serialize / deserialize and empty response bodies (thanks @gRoussac and @dz0ny)
|
||||
- 1.10.2
|
||||
- 1.10.2
|
||||
- Fix 1.10.1 release - add aiohttp support back in
|
||||
- 1.10.1
|
||||
- 1.10.1
|
||||
- [bad release] Fix build for Fedora package + python2 (thanks @puiterwijk and @lamenezes)
|
||||
- 1.10.0
|
||||
- 1.10.0
|
||||
- Add support for aiohttp (thanks @lamenezes)
|
||||
- 1.9.0
|
||||
- Add support for boto3 (thanks @desdm, @foorbarna).
|
||||
- 1.9.0
|
||||
- Add support for boto3 (thanks @desdm, @foorbarna).
|
||||
- Fix deepcopy issue for response headers when `decode_compressed_response` is enabled (thanks @nickdirienzo)
|
||||
- 1.8.0
|
||||
- Fix for Serialization errors with JSON adapter (thanks @aliaksandrb).
|
||||
- Avoid concatenating bytes with strings (thanks @jaysonsantos).
|
||||
- Exclude __pycache__ dirs & compiled files in sdist (thanks @koobs).
|
||||
- Fix Tornado support behavior for Tornado 3 (thanks @abhinav).
|
||||
- 1.8.0
|
||||
- Fix for Serialization errors with JSON adapter (thanks @aliaksandrb).
|
||||
- Avoid concatenating bytes with strings (thanks @jaysonsantos).
|
||||
- Exclude __pycache__ dirs & compiled files in sdist (thanks @koobs).
|
||||
- Fix Tornado support behavior for Tornado 3 (thanks @abhinav).
|
||||
- decode_compressed_response option and filter (thanks @jayvdb).
|
||||
- 1.7.4 [#217]
|
||||
- Make use_cassette decorated functions actually return a value (thanks @bcen).
|
||||
- 1.7.4 [#217]
|
||||
- Make use_cassette decorated functions actually return a value (thanks @bcen).
|
||||
- [#199] Fix path transformation defaults.
|
||||
- Better headers dictionary management.
|
||||
- 1.7.3 [#188]
|
||||
- 1.7.3 [#188]
|
||||
- ``additional_matchers`` kwarg on ``use_cassette``.
|
||||
- [#191] Actually support passing multiple before_record_request functions (thanks @agriffis).
|
||||
- 1.7.2
|
||||
- 1.7.2
|
||||
- [#186] Get effective_url in tornado (thanks @mvschaik)
|
||||
- [#187] Set request_time on Response object in tornado (thanks @abhinav).
|
||||
- 1.7.1
|
||||
- 1.7.1
|
||||
- [#183] Patch ``fetch_impl`` instead of the entire HTTPClient class for Tornado (thanks @abhinav).
|
||||
- 1.7.0
|
||||
- [#177] Properly support coroutine/generator decoration.
|
||||
- 1.7.0
|
||||
- [#177] Properly support coroutine/generator decoration.
|
||||
- [#178] Support distribute (thanks @graingert). [#163] Make compatibility between python2 and python3 recorded cassettes more robust (thanks @gward).
|
||||
- 1.6.1
|
||||
- 1.6.1
|
||||
- [#169] Support conditional requirements in old versions of pip
|
||||
- Fix RST parse errors generated by pandoc
|
||||
- [Tornado] Fix unsupported features exception not being raised
|
||||
@@ -178,17 +179,17 @@ All help in providing PRs to close out bug issues is appreciated. Even if that i
|
||||
- Fix crash when cassette path contains cassette library directory (thanks @gazpachoking).
|
||||
- 1.5.0
|
||||
- Automatic cassette naming and 'application/json' post data filtering (thanks @marco-santamaria).
|
||||
- 1.4.2
|
||||
- 1.4.2
|
||||
- Fix a bug caused by requests 2.7 and chunked transfer encoding
|
||||
- 1.4.1
|
||||
- 1.4.1
|
||||
- Include README, tests, LICENSE in package. Thanks @ralphbean.
|
||||
- 1.4.0
|
||||
- 1.4.0
|
||||
- Filter post data parameters (thanks @eadmundo)
|
||||
- Support for posting files through requests, inject\_cassette kwarg to access cassette from ``use_cassette`` decorated function, ``with_current_defaults`` actually works (thanks @samstav).
|
||||
- 1.3.0
|
||||
- Fix/add support for urllib3 (thanks @aisch)
|
||||
- Fix default port for https (thanks @abhinav).
|
||||
- 1.2.0
|
||||
- 1.2.0
|
||||
- Add custom\_patches argument to VCR/Cassette objects to allow users to stub custom classes when cassettes become active.
|
||||
- 1.1.4
|
||||
- Add force reset around calls to actual connection from stubs, to ensure compatibility with the version of httplib/urlib2 in python 2.7.9.
|
||||
@@ -199,22 +200,22 @@ All help in providing PRs to close out bug issues is appreciated. Even if that i
|
||||
- fix Windows connectionpool stub bug (thanks @gazpachoking)
|
||||
- add support for requests 2.5
|
||||
- 1.1.2
|
||||
- Add urllib==1.7.1 support.
|
||||
- Add urllib==1.7.1 support.
|
||||
- Make json serialize error handling correct
|
||||
- Improve logging of match failures.
|
||||
- 1.1.1
|
||||
- Use function signature preserving ``wrapt.decorator`` to write the decorator version of use\_cassette in order to ensure compatibility with py.test fixtures and python 2.
|
||||
- 1.1.1
|
||||
- Use function signature preserving ``wrapt.decorator`` to write the decorator version of use\_cassette in order to ensure compatibility with py.test fixtures and python 2.
|
||||
- Move all request filtering into the ``before_record_callable``.
|
||||
- 1.1.0
|
||||
- 1.1.0
|
||||
- Add ``before_record_response``. Fix several bugs related to the context management of cassettes.
|
||||
- 1.0.3
|
||||
- 1.0.3
|
||||
- Fix an issue with requests 2.4 and make sure case sensitivity is consistent across python versions
|
||||
- 1.0.2
|
||||
- Fix an issue with requests 2.3
|
||||
- 1.0.1
|
||||
- Fix a bug with the new ignore requests feature and the once record mode
|
||||
- 1.0.0
|
||||
- *BACKWARDS INCOMPATIBLE*: Please see the 'upgrade' section in the README. Take a look at the matcher section as well, you might want to update your ``match_on`` settings.
|
||||
- 1.0.0
|
||||
- *BACKWARDS INCOMPATIBLE*: Please see the 'upgrade' section in the README. Take a look at the matcher section as well, you might want to update your ``match_on`` settings.
|
||||
- Add support for filtering sensitive data from requests, matching query strings after the order changes and improving the built-in matchers, (thanks to @mshytikov)
|
||||
- Support for ignoring requests to certain hosts, bump supported Python3 version to 3.4, fix some bugs with Boto support (thanks @marusich)
|
||||
- Fix error with URL field capitalization in README (thanks @simon-weber)
|
||||
@@ -222,27 +223,27 @@ All help in providing PRs to close out bug issues is appreciated. Even if that i
|
||||
- Added ``all_played`` property on cassette (thanks @mshytikov)
|
||||
|
||||
- 0.7.0
|
||||
- VCR.py now supports Python 3! (thanks @asundg)
|
||||
- VCR.py now supports Python 3! (thanks @asundg)
|
||||
- Also I refactored the stub connections quite a bit to add support for the putrequest and putheader calls.
|
||||
- This version also adds support for httplib2 (thanks @nilp0inter).
|
||||
- This version also adds support for httplib2 (thanks @nilp0inter).
|
||||
- I have added a couple tests for boto since it is an http client in its own right.
|
||||
- Finally, this version includes a fix for a bug where requests wasn't being patched properly (thanks @msabramo).
|
||||
- 0.6.0
|
||||
- Store response headers as a list since a HTTP response can have the same header twice (happens with set-cookie sometimes).
|
||||
- This has the added benefit of preserving the order of headers.
|
||||
- Thanks @smallcode for the bug report leading to this change.
|
||||
- This has the added benefit of preserving the order of headers.
|
||||
- Thanks @smallcode for the bug report leading to this change.
|
||||
- I have made an effort to ensure backwards compatibility with the old cassettes' header storage mechanism, but if you want to upgrade to the new header storage, you should delete your cassettes and re-record them.
|
||||
- Also this release adds better error messages (thanks @msabramo)
|
||||
- Also this release adds better error messages (thanks @msabramo)
|
||||
- and adds support for using VCR as a decorator (thanks @smallcode for the motivation)
|
||||
- 0.5.0
|
||||
- Change the ``response_of`` method to ``responses_of`` since cassettes can now contain more than one response for a request.
|
||||
- Since this changes the API, I'm bumping the version.
|
||||
- Also includes 2 bugfixes:
|
||||
- a better error message when attempting to overwrite a cassette file,
|
||||
- Change the ``response_of`` method to ``responses_of`` since cassettes can now contain more than one response for a request.
|
||||
- Since this changes the API, I'm bumping the version.
|
||||
- Also includes 2 bugfixes:
|
||||
- a better error message when attempting to overwrite a cassette file,
|
||||
- and a fix for a bug with requests sessions (thanks @msabramo)
|
||||
- 0.4.0
|
||||
- Change default request recording behavior for multiple requests.
|
||||
- If you make the same request multiple times to the same URL, the response might be different each time (maybe the response has a timestamp in it or something), so this will make the same request multiple times and save them all.
|
||||
- If you make the same request multiple times to the same URL, the response might be different each time (maybe the response has a timestamp in it or something), so this will make the same request multiple times and save them all.
|
||||
- Then, when you are replaying the cassette, the responses will be played back in the same order in which they were received.
|
||||
- If you were making multiple requests to the same URL in a cassette before version 0.4.0, you might need to regenerate your cassette files.
|
||||
- Also, removes support for the cassette.play\_count counter API, since individual requests aren't unique anymore.
|
||||
@@ -262,7 +263,7 @@ All help in providing PRs to close out bug issues is appreciated. Even if that i
|
||||
- 0.3.0
|
||||
- *Backwards incompatible release*
|
||||
- Added support for record modes, and changed the default recording behavior to the "once" record mode. Please see the documentation on record modes for more.
|
||||
- Added support for custom request matching, and changed the default request matching behavior to match only on the URL and method.
|
||||
- Added support for custom request matching, and changed the default request matching behavior to match only on the URL and method.
|
||||
- Also, improved the httplib mocking to add support for the ``HTTPConnection.send()`` method.
|
||||
- This means that requests won't actually be sent until the response is read, since I need to record the entire request in order to match up the appropriate response.
|
||||
- I don't think this should cause any issues unless you are sending requests without ever loading the response (which none of the standard httplib wrappers do, as far as I know).
|
||||
@@ -270,13 +271,13 @@ All help in providing PRs to close out bug issues is appreciated. Even if that i
|
||||
- 0.2.1
|
||||
- Fixed missing modules in setup.py
|
||||
- 0.2.0
|
||||
- Added configuration API, which lets you configure some settings on VCR (see the README).
|
||||
- Added configuration API, which lets you configure some settings on VCR (see the README).
|
||||
- Also, VCR no longer saves cassettes if they haven't changed at all and supports JSON as well as YAML (thanks @sirpengi).
|
||||
- Added amazing new skeumorphic logo, thanks @hairarrow.
|
||||
- 0.1.0
|
||||
- *backwards incompatible release - delete your old cassette files*
|
||||
- This release adds the ability to access the cassette to make assertions on it
|
||||
- as well as a major code refactor thanks to @dlecocq.
|
||||
- as well as a major code refactor thanks to @dlecocq.
|
||||
- It also fixes a couple longstanding bugs with redirects and HTTPS. [#3 and #4]
|
||||
- 0.0.4
|
||||
- If you have libyaml installed, vcrpy will use the c bindings instead. Speed up your tests! Thanks @dlecocq
|
||||
@@ -286,4 +287,3 @@ All help in providing PRs to close out bug issues is appreciated. Even if that i
|
||||
- Add support for requests / urllib3
|
||||
- 0.0.1
|
||||
- Initial Release
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ So whilst reporting issues are valuable, please consider:
|
||||
- contributing an issue with a toy repo that replicates the issue.
|
||||
- contributing PRs is a more valuable donation of your time and effort.
|
||||
|
||||
Thanks again for your interest and support in VCRpy.
|
||||
Thanks again for your interest and support in VCRpy.
|
||||
|
||||
We really appreciate it.
|
||||
|
||||
@@ -57,7 +57,7 @@ Simply adding these three labels for incoming issues means a lot for maintaining
|
||||
- Which library does it affect? ``core``, ``aiohttp``, ``requests``, ``urllib3``, ``tornado4``, ``httplib2``
|
||||
- If it is a bug, is it ``Verified Can Replicate`` or ``Requires Help Replicating``
|
||||
- Thanking people for raising issues. Feedback is always appreciated.
|
||||
- Politely asking if they are able to link to an example repo that replicates the issue if they haven't already. Being able to *clone and go* helps the next person and we like that. 😃
|
||||
- Politely asking if they are able to link to an example repo that replicates the issue if they haven't already. Being able to *clone and go* helps the next person and we like that. 😃
|
||||
|
||||
**Maintainer:**
|
||||
|
||||
@@ -68,7 +68,7 @@ This involves creating PRs to address bugs and enhancement requests. It also mea
|
||||
The PR reviewer is a second set of eyes to see if:
|
||||
- Are there tests covering the code paths added/modified?
|
||||
- Do the tests and modifications make sense seem appropriate?
|
||||
- Add specific feedback, even on approvals, why it is accepted. eg "I like how you use a context manager there. 😄 "
|
||||
- Add specific feedback, even on approvals, why it is accepted. eg "I like how you use a context manager there. 😄 "
|
||||
- Also make sure they add a line to `docs/changelog.rst` to claim credit for their contribution.
|
||||
|
||||
**Release Manager:**
|
||||
@@ -86,10 +86,10 @@ Running VCR's test suite
|
||||
The tests are all run automatically on `Travis
|
||||
CI <https://travis-ci.org/kevin1024/vcrpy>`__, but you can also run them
|
||||
yourself using `pytest <http://pytest.org/>`__ and
|
||||
`Tox <http://tox.testrun.org/>`__.
|
||||
`Tox <http://tox.testrun.org/>`__.
|
||||
|
||||
Tox will automatically run them in all environments VCR.py supports if they are available on your `PATH`. Alternatively you can use `tox-pyenv <https://pypi.org/project/tox-pyenv/>`_ with
|
||||
`pyenv <https://github.com/pyenv/pyenv>`_.
|
||||
Tox will automatically run them in all environments VCR.py supports if they are available on your `PATH`. Alternatively you can use `tox-pyenv <https://pypi.org/project/tox-pyenv/>`_ with
|
||||
`pyenv <https://github.com/pyenv/pyenv>`_.
|
||||
We recommend you read the documentation for each and see the section further below.
|
||||
|
||||
The test suite is pretty big and slow, but you can tell tox to only run specific tests like this::
|
||||
@@ -114,8 +114,8 @@ Using PyEnv with VCR's test suite
|
||||
---------------------------------
|
||||
|
||||
PyEnv is a tool for managing multiple installation of python on your system.
|
||||
See the full documentation at their `github <https://github.com/pyenv/pyenv>`_
|
||||
but we are also going to use `tox-pyenv <https://pypi.org/project/tox-pyenv/>`_
|
||||
See the full documentation at their `github <https://github.com/pyenv/pyenv>`_
|
||||
but we are also going to use `tox-pyenv <https://pypi.org/project/tox-pyenv/>`_
|
||||
in this example::
|
||||
|
||||
git clone https://github.com/pyenv/pyenv ~/.pyenv
|
||||
@@ -139,7 +139,6 @@ in this example::
|
||||
tox
|
||||
|
||||
# Run the whole test suite or just part of it
|
||||
tox -e lint
|
||||
tox -e py38-requests
|
||||
|
||||
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
sphinx<8
|
||||
sphinx_rtd_theme==1.2.2
|
||||
sphinx_rtd_theme==2.0.0
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
[tool.black]
|
||||
line-length=110
|
||||
|
||||
[tool.codespell]
|
||||
skip = '.git,*.pdf,*.svg,.tox'
|
||||
ignore-regex = "\\\\[fnrstv]"
|
||||
@@ -8,9 +5,7 @@ ignore-regex = "\\\\[fnrstv]"
|
||||
# ignore-words-list = ''
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
markers = [
|
||||
"online",
|
||||
]
|
||||
markers = ["online"]
|
||||
|
||||
[tool.ruff]
|
||||
select = [
|
||||
@@ -30,4 +25,4 @@ line-length = 110
|
||||
target-version = "py38"
|
||||
|
||||
[tool.ruff.isort]
|
||||
known-first-party = [ "vcr" ]
|
||||
known-first-party = ["vcr"]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
# https://blog.ionelmc.ro/2015/04/14/tox-tricks-and-patterns/#when-it-inevitably-leads-to-shell-scripts
|
||||
# If you are getting an INVOCATION ERROR for this script then there is
|
||||
# If you are getting an INVOCATION ERROR for this script then there is
|
||||
# a good chance you are running on Windows.
|
||||
# You can and should use WSL for running tox on Windows when it calls bash scripts.
|
||||
REQUESTS_CA_BUNDLE=`python3 -m pytest_httpbin.certs` exec pytest "$@"
|
||||
|
||||
7
setup.py
7
setup.py
@@ -46,13 +46,15 @@ install_requires = [
|
||||
"PyYAML",
|
||||
"wrapt",
|
||||
"yarl",
|
||||
# Support for urllib3 >=2 needs Python >=3.10
|
||||
# so we need to block urllib3 >=2 for Python <3.10 for now.
|
||||
# Support for urllib3 >=2 needs CPython >=3.10
|
||||
# so we need to block urllib3 >=2 for Python <3.10 and PyPy for now.
|
||||
# Note that vcrpy would work fine without any urllib3 around,
|
||||
# so this block and the dependency can be dropped at some point
|
||||
# in the future. For more Details:
|
||||
# https://github.com/kevin1024/vcrpy/pull/699#issuecomment-1551439663
|
||||
"urllib3 <2; python_version <'3.10'",
|
||||
# https://github.com/kevin1024/vcrpy/pull/775#issuecomment-1847849962
|
||||
"urllib3 <2; platform_python_implementation =='PyPy'",
|
||||
]
|
||||
|
||||
tests_require = [
|
||||
@@ -98,6 +100,7 @@ setup(
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Programming Language :: Python :: 3 :: Only",
|
||||
"Programming Language :: Python :: Implementation :: CPython",
|
||||
"Programming Language :: Python :: Implementation :: PyPy",
|
||||
|
||||
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
6
tests/fixtures/migration/new_cassette.json
vendored
6
tests/fixtures/migration/new_cassette.json
vendored
@@ -15,9 +15,9 @@
|
||||
},
|
||||
"response": {
|
||||
"status": {
|
||||
"message": "OK",
|
||||
"message": "OK",
|
||||
"code": 200
|
||||
},
|
||||
},
|
||||
"headers": {
|
||||
"access-control-allow-origin": ["*"],
|
||||
"content-type": ["application/json"],
|
||||
@@ -25,7 +25,7 @@
|
||||
"server": ["gunicorn/0.17.4"],
|
||||
"content-length": ["32"],
|
||||
"connection": ["keep-alive"]
|
||||
},
|
||||
},
|
||||
"body": {
|
||||
"string": "{\n \"origin\": \"217.122.164.194\"\n}"
|
||||
}
|
||||
|
||||
2
tests/fixtures/migration/new_cassette.yaml
vendored
2
tests/fixtures/migration/new_cassette.yaml
vendored
@@ -2,7 +2,7 @@ version: 1
|
||||
interactions:
|
||||
- request:
|
||||
body: null
|
||||
headers:
|
||||
headers:
|
||||
accept: ['*/*']
|
||||
accept-encoding: ['gzip, deflate, compress']
|
||||
user-agent: ['python-requests/2.2.1 CPython/2.6.1 Darwin/10.8.0']
|
||||
|
||||
34
tests/fixtures/migration/old_cassette.json
vendored
34
tests/fixtures/migration/old_cassette.json
vendored
@@ -1,31 +1,31 @@
|
||||
[
|
||||
{
|
||||
"request": {
|
||||
"body": null,
|
||||
"protocol": "http",
|
||||
"method": "GET",
|
||||
"body": null,
|
||||
"protocol": "http",
|
||||
"method": "GET",
|
||||
"headers": {
|
||||
"accept-encoding": "gzip, deflate, compress",
|
||||
"accept": "*/*",
|
||||
"accept-encoding": "gzip, deflate, compress",
|
||||
"accept": "*/*",
|
||||
"user-agent": "python-requests/2.2.1 CPython/2.6.1 Darwin/10.8.0"
|
||||
},
|
||||
"host": "httpbin.org",
|
||||
"path": "/ip",
|
||||
},
|
||||
"host": "httpbin.org",
|
||||
"path": "/ip",
|
||||
"port": 80
|
||||
},
|
||||
},
|
||||
"response": {
|
||||
"status": {
|
||||
"message": "OK",
|
||||
"message": "OK",
|
||||
"code": 200
|
||||
},
|
||||
},
|
||||
"headers": [
|
||||
"access-control-allow-origin: *\r\n",
|
||||
"content-type: application/json\r\n",
|
||||
"date: Mon, 21 Apr 2014 23:13:40 GMT\r\n",
|
||||
"server: gunicorn/0.17.4\r\n",
|
||||
"content-length: 32\r\n",
|
||||
"access-control-allow-origin: *\r\n",
|
||||
"content-type: application/json\r\n",
|
||||
"date: Mon, 21 Apr 2014 23:13:40 GMT\r\n",
|
||||
"server: gunicorn/0.17.4\r\n",
|
||||
"content-length: 32\r\n",
|
||||
"connection: keep-alive\r\n"
|
||||
],
|
||||
],
|
||||
"body": {
|
||||
"string": "{\n \"origin\": \"217.122.164.194\"\n}"
|
||||
}
|
||||
|
||||
2
tests/fixtures/wild/domain_redirect.yaml
vendored
2
tests/fixtures/wild/domain_redirect.yaml
vendored
@@ -10,7 +10,7 @@ interactions:
|
||||
uri: http://seomoz.org/
|
||||
response:
|
||||
body: {string: ''}
|
||||
headers:
|
||||
headers:
|
||||
Location: ['http://moz.com/']
|
||||
Server: ['BigIP']
|
||||
Connection: ['Keep-Alive']
|
||||
|
||||
@@ -4,22 +4,6 @@ import ssl
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture(params=["https", "http"])
|
||||
def scheme(request):
|
||||
"""Fixture that returns both http and https."""
|
||||
return request.param
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mockbin(scheme):
|
||||
return scheme + "://mockbin.org"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mockbin_request_url(mockbin):
|
||||
return mockbin + "/request"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def httpbin_ssl_context():
|
||||
ssl_ca_location = os.environ["REQUESTS_CA_BUNDLE"]
|
||||
|
||||
@@ -36,8 +36,8 @@ def post(url, output="text", **kwargs):
|
||||
|
||||
|
||||
@pytest.mark.online
|
||||
def test_status(tmpdir, mockbin_request_url):
|
||||
url = mockbin_request_url
|
||||
def test_status(tmpdir, httpbin):
|
||||
url = httpbin.url
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join("status.yaml"))):
|
||||
response, _ = get(url)
|
||||
@@ -50,8 +50,8 @@ def test_status(tmpdir, mockbin_request_url):
|
||||
|
||||
@pytest.mark.online
|
||||
@pytest.mark.parametrize("auth", [None, aiohttp.BasicAuth("vcrpy", "test")])
|
||||
def test_headers(tmpdir, auth, mockbin_request_url):
|
||||
url = mockbin_request_url
|
||||
def test_headers(tmpdir, auth, httpbin):
|
||||
url = httpbin.url
|
||||
with vcr.use_cassette(str(tmpdir.join("headers.yaml"))):
|
||||
response, _ = get(url, auth=auth)
|
||||
|
||||
@@ -67,8 +67,8 @@ def test_headers(tmpdir, auth, mockbin_request_url):
|
||||
|
||||
|
||||
@pytest.mark.online
|
||||
def test_case_insensitive_headers(tmpdir, mockbin_request_url):
|
||||
url = mockbin_request_url
|
||||
def test_case_insensitive_headers(tmpdir, httpbin):
|
||||
url = httpbin.url
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join("whatever.yaml"))):
|
||||
_, _ = get(url)
|
||||
@@ -81,8 +81,8 @@ def test_case_insensitive_headers(tmpdir, mockbin_request_url):
|
||||
|
||||
|
||||
@pytest.mark.online
|
||||
def test_text(tmpdir, mockbin_request_url):
|
||||
url = mockbin_request_url
|
||||
def test_text(tmpdir, httpbin):
|
||||
url = httpbin.url
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join("text.yaml"))):
|
||||
_, response_text = get(url)
|
||||
@@ -94,8 +94,8 @@ def test_text(tmpdir, mockbin_request_url):
|
||||
|
||||
|
||||
@pytest.mark.online
|
||||
def test_json(tmpdir, mockbin_request_url):
|
||||
url = mockbin_request_url
|
||||
def test_json(tmpdir, httpbin):
|
||||
url = httpbin.url + "/json"
|
||||
headers = {"Content-Type": "application/json"}
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join("json.yaml"))):
|
||||
@@ -108,8 +108,8 @@ def test_json(tmpdir, mockbin_request_url):
|
||||
|
||||
|
||||
@pytest.mark.online
|
||||
def test_binary(tmpdir, mockbin_request_url):
|
||||
url = mockbin_request_url + "/image/png"
|
||||
def test_binary(tmpdir, httpbin):
|
||||
url = httpbin.url + "/image/png"
|
||||
with vcr.use_cassette(str(tmpdir.join("binary.yaml"))):
|
||||
_, response_binary = get(url, output="raw")
|
||||
|
||||
@@ -120,8 +120,8 @@ def test_binary(tmpdir, mockbin_request_url):
|
||||
|
||||
|
||||
@pytest.mark.online
|
||||
def test_stream(tmpdir, mockbin_request_url):
|
||||
url = mockbin_request_url
|
||||
def test_stream(tmpdir, httpbin):
|
||||
url = httpbin.url
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join("stream.yaml"))):
|
||||
_, body = get(url, output="raw") # Do not use stream here, as the stream is exhausted by vcr
|
||||
@@ -134,10 +134,10 @@ def test_stream(tmpdir, mockbin_request_url):
|
||||
|
||||
@pytest.mark.online
|
||||
@pytest.mark.parametrize("body", ["data", "json"])
|
||||
def test_post(tmpdir, body, caplog, mockbin_request_url):
|
||||
def test_post(tmpdir, body, caplog, httpbin):
|
||||
caplog.set_level(logging.INFO)
|
||||
data = {"key1": "value1", "key2": "value2"}
|
||||
url = mockbin_request_url
|
||||
url = httpbin.url
|
||||
with vcr.use_cassette(str(tmpdir.join("post.yaml"))):
|
||||
_, response_json = post(url, **{body: data})
|
||||
|
||||
@@ -159,14 +159,14 @@ def test_post(tmpdir, body, caplog, mockbin_request_url):
|
||||
|
||||
|
||||
@pytest.mark.online
|
||||
def test_params(tmpdir, mockbin_request_url):
|
||||
url = mockbin_request_url + "?d=d"
|
||||
def test_params(tmpdir, httpbin):
|
||||
url = httpbin.url + "/get?d=d"
|
||||
headers = {"Content-Type": "application/json"}
|
||||
params = {"a": 1, "b": 2, "c": "c"}
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join("get.yaml"))) as cassette:
|
||||
_, response_json = get(url, output="json", params=params, headers=headers)
|
||||
assert response_json["queryString"] == {"a": "1", "b": "2", "c": "c", "d": "d"}
|
||||
assert response_json["args"] == {"a": "1", "b": "2", "c": "c", "d": "d"}
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join("get.yaml"))) as cassette:
|
||||
_, cassette_response_json = get(url, output="json", params=params, headers=headers)
|
||||
@@ -175,8 +175,8 @@ def test_params(tmpdir, mockbin_request_url):
|
||||
|
||||
|
||||
@pytest.mark.online
|
||||
def test_params_same_url_distinct_params(tmpdir, mockbin_request_url):
|
||||
url = mockbin_request_url
|
||||
def test_params_same_url_distinct_params(tmpdir, httpbin):
|
||||
url = httpbin.url + "/json"
|
||||
headers = {"Content-Type": "application/json"}
|
||||
params = {"a": 1, "b": 2, "c": "c"}
|
||||
|
||||
@@ -195,8 +195,8 @@ def test_params_same_url_distinct_params(tmpdir, mockbin_request_url):
|
||||
|
||||
|
||||
@pytest.mark.online
|
||||
def test_params_on_url(tmpdir, mockbin_request_url):
|
||||
url = mockbin_request_url + "?a=1&b=foo"
|
||||
def test_params_on_url(tmpdir, httpbin):
|
||||
url = httpbin.url + "/get?a=1&b=foo"
|
||||
headers = {"Content-Type": "application/json"}
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join("get.yaml"))) as cassette:
|
||||
@@ -261,8 +261,8 @@ def test_aiohttp_test_client_json(aiohttp_client, tmpdir):
|
||||
|
||||
|
||||
@pytest.mark.online
|
||||
def test_redirect(tmpdir, mockbin):
|
||||
url = mockbin + "/redirect/302/2"
|
||||
def test_redirect(tmpdir, httpbin):
|
||||
url = httpbin.url + "/redirect/2"
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join("redirect.yaml"))):
|
||||
response, _ = get(url)
|
||||
@@ -284,9 +284,9 @@ def test_redirect(tmpdir, mockbin):
|
||||
|
||||
|
||||
@pytest.mark.online
|
||||
def test_not_modified(tmpdir, mockbin):
|
||||
def test_not_modified(tmpdir, httpbin):
|
||||
"""It doesn't try to redirect on 304"""
|
||||
url = mockbin + "/status/304"
|
||||
url = httpbin.url + "/status/304"
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join("not_modified.yaml"))):
|
||||
response, _ = get(url)
|
||||
@@ -302,13 +302,13 @@ def test_not_modified(tmpdir, mockbin):
|
||||
|
||||
|
||||
@pytest.mark.online
|
||||
def test_double_requests(tmpdir, mockbin_request_url):
|
||||
def test_double_requests(tmpdir, httpbin):
|
||||
"""We should capture, record, and replay all requests and response chains,
|
||||
even if there are duplicate ones.
|
||||
|
||||
We should replay in the order we saw them.
|
||||
"""
|
||||
url = mockbin_request_url
|
||||
url = httpbin.url
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join("text.yaml"))):
|
||||
_, response_text1 = get(url, output="text")
|
||||
@@ -426,18 +426,18 @@ def test_cookies_redirect(httpbin_both, httpbin_ssl_context, tmpdir):
|
||||
|
||||
|
||||
@pytest.mark.online
|
||||
def test_not_allow_redirects(tmpdir, mockbin):
|
||||
url = mockbin + "/redirect/308/5"
|
||||
def test_not_allow_redirects(tmpdir, httpbin):
|
||||
url = httpbin + "/redirect-to?url=.%2F&status_code=308"
|
||||
path = str(tmpdir.join("redirects.yaml"))
|
||||
|
||||
with vcr.use_cassette(path):
|
||||
response, _ = get(url, allow_redirects=False)
|
||||
assert response.url.path == "/redirect/308/5"
|
||||
assert response.url.path == "/redirect-to"
|
||||
assert response.status == 308
|
||||
|
||||
with vcr.use_cassette(path) as cassette:
|
||||
response, _ = get(url, allow_redirects=False)
|
||||
assert response.url.path == "/redirect/308/5"
|
||||
assert response.url.path == "/redirect-to"
|
||||
assert response.status == 308
|
||||
assert cassette.play_count == 1
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ def test_basic_json_use(tmpdir, httpbin):
|
||||
test_fixture = str(tmpdir.join("synopsis.json"))
|
||||
with vcr.use_cassette(test_fixture, serializer="json"):
|
||||
response = urlopen(httpbin.url).read()
|
||||
assert b"difficult sometimes" in response
|
||||
assert b"A simple HTTP Request & Response Service." in response
|
||||
|
||||
|
||||
def test_patched_content(tmpdir, httpbin):
|
||||
|
||||
@@ -8,12 +8,12 @@ import vcr
|
||||
|
||||
|
||||
@pytest.mark.online
|
||||
def test_set_serializer_default_config(tmpdir, mockbin_request_url):
|
||||
def test_set_serializer_default_config(tmpdir, httpbin):
|
||||
my_vcr = vcr.VCR(serializer="json")
|
||||
|
||||
with my_vcr.use_cassette(str(tmpdir.join("test.json"))):
|
||||
assert my_vcr.serializer == "json"
|
||||
urlopen(mockbin_request_url)
|
||||
urlopen(httpbin.url)
|
||||
|
||||
with open(str(tmpdir.join("test.json"))) as f:
|
||||
file_content = f.read()
|
||||
@@ -22,37 +22,37 @@ def test_set_serializer_default_config(tmpdir, mockbin_request_url):
|
||||
|
||||
|
||||
@pytest.mark.online
|
||||
def test_default_set_cassette_library_dir(tmpdir, mockbin_request_url):
|
||||
def test_default_set_cassette_library_dir(tmpdir, httpbin):
|
||||
my_vcr = vcr.VCR(cassette_library_dir=str(tmpdir.join("subdir")))
|
||||
|
||||
with my_vcr.use_cassette("test.json"):
|
||||
urlopen(mockbin_request_url)
|
||||
urlopen(httpbin.url)
|
||||
|
||||
assert os.path.exists(str(tmpdir.join("subdir").join("test.json")))
|
||||
|
||||
|
||||
@pytest.mark.online
|
||||
def test_override_set_cassette_library_dir(tmpdir, mockbin_request_url):
|
||||
def test_override_set_cassette_library_dir(tmpdir, httpbin):
|
||||
my_vcr = vcr.VCR(cassette_library_dir=str(tmpdir.join("subdir")))
|
||||
|
||||
cld = str(tmpdir.join("subdir2"))
|
||||
|
||||
with my_vcr.use_cassette("test.json", cassette_library_dir=cld):
|
||||
urlopen(mockbin_request_url)
|
||||
urlopen(httpbin.url)
|
||||
|
||||
assert os.path.exists(str(tmpdir.join("subdir2").join("test.json")))
|
||||
assert not os.path.exists(str(tmpdir.join("subdir").join("test.json")))
|
||||
|
||||
|
||||
@pytest.mark.online
|
||||
def test_override_match_on(tmpdir, mockbin_request_url):
|
||||
def test_override_match_on(tmpdir, httpbin):
|
||||
my_vcr = vcr.VCR(match_on=["method"])
|
||||
|
||||
with my_vcr.use_cassette(str(tmpdir.join("test.json"))):
|
||||
urlopen(mockbin_request_url)
|
||||
urlopen(httpbin.url)
|
||||
|
||||
with my_vcr.use_cassette(str(tmpdir.join("test.json"))) as cass:
|
||||
urlopen(mockbin_request_url)
|
||||
urlopen(httpbin.url)
|
||||
|
||||
assert len(cass) == 1
|
||||
assert cass.play_count == 1
|
||||
@@ -67,12 +67,12 @@ def test_missing_matcher():
|
||||
|
||||
|
||||
@pytest.mark.online
|
||||
def test_dont_record_on_exception(tmpdir, mockbin_request_url):
|
||||
def test_dont_record_on_exception(tmpdir, httpbin):
|
||||
my_vcr = vcr.VCR(record_on_exception=False)
|
||||
|
||||
@my_vcr.use_cassette(str(tmpdir.join("dontsave.yml")))
|
||||
def some_test():
|
||||
assert b"Not in content" in urlopen(mockbin_request_url)
|
||||
assert b"Not in content" in urlopen(httpbin.url)
|
||||
|
||||
with pytest.raises(AssertionError):
|
||||
some_test()
|
||||
@@ -82,6 +82,6 @@ def test_dont_record_on_exception(tmpdir, mockbin_request_url):
|
||||
# Make sure context decorator has the same behavior
|
||||
with pytest.raises(AssertionError):
|
||||
with my_vcr.use_cassette(str(tmpdir.join("dontsave2.yml"))):
|
||||
assert b"Not in content" in urlopen(mockbin_request_url).read()
|
||||
assert b"Not in content" in urlopen(httpbin.url).read()
|
||||
|
||||
assert not os.path.exists(str(tmpdir.join("dontsave2.yml")))
|
||||
|
||||
@@ -12,19 +12,19 @@ import vcr
|
||||
|
||||
|
||||
@pytest.mark.online
|
||||
def test_disk_saver_nowrite(tmpdir, mockbin_request_url):
|
||||
def test_disk_saver_nowrite(tmpdir, httpbin):
|
||||
"""
|
||||
Ensure that when you close a cassette without changing it it doesn't
|
||||
rewrite the file
|
||||
"""
|
||||
fname = str(tmpdir.join("synopsis.yaml"))
|
||||
with vcr.use_cassette(fname) as cass:
|
||||
urlopen(mockbin_request_url).read()
|
||||
urlopen(httpbin.url).read()
|
||||
assert cass.play_count == 0
|
||||
last_mod = os.path.getmtime(fname)
|
||||
|
||||
with vcr.use_cassette(fname) as cass:
|
||||
urlopen(mockbin_request_url).read()
|
||||
urlopen(httpbin.url).read()
|
||||
assert cass.play_count == 1
|
||||
assert cass.dirty is False
|
||||
last_mod2 = os.path.getmtime(fname)
|
||||
@@ -33,14 +33,14 @@ def test_disk_saver_nowrite(tmpdir, mockbin_request_url):
|
||||
|
||||
|
||||
@pytest.mark.online
|
||||
def test_disk_saver_write(tmpdir, mockbin_request_url):
|
||||
def test_disk_saver_write(tmpdir, httpbin):
|
||||
"""
|
||||
Ensure that when you close a cassette after changing it it does
|
||||
rewrite the file
|
||||
"""
|
||||
fname = str(tmpdir.join("synopsis.yaml"))
|
||||
with vcr.use_cassette(fname) as cass:
|
||||
urlopen(mockbin_request_url).read()
|
||||
urlopen(httpbin.url).read()
|
||||
assert cass.play_count == 0
|
||||
last_mod = os.path.getmtime(fname)
|
||||
|
||||
@@ -49,8 +49,8 @@ def test_disk_saver_write(tmpdir, mockbin_request_url):
|
||||
time.sleep(1)
|
||||
|
||||
with vcr.use_cassette(fname, record_mode=vcr.mode.ANY) as cass:
|
||||
urlopen(mockbin_request_url).read()
|
||||
urlopen(mockbin_request_url + "/get").read()
|
||||
urlopen(httpbin.url).read()
|
||||
urlopen(httpbin.url + "/get").read()
|
||||
assert cass.play_count == 1
|
||||
assert cass.dirty
|
||||
last_mod2 = os.path.getmtime(fname)
|
||||
|
||||
@@ -5,10 +5,11 @@ from urllib.parse import urlencode
|
||||
from urllib.request import Request, urlopen
|
||||
|
||||
import pytest
|
||||
from assertions import assert_cassette_has_one_response, assert_is_json_bytes
|
||||
|
||||
import vcr
|
||||
|
||||
from ..assertions import assert_cassette_has_one_response, assert_is_json_bytes
|
||||
|
||||
|
||||
def _request_with_auth(url, username, password):
|
||||
request = Request(url)
|
||||
|
||||
@@ -3,10 +3,11 @@ from urllib.parse import urlencode
|
||||
|
||||
import pytest
|
||||
import pytest_httpbin.certs
|
||||
from assertions import assert_cassette_has_one_response
|
||||
|
||||
import vcr
|
||||
|
||||
from ..assertions import assert_cassette_has_one_response
|
||||
|
||||
httplib2 = pytest.importorskip("httplib2")
|
||||
|
||||
|
||||
@@ -56,14 +57,14 @@ def test_response_headers(tmpdir, httpbin_both):
|
||||
|
||||
|
||||
@pytest.mark.online
|
||||
def test_effective_url(tmpdir):
|
||||
def test_effective_url(tmpdir, httpbin):
|
||||
"""Ensure that the effective_url is captured"""
|
||||
url = "http://mockbin.org/redirect/301"
|
||||
url = httpbin.url + "/redirect-to?url=.%2F&status_code=301"
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join("headers.yaml"))):
|
||||
resp, _ = http().request(url)
|
||||
effective_url = resp["content-location"]
|
||||
assert effective_url == "http://mockbin.org/redirect/301/0"
|
||||
assert effective_url == httpbin.url + "/"
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join("headers.yaml"))):
|
||||
resp, _ = http().request(url)
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
import vcr
|
||||
@@ -7,7 +5,11 @@ import vcr
|
||||
asyncio = pytest.importorskip("asyncio")
|
||||
httpx = pytest.importorskip("httpx")
|
||||
|
||||
from vcr.stubs.httpx_stubs import HTTPX_REDIRECT_PARAM # noqa: E402
|
||||
|
||||
@pytest.fixture(params=["https", "http"])
|
||||
def scheme(request):
|
||||
"""Fixture that returns both http and https."""
|
||||
return request.param
|
||||
|
||||
|
||||
class BaseDoRequest:
|
||||
@@ -16,6 +18,7 @@ class BaseDoRequest:
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._client_args = args
|
||||
self._client_kwargs = kwargs
|
||||
self._client_kwargs["follow_redirects"] = self._client_kwargs.get("follow_redirects", True)
|
||||
|
||||
def _make_client(self):
|
||||
return self._client_class(*self._client_args, **self._client_kwargs)
|
||||
@@ -41,6 +44,10 @@ class DoSyncRequest(BaseDoRequest):
|
||||
def __call__(self, *args, **kwargs):
|
||||
return self.client.request(*args, timeout=60, **kwargs)
|
||||
|
||||
def stream(self, *args, **kwargs):
|
||||
with self.client.stream(*args, **kwargs) as response:
|
||||
return b"".join(response.iter_bytes())
|
||||
|
||||
|
||||
class DoAsyncRequest(BaseDoRequest):
|
||||
_client_class = httpx.AsyncClient
|
||||
@@ -75,7 +82,22 @@ class DoAsyncRequest(BaseDoRequest):
|
||||
|
||||
# Use one-time context and dispose of the loop/client afterwards
|
||||
with self:
|
||||
return self(*args, **kwargs)
|
||||
return self._loop.run_until_complete(self.client.request(*args, **kwargs))
|
||||
|
||||
async def _get_stream(self, *args, **kwargs):
|
||||
async with self.client.stream(*args, **kwargs) as response:
|
||||
content = b""
|
||||
async for c in response.aiter_bytes():
|
||||
content += c
|
||||
return content
|
||||
|
||||
def stream(self, *args, **kwargs):
|
||||
if hasattr(self, "_loop"):
|
||||
return self._loop.run_until_complete(self._get_stream(*args, **kwargs))
|
||||
|
||||
# Use one-time context and dispose of the loop/client afterwards
|
||||
with self:
|
||||
return self._loop.run_until_complete(self._get_stream(*args, **kwargs))
|
||||
|
||||
|
||||
def pytest_generate_tests(metafunc):
|
||||
@@ -89,8 +111,8 @@ def yml(tmpdir, request):
|
||||
|
||||
|
||||
@pytest.mark.online
|
||||
def test_status(tmpdir, mockbin, do_request):
|
||||
url = mockbin
|
||||
def test_status(tmpdir, httpbin, do_request):
|
||||
url = httpbin.url
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join("status.yaml"))):
|
||||
response = do_request()("GET", url)
|
||||
@@ -102,8 +124,8 @@ def test_status(tmpdir, mockbin, do_request):
|
||||
|
||||
|
||||
@pytest.mark.online
|
||||
def test_case_insensitive_headers(tmpdir, mockbin, do_request):
|
||||
url = mockbin
|
||||
def test_case_insensitive_headers(tmpdir, httpbin, do_request):
|
||||
url = httpbin.url
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join("whatever.yaml"))):
|
||||
do_request()("GET", url)
|
||||
@@ -116,8 +138,8 @@ def test_case_insensitive_headers(tmpdir, mockbin, do_request):
|
||||
|
||||
|
||||
@pytest.mark.online
|
||||
def test_content(tmpdir, mockbin, do_request):
|
||||
url = mockbin
|
||||
def test_content(tmpdir, httpbin, do_request):
|
||||
url = httpbin.url
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join("cointent.yaml"))):
|
||||
response = do_request()("GET", url)
|
||||
@@ -129,23 +151,21 @@ def test_content(tmpdir, mockbin, do_request):
|
||||
|
||||
|
||||
@pytest.mark.online
|
||||
def test_json(tmpdir, mockbin, do_request):
|
||||
url = mockbin + "/request"
|
||||
|
||||
headers = {"content-type": "application/json"}
|
||||
def test_json(tmpdir, httpbin, do_request):
|
||||
url = httpbin.url + "/json"
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join("json.yaml"))):
|
||||
response = do_request(headers=headers)("GET", url)
|
||||
response = do_request()("GET", url)
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join("json.yaml"))) as cassette:
|
||||
cassette_response = do_request(headers=headers)("GET", url)
|
||||
cassette_response = do_request()("GET", url)
|
||||
assert cassette_response.json() == response.json()
|
||||
assert cassette.play_count == 1
|
||||
|
||||
|
||||
@pytest.mark.online
|
||||
def test_params_same_url_distinct_params(tmpdir, mockbin, do_request):
|
||||
url = mockbin + "/request"
|
||||
def test_params_same_url_distinct_params(tmpdir, httpbin, do_request):
|
||||
url = httpbin.url + "/get"
|
||||
headers = {"Content-Type": "application/json"}
|
||||
params = {"a": 1, "b": False, "c": "c"}
|
||||
|
||||
@@ -165,22 +185,20 @@ def test_params_same_url_distinct_params(tmpdir, mockbin, do_request):
|
||||
|
||||
|
||||
@pytest.mark.online
|
||||
def test_redirect(mockbin, yml, do_request):
|
||||
url = mockbin + "/redirect/303/2"
|
||||
def test_redirect(httpbin, yml, do_request):
|
||||
url = httpbin.url + "/redirect-to"
|
||||
|
||||
redirect_kwargs = {HTTPX_REDIRECT_PARAM.name: True}
|
||||
|
||||
response = do_request()("GET", url, **redirect_kwargs)
|
||||
response = do_request()("GET", url)
|
||||
with vcr.use_cassette(yml):
|
||||
response = do_request()("GET", url, **redirect_kwargs)
|
||||
response = do_request()("GET", url, params={"url": "./get", "status_code": 302})
|
||||
|
||||
with vcr.use_cassette(yml) as cassette:
|
||||
cassette_response = do_request()("GET", url, **redirect_kwargs)
|
||||
cassette_response = do_request()("GET", url, params={"url": "./get", "status_code": 302})
|
||||
|
||||
assert cassette_response.status_code == response.status_code
|
||||
assert len(cassette_response.history) == len(response.history)
|
||||
assert len(cassette) == 3
|
||||
assert cassette.play_count == 3
|
||||
assert len(cassette) == 2
|
||||
assert cassette.play_count == 2
|
||||
|
||||
# Assert that the real response and the cassette response have a similar
|
||||
# looking request_info.
|
||||
@@ -190,8 +208,8 @@ def test_redirect(mockbin, yml, do_request):
|
||||
|
||||
|
||||
@pytest.mark.online
|
||||
def test_work_with_gzipped_data(mockbin, do_request, yml):
|
||||
url = mockbin + "/gzip?foo=bar"
|
||||
def test_work_with_gzipped_data(httpbin, do_request, yml):
|
||||
url = httpbin.url + "/gzip?foo=bar"
|
||||
headers = {"accept-encoding": "deflate, gzip"}
|
||||
|
||||
with vcr.use_cassette(yml):
|
||||
@@ -217,55 +235,32 @@ def test_simple_fetching(do_request, yml, url):
|
||||
assert cassette.play_count == 1
|
||||
|
||||
|
||||
def test_behind_proxy(do_request):
|
||||
# This is recorded because otherwise we should have a live proxy somewhere.
|
||||
yml = (
|
||||
os.path.dirname(os.path.realpath(__file__)) + "/cassettes/" + "test_httpx_test_test_behind_proxy.yml"
|
||||
)
|
||||
url = "https://mockbin.org/headers"
|
||||
proxy = "http://localhost:8080"
|
||||
proxies = {"http://": proxy, "https://": proxy}
|
||||
|
||||
with vcr.use_cassette(yml):
|
||||
response = do_request(proxies=proxies, verify=False)("GET", url)
|
||||
|
||||
with vcr.use_cassette(yml) as cassette:
|
||||
cassette_response = do_request(proxies=proxies, verify=False)("GET", url)
|
||||
assert str(cassette_response.request.url) == url
|
||||
assert cassette.play_count == 1
|
||||
|
||||
assert cassette_response.headers["Via"] == "my_own_proxy", str(cassette_response.headers)
|
||||
assert cassette_response.request.url == response.request.url
|
||||
|
||||
|
||||
@pytest.mark.online
|
||||
def test_cookies(tmpdir, mockbin, do_request):
|
||||
def test_cookies(tmpdir, httpbin, do_request):
|
||||
def client_cookies(client):
|
||||
return list(client.client.cookies)
|
||||
|
||||
def response_cookies(response):
|
||||
return list(response.cookies)
|
||||
|
||||
url = mockbin + "/bin/26148652-fe25-4f21-aaf5-689b5b4bf65f"
|
||||
headers = {"cookie": "k1=v1;k2=v2"}
|
||||
url = httpbin.url + "/cookies/set"
|
||||
params = {"k1": "v1", "k2": "v2"}
|
||||
|
||||
with do_request(headers=headers) as client:
|
||||
with do_request(params=params, follow_redirects=False) as client:
|
||||
assert client_cookies(client) == []
|
||||
|
||||
redirect_kwargs = {HTTPX_REDIRECT_PARAM.name: True}
|
||||
|
||||
testfile = str(tmpdir.join("cookies.yml"))
|
||||
with vcr.use_cassette(testfile):
|
||||
r1 = client("GET", url, **redirect_kwargs)
|
||||
r1 = client("GET", url)
|
||||
|
||||
assert response_cookies(r1) == ["k1", "k2"]
|
||||
|
||||
r2 = client("GET", url, **redirect_kwargs)
|
||||
r2 = client("GET", url)
|
||||
|
||||
assert response_cookies(r2) == ["k1", "k2"]
|
||||
assert client_cookies(client) == ["k1", "k2"]
|
||||
|
||||
with do_request(headers=headers) as new_client:
|
||||
with do_request(params=params, follow_redirects=False) as new_client:
|
||||
assert client_cookies(new_client) == []
|
||||
|
||||
with vcr.use_cassette(testfile) as cassette:
|
||||
@@ -277,40 +272,44 @@ def test_cookies(tmpdir, mockbin, do_request):
|
||||
|
||||
|
||||
@pytest.mark.online
|
||||
def test_relative_redirects(tmpdir, scheme, do_request, mockbin):
|
||||
redirect_kwargs = {HTTPX_REDIRECT_PARAM.name: True}
|
||||
def test_stream(tmpdir, httpbin, do_request):
|
||||
url = httpbin.url + "/stream-bytes/512"
|
||||
testfile = str(tmpdir.join("stream.yml"))
|
||||
|
||||
url = mockbin + "/redirect/301?to=/redirect/301?to=/request"
|
||||
testfile = str(tmpdir.join("relative_redirects.yml"))
|
||||
with vcr.use_cassette(testfile):
|
||||
response = do_request()("GET", url, **redirect_kwargs)
|
||||
assert len(response.history) == 2, response
|
||||
assert response.json()["url"].endswith("request")
|
||||
response_content = do_request().stream("GET", url)
|
||||
assert len(response_content) == 512
|
||||
|
||||
with vcr.use_cassette(testfile) as cassette:
|
||||
response = do_request()("GET", url, **redirect_kwargs)
|
||||
assert len(response.history) == 2
|
||||
assert response.json()["url"].endswith("request")
|
||||
|
||||
assert cassette.play_count == 3
|
||||
cassette_content = do_request().stream("GET", url)
|
||||
assert cassette_content == response_content
|
||||
assert len(cassette_content) == 512
|
||||
assert cassette.play_count == 1
|
||||
|
||||
|
||||
@pytest.mark.online
|
||||
def test_redirect_wo_allow_redirects(do_request, mockbin, yml):
|
||||
url = mockbin + "/redirect/308/5"
|
||||
def test_text_content_type(tmpdir, httpbin, do_request):
|
||||
url = httpbin.url + "/json"
|
||||
|
||||
redirect_kwargs = {HTTPX_REDIRECT_PARAM.name: False}
|
||||
|
||||
with vcr.use_cassette(yml):
|
||||
response = do_request()("GET", url, **redirect_kwargs)
|
||||
|
||||
assert str(response.url).endswith("308/5")
|
||||
assert response.status_code == 308
|
||||
|
||||
with vcr.use_cassette(yml) as cassette:
|
||||
response = do_request()("GET", url, **redirect_kwargs)
|
||||
|
||||
assert str(response.url).endswith("308/5")
|
||||
assert response.status_code == 308
|
||||
with vcr.use_cassette(str(tmpdir.join("json_type.yaml"))):
|
||||
response = do_request()("GET", url)
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join("json_type.yaml"))) as cassette:
|
||||
cassette_response = do_request()("GET", url)
|
||||
assert cassette_response.content == response.content
|
||||
assert cassette.play_count == 1
|
||||
assert isinstance(cassette.responses[0]["content"], str)
|
||||
|
||||
|
||||
@pytest.mark.online
|
||||
def test_binary_content_type(tmpdir, httpbin, do_request):
|
||||
url = httpbin.url + "/bytes/1024"
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join("json_type.yaml"))):
|
||||
response = do_request()("GET", url)
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join("json_type.yaml"))) as cassette:
|
||||
cassette_response = do_request()("GET", url)
|
||||
assert cassette_response.content == response.content
|
||||
assert cassette.play_count == 1
|
||||
assert isinstance(cassette.responses[0]["content"], bytes)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
"""Test using a proxy."""
|
||||
|
||||
import http.server
|
||||
import multiprocessing
|
||||
import socketserver
|
||||
import threading
|
||||
from urllib.request import urlopen
|
||||
|
||||
import pytest
|
||||
@@ -29,7 +29,8 @@ class Proxy(http.server.SimpleHTTPRequestHandler):
|
||||
# In Python 2 the response is an addinfourl instance.
|
||||
status = upstream_response.code
|
||||
headers = upstream_response.info().items()
|
||||
self.send_response(status, upstream_response.msg)
|
||||
self.log_request(status)
|
||||
self.send_response_only(status, upstream_response.msg)
|
||||
for header in headers:
|
||||
self.send_header(*header)
|
||||
self.end_headers()
|
||||
@@ -39,10 +40,11 @@ class Proxy(http.server.SimpleHTTPRequestHandler):
|
||||
@pytest.fixture(scope="session")
|
||||
def proxy_server():
|
||||
httpd = socketserver.ThreadingTCPServer(("", 0), Proxy)
|
||||
proxy_process = multiprocessing.Process(target=httpd.serve_forever)
|
||||
proxy_process = threading.Thread(target=httpd.serve_forever)
|
||||
proxy_process.start()
|
||||
yield "http://{}:{}".format(*httpd.server_address)
|
||||
proxy_process.terminate()
|
||||
httpd.shutdown()
|
||||
proxy_process.join()
|
||||
|
||||
|
||||
def test_use_proxy(tmpdir, httpbin, proxy_server):
|
||||
@@ -50,8 +52,10 @@ def test_use_proxy(tmpdir, httpbin, proxy_server):
|
||||
with vcr.use_cassette(str(tmpdir.join("proxy.yaml"))):
|
||||
response = requests.get(httpbin.url, proxies={"http": proxy_server})
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join("proxy.yaml"))) as cassette:
|
||||
with vcr.use_cassette(str(tmpdir.join("proxy.yaml")), mode="once") as cassette:
|
||||
cassette_response = requests.get(httpbin.url, proxies={"http": proxy_server})
|
||||
|
||||
for key in set(cassette_response.headers.keys()) & set(response.headers.keys()):
|
||||
assert cassette_response.headers[key] == response.headers[key]
|
||||
assert cassette_response.headers == response.headers
|
||||
assert cassette.play_count == 1
|
||||
|
||||
@@ -14,28 +14,28 @@ def false_matcher(r1, r2):
|
||||
|
||||
|
||||
@pytest.mark.online
|
||||
def test_registered_true_matcher(tmpdir, mockbin_request_url):
|
||||
def test_registered_true_matcher(tmpdir, httpbin):
|
||||
my_vcr = vcr.VCR()
|
||||
my_vcr.register_matcher("true", true_matcher)
|
||||
testfile = str(tmpdir.join("test.yml"))
|
||||
with my_vcr.use_cassette(testfile, match_on=["true"]):
|
||||
# These 2 different urls are stored as the same request
|
||||
urlopen(mockbin_request_url)
|
||||
urlopen(mockbin_request_url + "/get")
|
||||
urlopen(httpbin.url)
|
||||
urlopen(httpbin.url + "/get")
|
||||
|
||||
with my_vcr.use_cassette(testfile, match_on=["true"]):
|
||||
# I can get the response twice even though I only asked for it once
|
||||
urlopen(mockbin_request_url)
|
||||
urlopen(mockbin_request_url)
|
||||
urlopen(httpbin.url)
|
||||
urlopen(httpbin.url)
|
||||
|
||||
|
||||
@pytest.mark.online
|
||||
def test_registered_false_matcher(tmpdir, mockbin_request_url):
|
||||
def test_registered_false_matcher(tmpdir, httpbin):
|
||||
my_vcr = vcr.VCR()
|
||||
my_vcr.register_matcher("false", false_matcher)
|
||||
testfile = str(tmpdir.join("test.yml"))
|
||||
with my_vcr.use_cassette(testfile, match_on=["false"]) as cass:
|
||||
# These 2 different urls are stored as different requests
|
||||
urlopen(mockbin_request_url)
|
||||
urlopen(mockbin_request_url + "/get")
|
||||
urlopen(httpbin.url)
|
||||
urlopen(httpbin.url + "/get")
|
||||
assert len(cass) == 2
|
||||
|
||||
@@ -66,7 +66,7 @@ def test_load_cassette_with_custom_persister(tmpdir, httpbin):
|
||||
|
||||
with my_vcr.use_cassette(test_fixture, serializer="json"):
|
||||
response = urlopen(httpbin.url).read()
|
||||
assert b"difficult sometimes" in response
|
||||
assert b"A simple HTTP Request & Response Service." in response
|
||||
|
||||
|
||||
def test_load_cassette_persister_exception_handling(tmpdir, httpbin):
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
"""Test requests' interaction with vcr"""
|
||||
import pytest
|
||||
from assertions import assert_cassette_empty, assert_is_json_bytes
|
||||
|
||||
import vcr
|
||||
|
||||
from ..assertions import assert_cassette_empty, assert_is_json_bytes
|
||||
|
||||
requests = pytest.importorskip("requests")
|
||||
|
||||
|
||||
|
||||
@@ -2,10 +2,10 @@ import http.client as httplib
|
||||
import json
|
||||
import zlib
|
||||
|
||||
from assertions import assert_is_json_bytes
|
||||
|
||||
import vcr
|
||||
|
||||
from ..assertions import assert_is_json_bytes
|
||||
|
||||
|
||||
def _headers_are_case_insensitive(host, port):
|
||||
conn = httplib.HTTPConnection(host, port)
|
||||
|
||||
@@ -3,11 +3,12 @@
|
||||
import json
|
||||
|
||||
import pytest
|
||||
from assertions import assert_cassette_empty, assert_is_json_bytes
|
||||
|
||||
import vcr
|
||||
from vcr.errors import CannotOverwriteExistingCassetteException
|
||||
|
||||
from ..assertions import assert_cassette_empty, assert_is_json_bytes
|
||||
|
||||
tornado = pytest.importorskip("tornado")
|
||||
http = pytest.importorskip("tornado.httpclient")
|
||||
|
||||
@@ -81,12 +82,12 @@ def test_body(get_client, tmpdir, scheme):
|
||||
|
||||
|
||||
@pytest.mark.gen_test
|
||||
def test_effective_url(get_client, scheme, tmpdir):
|
||||
def test_effective_url(get_client, tmpdir, httpbin):
|
||||
"""Ensure that the effective_url is captured"""
|
||||
url = scheme + "://mockbin.org/redirect/301?url=/html"
|
||||
url = httpbin.url + "/redirect/1"
|
||||
with vcr.use_cassette(str(tmpdir.join("url.yaml"))):
|
||||
effective_url = (yield get(get_client(), url)).effective_url
|
||||
assert effective_url == scheme + "://mockbin.org/redirect/301/0"
|
||||
assert effective_url == httpbin.url + "/get"
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join("url.yaml"))) as cass:
|
||||
assert effective_url == (yield get(get_client(), url)).effective_url
|
||||
|
||||
@@ -5,12 +5,13 @@ from urllib.parse import urlencode
|
||||
from urllib.request import urlopen
|
||||
|
||||
import pytest_httpbin.certs
|
||||
from assertions import assert_cassette_has_one_response
|
||||
from pytest import mark
|
||||
|
||||
# Internal imports
|
||||
import vcr
|
||||
|
||||
from ..assertions import assert_cassette_has_one_response
|
||||
|
||||
|
||||
def urlopen_with_cafile(*args, **kwargs):
|
||||
context = ssl.create_default_context(cafile=pytest_httpbin.certs.where())
|
||||
@@ -57,13 +58,13 @@ def test_response_headers(httpbin_both, tmpdir):
|
||||
|
||||
|
||||
@mark.online
|
||||
def test_effective_url(tmpdir):
|
||||
def test_effective_url(tmpdir, httpbin):
|
||||
"""Ensure that the effective_url is captured"""
|
||||
url = "http://mockbin.org/redirect/301"
|
||||
url = httpbin.url + "/redirect-to?url=.%2F&status_code=301"
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join("headers.yaml"))):
|
||||
effective_url = urlopen_with_cafile(url).geturl()
|
||||
assert effective_url == "http://mockbin.org/redirect/301/0"
|
||||
assert effective_url == httpbin.url + "/"
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join("headers.yaml"))):
|
||||
assert effective_url == urlopen_with_cafile(url).geturl()
|
||||
|
||||
@@ -4,12 +4,13 @@
|
||||
|
||||
import pytest
|
||||
import pytest_httpbin
|
||||
from assertions import assert_cassette_empty, assert_is_json_bytes
|
||||
|
||||
import vcr
|
||||
from vcr.patch import force_reset
|
||||
from vcr.stubs.compat import get_headers
|
||||
|
||||
from ..assertions import assert_cassette_empty, assert_is_json_bytes
|
||||
|
||||
urllib3 = pytest.importorskip("urllib3")
|
||||
|
||||
|
||||
@@ -99,9 +100,9 @@ def test_post(tmpdir, httpbin_both, verify_pool_mgr):
|
||||
|
||||
|
||||
@pytest.mark.online
|
||||
def test_redirects(tmpdir, verify_pool_mgr):
|
||||
def test_redirects(tmpdir, verify_pool_mgr, httpbin):
|
||||
"""Ensure that we can handle redirects"""
|
||||
url = "http://mockbin.org/redirect/301"
|
||||
url = httpbin.url + "/redirect/1"
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join("verify_pool_mgr.yaml"))):
|
||||
content = verify_pool_mgr.request("GET", url).data
|
||||
|
||||
30
tox.ini
30
tox.ini
@@ -2,11 +2,10 @@
|
||||
skip_missing_interpreters=true
|
||||
envlist =
|
||||
cov-clean,
|
||||
lint,
|
||||
{py38,py39,py310,py311}-{requests-urllib3-1,httplib2,urllib3-1,tornado4,boto3,aiohttp,httpx},
|
||||
{py310,py311}-{requests-urllib3-2,urllib3-2},
|
||||
{py38,py39,py310,py311,py312}-{requests-urllib3-1,httplib2,urllib3-1,tornado4,boto3,aiohttp,httpx},
|
||||
{py310,py311,py312}-{requests-urllib3-2,urllib3-2},
|
||||
{pypy3}-{requests-urllib3-1,httplib2,urllib3-1,tornado4,boto3},
|
||||
{py310}-httpx019,
|
||||
#{py310}-httpx019,
|
||||
cov-report
|
||||
|
||||
|
||||
@@ -14,8 +13,9 @@ envlist =
|
||||
python =
|
||||
3.8: py38
|
||||
3.9: py39
|
||||
3.10: py310, lint
|
||||
3.10: py310
|
||||
3.11: py311
|
||||
3.12: py312
|
||||
pypy-3: pypy3
|
||||
|
||||
# Coverage environment tasks: cov-clean and cov-report
|
||||
@@ -32,18 +32,6 @@ commands =
|
||||
coverage html
|
||||
coverage report --fail-under=90
|
||||
|
||||
[testenv:lint]
|
||||
skipsdist = True
|
||||
commands =
|
||||
black --version
|
||||
black --check --diff .
|
||||
ruff --version
|
||||
ruff check .
|
||||
deps =
|
||||
black
|
||||
ruff
|
||||
basepython = python3.10
|
||||
|
||||
[testenv]
|
||||
# Need to use develop install so that paths
|
||||
# for aggregate code coverage combine
|
||||
@@ -74,9 +62,13 @@ deps =
|
||||
httpx019: httpx==0.19
|
||||
{py38,py39,py310}-{httpx}: pytest-asyncio
|
||||
depends =
|
||||
lint,{py38,py39,py310,py311,pypy3}-{requests-urllib3-1,httplib2,urllib3-1,tornado4,boto3},{py310,py311}-{requests-urllib3-2,urllib3-2},{py38,py39,py310,py311}-{aiohttp},{py38,py39,py310,py311}-{httpx}: cov-clean
|
||||
cov-report: lint,{py38,py39,py310,py311,pypy3}-{requests-urllib3-1,httplib2,urllib3-1,tornado4,boto3},{py310,py311}-{requests-urllib3-2,urllib3-2},{py38,py39,py310,py311}-{aiohttp}
|
||||
{py38,py39,py310,py311,py312,pypy3}-{requests-urllib3-1,httplib2,urllib3-1,tornado4,boto3},{py310,py311,py312}-{requests-urllib3-2,urllib3-2},{py38,py39,py310,py311,py312}-{aiohttp},{py38,py39,py310,py311,py312}-{httpx}: cov-clean
|
||||
cov-report: {py38,py39,py310,py311,py312,pypy3}-{requests-urllib3-1,httplib2,urllib3-1,tornado4,boto3},{py310,py311,py312}-{requests-urllib3-2,urllib3-2},{py38,py39,py310,py311,py312}-{aiohttp}
|
||||
passenv =
|
||||
AWS_ACCESS_KEY_ID
|
||||
AWS_DEFAULT_REGION
|
||||
AWS_SECRET_ACCESS_KEY
|
||||
setenv =
|
||||
# workaround for broken C extension in aiohttp
|
||||
# see: https://github.com/aio-libs/aiohttp/issues/7229
|
||||
py312: AIOHTTP_NO_EXTENSIONS=1
|
||||
|
||||
24
vcr/patch.py
24
vcr/patch.py
@@ -95,8 +95,8 @@ try:
|
||||
except ImportError: # pragma: no cover
|
||||
pass
|
||||
else:
|
||||
_HttpxSyncClient_send = httpx.Client.send
|
||||
_HttpxAsyncClient_send = httpx.AsyncClient.send
|
||||
_HttpxSyncClient_send_single_request = httpx.Client._send_single_request
|
||||
_HttpxAsyncClient_send_single_request = httpx.AsyncClient._send_single_request
|
||||
|
||||
|
||||
class CassettePatcherBuilder:
|
||||
@@ -260,10 +260,14 @@ class CassettePatcherBuilder:
|
||||
|
||||
yield cpool, "HTTPConnectionWithTimeout", VCRHTTPConnectionWithTimeout
|
||||
yield cpool, "HTTPSConnectionWithTimeout", VCRHTTPSConnectionWithTimeout
|
||||
yield cpool, "SCHEME_TO_CONNECTION", {
|
||||
"http": VCRHTTPConnectionWithTimeout,
|
||||
"https": VCRHTTPSConnectionWithTimeout,
|
||||
}
|
||||
yield (
|
||||
cpool,
|
||||
"SCHEME_TO_CONNECTION",
|
||||
{
|
||||
"http": VCRHTTPConnectionWithTimeout,
|
||||
"https": VCRHTTPSConnectionWithTimeout,
|
||||
},
|
||||
)
|
||||
|
||||
@_build_patchers_from_mock_triples_decorator
|
||||
def _tornado(self):
|
||||
@@ -307,11 +311,11 @@ class CassettePatcherBuilder:
|
||||
else:
|
||||
from .stubs.httpx_stubs import async_vcr_send, sync_vcr_send
|
||||
|
||||
new_async_client_send = async_vcr_send(self._cassette, _HttpxAsyncClient_send)
|
||||
yield httpx.AsyncClient, "send", new_async_client_send
|
||||
new_async_client_send = async_vcr_send(self._cassette, _HttpxAsyncClient_send_single_request)
|
||||
yield httpx.AsyncClient, "_send_single_request", new_async_client_send
|
||||
|
||||
new_sync_client_send = sync_vcr_send(self._cassette, _HttpxSyncClient_send)
|
||||
yield httpx.Client, "send", new_sync_client_send
|
||||
new_sync_client_send = sync_vcr_send(self._cassette, _HttpxSyncClient_send_single_request)
|
||||
yield httpx.Client, "_send_single_request", new_sync_client_send
|
||||
|
||||
def _urllib3_patchers(self, cpool, conn, stubs):
|
||||
http_connection_remover = ConnectionRemover(
|
||||
|
||||
@@ -56,7 +56,7 @@ def convert_body_to_unicode(resp):
|
||||
If the request or responses body is bytes, decode it to a string
|
||||
(for python3 support)
|
||||
"""
|
||||
if type(resp) is not dict:
|
||||
if not isinstance(resp, dict):
|
||||
# Some of the tests just serialize and deserialize a string.
|
||||
return _convert_string_to_unicode(resp)
|
||||
else:
|
||||
|
||||
@@ -389,6 +389,8 @@ class VCRHTTPConnection(VCRConnection):
|
||||
|
||||
_baseclass = HTTPConnection
|
||||
_protocol = "http"
|
||||
debuglevel = _baseclass.debuglevel
|
||||
_http_vsn = _baseclass._http_vsn
|
||||
|
||||
|
||||
class VCRHTTPSConnection(VCRConnection):
|
||||
@@ -397,3 +399,5 @@ class VCRHTTPSConnection(VCRConnection):
|
||||
_baseclass = HTTPSConnection
|
||||
_protocol = "https"
|
||||
is_verified = True
|
||||
debuglevel = _baseclass.debuglevel
|
||||
_http_vsn = _baseclass._http_vsn
|
||||
|
||||
@@ -34,11 +34,16 @@ def _transform_headers(httpx_response):
|
||||
|
||||
|
||||
def _to_serialized_response(httpx_response):
|
||||
try:
|
||||
content = httpx_response.content.decode("utf-8")
|
||||
except UnicodeDecodeError:
|
||||
content = httpx_response.content
|
||||
|
||||
return {
|
||||
"status_code": httpx_response.status_code,
|
||||
"http_version": httpx_response.http_version,
|
||||
"headers": _transform_headers(httpx_response),
|
||||
"content": httpx_response.content.decode("utf-8", "ignore"),
|
||||
"content": content,
|
||||
}
|
||||
|
||||
|
||||
@@ -57,7 +62,9 @@ def _from_serialized_headers(headers):
|
||||
@patch("httpx.Response.close", MagicMock())
|
||||
@patch("httpx.Response.read", MagicMock())
|
||||
def _from_serialized_response(request, serialized_response, history=None):
|
||||
content = serialized_response.get("content").encode()
|
||||
content = serialized_response.get("content")
|
||||
if isinstance(content, str):
|
||||
content = content.encode("utf-8")
|
||||
response = httpx.Response(
|
||||
status_code=serialized_response.get("status_code"),
|
||||
request=request,
|
||||
@@ -106,30 +113,8 @@ def _record_responses(cassette, vcr_request, real_response):
|
||||
|
||||
|
||||
def _play_responses(cassette, request, vcr_request, client, kwargs):
|
||||
history = []
|
||||
|
||||
allow_redirects = kwargs.get(
|
||||
HTTPX_REDIRECT_PARAM.name,
|
||||
HTTPX_REDIRECT_PARAM.default,
|
||||
)
|
||||
vcr_response = cassette.play_response(vcr_request)
|
||||
response = _from_serialized_response(request, vcr_response)
|
||||
|
||||
while allow_redirects and 300 <= response.status_code <= 399:
|
||||
next_url = response.headers.get("location")
|
||||
if not next_url:
|
||||
break
|
||||
|
||||
vcr_request = VcrRequest("GET", next_url, None, dict(response.headers))
|
||||
vcr_request = cassette.find_requests_with_most_matches(vcr_request)[0][0]
|
||||
|
||||
history.append(response)
|
||||
# add cookies from response to session cookie store
|
||||
client.cookies.extract_cookies(response)
|
||||
|
||||
vcr_response = cassette.play_response(vcr_request)
|
||||
response = _from_serialized_response(vcr_request, vcr_response, history)
|
||||
|
||||
return response
|
||||
|
||||
|
||||
@@ -141,6 +126,7 @@ async def _async_vcr_send(cassette, real_send, *args, **kwargs):
|
||||
return response
|
||||
|
||||
real_response = await real_send(*args, **kwargs)
|
||||
await real_response.aread()
|
||||
return _record_responses(cassette, vcr_request, real_response)
|
||||
|
||||
|
||||
@@ -160,6 +146,7 @@ def _sync_vcr_send(cassette, real_send, *args, **kwargs):
|
||||
return response
|
||||
|
||||
real_response = real_send(*args, **kwargs)
|
||||
real_response.read()
|
||||
return _record_responses(cassette, vcr_request, real_response)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user