Skip to content

Commit 928e6d7

Browse files
[PR #10552/44e669be backport][3.11] Cache parsing of the content-type (#10557)
**This is a backport of PR #10552 as merged into master (44e669b).** <!-- Thank you for your contribution! --> ## What do these changes do? When profiling some frequent POST requests, I found the bulk of the time was spent parsing the content-type string. Use the same strategy as we do for `parse_mimetype` to cache the parsing. ## Are there changes in behavior for the user? performance improvement ## Is it a substantial burden for the maintainers to support this? no ## Related issue number <!-- Are there any issues opened that will be resolved by merging this change? --> <!-- Remember to prefix with 'Fixes' if it should close the issue (e.g. 'Fixes #123'). --> ## Checklist - [x] I think the code is well written - [ ] Unit tests for the changes exist - [ ] Documentation reflects the changes - [ ] If you provide code modification, please add yourself to `CONTRIBUTORS.txt` * The format is &lt;Name&gt; &lt;Surname&gt;. * Please keep alphabetical order, the file is sorted by names. - [ ] Add a new news fragment into the `CHANGES/` folder * name it `<issue_or_pr_num>.<type>.rst` (e.g. `588.bugfix.rst`) * if you don't have an issue number, change it to the pull request number after creating the PR * `.bugfix`: A bug fix for something the maintainers deemed an improper undesired behavior that got corrected to match pre-agreed expectations. * `.feature`: A new behavior, public APIs. That sort of stuff. * `.deprecation`: A declaration of future API removals and breaking changes in behavior. * `.breaking`: When something public is removed in a breaking way. Could be deprecated in an earlier release. * `.doc`: Notable updates to the documentation structure or build process. * `.packaging`: Notes for downstreams about unobvious side effects and tooling. Changes in the test invocation considerations and runtime assumptions. * `.contrib`: Stuff that affects the contributor experience. e.g. Running tests, building the docs, setting up the development environment. * `.misc`: Changes that are hard to assign to any of the above categories. * Make sure to use full sentences with correct case and punctuation, for example: ```rst Fixed issue with non-ascii contents in doctest text files -- by :user:`contributor-gh-handle`. ``` Use the past tense or the present tense a non-imperative mood, referring to what's changed compared to the last released version of this project. <img width="570" alt="Screenshot 2025-03-15 at 11 25 10 AM" src="https://github.com/user-attachments/assets/cabaaa7c-3a39-4f90-b450-a6a0559d22d6" /> Co-authored-by: J. Nick Koston <[email protected]>
1 parent 7205661 commit 928e6d7

File tree

2 files changed

+20
-5
lines changed

2 files changed

+20
-5
lines changed

CHANGES/10552.misc.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Improved performance of parsing content types by adding a cache in the same manner currently done with mime types -- by :user:`bdraco`.

aiohttp/helpers.py

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from email.utils import parsedate
2222
from math import ceil
2323
from pathlib import Path
24-
from types import TracebackType
24+
from types import MappingProxyType, TracebackType
2525
from typing import (
2626
Any,
2727
Callable,
@@ -357,6 +357,20 @@ def parse_mimetype(mimetype: str) -> MimeType:
357357
)
358358

359359

360+
@functools.lru_cache(maxsize=56)
361+
def parse_content_type(raw: str) -> Tuple[str, MappingProxyType[str, str]]:
362+
"""Parse Content-Type header.
363+
364+
Returns a tuple of the parsed content type and a
365+
MappingProxyType of parameters.
366+
"""
367+
msg = HeaderParser().parsestr(f"Content-Type: {raw}")
368+
content_type = msg.get_content_type()
369+
params = msg.get_params(())
370+
content_dict = dict(params[1:]) # First element is content type again
371+
return content_type, MappingProxyType(content_dict)
372+
373+
360374
def guess_filename(obj: Any, default: Optional[str] = None) -> Optional[str]:
361375
name = getattr(obj, "name", None)
362376
if name and isinstance(name, str) and name[0] != "<" and name[-1] != ">":
@@ -710,10 +724,10 @@ def _parse_content_type(self, raw: Optional[str]) -> None:
710724
self._content_type = "application/octet-stream"
711725
self._content_dict = {}
712726
else:
713-
msg = HeaderParser().parsestr("Content-Type: " + raw)
714-
self._content_type = msg.get_content_type()
715-
params = msg.get_params(())
716-
self._content_dict = dict(params[1:]) # First element is content type again
727+
content_type, content_mapping_proxy = parse_content_type(raw)
728+
self._content_type = content_type
729+
# _content_dict needs to be mutable so we can update it
730+
self._content_dict = content_mapping_proxy.copy()
717731

718732
@property
719733
def content_type(self) -> str:

0 commit comments

Comments
 (0)