python-multipart 0.0.10__tar.gz → 0.0.12__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.
Files changed (62) hide show
  1. {python_multipart-0.0.10 → python_multipart-0.0.12}/.gitignore +1 -0
  2. {python_multipart-0.0.10 → python_multipart-0.0.12}/CHANGELOG.md +11 -0
  3. {python_multipart-0.0.10 → python_multipart-0.0.12}/PKG-INFO +1 -1
  4. {python_multipart-0.0.10 → python_multipart-0.0.12}/multipart/__init__.py +1 -1
  5. {python_multipart-0.0.10 → python_multipart-0.0.12}/multipart/decoders.py +19 -6
  6. {python_multipart-0.0.10 → python_multipart-0.0.12}/multipart/multipart.py +168 -148
  7. {python_multipart-0.0.10 → python_multipart-0.0.12}/pyproject.toml +6 -1
  8. python_multipart-0.0.12/tests/__init__.py +0 -0
  9. {python_multipart-0.0.10 → python_multipart-0.0.12}/tests/compat.py +17 -9
  10. python_multipart-0.0.12/tests/test_data/http/CRLF_in_header.http +6 -0
  11. {python_multipart-0.0.10 → python_multipart-0.0.12}/tests/test_data/http/CR_in_header.yaml +1 -1
  12. python_multipart-0.0.12/tests/test_data/http/bad_header_char.yaml +3 -0
  13. {python_multipart-0.0.10 → python_multipart-0.0.12}/tests/test_multipart.py +216 -173
  14. {python_multipart-0.0.10 → python_multipart-0.0.12}/LICENSE.txt +0 -0
  15. {python_multipart-0.0.10 → python_multipart-0.0.12}/README.md +0 -0
  16. {python_multipart-0.0.10 → python_multipart-0.0.12}/multipart/exceptions.py +0 -0
  17. /python_multipart-0.0.10/tests/__init__.py → /python_multipart-0.0.12/multipart/py.typed +0 -0
  18. /python_multipart-0.0.10/tests/test_data/http/bad_header_char.yaml → /python_multipart-0.0.12/tests/test_data/http/CRLF_in_header.yaml +0 -0
  19. {python_multipart-0.0.10 → python_multipart-0.0.12}/tests/test_data/http/CR_in_header.http +0 -0
  20. {python_multipart-0.0.10 → python_multipart-0.0.12}/tests/test_data/http/CR_in_header_value.http +0 -0
  21. {python_multipart-0.0.10 → python_multipart-0.0.12}/tests/test_data/http/CR_in_header_value.yaml +0 -0
  22. {python_multipart-0.0.10 → python_multipart-0.0.12}/tests/test_data/http/almost_match_boundary.http +0 -0
  23. {python_multipart-0.0.10 → python_multipart-0.0.12}/tests/test_data/http/almost_match_boundary.yaml +0 -0
  24. {python_multipart-0.0.10 → python_multipart-0.0.12}/tests/test_data/http/almost_match_boundary_without_CR.http +0 -0
  25. {python_multipart-0.0.10 → python_multipart-0.0.12}/tests/test_data/http/almost_match_boundary_without_CR.yaml +0 -0
  26. {python_multipart-0.0.10 → python_multipart-0.0.12}/tests/test_data/http/almost_match_boundary_without_LF.http +0 -0
  27. {python_multipart-0.0.10 → python_multipart-0.0.12}/tests/test_data/http/almost_match_boundary_without_LF.yaml +0 -0
  28. {python_multipart-0.0.10 → python_multipart-0.0.12}/tests/test_data/http/almost_match_boundary_without_final_hyphen.http +0 -0
  29. {python_multipart-0.0.10 → python_multipart-0.0.12}/tests/test_data/http/almost_match_boundary_without_final_hyphen.yaml +0 -0
  30. {python_multipart-0.0.10 → python_multipart-0.0.12}/tests/test_data/http/bad_end_of_headers.http +0 -0
  31. {python_multipart-0.0.10 → python_multipart-0.0.12}/tests/test_data/http/bad_end_of_headers.yaml +0 -0
  32. {python_multipart-0.0.10 → python_multipart-0.0.12}/tests/test_data/http/bad_header_char.http +0 -0
  33. {python_multipart-0.0.10 → python_multipart-0.0.12}/tests/test_data/http/bad_initial_boundary.http +0 -0
  34. {python_multipart-0.0.10 → python_multipart-0.0.12}/tests/test_data/http/bad_initial_boundary.yaml +0 -0
  35. {python_multipart-0.0.10 → python_multipart-0.0.12}/tests/test_data/http/base64_encoding.http +0 -0
  36. {python_multipart-0.0.10 → python_multipart-0.0.12}/tests/test_data/http/base64_encoding.yaml +0 -0
  37. {python_multipart-0.0.10 → python_multipart-0.0.12}/tests/test_data/http/empty_header.http +0 -0
  38. {python_multipart-0.0.10 → python_multipart-0.0.12}/tests/test_data/http/empty_header.yaml +0 -0
  39. {python_multipart-0.0.10 → python_multipart-0.0.12}/tests/test_data/http/header_with_number.http +0 -0
  40. {python_multipart-0.0.10 → python_multipart-0.0.12}/tests/test_data/http/header_with_number.yaml +0 -0
  41. {python_multipart-0.0.10 → python_multipart-0.0.12}/tests/test_data/http/mixed_plain_and_base64_encoding.http +0 -0
  42. {python_multipart-0.0.10 → python_multipart-0.0.12}/tests/test_data/http/mixed_plain_and_base64_encoding.yaml +0 -0
  43. {python_multipart-0.0.10 → python_multipart-0.0.12}/tests/test_data/http/multiple_fields.http +0 -0
  44. {python_multipart-0.0.10 → python_multipart-0.0.12}/tests/test_data/http/multiple_fields.yaml +0 -0
  45. {python_multipart-0.0.10 → python_multipart-0.0.12}/tests/test_data/http/multiple_files.http +0 -0
  46. {python_multipart-0.0.10 → python_multipart-0.0.12}/tests/test_data/http/multiple_files.yaml +0 -0
  47. {python_multipart-0.0.10 → python_multipart-0.0.12}/tests/test_data/http/quoted_printable_encoding.http +0 -0
  48. {python_multipart-0.0.10 → python_multipart-0.0.12}/tests/test_data/http/quoted_printable_encoding.yaml +0 -0
  49. {python_multipart-0.0.10 → python_multipart-0.0.12}/tests/test_data/http/single_field.http +0 -0
  50. {python_multipart-0.0.10 → python_multipart-0.0.12}/tests/test_data/http/single_field.yaml +0 -0
  51. {python_multipart-0.0.10 → python_multipart-0.0.12}/tests/test_data/http/single_field_blocks.http +0 -0
  52. {python_multipart-0.0.10 → python_multipart-0.0.12}/tests/test_data/http/single_field_blocks.yaml +0 -0
  53. {python_multipart-0.0.10 → python_multipart-0.0.12}/tests/test_data/http/single_field_longer.http +0 -0
  54. {python_multipart-0.0.10 → python_multipart-0.0.12}/tests/test_data/http/single_field_longer.yaml +0 -0
  55. {python_multipart-0.0.10 → python_multipart-0.0.12}/tests/test_data/http/single_field_single_file.http +0 -0
  56. {python_multipart-0.0.10 → python_multipart-0.0.12}/tests/test_data/http/single_field_single_file.yaml +0 -0
  57. {python_multipart-0.0.10 → python_multipart-0.0.12}/tests/test_data/http/single_field_with_leading_newlines.http +0 -0
  58. {python_multipart-0.0.10 → python_multipart-0.0.12}/tests/test_data/http/single_field_with_leading_newlines.yaml +0 -0
  59. {python_multipart-0.0.10 → python_multipart-0.0.12}/tests/test_data/http/single_file.http +0 -0
  60. {python_multipart-0.0.10 → python_multipart-0.0.12}/tests/test_data/http/single_file.yaml +0 -0
  61. {python_multipart-0.0.10 → python_multipart-0.0.12}/tests/test_data/http/utf8_filename.http +0 -0
  62. {python_multipart-0.0.10 → python_multipart-0.0.12}/tests/test_data/http/utf8_filename.yaml +0 -0
