1
0
mirror of https://github.com/kevin1024/vcrpy.git synced 2025-12-08 16:53:23 +00:00

Compare commits

...

47 Commits

Author SHA1 Message Date
Kevin McCarthy
1d100dda25 release v6.0.2 2024-10-07 08:55:44 -04:00
Sebastian Pipping
c6be705fb4 Merge pull request #871 from kevin1024/precommit-autoupdate
pre-commit: Autoupdate
2024-09-20 22:37:01 +02:00
pre-commit
10b7f4efb3 pre-commit: Autoupdate 2024-09-20 16:05:30 +00:00
Sebastian Pipping
7a6ef00f4d Merge pull request #870 from kevin1024/dependabot/github_actions/peter-evans/create-pull-request-7
build(deps): bump peter-evans/create-pull-request from 6 to 7
2024-09-19 00:33:49 +02:00
Sebastian Pipping
3bf6ac7184 Merge pull request #867 from kevin1024/precommit-autoupdate
pre-commit: Autoupdate
2024-09-19 00:32:45 +02:00
pre-commit
983b2202ed pre-commit: Autoupdate 2024-09-19 00:28:52 +02:00
dependabot[bot]
15a6b71997 build(deps): bump peter-evans/create-pull-request from 6 to 7
Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 6 to 7.
- [Release notes](https://github.com/peter-evans/create-pull-request/releases)
- [Commits](https://github.com/peter-evans/create-pull-request/compare/v6...v7)

---
updated-dependencies:
- dependency-name: peter-evans/create-pull-request
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-18 22:27:51 +00:00
Sebastian Pipping
1ca708dcff Merge pull request #862 from kevin1024/test-on-313
Start testing with CPython 3.13
2024-09-19 00:00:50 +02:00
Thomas Grainger
f5597fa6c1 use pytest_httbin.certs.where() for cafile 2024-09-18 22:36:17 +01:00
Thomas Grainger
2b3247b3df remove redundant load_cert_chain 2024-09-18 22:35:15 +01:00
Thomas Grainger
d123a5e8d0 replace fixture with constant 2024-09-18 22:34:48 +01:00
Thomas Grainger
e2815fbc88 move httbin_ssl_context fixture into the one place it's used 2024-09-18 22:31:36 +01:00
Thomas Grainger
f9d4500c6e test on 3.13 2024-09-18 19:24:37 +01:00
Sebastian Pipping
71eb624708 Merge pull request #851 from sathieu/consume-body-once2
Ensure body is consumed only once (alternative to #847)
2024-08-02 19:16:38 +02:00
Sebastian Pipping
dc449715c1 Merge pull request #864 from kevin1024/precommit-autoupdate
pre-commit: Autoupdate
2024-08-02 19:15:52 +02:00
pre-commit
275b9085f3 pre-commit: Autoupdate 2024-08-02 16:05:06 +00:00
Thomas Grainger
35650b141b Merge pull request #856 from connortann/patch-1
Fix package install: remove use of deprecated setuptools.command.test
2024-07-29 17:35:23 +01:00
Thomas Grainger
9c8b679136 remove unused sys import from setup.py 2024-07-29 17:27:19 +01:00
connortann
fab082eff5 Update setup.py: remove use of deprecated setuptools.command.test 2024-07-29 11:10:18 +01:00
Sebastian Pipping
ffc04f9128 Merge pull request #853 from kevin1024/precommit-autoupdate
pre-commit: Autoupdate
2024-07-27 01:25:13 +02:00
pre-commit
4d84da1809 pre-commit: Autoupdate 2024-07-26 16:05:08 +00:00
Mathieu Parent
241b0bbd91 Ensure body is consumed only once
Fixes: #846
Signed-off-by: Mathieu Parent <math.parent@gmail.com>
2024-07-21 22:53:45 +02:00
Sebastian Pipping
042e16c3e4 Merge pull request #850 from kevin1024/precommit-autoupdate
pre-commit: Autoupdate
2024-07-13 21:52:05 +02:00
pre-commit
acef3f49bf pre-commit: Autoupdate 2024-07-05 16:05:04 +00:00
Sebastian Pipping
9cfa6c5173 Merge pull request #845 from kevin1024/precommit-autoupdate
pre-commit: Autoupdate
2024-06-30 15:24:31 +02:00
Sebastian Pipping
39a86ba3cf pre-commit: Fix Ruff invocation for >=0.5.0
Related changes in Ruff:
https://github.com/astral-sh/ruff/pull/9687
2024-06-30 15:18:35 +02:00
pre-commit
543c72ba51 pre-commit: Autoupdate 2024-06-28 16:04:49 +00:00
Sebastian Pipping
86b114f2f5 Merge pull request #831 from kevin1024/precommit-autoupdate
pre-commit: Autoupdate
2024-06-02 15:33:33 +02:00
Sebastian Pipping
4b06f3dba1 Merge pull request #836 from chuckwondo/fix-testing-instructions
Fix typos in testing instructions
2024-06-02 15:29:57 +02:00
pre-commit
1c6503526b pre-commit: Autoupdate 2024-06-02 15:27:57 +02:00
Sebastian Pipping
c9c05682cb Merge pull request #843 from kevin1024/fix-ci-through-recent-setuptools
Fix CI through recent Setuptools + start building once a week to notice CI issues earlier
2024-06-02 15:27:23 +02:00
Sebastian Pipping
39c8648aa7 main.yml: Make sure that build issues do not go unnoticed for >7 days 2024-06-02 15:23:51 +02:00
Sebastian Pipping
dfff84d5bb main.yml: Fix "pypy-3.9,urllib3<2" CI through recent Setuptools
Command "pip install codecov '.[tests]' 'urllib3<2'" was failing
with error:
> hpy 0.9.0 has requirement setuptools>=64.0, but you have
> setuptools 58.1.0.
2024-06-02 15:23:08 +02:00
Chuck Daniels
40ac0de652 Fix typos in test commands 2024-04-21 19:58:00 -04:00
Sebastian Pipping
f3147f574b Merge pull request #830 from pjonsson/permit-urllib3
Permit urllib3 >=2 for non-PyPy Python >=3.10 in order to help users of Poetry
2024-03-11 19:35:43 +01:00
Sebastian Pipping
298a6933ff Merge pull request #829 from kevin1024/precommit-autoupdate
pre-commit: Autoupdate
2024-03-10 23:17:01 +01:00
Peter A. Jonsson
52da776b59 Permit urllib3 2.x for non-PyPy Python >=3.10
Poetry makes platform indepdent lock files,
so the PyPy marker is there even when using
CPython >= 3.10.

Add a third constraint that permits any urllib3
version when using Python >=3.10 and some
other implementation than PyPy.
2024-03-10 22:37:04 +01:00
pre-commit
8842fb1c3a pre-commit: Autoupdate 2024-03-08 16:04:43 +00:00
Sebastian Pipping
6c4ba172d8 Merge pull request #827 from kevin1024/precommit-autoupdate
pre-commit: Autoupdate
2024-03-06 14:40:32 +01:00
pre-commit
c88f2c0dab pre-commit: Mass-apply ruff formatter 2024-03-06 14:35:01 +01:00
pre-commit
3fd6b1c0b4 pre-commit: Autoupdate 2024-03-01 16:04:42 +00:00
Sebastian Pipping
c6d87309f4 Merge pull request #823 from mgorny/httpbin-compat
Improve test compatibility with legacy httpbin index
2024-02-28 20:38:23 +01:00
Sebastian Pipping
1fb9179cf9 Merge pull request #813 from kevin1024/precommit-autoupdate
pre-commit: Autoupdate
2024-02-28 20:37:08 +01:00
pre-commit
a58e0d8830 pre-commit: Autoupdate 2024-02-23 16:04:44 +00:00
dependabot[bot]
acc101412d build(deps): bump pre-commit/action from 3.0.0 to 3.0.1
Bumps [pre-commit/action](https://github.com/pre-commit/action) from 3.0.0 to 3.0.1.
- [Release notes](https://github.com/pre-commit/action/releases)
- [Commits](https://github.com/pre-commit/action/compare/v3.0.0...v3.0.1)

---
updated-dependencies:
- dependency-name: pre-commit/action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-21 10:39:22 -03:00
Michał Górny
e60dafb8dc Improve test compatibility with legacy httpbin index
Make the tests slightly more flexible to match both the flasgger-based
and legacy httpbin index.  This is needed for compatibility with
https://github.com/psf/httpbin/pull/44 when flasgger is not installed
(e.g. on architectures that are not supported by Rust).
2024-02-16 19:33:41 +01:00
dependabot[bot]
3ce5979acb build(deps): bump peter-evans/create-pull-request from 5 to 6
Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 5 to 6.
- [Release notes](https://github.com/peter-evans/create-pull-request/releases)
- [Commits](https://github.com/peter-evans/create-pull-request/compare/v5...v6)

---
updated-dependencies:
- dependency-name: peter-evans/create-pull-request
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-06 10:13:52 -03:00
23 changed files with 157 additions and 55 deletions

View File

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

View File

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

View File

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

View File

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

View File

@@ -7,6 +7,11 @@ 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

View File

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

View File

@@ -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 = {

View File

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

View File

@@ -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"

View File

@@ -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 &amp; Response Service." in response
assert b"HTTP Request &amp; Response Service" in response
def test_patched_content(tmpdir, httpbin):

View File

@@ -1,4 +1,5 @@
"""Integration tests with httplib2"""
from urllib.parse import urlencode
import pytest

View File

@@ -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 &amp; Response Service." in response
assert b"HTTP Request &amp; Response Service" in response
def test_load_cassette_persister_exception_handling(tmpdir, httpbin):

View File

@@ -1,4 +1,5 @@
"""Test requests' interaction with vcr"""
import pytest
import vcr

View File

@@ -1,4 +1,5 @@
"""Test requests' interaction with vcr"""
import asyncio
import functools
import inspect

View File

@@ -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
View 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",)

View File

@@ -4,7 +4,7 @@ from logging import NullHandler
from .config import VCR
from .record_mode import RecordMode as mode # noqa: F401
__version__ = "6.0.1"
__version__ = "6.0.2"
logging.getLogger(__name__).addHandler(NullHandler())

View File

@@ -1,4 +1,5 @@
"""Utilities for patching in cassettes"""
import contextlib
import functools
import http.client as httplib

View File

@@ -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):

View File

@@ -1,4 +1,5 @@
"""Stubs for aiohttp HTTP clients"""
import asyncio
import functools
import json

View File

@@ -1,4 +1,5 @@
"""Stubs for boto3"""
from botocore.awsrequest import AWSHTTPConnection as HTTPConnection
from botocore.awsrequest import AWSHTTPSConnection as VerifiedHTTPSConnection

View File

@@ -1,4 +1,5 @@
"""Stubs for tornado HTTP clients"""
import functools
from io import BytesIO

View File

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