1
0
mirror of https://github.com/kevin1024/vcrpy.git synced 2025-12-09 17:15:35 +00:00

Merge branch 'master' of github.com:kevin1024/vcrpy into fix-resource-warning-2

This commit is contained in:
Thomas Grainger
2023-12-15 10:48:27 +00:00
42 changed files with 445 additions and 370 deletions

View File

@@ -17,6 +17,6 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v4
- name: Codespell - name: Codespell
uses: codespell-project/actions-codespell@v2 uses: codespell-project/actions-codespell@v2

View File

@@ -10,10 +10,10 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: actions/setup-python@v4 - uses: actions/setup-python@v5
with: with:
python-version: "3.11" python-version: "3.12"
- name: Install build dependencies - name: Install build dependencies
run: pip install -r docs/requirements.txt run: pip install -r docs/requirements.txt

View File

@@ -13,13 +13,13 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: 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: steps:
- uses: actions/checkout@v3.5.2 - uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }} - name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4 uses: actions/setup-python@v5
with: with:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}

View 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
View 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
View 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

View File

@@ -9,7 +9,7 @@ version: 2
build: build:
os: ubuntu-22.04 os: ubuntu-22.04
tools: tools:
python: "3.11" python: "3.12"
# Build documentation in the docs/ directory with Sphinx # Build documentation in the docs/ directory with Sphinx
sphinx: sphinx:
@@ -20,3 +20,5 @@ sphinx:
python: python:
install: install:
- requirements: docs/requirements.txt - requirements: docs/requirements.txt
- method: pip
path: .

View File

@@ -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 .. |CodeCov| image:: https://codecov.io/gh/kevin1024/vcrpy/branch/master/graph/badge.svg
:target: https://codecov.io/gh/kevin1024/vcrpy :target: https://codecov.io/gh/kevin1024/vcrpy
:alt: Code Coverage Status :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

View File

@@ -23,6 +23,7 @@ All help in providing PRs to close out bug issues is appreciated. Even if that i
- 5.0.0 - 5.0.0
- BREAKING CHANGE: Drop support for Python 3.7. 3.7 is EOL as of 6/27/23 Thanks @jairhenrique - 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 - 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 - 4.4.0
- HUGE thanks to @hartwork for all the work done on this release! - 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 - 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
@@ -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 - Add support for requests / urllib3
- 0.0.1 - 0.0.1
- Initial Release - Initial Release

View File

@@ -139,7 +139,6 @@ in this example::
tox tox
# Run the whole test suite or just part of it # Run the whole test suite or just part of it
tox -e lint
tox -e py38-requests tox -e py38-requests

View File

@@ -1,2 +1,2 @@
sphinx<8 sphinx<8
sphinx_rtd_theme==1.2.2 sphinx_rtd_theme==2.0.0

View File

