Skip to content

Commit 47ecfed

Browse files
authored
Add support for Python 3.14 and drop EOL 3.8 and 3.9 (#216)
1 parent f18b709 commit 47ecfed

File tree

6 files changed

+62
-788
lines changed

6 files changed

+62
-788
lines changed

.github/workflows/main.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ jobs:
1111
runs-on: ubuntu-latest
1212
strategy:
1313
matrix:
14-
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
14+
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
1515
steps:
1616
- uses: actions/checkout@v6
1717

pyproject.toml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ dynamic = ["version"]
88
description = "A streaming multipart parser for Python"
99
readme = "README.md"
1010
license = "Apache-2.0"
11-
requires-python = ">=3.8"
11+
requires-python = ">=3.10"
1212
authors = [
1313
{ name = "Andrew Dunham", email = "andrew@du.nham.ca" },
1414
{ name = "Marcelo Trylesinski", email = "marcelotryle@gmail.com" },
@@ -21,12 +21,11 @@ classifiers = [
2121
'Operating System :: OS Independent',
2222
'Programming Language :: Python :: 3 :: Only',
2323
'Programming Language :: Python :: 3',
24-
'Programming Language :: Python :: 3.8',
25-
'Programming Language :: Python :: 3.9',
2624
'Programming Language :: Python :: 3.10',
2725
'Programming Language :: Python :: 3.11',
2826
'Programming Language :: Python :: 3.12',
2927
'Programming Language :: Python :: 3.13',
28+
'Programming Language :: Python :: 3.14',
3029
'Topic :: Software Development :: Libraries :: Python Modules',
3130
]
3231
dependencies = []

python_multipart/multipart.py

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,8 @@
1515
from .exceptions import FileError, FormParserError, MultipartParseError, QuerystringParseError
1616

1717
if TYPE_CHECKING: # pragma: no cover
18-
from typing import Any, Callable, Literal, Protocol, TypedDict
19-
20-
from typing_extensions import TypeAlias
18+
from collections.abc import Callable
19+
from typing import Any, Literal, Protocol, TypeAlias, TypedDict
2120

2221
class SupportsRead(Protocol):
2322
def read(self, __n: int) -> bytes: ...
@@ -332,7 +331,7 @@ def __repr__(self) -> str:
332331
else:
333332
v = repr(self.value)
334333

335-
return "{}(field_name={!r}, value={})".format(self.__class__.__name__, self.field_name, v)
334+
return f"{self.__class__.__name__}(field_name={self.field_name!r}, value={v})"
336335

337336

338337
class File:
@@ -570,7 +569,7 @@ def close(self) -> None:
570569
self._fileobj.close()
571570

572571
def __repr__(self) -> str:
573-
return "{}(file_name={!r}, field_name={!r})".format(self.__class__.__name__, self.file_name, self.field_name)
572+
return f"{self.__class__.__name__}(file_name={self.file_name!r}, field_name={self.field_name!r})"
574573

575574

576575
class BaseParser:
@@ -1241,7 +1240,7 @@ def data_callback(name: CallbackName, end_i: int, remaining: bool = False) -> No
12411240
elif state == MultipartState.HEADER_VALUE_ALMOST_DONE:
12421241
# The last character should be a LF. If not, it's an error.
12431242
if c != LF:
1244-
msg = "Did not find LF character at end of header (found %r)" % (c,)
1243+
msg = f"Did not find LF character at end of header (found {c!r})"
12451244
self.logger.warning(msg)
12461245
e = MultipartParseError(msg)
12471246
e.offset = i
@@ -1715,7 +1714,7 @@ def on_headers_finished() -> None:
17151714
else:
17161715
self.logger.warning("Unknown Content-Transfer-Encoding: %r", transfer_encoding)
17171716
if self.config["UPLOAD_ERROR_ON_BAD_CTE"]:
1718-
raise FormParserError('Unknown Content-Transfer-Encoding "{!r}"'.format(transfer_encoding))
1717+
raise FormParserError(f'Unknown Content-Transfer-Encoding "{transfer_encoding!r}"')
17191718
else:
17201719
# If we aren't erroring, then we just treat this as an
17211720
# unencoded Content-Transfer-Encoding.
@@ -1746,7 +1745,7 @@ def _on_end() -> None:
17461745

17471746
else:
17481747
self.logger.warning("Unknown Content-Type: %r", content_type)
1749-
raise FormParserError("Unknown Content-Type: {}".format(content_type))
1748+
raise FormParserError(f"Unknown Content-Type: {content_type}")
17501749

17511750
self.parser = parser
17521751

@@ -1776,7 +1775,7 @@ def close(self) -> None:
17761775
self.parser.close()
17771776

17781777
def __repr__(self) -> str:
1779-
return "{}(content_type={!r}, parser={!r})".format(self.__class__.__name__, self.content_type, self.parser)
1778+
return f"{self.__class__.__name__}(content_type={self.content_type!r}, parser={self.parser!r})"
17801779

17811780

17821781
def create_form_parser(

tests/compat.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
from typing import TYPE_CHECKING
99

1010
if TYPE_CHECKING:
11-
from typing import Any, Callable
11+
from collections.abc import Callable
12+
from typing import Any
1213

1314

1415
def ensure_in_path(path: str) -> None:

tests/test_multipart.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@
3737
from .compat import parametrize, parametrize_class
3838

3939
if TYPE_CHECKING:
40-
from typing import Any, Iterator, TypedDict
40+
from collections.abc import Iterator
41+
from typing import Any, TypedDict
4142

4243
from python_multipart.multipart import FieldProtocol, FileConfig, FileProtocol
4344

@@ -1069,7 +1070,9 @@ def test_bad_start_boundary(self) -> None:
10691070

10701071
self.make("boundary")
10711072
data = b"--Boundary\r\nfoobar"
1072-
with self.assertRaisesRegex(MultipartParseError, "Expected boundary character %r, got %r" % (b"b"[0], b"B"[0])):
1073+
with self.assertRaisesRegex(
1074+
MultipartParseError, "Expected boundary character {!r}, got {!r}".format(b"b"[0], b"B"[0])
1075+
):
10731076
self.f.write(data)
10741077

10751078
def test_octet_stream(self) -> None:

0 commit comments

Comments
 (0)