@@ -89,6 +89,7 @@ coverage.xml
89
89
  *.py,cover
90
90
  .hypothesis/
91
91
  .pytest_cache/
92
+ .ruff_cache/
92
93
  cover/
93
94
 
94
95
  # Translations
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.0.12 (2024-09-29)
4
+
5
+ * Improve error message when boundary character does not match [#124](https://github.com/Kludex/python-multipart/pull/124).
6
+ * Add mypy strict typing [#140](https://github.com/Kludex/python-multipart/pull/140).
7
+ * Enforce 100% coverage [#159](https://github.com/Kludex/python-multipart/pull/159).
8
+
9
+ ## 0.0.11 (2024-09-28)
10
+
11
+ * Improve performance, especially in data with many CR-LF [#137](https://github.com/Kludex/python-multipart/pull/137).
12
+ * Handle invalid CRLF in header name [#141](https://github.com/Kludex/python-multipart/pull/141).
13
+
3
14
  ## 0.0.10 (2024-09-21)
4
15
 
5
16
  * Support `on_header_begin` [#103](https://github.com/Kludex/python-multipart/pull/103).
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: python-multipart
3
- Version: 0.0.10
3
+ Version: 0.0.12
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/
@@ -2,7 +2,7 @@
2
2
  __author__ = "Andrew Dunham"
3
3
  __license__ = "Apache"
4
4
  __copyright__ = "Copyright (c) 2012-2013, Andrew Dunham"
5
- __version__ = "0.0.10"
5
+ __version__ = "0.0.12"
6
6
 
7
7
  from .multipart import (
8
8
  BaseParser,
@@ -1,9 +1,22 @@
1
1
  import base64
2
2
  import binascii
3
- from io import BufferedWriter
3
+ from typing import TYPE_CHECKING
4
4
 
5
5
  from .exceptions import DecodeError
6
6
 
7
+ if TYPE_CHECKING: # pragma: no cover
8
+ from typing import Protocol, TypeVar
9
+
10
+ _T_contra = TypeVar("_T_contra", contravariant=True)
11
+
12
+ class SupportsWrite(Protocol[_T_contra]):
13
+ def write(self, __b: _T_contra) -> object: ...
14
+
15
+ # No way to specify optional methods. See
16
+ # https://github.com/python/typing/issues/601
17
+ # close() [Optional]
18
+ # finalize() [Optional]
19
+
7
20
 
8
21
  class Base64Decoder:
9
22
  """This object provides an interface to decode a stream of Base64 data. It
@@ -34,7 +47,7 @@ class Base64Decoder:
34
47
  :param underlying: the underlying object to pass writes to
35
48
  """
36
49
 
37
- def __init__(self, underlying: BufferedWriter):
50
+ def __init__(self, underlying: "SupportsWrite[bytes]") -> None:
38
51
  self.cache = bytearray()
39
52
  self.underlying = underlying
40
53
 
@@ -67,9 +80,9 @@ class Base64Decoder:
67
80
  # Get the remaining bytes and save in our cache.
68
81
  remaining_len = len(data) % 4
69
82
  if remaining_len > 0:
70
- self.cache = data[-remaining_len:]
83
+ self.cache[:] = data[-remaining_len:]
71
84
  else:
72
- self.cache = b""
85
+ self.cache[:] = b""
73
86
 
74
87
  # Return the length of the data to indicate no error.
75
88
  return len(data)
@@ -112,7 +125,7 @@ class QuotedPrintableDecoder:
112
125
  :param underlying: the underlying object to pass writes to
113
126
  """
114
127
 
115
- def __init__(self, underlying: BufferedWriter) -> None:
128
+ def __init__(self, underlying: "SupportsWrite[bytes]") -> None:
116
129
  self.cache = b""
117
130
  self.underlying = underlying
118
131
 
@@ -160,7 +173,7 @@ class QuotedPrintableDecoder:
160
173
  call it.
161
174
  """
162
175
  # If we have a cache, write and then remove it.
163
- if len(self.cache) > 0:
176
+ if len(self.cache) > 0: # pragma: no cover
164
177
  self.underlying.write(binascii.a2b_qp(self.cache))
165
178
  self.cache = b""
166
179