mirror of
https://github.com/kevin1024/vcrpy.git
synced 2025-12-08 16:53:23 +00:00
Compare commits
51 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1d100dda25 | ||
|
|
c6be705fb4 | ||
|
|
10b7f4efb3 | ||
|
|
7a6ef00f4d | ||
|
|
3bf6ac7184 | ||
|
|
983b2202ed | ||
|
|
15a6b71997 | ||
|
|
1ca708dcff | ||
|
|
f5597fa6c1 | ||
|
|
2b3247b3df | ||
|
|
d123a5e8d0 | ||
|
|
e2815fbc88 | ||
|
|
f9d4500c6e | ||
|
|
71eb624708 | ||
|
|
dc449715c1 | ||
|
|
275b9085f3 | ||
|
|
35650b141b | ||
|
|
9c8b679136 | ||
|
|
fab082eff5 | ||
|
|
ffc04f9128 | ||
|
|
4d84da1809 | ||
|
|
241b0bbd91 | ||
|
|
042e16c3e4 | ||
|
|
acef3f49bf | ||
|
|
9cfa6c5173 | ||
|
|
39a86ba3cf | ||
|
|
543c72ba51 | ||
|
|
86b114f2f5 | ||
|
|
4b06f3dba1 | ||
|
|
1c6503526b | ||
|
|
c9c05682cb | ||
|
|
39c8648aa7 | ||
|
|
dfff84d5bb | ||
|
|
40ac0de652 | ||
|
|
f3147f574b | ||
|
|
298a6933ff | ||
|
|
52da776b59 | ||
|
|
8842fb1c3a | ||
|
|
6c4ba172d8 | ||
|
|
c88f2c0dab | ||
|
|
3fd6b1c0b4 | ||
|
|
c6d87309f4 | ||
|
|
1fb9179cf9 | ||
|
|
a58e0d8830 | ||
|
|
acc101412d | ||
|
|
e60dafb8dc | ||
|
|
3ce5979acb | ||
|
|
68038d0559 | ||
|
|
f76289aa78 | ||
|
|
6252b92f50 | ||
|
|
b1c45cd249 |
6
.github/workflows/main.yml
vendored
6
.github/workflows/main.yml
vendored
@@ -5,6 +5,8 @@ on:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
schedule:
|
||||
- cron: '0 16 * * 5' # Every Friday 4pm
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
@@ -19,6 +21,7 @@ jobs:
|
||||
- "3.10"
|
||||
- "3.11"
|
||||
- "3.12"
|
||||
- "3.13"
|
||||
- "pypy-3.8"
|
||||
- "pypy-3.9"
|
||||
- "pypy-3.10"
|
||||
@@ -46,10 +49,11 @@ jobs:
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
cache: pip
|
||||
allow-prereleases: true
|
||||
|
||||
- name: Install project dependencies
|
||||
run: |
|
||||
pip install --upgrade pip
|
||||
pip install --upgrade pip setuptools
|
||||
pip install codecov '.[tests]' '${{ matrix.urllib3-requirement }}'
|
||||
pip check
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ jobs:
|
||||
|
||||
- name: Create pull request from changes (if any)
|
||||
id: create-pull-request
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
with:
|
||||
author: 'pre-commit <pre-commit@tools.invalid>'
|
||||
base: master
|
||||
|
||||
2
.github/workflows/pre-commit.yml
vendored
2
.github/workflows/pre-commit.yml
vendored
@@ -17,4 +17,4 @@ jobs:
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.12
|
||||
- uses: pre-commit/action@v3.0.0
|
||||
- uses: pre-commit/action@v3.0.1
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.1.13
|
||||
rev: v0.6.6
|
||||
hooks:
|
||||
- id: ruff
|
||||
args: ["--show-source"]
|
||||
args: ["--output-format=full"]
|
||||
- id: ruff-format
|
||||
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.5.0
|
||||
rev: v4.6.0
|
||||
hooks:
|
||||
- id: check-merge-conflict
|
||||
- id: end-of-file-fixer
|
||||
|
||||
@@ -7,6 +7,13 @@ 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.
|
||||
|
||||
- 6.0.2
|
||||
- Ensure body is consumed only once (#846) - thanks @sathieu
|
||||
- Permit urllib3 2.x for non-PyPy Python >=3.10
|
||||
- Fix typos in test commands - thanks @chuckwondo
|
||||
- Several test and workflow improvements - thanks @hartwork and @graingert
|
||||
- 6.0.1
|
||||
- Bugfix with to Tornado cassette generator (thanks @graingert)
|
||||
- 6.0.0
|
||||
- BREAKING: Fix issue with httpx support (thanks @parkerhancock) in #784. NOTE: You may have to recreate some of your cassettes produced in previous releases due to the binary format being saved incorrectly in previous releases
|
||||
- BREAKING: Drop support for `boto` (vcrpy still supports boto3, but is dropping the deprecated `boto` support in this release. (thanks @jairhenrique)
|
||||
|
||||
@@ -115,8 +115,8 @@ in this example::
|
||||
pyenv local 3.12.0 pypy3.10
|
||||
|
||||
# Run the whole test suite
|
||||
pip install .[test]
|
||||
./run_tests.sh
|
||||
pip install .[tests]
|
||||
./runtests.sh
|
||||
|
||||
|
||||
Troubleshooting on MacOSX
|
||||
|
||||
19
setup.py
19
setup.py
@@ -3,10 +3,8 @@
|
||||
import codecs
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
from setuptools import find_packages, setup
|
||||
from setuptools.command.test import test as TestCommand
|
||||
|
||||
long_description = open("README.rst").read()
|
||||
here = os.path.abspath(os.path.dirname(__file__))
|
||||
@@ -28,20 +26,6 @@ def find_version(*file_paths):
|
||||
raise RuntimeError("Unable to find version string.")
|
||||
|
||||
|
||||
class PyTest(TestCommand):
|
||||
def finalize_options(self):
|
||||
TestCommand.finalize_options(self)
|
||||
self.test_args = []
|
||||
self.test_suite = True
|
||||
|
||||
def run_tests(self):
|
||||
# import here, cause outside the eggs aren't loaded
|
||||
import pytest
|
||||
|
||||
errno = pytest.main(self.test_args)
|
||||
sys.exit(errno)
|
||||
|
||||
|
||||
install_requires = [
|
||||
"PyYAML",
|
||||
"wrapt",
|
||||
@@ -55,6 +39,9 @@ install_requires = [
|
||||
"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 = {
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
import os
|
||||
import ssl
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def httpbin_ssl_context():
|
||||
ssl_ca_location = os.environ["REQUESTS_CA_BUNDLE"]
|
||||
ssl_cert_location = os.environ["REQUESTS_CA_BUNDLE"].replace("cacert.pem", "cert.pem")
|
||||
ssl_key_location = os.environ["REQUESTS_CA_BUNDLE"].replace("cacert.pem", "key.pem")
|
||||
|
||||
ssl_context = ssl.create_default_context(cafile=ssl_ca_location)
|
||||
ssl_context.load_cert_chain(ssl_cert_location, ssl_key_location)
|
||||
|
||||
return ssl_context
|
||||
@@ -1,7 +1,9 @@
|
||||
import logging
|
||||
import ssl
|
||||
import urllib.parse
|
||||
|
||||
import pytest
|
||||
import pytest_httpbin.certs
|
||||
|
||||
import vcr
|
||||
|
||||
@@ -11,6 +13,8 @@ aiohttp = pytest.importorskip("aiohttp")
|
||||
|
||||
from .aiohttp_utils import aiohttp_app, aiohttp_request # noqa: E402
|
||||
|
||||
HTTPBIN_SSL_CONTEXT = ssl.create_default_context(cafile=pytest_httpbin.certs.where())
|
||||
|
||||
|
||||
def run_in_loop(fn):
|
||||
async def wrapper():
|
||||
@@ -338,7 +342,7 @@ def test_double_requests(tmpdir, httpbin):
|
||||
assert cassette.play_count == 2
|
||||
|
||||
|
||||
def test_cookies(httpbin_both, httpbin_ssl_context, tmpdir):
|
||||
def test_cookies(httpbin_both, tmpdir):
|
||||
async def run(loop):
|
||||
cookies_url = httpbin_both.url + (
|
||||
"/response-headers?"
|
||||
@@ -353,12 +357,12 @@ def test_cookies(httpbin_both, httpbin_ssl_context, tmpdir):
|
||||
# ------------------------- Record -------------------------- #
|
||||
with vcr.use_cassette(tmp) as cassette:
|
||||
async with aiohttp.ClientSession(loop=loop, cookie_jar=aiohttp.CookieJar(unsafe=True)) as session:
|
||||
cookies_resp = await session.get(cookies_url, ssl=httpbin_ssl_context)
|
||||
cookies_resp = await session.get(cookies_url, ssl=HTTPBIN_SSL_CONTEXT)
|
||||
home_resp = await session.get(
|
||||
home_url,
|
||||
cookies=req_cookies,
|
||||
headers=req_headers,
|
||||
ssl=httpbin_ssl_context,
|
||||
ssl=HTTPBIN_SSL_CONTEXT,
|
||||
)
|
||||
assert cassette.play_count == 0
|
||||
assert_responses(cookies_resp, home_resp)
|
||||
@@ -366,12 +370,12 @@ def test_cookies(httpbin_both, httpbin_ssl_context, tmpdir):
|
||||
# -------------------------- Play --------------------------- #
|
||||
with vcr.use_cassette(tmp, record_mode=vcr.mode.NONE) as cassette:
|
||||
async with aiohttp.ClientSession(loop=loop, cookie_jar=aiohttp.CookieJar(unsafe=True)) as session:
|
||||
cookies_resp = await session.get(cookies_url, ssl=httpbin_ssl_context)
|
||||
cookies_resp = await session.get(cookies_url, ssl=HTTPBIN_SSL_CONTEXT)
|
||||
home_resp = await session.get(
|
||||
home_url,
|
||||
cookies=req_cookies,
|
||||
headers=req_headers,
|
||||
ssl=httpbin_ssl_context,
|
||||
ssl=HTTPBIN_SSL_CONTEXT,
|
||||
)
|
||||
assert cassette.play_count == 2
|
||||
assert_responses(cookies_resp, home_resp)
|
||||
@@ -388,7 +392,7 @@ def test_cookies(httpbin_both, httpbin_ssl_context, tmpdir):
|
||||
run_in_loop(run)
|
||||
|
||||
|
||||
def test_cookies_redirect(httpbin_both, httpbin_ssl_context, tmpdir):
|
||||
def test_cookies_redirect(httpbin_both, tmpdir):
|
||||
async def run(loop):
|
||||
# Sets cookie as provided by the query string and redirects
|
||||
cookies_url = httpbin_both.url + "/cookies/set?Cookie_1=Val_1"
|
||||
@@ -397,7 +401,7 @@ def test_cookies_redirect(httpbin_both, httpbin_ssl_context, tmpdir):
|
||||
# ------------------------- Record -------------------------- #
|
||||
with vcr.use_cassette(tmp) as cassette:
|
||||
async with aiohttp.ClientSession(loop=loop, cookie_jar=aiohttp.CookieJar(unsafe=True)) as session:
|
||||
cookies_resp = await session.get(cookies_url, ssl=httpbin_ssl_context)
|
||||
cookies_resp = await session.get(cookies_url, ssl=HTTPBIN_SSL_CONTEXT)
|
||||
assert not cookies_resp.cookies
|
||||
cookies = session.cookie_jar.filter_cookies(cookies_url)
|
||||
assert cookies["Cookie_1"].value == "Val_1"
|
||||
@@ -408,7 +412,7 @@ def test_cookies_redirect(httpbin_both, httpbin_ssl_context, tmpdir):
|
||||
# -------------------------- Play --------------------------- #
|
||||
with vcr.use_cassette(tmp, record_mode=vcr.mode.NONE) as cassette:
|
||||
async with aiohttp.ClientSession(loop=loop, cookie_jar=aiohttp.CookieJar(unsafe=True)) as session:
|
||||
cookies_resp = await session.get(cookies_url, ssl=httpbin_ssl_context)
|
||||
cookies_resp = await session.get(cookies_url, ssl=HTTPBIN_SSL_CONTEXT)
|
||||
assert not cookies_resp.cookies
|
||||
cookies = session.cookie_jar.filter_cookies(cookies_url)
|
||||
assert cookies["Cookie_1"].value == "Val_1"
|
||||
@@ -422,7 +426,7 @@ def test_cookies_redirect(httpbin_both, httpbin_ssl_context, tmpdir):
|
||||
"Cookie_1=Val_1; Expires=Wed, 21 Oct 2015 07:28:00 GMT",
|
||||
]
|
||||
async with aiohttp.ClientSession(loop=loop, cookie_jar=aiohttp.CookieJar(unsafe=True)) as session:
|
||||
cookies_resp = await session.get(cookies_url, ssl=httpbin_ssl_context)
|
||||
cookies_resp = await session.get(cookies_url, ssl=HTTPBIN_SSL_CONTEXT)
|
||||
assert not cookies_resp.cookies
|
||||
cookies = session.cookie_jar.filter_cookies(cookies_url)
|
||||
assert cookies["Cookie_1"].value == "Val_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"A simple HTTP Request & Response Service." in response
|
||||
assert b"HTTP Request & Response Service" in response
|
||||
|
||||
|
||||
def test_patched_content(tmpdir, httpbin):
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
"""Integration tests with httplib2"""
|
||||
|
||||
from urllib.parse import urlencode
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -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"A simple HTTP Request & Response Service." in response
|
||||
assert b"HTTP Request & Response Service" in response
|
||||
|
||||
|
||||
def test_load_cassette_persister_exception_handling(tmpdir, httpbin):
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
"""Test requests' interaction with vcr"""
|
||||
|
||||
import pytest
|
||||
|
||||
import vcr
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
"""Test requests' interaction with vcr"""
|
||||
|
||||
import asyncio
|
||||
import functools
|
||||
import inspect
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import contextlib
|
||||
import http.client as httplib
|
||||
from io import BytesIO
|
||||
from tempfile import NamedTemporaryFile
|
||||
from unittest import mock
|
||||
|
||||
from pytest import mark
|
||||
|
||||
from vcr import mode
|
||||
from vcr import mode, use_cassette
|
||||
from vcr.cassette import Cassette
|
||||
from vcr.stubs import VCRHTTPSConnection
|
||||
|
||||
@@ -21,3 +24,52 @@ class TestVCRConnection:
|
||||
vcr_connection.cassette = Cassette("test", record_mode=mode.ALL)
|
||||
vcr_connection.real_connection.connect()
|
||||
assert vcr_connection.real_connection.sock is not None
|
||||
|
||||
def test_body_consumed_once_stream(self, tmpdir, httpbin):
|
||||
self._test_body_consumed_once(
|
||||
tmpdir,
|
||||
httpbin,
|
||||
BytesIO(b"1234567890"),
|
||||
BytesIO(b"9876543210"),
|
||||
BytesIO(b"9876543210"),
|
||||
)
|
||||
|
||||
def test_body_consumed_once_iterator(self, tmpdir, httpbin):
|
||||
self._test_body_consumed_once(
|
||||
tmpdir,
|
||||
httpbin,
|
||||
iter([b"1234567890"]),
|
||||
iter([b"9876543210"]),
|
||||
iter([b"9876543210"]),
|
||||
)
|
||||
|
||||
# data2 and data3 should serve the same data, potentially as iterators
|
||||
def _test_body_consumed_once(
|
||||
self,
|
||||
tmpdir,
|
||||
httpbin,
|
||||
data1,
|
||||
data2,
|
||||
data3,
|
||||
):
|
||||
with NamedTemporaryFile(dir=tmpdir, suffix=".yml") as f:
|
||||
testpath = f.name
|
||||
# NOTE: ``use_cassette`` is not okay with the file existing
|
||||
# already. So we using ``.close()`` to not only
|
||||
# close but also delete the empty file, before we start.
|
||||
f.close()
|
||||
host, port = httpbin.host, httpbin.port
|
||||
match_on = ["method", "uri", "body"]
|
||||
with use_cassette(testpath, match_on=match_on):
|
||||
conn1 = httplib.HTTPConnection(host, port)
|
||||
conn1.request("POST", "/anything", body=data1)
|
||||
conn1.getresponse()
|
||||
conn2 = httplib.HTTPConnection(host, port)
|
||||
conn2.request("POST", "/anything", body=data2)
|
||||
conn2.getresponse()
|
||||
with use_cassette(testpath, match_on=match_on) as cass:
|
||||
conn3 = httplib.HTTPConnection(host, port)
|
||||
conn3.request("POST", "/anything", body=data3)
|
||||
conn3.getresponse()
|
||||
assert cass.play_counts[0] == 0
|
||||
assert cass.play_counts[1] == 1
|
||||
|
||||
33
tests/unit/test_util.py
Normal file
33
tests/unit/test_util.py
Normal file
@@ -0,0 +1,33 @@
|
||||
from io import BytesIO, StringIO
|
||||
|
||||
import pytest
|
||||
|
||||
from vcr import request
|
||||
from vcr.util import read_body
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"input_, expected_output",
|
||||
[
|
||||
(BytesIO(b"Stream"), b"Stream"),
|
||||
(StringIO("Stream"), b"Stream"),
|
||||
(iter(["StringIter"]), b"StringIter"),
|
||||
(iter(["String", "Iter"]), b"StringIter"),
|
||||
(iter([b"BytesIter"]), b"BytesIter"),
|
||||
(iter([b"Bytes", b"Iter"]), b"BytesIter"),
|
||||
(iter([70, 111, 111]), b"Foo"),
|
||||
(iter([]), b""),
|
||||
("String", b"String"),
|
||||
(b"Bytes", b"Bytes"),
|
||||
],
|
||||
)
|
||||
def test_read_body(input_, expected_output):
|
||||
r = request.Request("POST", "http://host.com/", input_, {})
|
||||
assert read_body(r) == expected_output
|
||||
|
||||
|
||||
def test_unsupported_read_body():
|
||||
r = request.Request("POST", "http://host.com/", iter([[]]), {})
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
assert read_body(r)
|
||||
assert excinfo.value.args == ("Body type <class 'list'> not supported",)
|
||||
@@ -372,3 +372,19 @@ def test_path_class_as_cassette():
|
||||
)
|
||||
with use_cassette(path):
|
||||
pass
|
||||
|
||||
|
||||
def test_use_cassette_generator_return():
|
||||
ret_val = object()
|
||||
|
||||
vcr = VCR()
|
||||
|
||||
@vcr.use_cassette("test")
|
||||
def gen():
|
||||
return ret_val
|
||||
yield
|
||||
|
||||
with pytest.raises(StopIteration) as exc_info:
|
||||
next(gen())
|
||||
|
||||
assert exc_info.value.value is ret_val
|
||||
|
||||
@@ -4,7 +4,7 @@ from logging import NullHandler
|
||||
from .config import VCR
|
||||
from .record_mode import RecordMode as mode # noqa: F401
|
||||
|
||||
__version__ = "6.0.0"
|
||||
__version__ = "6.0.2"
|
||||
|
||||
logging.getLogger(__name__).addHandler(NullHandler())
|
||||
|
||||
|
||||
@@ -125,7 +125,7 @@ class CassetteContextDecorator:
|
||||
duration of the generator.
|
||||
"""
|
||||
with self as cassette:
|
||||
yield from fn(cassette)
|
||||
return (yield from fn(cassette))
|
||||
|
||||
def _handle_function(self, fn):
|
||||
with self as cassette:
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
"""Utilities for patching in cassettes"""
|
||||
|
||||
import contextlib
|
||||
import functools
|
||||
import http.client as httplib
|
||||
|
||||
@@ -3,7 +3,7 @@ import warnings
|
||||
from io import BytesIO
|
||||
from urllib.parse import parse_qsl, urlparse
|
||||
|
||||
from .util import CaseInsensitiveDict
|
||||
from .util import CaseInsensitiveDict, _is_nonsequence_iterator
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -17,8 +17,11 @@ class Request:
|
||||
self.method = method
|
||||
self.uri = uri
|
||||
self._was_file = hasattr(body, "read")
|
||||
self._was_iter = _is_nonsequence_iterator(body)
|
||||
if self._was_file:
|
||||
self.body = body.read()
|
||||
elif self._was_iter:
|
||||
self.body = list(body)
|
||||
else:
|
||||
self.body = body
|
||||
self.headers = headers
|
||||
@@ -36,7 +39,11 @@ class Request:
|
||||
|
||||
@property
|
||||
def body(self):
|
||||
return BytesIO(self._body) if self._was_file else self._body
|
||||
if self._was_file:
|
||||
return BytesIO(self._body)
|
||||
if self._was_iter:
|
||||
return iter(self._body)
|
||||
return self._body
|
||||
|
||||
@body.setter
|
||||
def body(self, value):
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
"""Stubs for aiohttp HTTP clients"""
|
||||
|
||||
import asyncio
|
||||
import functools
|
||||
import json
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
"""Stubs for boto3"""
|
||||
|
||||
from botocore.awsrequest import AWSHTTPConnection as HTTPConnection
|
||||
from botocore.awsrequest import AWSHTTPSConnection as VerifiedHTTPSConnection
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
"""Stubs for tornado HTTP clients"""
|
||||
|
||||
import functools
|
||||
from io import BytesIO
|
||||
|
||||
|
||||
19
vcr/util.py
19
vcr/util.py
@@ -89,9 +89,28 @@ def compose(*functions):
|
||||
return composed
|
||||
|
||||
|
||||
def _is_nonsequence_iterator(obj):
|
||||
return hasattr(obj, "__iter__") and not isinstance(
|
||||
obj,
|
||||
(bytearray, bytes, dict, list, str),
|
||||
)
|
||||
|
||||
|
||||
def read_body(request):
|
||||
if hasattr(request.body, "read"):
|
||||
return request.body.read()
|
||||
if _is_nonsequence_iterator(request.body):
|
||||
body = list(request.body)
|
||||
if body:
|
||||
if isinstance(body[0], str):
|
||||
return "".join(body).encode("utf-8")
|
||||
elif isinstance(body[0], (bytes, bytearray)):
|
||||
return b"".join(body)
|
||||
elif isinstance(body[0], int):
|
||||
return bytes(body)
|
||||
else:
|
||||
raise ValueError(f"Body type {type(body[0])} not supported")
|
||||
return b""
|
||||
return request.body
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user