mirror of
https://github.com/kevin1024/vcrpy.git
synced 2025-12-08 16:53:23 +00:00
Drops Python 3.9 support
This commit is contained in:
19
.github/workflows/main.yml
vendored
19
.github/workflows/main.yml
vendored
@@ -6,7 +6,7 @@ on:
|
||||
- master
|
||||
pull_request:
|
||||
schedule:
|
||||
- cron: '0 16 * * 5' # Every Friday 4pm
|
||||
- cron: "0 16 * * 5" # Every Friday 4pm
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
@@ -16,24 +16,12 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version:
|
||||
- "3.9"
|
||||
- "3.10"
|
||||
- "3.11"
|
||||
- "3.12"
|
||||
- "3.13"
|
||||
- "pypy-3.9"
|
||||
- "pypy-3.10"
|
||||
urllib3-requirement:
|
||||
- "urllib3>=2"
|
||||
- "urllib3<2"
|
||||
|
||||
exclude:
|
||||
- python-version: "3.9"
|
||||
urllib3-requirement: "urllib3>=2"
|
||||
- python-version: "pypy-3.9"
|
||||
urllib3-requirement: "urllib3>=2"
|
||||
- python-version: "pypy-3.10"
|
||||
urllib3-requirement: "urllib3>=2"
|
||||
- "pypy-3.11"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
@@ -44,13 +32,12 @@ jobs:
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
cache: pip
|
||||
allow-prereleases: true
|
||||
|
||||
- name: Install project dependencies
|
||||
run: |
|
||||
uv pip install --system --upgrade pip setuptools
|
||||
uv pip install --system codecov '.[tests]' '${{ matrix.urllib3-requirement }}'
|
||||
uv pip install --system codecov '.[tests]'
|
||||
uv pip check
|
||||
|
||||
- name: Allow creation of user namespaces (e.g. to the unshare command)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.12.7
|
||||
rev: v0.14.5
|
||||
hooks:
|
||||
- id: ruff
|
||||
args: ["--output-format=full"]
|
||||
|
||||
@@ -7,6 +7,10 @@ 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.
|
||||
|
||||
- Unreleased
|
||||
- Drop support for Python 3.9
|
||||
- Drop support for urllib3 < 2
|
||||
|
||||
- 7.0.0
|
||||
- Drop support for python 3.8 (major version bump) - thanks @jairhenrique
|
||||
- Various linting and test fixes - thanks @jairhenrique
|
||||
|
||||
@@ -2,14 +2,15 @@
|
||||
skip = '.git,*.pdf,*.svg,.tox'
|
||||
ignore-regex = "\\\\[fnrstv]"
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
[tool.pytest]
|
||||
addopts = ["--strict-config", "--strict-markers"]
|
||||
asyncio_default_fixture_loop_scope = "function"
|
||||
asyncio_default_fixture_loop_scope = "session"
|
||||
asyncio_default_test_loop_scope = "session"
|
||||
markers = ["online"]
|
||||
|
||||
[tool.ruff]
|
||||
line-length = 110
|
||||
target-version = "py39"
|
||||
target-version = "py310"
|
||||
|
||||
[tool.ruff.lint]
|
||||
select = [
|
||||
|
||||
25
setup.py
25
setup.py
@@ -30,18 +30,6 @@ install_requires = [
|
||||
"PyYAML",
|
||||
"wrapt",
|
||||
"yarl",
|
||||
# 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'",
|
||||
# Workaround for Poetry with CPython >= 3.10, problem description at:
|
||||
# https://github.com/kevin1024/vcrpy/pull/826
|
||||
"urllib3; platform_python_implementation !='PyPy' and python_version >='3.10'",
|
||||
]
|
||||
|
||||
extras_require = {
|
||||
@@ -49,22 +37,16 @@ extras_require = {
|
||||
"aiohttp",
|
||||
"boto3",
|
||||
"httplib2",
|
||||
"httpbin",
|
||||
"httpx",
|
||||
"pytest",
|
||||
"pytest-aiohttp",
|
||||
"pytest-asyncio",
|
||||
"pytest-cov",
|
||||
"pytest-httpbin",
|
||||
"pytest",
|
||||
"requests>=2.22.0",
|
||||
"tornado",
|
||||
"urllib3",
|
||||
# Needed to un-break httpbin 0.7.0. For httpbin >=0.7.1 and after,
|
||||
# this pin and the dependency itself can be removed, provided
|
||||
# that the related bug in httpbin has been fixed:
|
||||
# https://github.com/kevin1024/vcrpy/issues/645#issuecomment-1562489489
|
||||
# https://github.com/postmanlabs/httpbin/issues/673
|
||||
# https://github.com/postmanlabs/httpbin/pull/674
|
||||
"Werkzeug==2.0.3",
|
||||
],
|
||||
}
|
||||
|
||||
@@ -78,7 +60,7 @@ setup(
|
||||
author_email="me@kevinmccarthy.org",
|
||||
url="https://github.com/kevin1024/vcrpy",
|
||||
packages=find_packages(exclude=["tests*"]),
|
||||
python_requires=">=3.9",
|
||||
python_requires=">=3.10",
|
||||
install_requires=install_requires,
|
||||
license="MIT",
|
||||
extras_require=extras_require,
|
||||
@@ -89,7 +71,6 @@ setup(
|
||||
"Intended Audience :: Developers",
|
||||
"Programming Language :: Python",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
|
||||
@@ -264,12 +264,6 @@ def test_aiohttp_test_client_json(aiohttp_client, tmpdir):
|
||||
assert cassette.play_count == 1
|
||||
|
||||
|
||||
def test_cleanup_from_pytest_asyncio():
|
||||
# work around https://github.com/pytest-dev/pytest-asyncio/issues/724
|
||||
asyncio.get_event_loop().close()
|
||||
asyncio.set_event_loop(None)
|
||||
|
||||
|
||||
@pytest.mark.online
|
||||
def test_redirect(tmpdir, httpbin):
|
||||
url = httpbin.url + "/redirect/2"
|
||||
|
||||
@@ -42,17 +42,13 @@ def scheme(request):
|
||||
return request.param
|
||||
|
||||
|
||||
@pytest.fixture(params=["simple", "curl", "default"])
|
||||
@pytest.fixture(params=["curl", "default"])
|
||||
def get_client(request):
|
||||
if request.param == "simple":
|
||||
from tornado import simple_httpclient as simple
|
||||
|
||||
return lambda: simple.SimpleAsyncHTTPClient()
|
||||
elif request.param == "curl":
|
||||
if request.param == "curl":
|
||||
curl = pytest.importorskip("tornado.curl_httpclient")
|
||||
return lambda: curl.CurlAsyncHTTPClient()
|
||||
else:
|
||||
return lambda: http.AsyncHTTPClient()
|
||||
|
||||
return lambda: http.AsyncHTTPClient()
|
||||
|
||||
|
||||
def get(client, url, **kwargs):
|
||||
@@ -192,7 +188,7 @@ def test_redirects(get_client, tmpdir, httpbin):
|
||||
|
||||
@pytest.mark.online
|
||||
@gen_test
|
||||
def test_cross_scheme(get_client, tmpdir, scheme):
|
||||
def test_cross_scheme(get_client, tmpdir):
|
||||
"""Ensure that requests between schemes are treated separately"""
|
||||
# First fetch a url under http, and then again under https and then
|
||||
# ensure that we haven't served anything out of cache, and we have two
|
||||
|
||||
@@ -1,147 +0,0 @@
|
||||
"""Integration tests with urllib2"""
|
||||
|
||||
import ssl
|
||||
from urllib.parse import urlencode
|
||||
from urllib.request import urlopen
|
||||
|
||||
import pytest_httpbin.certs
|
||||
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())
|
||||
context.check_hostname = False
|
||||
kwargs["context"] = context
|
||||
try:
|
||||
return urlopen(*args, **kwargs)
|
||||
except TypeError:
|
||||
# python2/pypi don't let us override this
|
||||
del kwargs["cafile"]
|
||||
return urlopen(*args, **kwargs)
|
||||
|
||||
|
||||
def test_response_code(httpbin_both, tmpdir):
|
||||
"""Ensure we can read a response code from a fetch"""
|
||||
url = httpbin_both.url
|
||||
with vcr.use_cassette(str(tmpdir.join("atts.yaml"))):
|
||||
code = urlopen_with_cafile(url).getcode()
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join("atts.yaml"))):
|
||||
assert code == urlopen_with_cafile(url).getcode()
|
||||
|
||||
|
||||
def test_random_body(httpbin_both, tmpdir):
|
||||
"""Ensure we can read the content, and that it's served from cache"""
|
||||
url = httpbin_both.url + "/bytes/1024"
|
||||
with vcr.use_cassette(str(tmpdir.join("body.yaml"))):
|
||||
body = urlopen_with_cafile(url).read()
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join("body.yaml"))):
|
||||
assert body == urlopen_with_cafile(url).read()
|
||||
|
||||
|
||||
def test_response_headers(httpbin_both, tmpdir):
|
||||
"""Ensure we can get information from the response"""
|
||||
url = httpbin_both.url
|
||||
with vcr.use_cassette(str(tmpdir.join("headers.yaml"))):
|
||||
open1 = urlopen_with_cafile(url).info().items()
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join("headers.yaml"))):
|
||||
open2 = urlopen_with_cafile(url).info().items()
|
||||
|
||||
assert sorted(open1) == sorted(open2)
|
||||
|
||||
|
||||
@mark.online
|
||||
def test_effective_url(tmpdir, httpbin):
|
||||
"""Ensure that the effective_url is captured"""
|
||||
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 == httpbin.url + "/"
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join("headers.yaml"))):
|
||||
assert effective_url == urlopen_with_cafile(url).geturl()
|
||||
|
||||
|
||||
def test_multiple_requests(httpbin_both, tmpdir):
|
||||
"""Ensure that we can cache multiple requests"""
|
||||
urls = [httpbin_both.url, httpbin_both.url, httpbin_both.url + "/get", httpbin_both.url + "/bytes/1024"]
|
||||
with vcr.use_cassette(str(tmpdir.join("multiple.yaml"))) as cass:
|
||||
[urlopen_with_cafile(url) for url in urls]
|
||||
assert len(cass) == len(urls)
|
||||
|
||||
|
||||
def test_get_data(httpbin_both, tmpdir):
|
||||
"""Ensure that it works with query data"""
|
||||
data = urlencode({"some": 1, "data": "here"})
|
||||
url = httpbin_both.url + "/get?" + data
|
||||
with vcr.use_cassette(str(tmpdir.join("get_data.yaml"))):
|
||||
res1 = urlopen_with_cafile(url).read()
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join("get_data.yaml"))):
|
||||
res2 = urlopen_with_cafile(url).read()
|
||||
assert res1 == res2
|
||||
|
||||
|
||||
def test_post_data(httpbin_both, tmpdir):
|
||||
"""Ensure that it works when posting data"""
|
||||
data = urlencode({"some": 1, "data": "here"}).encode("utf-8")
|
||||
url = httpbin_both.url + "/post"
|
||||
with vcr.use_cassette(str(tmpdir.join("post_data.yaml"))):
|
||||
res1 = urlopen_with_cafile(url, data).read()
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join("post_data.yaml"))) as cass:
|
||||
res2 = urlopen_with_cafile(url, data).read()
|
||||
assert len(cass) == 1
|
||||
|
||||
assert res1 == res2
|
||||
assert_cassette_has_one_response(cass)
|
||||
|
||||
|
||||
def test_post_unicode_data(httpbin_both, tmpdir):
|
||||
"""Ensure that it works when posting unicode data"""
|
||||
data = urlencode({"snowman": "☃".encode()}).encode("utf-8")
|
||||
url = httpbin_both.url + "/post"
|
||||
with vcr.use_cassette(str(tmpdir.join("post_data.yaml"))):
|
||||
res1 = urlopen_with_cafile(url, data).read()
|
||||
|
||||
with vcr.use_cassette(str(tmpdir.join("post_data.yaml"))) as cass:
|
||||
res2 = urlopen_with_cafile(url, data).read()
|
||||
assert len(cass) == 1
|
||||
|
||||
assert res1 == res2
|
||||
assert_cassette_has_one_response(cass)
|
||||
|
||||
|
||||
def test_cross_scheme(tmpdir, httpbin_secure, httpbin):
|
||||
"""Ensure that requests between schemes are treated separately"""
|
||||
# First fetch a url under https, and then again under https and then
|
||||
# ensure that we haven't served anything out of cache, and we have two
|
||||
# requests / response pairs in the cassette
|
||||
with vcr.use_cassette(str(tmpdir.join("cross_scheme.yaml"))) as cass:
|
||||
urlopen_with_cafile(httpbin_secure.url)
|
||||
urlopen_with_cafile(httpbin.url)
|
||||
assert len(cass) == 2
|
||||
assert cass.play_count == 0
|
||||
|
||||
|
||||
def test_decorator(httpbin_both, tmpdir):
|
||||
"""Test the decorator version of VCR.py"""
|
||||
url = httpbin_both.url
|
||||
|
||||
@vcr.use_cassette(str(tmpdir.join("atts.yaml")))
|
||||
def inner1():
|
||||
return urlopen_with_cafile(url).getcode()
|
||||
|
||||
@vcr.use_cassette(str(tmpdir.join("atts.yaml")))
|
||||
def inner2():
|
||||
return urlopen_with_cafile(url).getcode()
|
||||
|
||||
assert inner1() == inner2()
|
||||
@@ -76,7 +76,7 @@ def test_deserialize_py2py3_yaml_cassette(tmpdir, req_body, expect):
|
||||
cfile = tmpdir.join("test_cassette.yaml")
|
||||
cfile.write(REQBODY_TEMPLATE.format(req_body=req_body))
|
||||
with open(str(cfile)) as f:
|
||||
(requests, responses) = deserialize(f.read(), yamlserializer)
|
||||
(requests, _) = deserialize(f.read(), yamlserializer)
|
||||
assert requests[0].body == expect
|
||||
|
||||
|
||||
|
||||
@@ -178,7 +178,7 @@ def test_testcase_playback(tmpdir):
|
||||
return str(cassette_dir)
|
||||
|
||||
test = run_testcase(MyTest)[0][0]
|
||||
assert b"illustrative examples" in test.response
|
||||
assert b"Example Domain" in test.response
|
||||
assert len(test.cassette.requests) == 1
|
||||
assert test.cassette.play_count == 0
|
||||
|
||||
@@ -186,7 +186,7 @@ def test_testcase_playback(tmpdir):
|
||||
|
||||
test2 = run_testcase(MyTest)[0][0]
|
||||
assert test.cassette is not test2.cassette
|
||||
assert b"illustrative examples" in test.response
|
||||
assert b"Example Domain" in test.response
|
||||
assert len(test2.cassette.requests) == 1
|
||||
assert test2.cassette.play_count == 1
|
||||
|
||||
|
||||
@@ -359,7 +359,7 @@ class Cassette:
|
||||
def _load(self):
|
||||
try:
|
||||
requests, responses = self._persister.load_cassette(self._path, serializer=self._serializer)
|
||||
for request, response in zip(requests, responses):
|
||||
for request, response in zip(requests, responses, strict=False):
|
||||
self.append(request, response)
|
||||
self._old_interactions.append((request, response))
|
||||
self.dirty = False
|
||||
|
||||
@@ -162,7 +162,7 @@ def _get_transformers(request):
|
||||
|
||||
|
||||
def requests_match(r1, r2, matchers):
|
||||
successes, failures = get_matchers_results(r1, r2, matchers)
|
||||
_, failures = get_matchers_results(r1, r2, matchers)
|
||||
if failures:
|
||||
log.debug(f"Requests {r1} and {r2} differ.\nFailure details:\n{failures}")
|
||||
return len(failures) == 0
|
||||
|
||||
@@ -53,7 +53,7 @@ def serialize(cassette_dict, serializer):
|
||||
"request": compat.convert_to_unicode(request._to_dict()),
|
||||
"response": compat.convert_to_unicode(response),
|
||||
}
|
||||
for request, response in zip(cassette_dict["requests"], cassette_dict["responses"])
|
||||
for request, response in zip(cassette_dict["requests"], cassette_dict["responses"], strict=False)
|
||||
]
|
||||
data = {"version": CASSETTE_FORMAT_VERSION, "interactions": interactions}
|
||||
return serializer.serialize(data)
|
||||
|
||||
@@ -6,7 +6,6 @@ import json
|
||||
import logging
|
||||
from collections.abc import Mapping
|
||||
from http.cookies import CookieError, Morsel, SimpleCookie
|
||||
from typing import Union
|
||||
|
||||
from aiohttp import ClientConnectionError, ClientResponse, CookieJar, RequestInfo, hdrs, streams
|
||||
from aiohttp.helpers import strip_auth_from_url
|
||||
@@ -230,7 +229,7 @@ def _build_cookie_header(session, cookies, cookie_header, url):
|
||||
return c.output(header="", sep=";").strip()
|
||||
|
||||
|
||||
def _build_url_with_params(url_str: str, params: Mapping[str, Union[str, int, float]]) -> URL:
|
||||
def _build_url_with_params(url_str: str, params: Mapping[str, str | int | float]) -> URL:
|
||||
# This code is basically a copy&paste of aiohttp.
|
||||
# https://github.com/aio-libs/aiohttp/blob/master/aiohttp/client_reqrep.py#L225
|
||||
url = URL(url_str)
|
||||
|
||||
Reference in New Issue
Block a user