Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 31 additions & 13 deletions aiohttp/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,33 +69,51 @@
DEFAULT_CHUNK_SIZE = 2**18 # 256 KiB
COOKIE_MAX_LENGTH = 4096
_QUOTED_PAIR_SUB = re.compile(r"\\(.)")
_QUOTED_STRING = r'"(?:[^"\\]|\\.)*"'
_ESCAPED_COMMENT = r"(?:[^()\\]|\\.)*"
# Matches one element in a comma-separated header list.
# Group 1: content of a top-level quoted-string (quotes stripped).
# Group 2: an unquoted element (may contain parameter quoted-strings / comments).
_LIST_ELEMENT_RE = re.compile(
rf"""
if sys.version_info >= (3, 11):
_QUOTED_STRING_CONTENT = r'(?:[^"\\]++|\\.)*+'
_ESCAPED_COMMENT = r"(?:[^()\\]++|\\.)*+"
_LIST_ELEMENT = rf"""
[ \t]*
(?:
"( (?:[^"\\]|\\.)* )" # group 1: top-level quoted-string
"( {_QUOTED_STRING_CONTENT} )" # group 1: top-level quoted-string
[ \t]* (?:,|\Z)
| ( # group 2: unquoted element
(?:
(?<=[^\s]=) {_QUOTED_STRING} # parameter quoted value
(?<=[^\s]=) "{_QUOTED_STRING_CONTENT}" # parameter quoted value
| (?<=\s) \( {_ESCAPED_COMMENT} \) # comment
| [^,"(\\]++ # run of ordinary characters
| [^,] # quote, paren or backslash the branches above rejected
)++
)
(?:,|\Z)
)
"""
else:
_QUOTED_STRING_CONTENT = r'(?:[^"\\]|\\.)*'
_ESCAPED_COMMENT = r"(?:[^()\\]|\\.)*"
_LIST_ELEMENT = rf"""
[ \t]*
(?:
"( {_QUOTED_STRING_CONTENT} )" # group 1: top-level quoted-string
[ \t]* (?:,|\Z)
| ( # group 2: unquoted element
(?:
(?<=[^\s]=) "{_QUOTED_STRING_CONTENT}" # parameter quoted value
| (?<=\s) \( {_ESCAPED_COMMENT} \) # comment
| [^,] # any non-comma character
)+?
)
(?:,|\Z)
)
""",
re.VERBOSE,
)
"""
# Matches one element in a comma-separated header list.
# Group 1: content of a top-level quoted-string (quotes stripped).
# Group 2: an unquoted element (may contain parameter quoted-strings / comments).
_LIST_ELEMENT_RE = re.compile(_LIST_ELEMENT, re.VERBOSE)
# Finds parameter quoted-strings and comments inside an unquoted element for unescaping.
_PROTECTED_RE = re.compile(
rf"""
(?<=[^\s]=) {_QUOTED_STRING} # parameter quoted-string
(?<=[^\s]=) "{_QUOTED_STRING_CONTENT}" # parameter quoted-string
| (?<=\s) \( {_ESCAPED_COMMENT} \) # comment
""",
re.VERBOSE,
Expand Down
2 changes: 1 addition & 1 deletion requirements/base-ft.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ frozenlist==1.8.0
# via
# -r requirements/runtime-deps.in
# aiosignal
gunicorn==26.1.0
gunicorn==26.2.0
# via -r requirements/base-ft.in
idna==3.19
# via yarl
Expand Down
2 changes: 1 addition & 1 deletion requirements/base.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ frozenlist==1.8.0
# via
# -r requirements/runtime-deps.in
# aiosignal
gunicorn==26.1.0
gunicorn==26.2.0
# via -r requirements/base.in
idna==3.19
# via yarl
Expand Down
10 changes: 5 additions & 5 deletions requirements/constraints.txt
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ frozenlist==1.8.0
# -r requirements/runtime-deps.in
# aiohttp
# aiosignal
gunicorn==26.1.0
gunicorn==26.2.0
# via -r requirements/base.in
identify==2.6.19
# via pre-commit
Expand All @@ -109,7 +109,7 @@ idna==3.19
# requests
# trustme
# yarl
imagesize==2.0.0
imagesize==2.0.1
# via sphinx
iniconfig==2.3.0
# via pytest
Expand Down Expand Up @@ -166,7 +166,7 @@ pip-tools==7.6.1
# via -r requirements/dev.in
pkgconfig==1.6.0
# via -r requirements/test-common-base.in
platformdirs==4.11.3
platformdirs==4.11.4
# via virtualenv
pluggy==1.6.0
# via
Expand Down Expand Up @@ -235,7 +235,7 @@ pytest-xdist==3.8.0
# via -r requirements/test-common.in
python-dateutil==2.9.0.post0
# via freezegun
python-discovery==1.5.2
python-discovery==1.5.3
# via virtualenv
python-on-whales==0.81.0
# via
Expand Down Expand Up @@ -331,7 +331,7 @@ uvloop==0.22.1 ; platform_system != "Windows"
# -r requirements/lint.in
valkey==6.1.1
# via -r requirements/lint.in
virtualenv==21.7.4
virtualenv==21.7.5
# via pre-commit
wheel==0.48.0
# via pip-tools
Expand Down
10 changes: 5 additions & 5 deletions requirements/dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ frozenlist==1.8.0
# -r requirements/runtime-deps.in
# aiohttp
# aiosignal
gunicorn==26.1.0
gunicorn==26.2.0
# via -r requirements/base.in
identify==2.6.19
# via pre-commit
Expand All @@ -107,7 +107,7 @@ idna==3.19
# requests
# trustme
# yarl
imagesize==2.0.0
imagesize==2.0.1
# via sphinx
iniconfig==2.3.0
# via pytest
Expand Down Expand Up @@ -163,7 +163,7 @@ pip-tools==7.6.1
# via -r requirements/dev.in
pkgconfig==1.6.0
# via -r requirements/test-common-base.in
platformdirs==4.11.3
platformdirs==4.11.4
# via virtualenv
pluggy==1.6.0
# via
Expand Down Expand Up @@ -230,7 +230,7 @@ pytest-xdist==3.8.0
# via -r requirements/test-common.in
python-dateutil==2.9.0.post0
# via freezegun
python-discovery==1.5.2
python-discovery==1.5.3
# via virtualenv
python-on-whales==0.81.0
# via
Expand Down Expand Up @@ -321,7 +321,7 @@ uvloop==0.22.1 ; platform_system != "Windows" and implementation_name == "cpytho
# -r requirements/lint.in
valkey==6.1.1
# via -r requirements/lint.in
virtualenv==21.7.4
virtualenv==21.7.5
# via pre-commit
wheel==0.48.0
# via pip-tools
Expand Down
2 changes: 1 addition & 1 deletion requirements/doc-spelling.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ docutils==0.21.2
# sphinx
idna==3.19
# via requests
imagesize==2.0.0
imagesize==2.0.1
# via sphinx
jinja2==3.1.6
# via
Expand Down
2 changes: 1 addition & 1 deletion requirements/doc.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ docutils==0.21.2
# sphinx
idna==3.19
# via requests
imagesize==2.0.0
imagesize==2.0.1
# via sphinx
jinja2==3.1.6
# via
Expand Down
6 changes: 3 additions & 3 deletions requirements/lint.txt
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ packaging==26.3
# via pytest
pathspec==1.1.1
# via mypy
platformdirs==4.11.3
platformdirs==4.11.4
# via virtualenv
pluggy==1.6.0
# via pytest
Expand Down Expand Up @@ -125,7 +125,7 @@ pytest-mock==3.15.1
# via -r requirements/lint.in
python-dateutil==2.9.0.post0
# via freezegun
python-discovery==1.5.2
python-discovery==1.5.3
# via virtualenv
python-on-whales==0.81.0
# via -r requirements/lint.in
Expand Down Expand Up @@ -164,7 +164,7 @@ uvloop==0.22.1 ; platform_system != "Windows"
# via -r requirements/lint.in
valkey==6.1.1
# via -r requirements/lint.in
virtualenv==21.7.4
virtualenv==21.7.5
# via pre-commit
yarl==1.24.5
# via aiohttp
Expand Down
2 changes: 1 addition & 1 deletion requirements/test-ft.txt
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ frozenlist==1.8.0
# -r requirements/runtime-deps.in
# aiohttp
# aiosignal
gunicorn==26.1.0
gunicorn==26.2.0
# via -r requirements/base-ft.in
idna==3.19
# via
Expand Down
2 changes: 1 addition & 1 deletion requirements/test-mobile.txt
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ frozenlist==1.8.0
# -r requirements/runtime-deps.in
# aiohttp
# aiosignal
gunicorn==26.1.0
gunicorn==26.2.0
# via -r requirements/base-ft.in
idna==3.19
# via yarl
Expand Down
2 changes: 1 addition & 1 deletion requirements/test.txt
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ frozenlist==1.8.0
# -r requirements/runtime-deps.in
# aiohttp
# aiosignal
gunicorn==26.1.0
gunicorn==26.2.0
# via -r requirements/base.in
idna==3.19
# via
Expand Down
13 changes: 11 additions & 2 deletions tests/test_benchmarks_multipart.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,17 +48,26 @@ def _part(body: bytes, loop: asyncio.AbstractEventLoop) -> BodyPartReader:
def test_read_base64_part(
event_loop: asyncio.AbstractEventLoop, benchmark: BenchmarkFixture
) -> None:
"""Read a line-wrapped base64 part to completion.
"""Read a line-wrapped base64 part to completion chunk by chunk.

Every 8 KiB chunk lands mid-quartet, so this covers the common cost of
the base64 realignment in ``read_chunk`` on well-formed input.

The chunks are deliberately dropped rather than accumulated: growing one
large buffer inside the measured region made the measurement depend on
whether realloc could extend it in place -- allocator luck, which showed
up as double-digit swings between unrelated CodSpeed runs.
"""
body = base64.encodebytes(b"x" * (256 * 1024)).replace(b"\n", b"\r\n")
body += b"\r\n--:--"

async def read_all_chunks(part: BodyPartReader) -> None:
while not part.at_eof():
await part.read_chunk(BodyPartReader.chunk_size)

@benchmark
def _run() -> None:
event_loop.run_until_complete(_part(body, event_loop).read())
event_loop.run_until_complete(read_all_chunks(_part(body, event_loop)))


def test_read_chunk_base64_realignment(
Expand Down
Loading