@@ -1,6 +1,3 @@
[tool.black]
line-length=110
[tool.codespell] [tool.codespell]
skip = '.git,*.pdf,*.svg,.tox' skip = '.git,*.pdf,*.svg,.tox'
ignore-regex = "\\\\[fnrstv]" ignore-regex = "\\\\[fnrstv]"
@@ -8,9 +5,7 @@ ignore-regex = "\\\\[fnrstv]"
# ignore-words-list = '' # ignore-words-list = ''
[tool.pytest.ini_options] [tool.pytest.ini_options]
markers = [ markers = ["online"]
"online",
]
[tool.ruff] [tool.ruff]
select = [ select = [
@@ -30,4 +25,4 @@ line-length = 110
target-version = "py38" target-version = "py38"
[tool.ruff.isort] [tool.ruff.isort]
known-first-party = [ "vcr" ] known-first-party = ["vcr"]

View File

@@ -46,13 +46,15 @@ install_requires = [
"PyYAML", "PyYAML",
"wrapt", "wrapt",
"yarl", "yarl",
# Support for urllib3 >=2 needs Python >=3.10 # Support for urllib3 >=2 needs CPython >=3.10
# so we need to block urllib3 >=2 for Python <3.10 for now. # 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, # Note that vcrpy would work fine without any urllib3 around,
# so this block and the dependency can be dropped at some point # so this block and the dependency can be dropped at some point
# in the future. For more Details: # in the future. For more Details:
# https://github.com/kevin1024/vcrpy/pull/699#issuecomment-1551439663 # https://github.com/kevin1024/vcrpy/pull/699#issuecomment-1551439663
"urllib3 <2; python_version <'3.10'", "urllib3 <2; python_version <'3.10'",
# https://github.com/kevin1024/vcrpy/pull/775#issuecomment-1847849962
"urllib3 <2; platform_python_implementation =='PyPy'",
] ]
tests_require = [ tests_require = [
@@ -98,6 +100,7 @@ setup(
"Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy", "Programming Language :: Python :: Implementation :: PyPy",

0
tests/__init__.py Normal file
View File

View File

@@ -4,22 +4,6 @@ import ssl
import pytest 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 @pytest.fixture
def httpbin_ssl_context(): def httpbin_ssl_context():
ssl_ca_location = os.environ["REQUESTS_CA_BUNDLE"] ssl_ca_location = os.environ["REQUESTS_CA_BUNDLE"]

View File

@@ -36,8 +36,8 @@ def post(url, output="text", **kwargs):
@pytest.mark.online @pytest.mark.online
def test_status(tmpdir, mockbin_request_url): def test_status(tmpdir, httpbin):
url = mockbin_request_url url = httpbin.url
with vcr.use_cassette(str(tmpdir.join("status.yaml"))): with vcr.use_cassette(str(tmpdir.join("status.yaml"))):
response, _ = get(url) response, _ = get(url)
@@ -50,8 +50,8 @@ def test_status(tmpdir, mockbin_request_url):
@pytest.mark.online @pytest.mark.online
@pytest.mark.parametrize("auth", [None, aiohttp.BasicAuth("vcrpy", "test")]) @pytest.mark.parametrize("auth", [None, aiohttp.BasicAuth("vcrpy", "test")])
def test_headers(tmpdir, auth, mockbin_request_url): def test_headers(tmpdir, auth, httpbin):
url = mockbin_request_url url = httpbin.url
with vcr.use_cassette(str(tmpdir.join("headers.yaml"))): with vcr.use_cassette(str(tmpdir.join("headers.yaml"))):
response, _ = get(url, auth=auth) response, _ = get(url, auth=auth)
@@ -67,8 +67,8 @@ def test_headers(tmpdir, auth, mockbin_request_url):
@pytest.mark.online @pytest.mark.online
def test_case_insensitive_headers(tmpdir, mockbin_request_url): def test_case_insensitive_headers(tmpdir, httpbin):
url = mockbin_request_url url = httpbin.url
with vcr.use_cassette(str(tmpdir.join("whatever.yaml"))): with vcr.use_cassette(str(tmpdir.join("whatever.yaml"))):
_, _ = get(url) _, _ = get(url)
@@ -81,8 +81,8 @@ def test_case_insensitive_headers(tmpdir, mockbin_request_url):
@pytest.mark.online @pytest.mark.online
def test_text(tmpdir, mockbin_request_url): def test_text(tmpdir, httpbin):
url = mockbin_request_url url = httpbin.url
with vcr.use_cassette(str(tmpdir.join("text.yaml"))): with vcr.use_cassette(str(tmpdir.join("text.yaml"))):
_, response_text = get(url) _, response_text = get(url)
@@ -94,8 +94,8 @@ def test_text(tmpdir, mockbin_request_url):
@pytest.mark.online @pytest.mark.online
def test_json(tmpdir, mockbin_request_url): def test_json(tmpdir, httpbin):
url = mockbin_request_url url = httpbin.url + "/json"
headers = {"Content-Type": "application/json"} headers = {"Content-Type": "application/json"}
with vcr.use_cassette(str(tmpdir.join("json.yaml"))): with vcr.use_cassette(str(tmpdir.join("json.yaml"))):
@@ -108,8 +108,8 @@ def test_json(tmpdir, mockbin_request_url):
@pytest.mark.online @pytest.mark.online
def test_binary(tmpdir, mockbin_request_url): def test_binary(tmpdir, httpbin):
url = mockbin_request_url + "/image/png" url = httpbin.url + "/image/png"
with vcr.use_cassette(str(tmpdir.join("binary.yaml"))): with vcr.use_cassette(str(tmpdir.join("binary.yaml"))):
_, response_binary = get(url, output="raw") _, response_binary = get(url, output="raw")
@@ -120,8 +120,8 @@ def test_binary(tmpdir, mockbin_request_url):
@pytest.mark.online @pytest.mark.online
def test_stream(tmpdir, mockbin_request_url): def test_stream(tmpdir, httpbin):
url = mockbin_request_url url = httpbin.url
with vcr.use_cassette(str(tmpdir.join("stream.yaml"))): 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 _, 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.online
@pytest.mark.parametrize("body", ["data", "json"]) @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) caplog.set_level(logging.INFO)
data = {"key1": "value1", "key2": "value2"} data = {"key1": "value1", "key2": "value2"}
url = mockbin_request_url url = httpbin.url
with vcr.use_cassette(str(tmpdir.join("post.yaml"))): with vcr.use_cassette(str(tmpdir.join("post.yaml"))):
_, response_json = post(url, **{body: data}) _, response_json = post(url, **{body: data})
@@ -159,14 +159,14 @@ def test_post(tmpdir, body, caplog, mockbin_request_url):
@pytest.mark.online @pytest.mark.online
def test_params(tmpdir, mockbin_request_url): def test_params(tmpdir, httpbin):
url = mockbin_request_url + "?d=d" url = httpbin.url + "/get?d=d"
headers = {"Content-Type": "application/json"} headers = {"Content-Type": "application/json"}
params = {"a": 1, "b": 2, "c": "c"} params = {"a": 1, "b": 2, "c": "c"}
with vcr.use_cassette(str(tmpdir.join("get.yaml"))) as cassette: with vcr.use_cassette(str(tmpdir.join("get.yaml"))) as cassette:
_, response_json = get(url, output="json", params=params, headers=headers) _, 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: with vcr.use_cassette(str(tmpdir.join("get.yaml"))) as cassette:
_, cassette_response_json = get(url, output="json", params=params, headers=headers) _, 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 @pytest.mark.online
def test_params_same_url_distinct_params(tmpdir, mockbin_request_url): def test_params_same_url_distinct_params(tmpdir, httpbin):
url = mockbin_request_url url = httpbin.url + "/json"
headers = {"Content-Type": "application/json"} headers = {"Content-Type": "application/json"}
params = {"a": 1, "b": 2, "c": "c"} 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 @pytest.mark.online
def test_params_on_url(tmpdir, mockbin_request_url): def test_params_on_url(tmpdir, httpbin):
url = mockbin_request_url + "?a=1&b=foo" url = httpbin.url + "/get?a=1&b=foo"
headers = {"Content-Type": "application/json"} headers = {"Content-Type": "application/json"}
with vcr.use_cassette(str(tmpdir.join("get.yaml"))) as cassette: 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 @pytest.mark.online
def test_redirect(tmpdir, mockbin): def test_redirect(tmpdir, httpbin):
url = mockbin + "/redirect/302/2" url = httpbin.url + "/redirect/2"
with vcr.use_cassette(str(tmpdir.join("redirect.yaml"))): with vcr.use_cassette(str(tmpdir.join("redirect.yaml"))):
response, _ = get(url) response, _ = get(url)
@@ -284,9 +284,9 @@ def test_redirect(tmpdir, mockbin):
@pytest.mark.online @pytest.mark.online
def test_not_modified(tmpdir, mockbin): def test_not_modified(tmpdir, httpbin):
"""It doesn't try to redirect on 304""" """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"))): with vcr.use_cassette(str(tmpdir.join("not_modified.yaml"))):
response, _ = get(url) response, _ = get(url)
@@ -302,13 +302,13 @@ def test_not_modified(tmpdir, mockbin):
@pytest.mark.online @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, """We should capture, record, and replay all requests and response chains,
even if there are duplicate ones. even if there are duplicate ones.
We should replay in the order we saw them. 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"))): with vcr.use_cassette(str(tmpdir.join("text.yaml"))):
_, response_text1 = get(url, output="text") _, response_text1 = get(url, output="text")
@@ -426,18 +426,18 @@ def test_cookies_redirect(httpbin_both, httpbin_ssl_context, tmpdir):
@pytest.mark.online @pytest.mark.online
def test_not_allow_redirects(tmpdir, mockbin): def test_not_allow_redirects(tmpdir, httpbin):
url = mockbin + "/redirect/308/5" url = httpbin + "/redirect-to?url=.%2F&status_code=308"
path = str(tmpdir.join("redirects.yaml")) path = str(tmpdir.join("redirects.yaml"))
with vcr.use_cassette(path): with vcr.use_cassette(path):
response, _ = get(url, allow_redirects=False) response, _ = get(url, allow_redirects=False)
assert response.url.path == "/redirect/308/5" assert response.url.path == "/redirect-to"
assert response.status == 308 assert response.status == 308
with vcr.use_cassette(path) as cassette: with vcr.use_cassette(path) as cassette:
response, _ = get(url, allow_redirects=False) response, _ = get(url, allow_redirects=False)
assert response.url.path == "/redirect/308/5" assert response.url.path == "/redirect-to"
assert response.status == 308 assert response.status == 308
assert cassette.play_count == 1 assert cassette.play_count == 1

View File

@@ -39,7 +39,7 @@ def test_basic_json_use(tmpdir, httpbin):
test_fixture = str(tmpdir.join("synopsis.json")) test_fixture = str(tmpdir.join("synopsis.json"))
with vcr.use_cassette(test_fixture, serializer="json"): with vcr.use_cassette(test_fixture, serializer="json"):
response = urlopen(httpbin.url).read() response = urlopen(httpbin.url).read()
assert b"difficult sometimes" in response assert b"A simple HTTP Request &amp; Response Service." in response
def test_patched_content(tmpdir, httpbin): def test_patched_content(tmpdir, httpbin):

View File

@@ -8,12 +8,12 @@ import vcr
@pytest.mark.online @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") my_vcr = vcr.VCR(serializer="json")
with my_vcr.use_cassette(str(tmpdir.join("test.json"))): with my_vcr.use_cassette(str(tmpdir.join("test.json"))):
assert my_vcr.serializer == "json" assert my_vcr.serializer == "json"
urlopen(mockbin_request_url) urlopen(httpbin.url)
with open(str(tmpdir.join("test.json"))) as f: with open(str(tmpdir.join("test.json"))) as f:
file_content = f.read() file_content = f.read()
@@ -22,37 +22,37 @@ def test_set_serializer_default_config(tmpdir, mockbin_request_url):
@pytest.mark.online @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"))) my_vcr = vcr.VCR(cassette_library_dir=str(tmpdir.join("subdir")))
with my_vcr.use_cassette("test.json"): 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"))) assert os.path.exists(str(tmpdir.join("subdir").join("test.json")))
@pytest.mark.online @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"))) my_vcr = vcr.VCR(cassette_library_dir=str(tmpdir.join("subdir")))
cld = str(tmpdir.join("subdir2")) cld = str(tmpdir.join("subdir2"))
with my_vcr.use_cassette("test.json", cassette_library_dir=cld): 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 os.path.exists(str(tmpdir.join("subdir2").join("test.json")))
assert not os.path.exists(str(tmpdir.join("subdir").join("test.json"))) assert not os.path.exists(str(tmpdir.join("subdir").join("test.json")))
@pytest.mark.online @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"]) my_vcr = vcr.VCR(match_on=["method"])
with my_vcr.use_cassette(str(tmpdir.join("test.json"))): 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: with my_vcr.use_cassette(str(tmpdir.join("test.json"))) as cass:
urlopen(mockbin_request_url) urlopen(httpbin.url)
assert len(cass) == 1 assert len(cass) == 1
assert cass.play_count == 1 assert cass.play_count == 1
@@ -67,12 +67,12 @@ def test_missing_matcher():
@pytest.mark.online @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 = vcr.VCR(record_on_exception=False)
@my_vcr.use_cassette(str(tmpdir.join("dontsave.yml"))) @my_vcr.use_cassette(str(tmpdir.join("dontsave.yml")))
def some_test(): 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): with pytest.raises(AssertionError):
some_test() some_test()
@@ -82,6 +82,6 @@ def test_dont_record_on_exception(tmpdir, mockbin_request_url):
# Make sure context decorator has the same behavior # Make sure context decorator has the same behavior
with pytest.raises(AssertionError): with pytest.raises(AssertionError):
with my_vcr.use_cassette(str(tmpdir.join("dontsave2.yml"))): 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"))) assert not os.path.exists(str(tmpdir.join("dontsave2.yml")))

View File

@@ -12,19 +12,19 @@ import vcr
@pytest.mark.online @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 Ensure that when you close a cassette without changing it it doesn't
rewrite the file rewrite the file
""" """
fname = str(tmpdir.join("synopsis.yaml")) fname = str(tmpdir.join("synopsis.yaml"))
with vcr.use_cassette(fname) as cass: with vcr.use_cassette(fname) as cass:
urlopen(mockbin_request_url).read() urlopen(httpbin.url).read()
assert cass.play_count == 0 assert cass.play_count == 0
last_mod = os.path.getmtime(fname) last_mod = os.path.getmtime(fname)
with vcr.use_cassette(fname) as cass: with vcr.use_cassette(fname) as cass:
urlopen(mockbin_request_url).read() urlopen(httpbin.url).read()
assert cass.play_count == 1 assert cass.play_count == 1
assert cass.dirty is False assert cass.dirty is False
last_mod2 = os.path.getmtime(fname) last_mod2 = os.path.getmtime(fname)
@@ -33,14 +33,14 @@ def test_disk_saver_nowrite(tmpdir, mockbin_request_url):
@pytest.mark.online @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 Ensure that when you close a cassette after changing it it does
rewrite the file rewrite the file
""" """
fname = str(tmpdir.join("synopsis.yaml")) fname = str(tmpdir.join("synopsis.yaml"))
with vcr.use_cassette(fname) as cass: with vcr.use_cassette(fname) as cass:
urlopen(mockbin_request_url).read() urlopen(httpbin.url).read()
assert cass.play_count == 0 assert cass.play_count == 0
last_mod = os.path.getmtime(fname) last_mod = os.path.getmtime(fname)
@@ -49,8 +49,8 @@ def test_disk_saver_write(tmpdir, mockbin_request_url):
time.sleep(1) time.sleep(1)
with vcr.use_cassette(fname, record_mode=vcr.mode.ANY) as cass: with vcr.use_cassette(fname, record_mode=vcr.mode.ANY) as cass:
urlopen(mockbin_request_url).read() urlopen(httpbin.url).read()
urlopen(mockbin_request_url + "/get").read() urlopen(httpbin.url + "/get").read()
assert cass.play_count == 1 assert cass.play_count == 1
assert cass.dirty assert cass.dirty
last_mod2 = os.path.getmtime(fname) last_mod2 = os.path.getmtime(fname)

View File

@@ -5,10 +5,11 @@ from urllib.parse import urlencode
from urllib.request import Request, urlopen from urllib.request import Request, urlopen
import pytest import pytest
from assertions import assert_cassette_has_one_response, assert_is_json_bytes
import vcr import vcr
from ..assertions import assert_cassette_has_one_response, assert_is_json_bytes
def _request_with_auth(url, username, password): def _request_with_auth(url, username, password):
request = Request(url) request = Request(url)

View File

@@ -3,10 +3,11 @@ from urllib.parse import urlencode
import pytest import pytest
import pytest_httpbin.certs import pytest_httpbin.certs
from assertions import assert_cassette_has_one_response
import vcr import vcr
from ..assertions import assert_cassette_has_one_response
httplib2 = pytest.importorskip("httplib2") httplib2 = pytest.importorskip("httplib2")
@@ -56,14 +57,14 @@ def test_response_headers(tmpdir, httpbin_both):
@pytest.mark.online @pytest.mark.online
def test_effective_url(tmpdir): def test_effective_url(tmpdir, httpbin):
"""Ensure that the effective_url is captured""" """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"))): with vcr.use_cassette(str(tmpdir.join("headers.yaml"))):
resp, _ = http().request(url) resp, _ = http().request(url)
effective_url = resp["content-location"] 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"))): with vcr.use_cassette(str(tmpdir.join("headers.yaml"))):
resp, _ = http().request(url) resp, _ = http().request(url)

View File

@@ -1,5 +1,3 @@
import os
import pytest import pytest
import vcr import vcr
@@ -7,7 +5,11 @@ import vcr
asyncio = pytest.importorskip("asyncio") asyncio = pytest.importorskip("asyncio")
httpx = pytest.importorskip("httpx") 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: class BaseDoRequest:
@@ -16,6 +18,7 @@ class BaseDoRequest:
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self._client_args = args self._client_args = args
self._client_kwargs = kwargs self._client_kwargs = kwargs
self._client_kwargs["follow_redirects"] = self._client_kwargs.get("follow_redirects", True)
def _make_client(self): def _make_client(self):
return self._client_class(*self._client_args, **self._client_kwargs) return self._client_class(*self._client_args, **self._client_kwargs)
@@ -41,6 +44,10 @@ class DoSyncRequest(BaseDoRequest):
def __call__(self, *args, **kwargs): def __call__(self, *args, **kwargs):
return self.client.request(*args, timeout=60, **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): class DoAsyncRequest(BaseDoRequest):
_client_class = httpx.AsyncClient _client_class = httpx.AsyncClient
@@ -75,7 +82,22 @@ class DoAsyncRequest(BaseDoRequest):
# Use one-time context and dispose of the loop/client afterwards # Use one-time context and dispose of the loop/client afterwards
with self: 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): def pytest_generate_tests(metafunc):
@@ -89,8 +111,8 @@ def yml(tmpdir, request):
@pytest.mark.online @pytest.mark.online
def test_status(tmpdir, mockbin, do_request): def test_status(tmpdir, httpbin, do_request):
url = mockbin url = httpbin.url
with vcr.use_cassette(str(tmpdir.join("status.yaml"))): with vcr.use_cassette(str(tmpdir.join("status.yaml"))):
response = do_request()("GET", url) response = do_request()("GET", url)
@@ -102,8 +124,8 @@ def test_status(tmpdir, mockbin, do_request):
@pytest.mark.online @pytest.mark.online
def test_case_insensitive_headers(tmpdir, mockbin, do_request): def test_case_insensitive_headers(tmpdir, httpbin, do_request):
url = mockbin url = httpbin.url
with vcr.use_cassette(str(tmpdir.join("whatever.yaml"))): with vcr.use_cassette(str(tmpdir.join("whatever.yaml"))):
do_request()("GET", url) do_request()("GET", url)
@@ -116,8 +138,8 @@ def test_case_insensitive_headers(tmpdir, mockbin, do_request):
@pytest.mark.online @pytest.mark.online
def test_content(tmpdir, mockbin, do_request): def test_content(tmpdir, httpbin, do_request):
url = mockbin url = httpbin.url
with vcr.use_cassette(str(tmpdir.join("cointent.yaml"))): with vcr.use_cassette(str(tmpdir.join("cointent.yaml"))):
response = do_request()("GET", url) response = do_request()("GET", url)
@@ -129,23 +151,21 @@ def test_content(tmpdir, mockbin, do_request):
@pytest.mark.online @pytest.mark.online
def test_json(tmpdir, mockbin, do_request): def test_json(tmpdir, httpbin, do_request):
url = mockbin + "/request" url = httpbin.url + "/json"
headers = {"content-type": "application/json"}
with vcr.use_cassette(str(tmpdir.join("json.yaml"))): 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: 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_response.json() == response.json()
assert cassette.play_count == 1 assert cassette.play_count == 1
@pytest.mark.online @pytest.mark.online
def test_params_same_url_distinct_params(tmpdir, mockbin, do_request): def test_params_same_url_distinct_params(tmpdir, httpbin, do_request):
url = mockbin + "/request" url = httpbin.url + "/get"
headers = {"Content-Type": "application/json"} headers = {"Content-Type": "application/json"}
params = {"a": 1, "b": False, "c": "c"} 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 @pytest.mark.online
def test_redirect(mockbin, yml, do_request): def test_redirect(httpbin, yml, do_request):
url = mockbin + "/redirect/303/2" url = httpbin.url + "/redirect-to"
redirect_kwargs = {HTTPX_REDIRECT_PARAM.name: True} response = do_request()("GET", url)
response = do_request()("GET", url, **redirect_kwargs)
with vcr.use_cassette(yml): 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: 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 cassette_response.status_code == response.status_code
assert len(cassette_response.history) == len(response.history) assert len(cassette_response.history) == len(response.history)
assert len(cassette) == 3 assert len(cassette) == 2
assert cassette.play_count == 3 assert cassette.play_count == 2
# Assert that the real response and the cassette response have a similar # Assert that the real response and the cassette response have a similar
# looking request_info. # looking request_info.
@@ -190,8 +208,8 @@ def test_redirect(mockbin, yml, do_request):
@pytest.mark.online @pytest.mark.online
def test_work_with_gzipped_data(mockbin, do_request, yml): def test_work_with_gzipped_data(httpbin, do_request, yml):
url = mockbin + "/gzip?foo=bar" url = httpbin.url + "/gzip?foo=bar"
headers = {"accept-encoding": "deflate, gzip"} headers = {"accept-encoding": "deflate, gzip"}
with vcr.use_cassette(yml): with vcr.use_cassette(yml):
@@ -217,55 +235,32 @@ def test_simple_fetching(do_request, yml, url):
assert cassette.play_count == 1 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 @pytest.mark.online
def test_cookies(tmpdir, mockbin, do_request): def test_cookies(tmpdir, httpbin, do_request):
def client_cookies(client): def client_cookies(client):
return list(client.client.cookies) return list(client.client.cookies)
def response_cookies(response): def response_cookies(response):
return list(response.cookies) return list(response.cookies)
url = mockbin + "/bin/26148652-fe25-4f21-aaf5-689b5b4bf65f" url = httpbin.url + "/cookies/set"
headers = {"cookie": "k1=v1;k2=v2"} 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) == [] assert client_cookies(client) == []
redirect_kwargs = {HTTPX_REDIRECT_PARAM.name: True}
testfile = str(tmpdir.join("cookies.yml")) testfile = str(tmpdir.join("cookies.yml"))
with vcr.use_cassette(testfile): with vcr.use_cassette(testfile):
r1 = client("GET", url, **redirect_kwargs) r1 = client("GET", url)
assert response_cookies(r1) == ["k1", "k2"] assert response_cookies(r1) == ["k1", "k2"]
r2 = client("GET", url, **redirect_kwargs) r2 = client("GET", url)
assert response_cookies(r2) == ["k1", "k2"] assert response_cookies(r2) == ["k1", "k2"]
assert client_cookies(client) == ["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) == [] assert client_cookies(new_client) == []
with vcr.use_cassette(testfile) as cassette: with vcr.use_cassette(testfile) as cassette:
@@ -277,40 +272,44 @@ def test_cookies(tmpdir, mockbin, do_request):
@pytest.mark.online @pytest.mark.online
def test_relative_redirects(tmpdir, scheme, do_request, mockbin): def test_stream(tmpdir, httpbin, do_request):
redirect_kwargs = {HTTPX_REDIRECT_PARAM.name: True} 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): with vcr.use_cassette(testfile):
response = do_request()("GET", url, **redirect_kwargs) response_content = do_request().stream("GET", url)
assert len(response.history) == 2, response assert len(response_content) == 512
assert response.json()["url"].endswith("request")
with vcr.use_cassette(testfile) as cassette: with vcr.use_cassette(testfile) as cassette:
response = do_request()("GET", url, **redirect_kwargs) cassette_content = do_request().stream("GET", url)
assert len(response.history) == 2 assert cassette_content == response_content
assert response.json()["url"].endswith("request") assert len(cassette_content) == 512
assert cassette.play_count == 1
assert cassette.play_count == 3
@pytest.mark.online @pytest.mark.online
def test_redirect_wo_allow_redirects(do_request, mockbin, yml): def test_text_content_type(tmpdir, httpbin, do_request):
url = mockbin + "/redirect/308/5" url = httpbin.url + "/json"
redirect_kwargs = {HTTPX_REDIRECT_PARAM.name: False} with vcr.use_cassette(str(tmpdir.join("json_type.yaml"))):
response = do_request()("GET", url)
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"))) as cassette:
cassette_response = do_request()("GET", url)
assert cassette_response.content == response.content
assert cassette.play_count == 1 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)

View File

@@ -1,8 +1,8 @@
"""Test using a proxy.""" """Test using a proxy."""
import http.server import http.server
import multiprocessing
import socketserver import socketserver
import threading
from urllib.request import urlopen from urllib.request import urlopen
import pytest import pytest
@@ -29,7 +29,8 @@ class Proxy(http.server.SimpleHTTPRequestHandler):
# In Python 2 the response is an addinfourl instance. # In Python 2 the response is an addinfourl instance.
status = upstream_response.code status = upstream_response.code
headers = upstream_response.info().items() 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: for header in headers:
self.send_header(*header) self.send_header(*header)
self.end_headers() self.end_headers()
@@ -39,10 +40,11 @@ class Proxy(http.server.SimpleHTTPRequestHandler):
@pytest.fixture(scope="session") @pytest.fixture(scope="session")
def proxy_server(): def proxy_server():
httpd = socketserver.ThreadingTCPServer(("", 0), Proxy) httpd = socketserver.ThreadingTCPServer(("", 0), Proxy)
proxy_process = multiprocessing.Process(target=httpd.serve_forever) proxy_process = threading.Thread(target=httpd.serve_forever)
proxy_process.start() proxy_process.start()
yield "http://{}:{}".format(*httpd.server_address) yield "http://{}:{}".format(*httpd.server_address)
proxy_process.terminate() httpd.shutdown()
proxy_process.join()
def test_use_proxy(tmpdir, httpbin, proxy_server): 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"))): with vcr.use_cassette(str(tmpdir.join("proxy.yaml"))):
response = requests.get(httpbin.url, proxies={"http": proxy_server}) 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}) 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_response.headers == response.headers
assert cassette.play_count == 1 assert cassette.play_count == 1

View File

@@ -14,28 +14,28 @@ def false_matcher(r1, r2):
@pytest.mark.online @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 = vcr.VCR()
my_vcr.register_matcher("true", true_matcher) my_vcr.register_matcher("true", true_matcher)
testfile = str(tmpdir.join("test.yml")) testfile = str(tmpdir.join("test.yml"))
with my_vcr.use_cassette(testfile, match_on=["true"]): with my_vcr.use_cassette(testfile, match_on=["true"]):
# These 2 different urls are stored as the same request # These 2 different urls are stored as the same request
urlopen(mockbin_request_url) urlopen(httpbin.url)
urlopen(mockbin_request_url + "/get") urlopen(httpbin.url + "/get")
with my_vcr.use_cassette(testfile, match_on=["true"]): with my_vcr.use_cassette(testfile, match_on=["true"]):
# I can get the response twice even though I only asked for it once # I can get the response twice even though I only asked for it once
urlopen(mockbin_request_url) urlopen(httpbin.url)
urlopen(mockbin_request_url) urlopen(httpbin.url)
@pytest.mark.online @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 = vcr.VCR()
my_vcr.register_matcher("false", false_matcher) my_vcr.register_matcher("false", false_matcher)
testfile = str(tmpdir.join("test.yml")) testfile = str(tmpdir.join("test.yml"))
with my_vcr.use_cassette(testfile, match_on=["false"]) as cass: with my_vcr.use_cassette(testfile, match_on=["false"]) as cass:
# These 2 different urls are stored as different requests # These 2 different urls are stored as different requests
urlopen(mockbin_request_url) urlopen(httpbin.url)
urlopen(mockbin_request_url + "/get") urlopen(httpbin.url + "/get")
assert len(cass) == 2 assert len(cass) == 2

View File

@@ -66,7 +66,7 @@ def test_load_cassette_with_custom_persister(tmpdir, httpbin):
with my_vcr.use_cassette(test_fixture, serializer="json"): with my_vcr.use_cassette(test_fixture, serializer="json"):
response = urlopen(httpbin.url).read() response = urlopen(httpbin.url).read()
assert b"difficult sometimes" in response assert b"A simple HTTP Request &amp; Response Service." in response
def test_load_cassette_persister_exception_handling(tmpdir, httpbin): def test_load_cassette_persister_exception_handling(tmpdir, httpbin):

View File

@@ -1,9 +1,10 @@
"""Test requests' interaction with vcr""" """Test requests' interaction with vcr"""
import pytest import pytest
from assertions import assert_cassette_empty, assert_is_json_bytes
import vcr import vcr
from ..assertions import assert_cassette_empty, assert_is_json_bytes
requests = pytest.importorskip("requests") requests = pytest.importorskip("requests")

View File

@@ -2,10 +2,10 @@ import http.client as httplib
import json import json
import zlib import zlib
from assertions import assert_is_json_bytes
import vcr import vcr
from ..assertions import assert_is_json_bytes
def _headers_are_case_insensitive(host, port): def _headers_are_case_insensitive(host, port):
conn = httplib.HTTPConnection(host, port) conn = httplib.HTTPConnection(host, port)

View File

@@ -3,11 +3,12 @@
import json import json
import pytest import pytest
from assertions import assert_cassette_empty, assert_is_json_bytes
import vcr import vcr
from vcr.errors import CannotOverwriteExistingCassetteException from vcr.errors import CannotOverwriteExistingCassetteException
from ..assertions import assert_cassette_empty, assert_is_json_bytes
tornado = pytest.importorskip("tornado") tornado = pytest.importorskip("tornado")
http = pytest.importorskip("tornado.httpclient") http = pytest.importorskip("tornado.httpclient")
@@ -81,12 +82,12 @@ def test_body(get_client, tmpdir, scheme):
@pytest.mark.gen_test @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""" """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"))): with vcr.use_cassette(str(tmpdir.join("url.yaml"))):
effective_url = (yield get(get_client(), url)).effective_url 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: with vcr.use_cassette(str(tmpdir.join("url.yaml"))) as cass:
assert effective_url == (yield get(get_client(), url)).effective_url assert effective_url == (yield get(get_client(), url)).effective_url

View File

@@ -5,12 +5,13 @@ from urllib.parse import urlencode
from urllib.request import urlopen from urllib.request import urlopen
import pytest_httpbin.certs import pytest_httpbin.certs
from assertions import assert_cassette_has_one_response
from pytest import mark from pytest import mark
# Internal imports # Internal imports
import vcr import vcr
from ..assertions import assert_cassette_has_one_response
def urlopen_with_cafile(*args, **kwargs): def urlopen_with_cafile(*args, **kwargs):
context = ssl.create_default_context(cafile=pytest_httpbin.certs.where()) context = ssl.create_default_context(cafile=pytest_httpbin.certs.where())
@@ -57,13 +58,13 @@ def test_response_headers(httpbin_both, tmpdir):
@mark.online @mark.online
def test_effective_url(tmpdir): def test_effective_url(tmpdir, httpbin):
"""Ensure that the effective_url is captured""" """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"))): with vcr.use_cassette(str(tmpdir.join("headers.yaml"))):
effective_url = urlopen_with_cafile(url).geturl() 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"))): with vcr.use_cassette(str(tmpdir.join("headers.yaml"))):
assert effective_url == urlopen_with_cafile(url).geturl() assert effective_url == urlopen_with_cafile(url).geturl()

View File

@@ -4,12 +4,13 @@
import pytest import pytest
import pytest_httpbin import pytest_httpbin
from assertions import assert_cassette_empty, assert_is_json_bytes
import vcr import vcr
from vcr.patch import force_reset from vcr.patch import force_reset
from vcr.stubs.compat import get_headers from vcr.stubs.compat import get_headers
from ..assertions import assert_cassette_empty, assert_is_json_bytes
urllib3 = pytest.importorskip("urllib3") urllib3 = pytest.importorskip("urllib3")
@@ -99,9 +100,9 @@ def test_post(tmpdir, httpbin_both, verify_pool_mgr):
@pytest.mark.online @pytest.mark.online
def test_redirects(tmpdir, verify_pool_mgr): def test_redirects(tmpdir, verify_pool_mgr, httpbin):
"""Ensure that we can handle redirects""" """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"))): with vcr.use_cassette(str(tmpdir.join("verify_pool_mgr.yaml"))):
content = verify_pool_mgr.request("GET", url).data content = verify_pool_mgr.request("GET", url).data

30
tox.ini
View File

@@ -2,11 +2,10 @@
skip_missing_interpreters=true skip_missing_interpreters=true
envlist = envlist =
cov-clean, cov-clean,
lint, {py38,py39,py310,py311,py312}-{requests-urllib3-1,httplib2,urllib3-1,tornado4,boto3,aiohttp,httpx},
{py38,py39,py310,py311}-{requests-urllib3-1,httplib2,urllib3-1,tornado4,boto3,aiohttp,httpx}, {py310,py311,py312}-{requests-urllib3-2,urllib3-2},
{py310,py311}-{requests-urllib3-2,urllib3-2},
{pypy3}-{requests-urllib3-1,httplib2,urllib3-1,tornado4,boto3}, {pypy3}-{requests-urllib3-1,httplib2,urllib3-1,tornado4,boto3},
{py310}-httpx019, #{py310}-httpx019,
cov-report cov-report
@@ -14,8 +13,9 @@ envlist =
python = python =
3.8: py38 3.8: py38
3.9: py39 3.9: py39
3.10: py310, lint 3.10: py310
3.11: py311 3.11: py311
3.12: py312
pypy-3: pypy3 pypy-3: pypy3
# Coverage environment tasks: cov-clean and cov-report # Coverage environment tasks: cov-clean and cov-report
@@ -32,18 +32,6 @@ commands =
coverage html coverage html
coverage report --fail-under=90 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] [testenv]
# Need to use develop install so that paths # Need to use develop install so that paths
# for aggregate code coverage combine # for aggregate code coverage combine
@@ -74,9 +62,13 @@ deps =
httpx019: httpx==0.19 httpx019: httpx==0.19
{py38,py39,py310}-{httpx}: pytest-asyncio {py38,py39,py310}-{httpx}: pytest-asyncio
depends = 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 {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: 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} 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 = passenv =
AWS_ACCESS_KEY_ID AWS_ACCESS_KEY_ID
AWS_DEFAULT_REGION AWS_DEFAULT_REGION
AWS_SECRET_ACCESS_KEY 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

View File

@@ -95,8 +95,8 @@ try:
except ImportError: # pragma: no cover except ImportError: # pragma: no cover
pass pass
else: else:
_HttpxSyncClient_send = httpx.Client.send _HttpxSyncClient_send_single_request = httpx.Client._send_single_request
_HttpxAsyncClient_send = httpx.AsyncClient.send _HttpxAsyncClient_send_single_request = httpx.AsyncClient._send_single_request
class CassettePatcherBuilder: class CassettePatcherBuilder:
@@ -260,10 +260,14 @@ class CassettePatcherBuilder:
yield cpool, "HTTPConnectionWithTimeout", VCRHTTPConnectionWithTimeout yield cpool, "HTTPConnectionWithTimeout", VCRHTTPConnectionWithTimeout
yield cpool, "HTTPSConnectionWithTimeout", VCRHTTPSConnectionWithTimeout yield cpool, "HTTPSConnectionWithTimeout", VCRHTTPSConnectionWithTimeout
yield cpool, "SCHEME_TO_CONNECTION", { yield (
cpool,
"SCHEME_TO_CONNECTION",
{
"http": VCRHTTPConnectionWithTimeout, "http": VCRHTTPConnectionWithTimeout,
"https": VCRHTTPSConnectionWithTimeout, "https": VCRHTTPSConnectionWithTimeout,
} },
)
@_build_patchers_from_mock_triples_decorator @_build_patchers_from_mock_triples_decorator
def _tornado(self): def _tornado(self):
@@ -307,11 +311,11 @@ class CassettePatcherBuilder:
else: else:
from .stubs.httpx_stubs import async_vcr_send, sync_vcr_send from .stubs.httpx_stubs import async_vcr_send, sync_vcr_send
new_async_client_send = async_vcr_send(self._cassette, _HttpxAsyncClient_send) new_async_client_send = async_vcr_send(self._cassette, _HttpxAsyncClient_send_single_request)
yield httpx.AsyncClient, "send", new_async_client_send yield httpx.AsyncClient, "_send_single_request", new_async_client_send
new_sync_client_send = sync_vcr_send(self._cassette, _HttpxSyncClient_send) new_sync_client_send = sync_vcr_send(self._cassette, _HttpxSyncClient_send_single_request)
yield httpx.Client, "send", new_sync_client_send yield httpx.Client, "_send_single_request", new_sync_client_send
def _urllib3_patchers(self, cpool, conn, stubs): def _urllib3_patchers(self, cpool, conn, stubs):
http_connection_remover = ConnectionRemover( http_connection_remover = ConnectionRemover(

View File

@@ -56,7 +56,7 @@ def convert_body_to_unicode(resp):
If the request or responses body is bytes, decode it to a string If the request or responses body is bytes, decode it to a string
(for python3 support) (for python3 support)
""" """
if type(resp) is not dict: if not isinstance(resp, dict):
# Some of the tests just serialize and deserialize a string. # Some of the tests just serialize and deserialize a string.
return _convert_string_to_unicode(resp) return _convert_string_to_unicode(resp)
else: else:

View File

@@ -389,6 +389,8 @@ class VCRHTTPConnection(VCRConnection):
_baseclass = HTTPConnection _baseclass = HTTPConnection
_protocol = "http" _protocol = "http"
debuglevel = _baseclass.debuglevel
_http_vsn = _baseclass._http_vsn
class VCRHTTPSConnection(VCRConnection): class VCRHTTPSConnection(VCRConnection):
@@ -397,3 +399,5 @@ class VCRHTTPSConnection(VCRConnection):
_baseclass = HTTPSConnection _baseclass = HTTPSConnection
_protocol = "https" _protocol = "https"
is_verified = True is_verified = True
debuglevel = _baseclass.debuglevel
_http_vsn = _baseclass._http_vsn

View File

@@ -34,11 +34,16 @@ def _transform_headers(httpx_response):
def _to_serialized_response(httpx_response): def _to_serialized_response(httpx_response):
try:
content = httpx_response.content.decode("utf-8")
except UnicodeDecodeError:
content = httpx_response.content
return { return {
"status_code": httpx_response.status_code, "status_code": httpx_response.status_code,
"http_version": httpx_response.http_version, "http_version": httpx_response.http_version,
"headers": _transform_headers(httpx_response), "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.close", MagicMock())
@patch("httpx.Response.read", MagicMock()) @patch("httpx.Response.read", MagicMock())
def _from_serialized_response(request, serialized_response, history=None): 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( response = httpx.Response(
status_code=serialized_response.get("status_code"), status_code=serialized_response.get("status_code"),
request=request, request=request,
@@ -106,30 +113,8 @@ def _record_responses(cassette, vcr_request, real_response):
def _play_responses(cassette, request, vcr_request, client, kwargs): 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) vcr_response = cassette.play_response(vcr_request)
response = _from_serialized_response(request, vcr_response) 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 return response
@@ -141,6 +126,7 @@ async def _async_vcr_send(cassette, real_send, *args, **kwargs):
return response return response
real_response = await real_send(*args, **kwargs) real_response = await real_send(*args, **kwargs)
await real_response.aread()
return _record_responses(cassette, vcr_request, real_response) return _record_responses(cassette, vcr_request, real_response)
@@ -160,6 +146,7 @@ def _sync_vcr_send(cassette, real_send, *args, **kwargs):
return response return response
real_response = real_send(*args, **kwargs) real_response = real_send(*args, **kwargs)
real_response.read()
return _record_responses(cassette, vcr_request, real_response) return _record_responses(cassette, vcr_request, real_response)