python-multipart 0.0.20__tar.gz → 0.0.21__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {python_multipart-0.0.20 → python_multipart-0.0.21}/CHANGELOG.md +4 -0
- {python_multipart-0.0.20 → python_multipart-0.0.21}/PKG-INFO +4 -4
- {python_multipart-0.0.20 → python_multipart-0.0.21}/pyproject.toml +6 -6
- {python_multipart-0.0.20 → python_multipart-0.0.21}/python_multipart/__init__.py +1 -1
- {python_multipart-0.0.20 → python_multipart-0.0.21}/python_multipart/decoders.py +1 -1
- {python_multipart-0.0.20 → python_multipart-0.0.21}/python_multipart/multipart.py +8 -9
- {python_multipart-0.0.20 → python_multipart-0.0.21}/tests/compat.py +2 -1
- {python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_multipart.py +5 -2
- {python_multipart-0.0.20 → python_multipart-0.0.21}/.gitignore +0 -0
- {python_multipart-0.0.20 → python_multipart-0.0.21}/LICENSE.txt +0 -0
- {python_multipart-0.0.20 → python_multipart-0.0.21}/README.md +0 -0
- {python_multipart-0.0.20 → python_multipart-0.0.21}/multipart/__init__.py +0 -0
- {python_multipart-0.0.20 → python_multipart-0.0.21}/multipart/decoders.py +0 -0
- {python_multipart-0.0.20 → python_multipart-0.0.21}/multipart/exceptions.py +0 -0
- {python_multipart-0.0.20 → python_multipart-0.0.21}/multipart/multipart.py +0 -0
- {python_multipart-0.0.20 → python_multipart-0.0.21}/python_multipart/exceptions.py +0 -0
- {python_multipart-0.0.20 → python_multipart-0.0.21}/python_multipart/py.typed +0 -0
- {python_multipart-0.0.20 → python_multipart-0.0.21}/tests/__init__.py +0 -0
- {python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/CRLF_in_header.http +0 -0
- {python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/CRLF_in_header.yaml +0 -0
- {python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/CR_in_header.http +0 -0
- {python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/CR_in_header.yaml +0 -0
- {python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/CR_in_header_value.http +0 -0
- {python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/CR_in_header_value.yaml +0 -0
- {python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/almost_match_boundary.http +0 -0
- {python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/almost_match_boundary.yaml +0 -0
- {python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/almost_match_boundary_without_CR.http +0 -0
- {python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/almost_match_boundary_without_CR.yaml +0 -0
- {python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/almost_match_boundary_without_LF.http +0 -0
- {python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/almost_match_boundary_without_LF.yaml +0 -0
- {python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/almost_match_boundary_without_final_hyphen.http +0 -0
- {python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/almost_match_boundary_without_final_hyphen.yaml +0 -0
- {python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/bad_end_of_headers.http +0 -0
- {python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/bad_end_of_headers.yaml +0 -0
- {python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/bad_header_char.http +0 -0
- {python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/bad_header_char.yaml +0 -0
- {python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/bad_initial_boundary.http +0 -0
- {python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/bad_initial_boundary.yaml +0 -0
- {python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/base64_encoding.http +0 -0
- {python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/base64_encoding.yaml +0 -0
- {python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/empty_header.http +0 -0
- {python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/empty_header.yaml +0 -0
- {python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/empty_message.http +0 -0
- {python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/empty_message.yaml +0 -0
- {python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/empty_message_with_bad_end.http +0 -0
- {python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/empty_message_with_bad_end.yaml +0 -0
- {python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/header_with_number.http +0 -0
- {python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/header_with_number.yaml +0 -0
- {python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/mixed_plain_and_base64_encoding.http +0 -0
- {python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/mixed_plain_and_base64_encoding.yaml +0 -0
- {python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/multiple_fields.http +0 -0
- {python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/multiple_fields.yaml +0 -0
- {python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/multiple_files.http +0 -0
- {python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/multiple_files.yaml +0 -0
- {python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/quoted_printable_encoding.http +0 -0
- {python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/quoted_printable_encoding.yaml +0 -0
- {python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/single_field.http +0 -0
- {python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/single_field.yaml +0 -0
- {python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/single_field_blocks.http +0 -0
- {python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/single_field_blocks.yaml +0 -0
- {python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/single_field_longer.http +0 -0
- {python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/single_field_longer.yaml +0 -0
- {python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/single_field_single_file.http +0 -0
- {python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/single_field_single_file.yaml +0 -0
- {python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/single_field_with_leading_newlines.http +0 -0
- {python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/single_field_with_leading_newlines.yaml +0 -0
- {python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/single_file.http +0 -0
- {python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/single_file.yaml +0 -0
- {python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/utf8_filename.http +0 -0
- {python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/utf8_filename.yaml +0 -0
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.0.21 (2025-12-17)
|
|
4
|
+
|
|
5
|
+
* Add support for Python 3.14 and drop EOL 3.8 and 3.9 [#216](https://github.com/Kludex/python-multipart/pull/216).
|
|
6
|
+
|
|
3
7
|
## 0.0.20 (2024-12-16)
|
|
4
8
|
|
|
5
9
|
* Handle messages containing only end boundary [#142](https://github.com/Kludex/python-multipart/pull/142).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-multipart
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.21
|
|
4
4
|
Summary: A streaming multipart parser for Python
|
|
5
5
|
Project-URL: Homepage, https://github.com/Kludex/python-multipart
|
|
6
6
|
Project-URL: Documentation, https://kludex.github.io/python-multipart/
|
|
@@ -16,13 +16,13 @@ Classifier: License :: OSI Approved :: Apache Software License
|
|
|
16
16
|
Classifier: Operating System :: OS Independent
|
|
17
17
|
Classifier: Programming Language :: Python :: 3
|
|
18
18
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
20
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
21
19
|
Classifier: Programming Language :: Python :: 3.10
|
|
22
20
|
Classifier: Programming Language :: Python :: 3.11
|
|
23
21
|
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
24
24
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
25
|
-
Requires-Python: >=3.
|
|
25
|
+
Requires-Python: >=3.10
|
|
26
26
|
Description-Content-Type: text/markdown
|
|
27
27
|
|
|
28
28
|
# [Python-Multipart](https://kludex.github.io/python-multipart/)
|
|
@@ -8,7 +8,7 @@ dynamic = ["version"]
|
|
|
8
8
|
description = "A streaming multipart parser for Python"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "Apache-2.0"
|
|
11
|
-
requires-python = ">=3.
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
12
|
authors = [
|
|
13
13
|
{ name = "Andrew Dunham", email = "andrew@du.nham.ca" },
|
|
14
14
|
{ name = "Marcelo Trylesinski", email = "marcelotryle@gmail.com" },
|
|
@@ -21,17 +21,17 @@ classifiers = [
|
|
|
21
21
|
'Operating System :: OS Independent',
|
|
22
22
|
'Programming Language :: Python :: 3 :: Only',
|
|
23
23
|
'Programming Language :: Python :: 3',
|
|
24
|
-
'Programming Language :: Python :: 3.8',
|
|
25
|
-
'Programming Language :: Python :: 3.9',
|
|
26
24
|
'Programming Language :: Python :: 3.10',
|
|
27
25
|
'Programming Language :: Python :: 3.11',
|
|
28
26
|
'Programming Language :: Python :: 3.12',
|
|
27
|
+
'Programming Language :: Python :: 3.13',
|
|
28
|
+
'Programming Language :: Python :: 3.14',
|
|
29
29
|
'Topic :: Software Development :: Libraries :: Python Modules',
|
|
30
30
|
]
|
|
31
31
|
dependencies = []
|
|
32
32
|
|
|
33
|
-
[
|
|
34
|
-
dev
|
|
33
|
+
[dependency-groups]
|
|
34
|
+
dev = [
|
|
35
35
|
"atomicwrites==1.4.1",
|
|
36
36
|
"attrs==23.2.0",
|
|
37
37
|
"coverage==7.4.4",
|
|
@@ -44,7 +44,7 @@ dev-dependencies = [
|
|
|
44
44
|
"PyYAML==6.0.1",
|
|
45
45
|
"invoke==2.2.0",
|
|
46
46
|
"pytest-timeout==2.3.1",
|
|
47
|
-
"ruff==0.
|
|
47
|
+
"ruff==0.11.7",
|
|
48
48
|
"mypy",
|
|
49
49
|
"types-PyYAML",
|
|
50
50
|
"atheris==2.3.0; python_version <= '3.11'",
|
|
@@ -15,9 +15,8 @@ from .decoders import Base64Decoder, QuotedPrintableDecoder
|
|
|
15
15
|
from .exceptions import FileError, FormParserError, MultipartParseError, QuerystringParseError
|
|
16
16
|
|
|
17
17
|
if TYPE_CHECKING: # pragma: no cover
|
|
18
|
-
from
|
|
19
|
-
|
|
20
|
-
from typing_extensions import TypeAlias
|
|
18
|
+
from collections.abc import Callable
|
|
19
|
+
from typing import Any, Literal, Protocol, TypeAlias, TypedDict
|
|
21
20
|
|
|
22
21
|
class SupportsRead(Protocol):
|
|
23
22
|
def read(self, __n: int) -> bytes: ...
|
|
@@ -332,7 +331,7 @@ class Field:
|
|
|
332
331
|
else:
|
|
333
332
|
v = repr(self.value)
|
|
334
333
|
|
|
335
|
-
return "{}(field_name={!r}, value={})"
|
|
334
|
+
return f"{self.__class__.__name__}(field_name={self.field_name!r}, value={v})"
|
|
336
335
|
|
|
337
336
|
|
|
338
337
|
class File:
|
|
@@ -570,7 +569,7 @@ class File:
|
|
|
570
569
|
self._fileobj.close()
|
|
571
570
|
|
|
572
571
|
def __repr__(self) -> str:
|
|
573
|
-
return "{}(file_name={!r}, field_name={!r})"
|
|
572
|
+
return f"{self.__class__.__name__}(file_name={self.file_name!r}, field_name={self.field_name!r})"
|
|
574
573
|
|
|
575
574
|
|
|
576
575
|
class BaseParser:
|
|
@@ -1241,7 +1240,7 @@ class MultipartParser(BaseParser):
|
|
|
1241
1240
|
elif state == MultipartState.HEADER_VALUE_ALMOST_DONE:
|
|
1242
1241
|
# The last character should be a LF. If not, it's an error.
|
|
1243
1242
|
if c != LF:
|
|
1244
|
-
msg = "Did not find LF character at end of header
|
|
1243
|
+
msg = f"Did not find LF character at end of header (found {c!r})"
|
|
1245
1244
|
self.logger.warning(msg)
|
|
1246
1245
|
e = MultipartParseError(msg)
|
|
1247
1246
|
e.offset = i
|
|
@@ -1715,7 +1714,7 @@ class FormParser:
|
|
|
1715
1714
|
else:
|
|
1716
1715
|
self.logger.warning("Unknown Content-Transfer-Encoding: %r", transfer_encoding)
|
|
1717
1716
|
if self.config["UPLOAD_ERROR_ON_BAD_CTE"]:
|
|
1718
|
-
raise FormParserError('Unknown Content-Transfer-Encoding "{!r}"'
|
|
1717
|
+
raise FormParserError(f'Unknown Content-Transfer-Encoding "{transfer_encoding!r}"')
|
|
1719
1718
|
else:
|
|
1720
1719
|
# If we aren't erroring, then we just treat this as an
|
|
1721
1720
|
# unencoded Content-Transfer-Encoding.
|
|
@@ -1746,7 +1745,7 @@ class FormParser:
|
|
|
1746
1745
|
|
|
1747
1746
|
else:
|
|
1748
1747
|
self.logger.warning("Unknown Content-Type: %r", content_type)
|
|
1749
|
-
raise FormParserError("Unknown Content-Type: {}"
|
|
1748
|
+
raise FormParserError(f"Unknown Content-Type: {content_type}")
|
|
1750
1749
|
|
|
1751
1750
|
self.parser = parser
|
|
1752
1751
|
|
|
@@ -1776,7 +1775,7 @@ class FormParser:
|
|
|
1776
1775
|
self.parser.close()
|
|
1777
1776
|
|
|
1778
1777
|
def __repr__(self) -> str:
|
|
1779
|
-
return "{}(content_type={!r}, parser={!r})"
|
|
1778
|
+
return f"{self.__class__.__name__}(content_type={self.content_type!r}, parser={self.parser!r})"
|
|
1780
1779
|
|
|
1781
1780
|
|
|
1782
1781
|
def create_form_parser(
|
|
@@ -37,7 +37,8 @@ from python_multipart.multipart import (
|
|
|
37
37
|
from .compat import parametrize, parametrize_class
|
|
38
38
|
|
|
39
39
|
if TYPE_CHECKING:
|
|
40
|
-
from
|
|
40
|
+
from collections.abc import Iterator
|
|
41
|
+
from typing import Any, TypedDict
|
|
41
42
|
|
|
42
43
|
from python_multipart.multipart import FieldProtocol, FileConfig, FileProtocol
|
|
43
44
|
|
|
@@ -1069,7 +1070,9 @@ class TestFormParser(unittest.TestCase):
|
|
|
1069
1070
|
|
|
1070
1071
|
self.make("boundary")
|
|
1071
1072
|
data = b"--Boundary\r\nfoobar"
|
|
1072
|
-
with self.assertRaisesRegex(
|
|
1073
|
+
with self.assertRaisesRegex(
|
|
1074
|
+
MultipartParseError, "Expected boundary character {!r}, got {!r}".format(b"b"[0], b"B"[0])
|
|
1075
|
+
):
|
|
1073
1076
|
self.f.write(data)
|
|
1074
1077
|
|
|
1075
1078
|
def test_octet_stream(self) -> None:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/CRLF_in_header.http
RENAMED
|
File without changes
|
{python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/CRLF_in_header.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/CR_in_header_value.http
RENAMED
|
File without changes
|
{python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/CR_in_header_value.yaml
RENAMED
|
File without changes
|
{python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/almost_match_boundary.http
RENAMED
|
File without changes
|
{python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/almost_match_boundary.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/bad_end_of_headers.http
RENAMED
|
File without changes
|
{python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/bad_end_of_headers.yaml
RENAMED
|
File without changes
|
{python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/bad_header_char.http
RENAMED
|
File without changes
|
{python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/bad_header_char.yaml
RENAMED
|
File without changes
|
{python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/bad_initial_boundary.http
RENAMED
|
File without changes
|
{python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/bad_initial_boundary.yaml
RENAMED
|
File without changes
|
{python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/base64_encoding.http
RENAMED
|
File without changes
|
{python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/base64_encoding.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/header_with_number.http
RENAMED
|
File without changes
|
{python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/header_with_number.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/multiple_fields.http
RENAMED
|
File without changes
|
{python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/multiple_fields.yaml
RENAMED
|
File without changes
|
{python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/multiple_files.http
RENAMED
|
File without changes
|
{python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/multiple_files.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/single_field_blocks.http
RENAMED
|
File without changes
|
{python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/single_field_blocks.yaml
RENAMED
|
File without changes
|
{python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/single_field_longer.http
RENAMED
|
File without changes
|
{python_multipart-0.0.20 → python_multipart-0.0.21}/tests/test_data/http/single_field_longer.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|