http-sf 1.2.0__tar.gz → 1.2.1__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.
- {http_sf-1.2.0 → http_sf-1.2.1}/PKG-INFO +2 -1
- {http_sf-1.2.0 → http_sf-1.2.1}/http_sf/__init__.py +11 -12
- {http_sf-1.2.0 → http_sf-1.2.1}/http_sf/__main__.py +0 -1
- {http_sf-1.2.0 → http_sf-1.2.1}/http_sf/bare_item.py +6 -6
- {http_sf-1.2.0 → http_sf-1.2.1}/http_sf/byteseq.py +2 -1
- {http_sf-1.2.0 → http_sf-1.2.1}/http_sf/date.py +9 -3
- {http_sf-1.2.0 → http_sf-1.2.1}/http_sf/dictionary.py +2 -3
- {http_sf-1.2.0 → http_sf-1.2.1}/http_sf/innerlist.py +3 -4
- {http_sf-1.2.0 → http_sf-1.2.1}/http_sf/integer.py +3 -1
- {http_sf-1.2.0 → http_sf-1.2.1}/http_sf/item.py +2 -2
- {http_sf-1.2.0 → http_sf-1.2.1}/http_sf/list.py +0 -1
- {http_sf-1.2.0 → http_sf-1.2.1}/http_sf/parameters.py +1 -1
- {http_sf-1.2.0 → http_sf-1.2.1}/http_sf/token.py +2 -1
- {http_sf-1.2.0 → http_sf-1.2.1}/http_sf/types.py +2 -1
- {http_sf-1.2.0 → http_sf-1.2.1}/http_sf/util.py +8 -5
- {http_sf-1.2.0 → http_sf-1.2.1}/http_sf.egg-info/PKG-INFO +2 -1
- {http_sf-1.2.0 → http_sf-1.2.1}/http_sf.egg-info/SOURCES.txt +1 -0
- {http_sf-1.2.0 → http_sf-1.2.1}/http_sf.egg-info/requires.txt +1 -0
- {http_sf-1.2.0 → http_sf-1.2.1}/pyproject.toml +5 -1
- {http_sf-1.2.0 → http_sf-1.2.1}/test/test.py +5 -1
- http_sf-1.2.1/test/test_error_contract.py +69 -0
- {http_sf-1.2.0 → http_sf-1.2.1}/MANIFEST.in +0 -0
- {http_sf-1.2.0 → http_sf-1.2.1}/README.md +0 -0
- {http_sf-1.2.0 → http_sf-1.2.1}/http_sf/boolean.py +1 -1
- {http_sf-1.2.0 → http_sf-1.2.1}/http_sf/decimal.py +0 -0
- {http_sf-1.2.0 → http_sf-1.2.1}/http_sf/display_string.py +1 -1
- {http_sf-1.2.0 → http_sf-1.2.1}/http_sf/errors.py +0 -0
- {http_sf-1.2.0 → http_sf-1.2.1}/http_sf/py.typed +0 -0
- {http_sf-1.2.0 → http_sf-1.2.1}/http_sf/retrofit.py +0 -0
- {http_sf-1.2.0 → http_sf-1.2.1}/http_sf/state.py +0 -0
- {http_sf-1.2.0 → http_sf-1.2.1}/http_sf/string.py +1 -1
- {http_sf-1.2.0 → http_sf-1.2.1}/http_sf.egg-info/dependency_links.txt +0 -0
- {http_sf-1.2.0 → http_sf-1.2.1}/http_sf.egg-info/top_level.txt +0 -0
- {http_sf-1.2.0 → http_sf-1.2.1}/setup.cfg +0 -0
- {http_sf-1.2.0 → http_sf-1.2.1}/test/test_callback.py +0 -0
- {http_sf-1.2.0 → http_sf-1.2.1}/test/test_perf.py +0 -0
- {http_sf-1.2.0 → http_sf-1.2.1}/test/test_position.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: http-sf
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.1
|
|
4
4
|
Summary: Parse and serialise HTTP Structured Fields
|
|
5
5
|
Author-email: Mark Nottingham <mnot@mnot.net>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -13,6 +13,7 @@ Requires-Dist: typing_extensions
|
|
|
13
13
|
Provides-Extra: dev
|
|
14
14
|
Requires-Dist: mypy; extra == "dev"
|
|
15
15
|
Requires-Dist: black; extra == "dev"
|
|
16
|
+
Requires-Dist: isort; extra == "dev"
|
|
16
17
|
Requires-Dist: pylint; extra == "dev"
|
|
17
18
|
Requires-Dist: pytest; extra == "dev"
|
|
18
19
|
Requires-Dist: pytest-md; extra == "dev"
|
|
@@ -25,28 +25,27 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
|
25
25
|
THE SOFTWARE.
|
|
26
26
|
"""
|
|
27
27
|
|
|
28
|
-
__version__ = "1.2.
|
|
28
|
+
__version__ = "1.2.1"
|
|
29
29
|
|
|
30
|
-
from typing import
|
|
30
|
+
from typing import Callable, Dict, List, Optional, Tuple
|
|
31
31
|
|
|
32
32
|
from http_sf.dictionary import parse_dictionary, ser_dictionary
|
|
33
|
+
from http_sf.errors import StructuredFieldError
|
|
33
34
|
from http_sf.item import parse_item, ser_item
|
|
34
35
|
from http_sf.list import parse_list, ser_list
|
|
35
36
|
from http_sf.retrofit import retrofit
|
|
37
|
+
from http_sf.state import ParserState
|
|
36
38
|
from http_sf.types import (
|
|
37
|
-
StructuredType,
|
|
38
|
-
Token,
|
|
39
|
-
DisplayString,
|
|
40
|
-
OnDuplicateKeyType,
|
|
41
39
|
DictionaryType,
|
|
42
|
-
|
|
43
|
-
ItemType,
|
|
40
|
+
DisplayString,
|
|
44
41
|
InnerListType,
|
|
42
|
+
ItemType,
|
|
43
|
+
ListType,
|
|
44
|
+
OnDuplicateKeyType,
|
|
45
|
+
StructuredType,
|
|
46
|
+
Token,
|
|
45
47
|
)
|
|
46
|
-
from http_sf.util import discard_ows,
|
|
47
|
-
from http_sf.state import ParserState
|
|
48
|
-
from http_sf.errors import StructuredFieldError
|
|
49
|
-
|
|
48
|
+
from http_sf.util import discard_ows, from_json, to_json
|
|
50
49
|
|
|
51
50
|
__all__ = [
|
|
52
51
|
"parse",
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
from datetime import datetime
|
|
2
2
|
from decimal import Decimal
|
|
3
|
-
from typing import
|
|
3
|
+
from typing import Any, Callable, Dict, Type
|
|
4
4
|
|
|
5
5
|
from http_sf.boolean import parse_boolean, ser_boolean
|
|
6
|
-
from http_sf.byteseq import parse_byteseq, ser_byteseq
|
|
6
|
+
from http_sf.byteseq import BYTE_DELIMIT, parse_byteseq, ser_byteseq
|
|
7
7
|
from http_sf.date import parse_date, ser_date
|
|
8
8
|
from http_sf.decimal import ser_decimal
|
|
9
9
|
from http_sf.display_string import parse_display_string, ser_display_string
|
|
10
|
-
from http_sf.integer import parse_number, ser_integer
|
|
11
|
-
from http_sf.string import parse_string, ser_string
|
|
12
|
-
from http_sf.token import parse_token, ser_token
|
|
13
|
-
from http_sf.types import BareItemType,
|
|
10
|
+
from http_sf.integer import NUMBER_START_CHARS, parse_number, ser_integer
|
|
11
|
+
from http_sf.string import DQUOTE, parse_string, ser_string
|
|
12
|
+
from http_sf.token import TOKEN_START_CHARS, parse_token, ser_token
|
|
13
|
+
from http_sf.types import BareItemType, DisplayString, Token
|
|
14
14
|
|
|
15
15
|
from .errors import StructuredFieldError
|
|
16
16
|
from .state import ParserState
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import base64
|
|
2
2
|
import binascii
|
|
3
3
|
from string import ascii_letters, digits
|
|
4
|
-
|
|
4
|
+
|
|
5
5
|
from .errors import StructuredFieldError
|
|
6
|
+
from .state import ParserState
|
|
6
7
|
|
|
7
8
|
BYTE_DELIMIT = ord(b":")
|
|
8
9
|
B64CONTENT = set((ascii_letters + digits + "+/=").encode("ascii"))
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
from datetime import datetime
|
|
2
|
-
|
|
1
|
+
from datetime import datetime, timezone
|
|
2
|
+
|
|
3
3
|
from .errors import StructuredFieldError
|
|
4
4
|
from .integer import parse_integer
|
|
5
|
+
from .state import ParserState
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
def parse_date(state: ParserState) -> datetime:
|
|
@@ -11,7 +12,12 @@ def parse_date(state: ParserState) -> datetime:
|
|
|
11
12
|
raise StructuredFieldError(
|
|
12
13
|
"Non-integer Date", position=state.cursor, offending_char=None
|
|
13
14
|
)
|
|
14
|
-
|
|
15
|
+
try:
|
|
16
|
+
return datetime.fromtimestamp(value, tz=timezone.utc)
|
|
17
|
+
except (ValueError, OSError) as why:
|
|
18
|
+
raise StructuredFieldError(
|
|
19
|
+
"Date value out of range", position=state.cursor, offending_char=None
|
|
20
|
+
) from why
|
|
15
21
|
|
|
16
22
|
|
|
17
23
|
def ser_date(inval: datetime) -> str:
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
from typing import
|
|
1
|
+
from typing import Optional, cast
|
|
2
2
|
|
|
3
3
|
from .errors import StructuredFieldError
|
|
4
4
|
from .innerlist import parse_item_or_inner_list, ser_item_or_inner_list
|
|
5
5
|
from .parameters import parse_params, ser_params
|
|
6
6
|
from .state import ParserState
|
|
7
7
|
from .types import DictionaryType, ItemType, OnDuplicateKeyType
|
|
8
|
-
from .util import discard_http_ows,
|
|
9
|
-
|
|
8
|
+
from .util import discard_http_ows, parse_key, ser_key
|
|
10
9
|
|
|
11
10
|
EQUALS = ord(b"=")
|
|
12
11
|
COMMA = ord(b",")
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import List,
|
|
1
|
+
from typing import List, Optional, Union, cast
|
|
2
2
|
|
|
3
3
|
from .errors import StructuredFieldError
|
|
4
4
|
from .item import parse_item, ser_item
|
|
@@ -6,14 +6,13 @@ from .parameters import parse_params, ser_params
|
|
|
6
6
|
from .state import ParserState
|
|
7
7
|
from .types import (
|
|
8
8
|
InnerListType,
|
|
9
|
-
ItemType,
|
|
10
|
-
ParamsType,
|
|
11
9
|
ItemOrInnerListType,
|
|
10
|
+
ItemType,
|
|
12
11
|
OnDuplicateKeyType,
|
|
12
|
+
ParamsType,
|
|
13
13
|
)
|
|
14
14
|
from .util import discard_ows
|
|
15
15
|
|
|
16
|
-
|
|
17
16
|
PAREN_OPEN = ord(b"(")
|
|
18
17
|
PAREN_CLOSE = ord(b")")
|
|
19
18
|
INNERLIST_DELIMS = set(b" )")
|
|
@@ -61,7 +61,9 @@ def parse_number(state: ParserState) -> Union[int, Decimal]:
|
|
|
61
61
|
elif _type is INTEGER and char == PERIOD:
|
|
62
62
|
# spec says 12, but character is appended afterwards
|
|
63
63
|
if num_length > 13:
|
|
64
|
-
raise
|
|
64
|
+
raise StructuredFieldError(
|
|
65
|
+
"Decimal too long.", position=state.cursor, offending_char=None
|
|
66
|
+
)
|
|
65
67
|
state.cursor += 1
|
|
66
68
|
num_length = state.cursor - num_start - 1
|
|
67
69
|
if char in DIGITS:
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
from typing import
|
|
1
|
+
from typing import Optional, Tuple
|
|
2
2
|
|
|
3
3
|
from .bare_item import parse_bare_item, ser_bare_item
|
|
4
4
|
from .parameters import parse_params, ser_params
|
|
5
5
|
from .state import ParserState
|
|
6
|
-
from .types import BareItemType, ItemType,
|
|
6
|
+
from .types import BareItemType, ItemType, OnDuplicateKeyType, ParamsType
|
|
7
7
|
|
|
8
8
|
PAREN_OPEN = ord(b"(")
|
|
9
9
|
SEMICOLON = ord(b";")
|
|
@@ -3,7 +3,7 @@ from typing import Optional
|
|
|
3
3
|
from .bare_item import parse_bare_item, ser_bare_item
|
|
4
4
|
from .errors import StructuredFieldError
|
|
5
5
|
from .state import ParserState
|
|
6
|
-
from .types import BareItemType,
|
|
6
|
+
from .types import BareItemType, OnDuplicateKeyType, ParamsType
|
|
7
7
|
from .util import discard_ows, parse_key, ser_key
|
|
8
8
|
|
|
9
9
|
PAREN_OPEN = ord(b"(")
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from string import ascii_letters, digits
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
from http_sf.state import ParserState
|
|
4
|
+
from http_sf.types import Token
|
|
4
5
|
|
|
5
6
|
TOKEN_START_CHARS = set((ascii_letters + "*").encode("ascii"))
|
|
6
7
|
TOKEN_CHARS = set((ascii_letters + digits + ":/!#$%&'*+-.^_`|~").encode("ascii"))
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
from collections import UserString
|
|
2
2
|
from datetime import datetime
|
|
3
3
|
from decimal import Decimal
|
|
4
|
-
from typing import
|
|
4
|
+
from typing import Any, Callable, Dict, List, Tuple, Union
|
|
5
|
+
|
|
5
6
|
from typing_extensions import TypeAlias
|
|
6
7
|
|
|
7
8
|
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import base64
|
|
2
|
-
from datetime import datetime
|
|
3
|
-
from decimal import Decimal
|
|
4
2
|
import json
|
|
5
3
|
import re
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
|
+
from decimal import Decimal
|
|
6
6
|
from string import ascii_lowercase, ascii_uppercase, digits
|
|
7
7
|
from typing import Any
|
|
8
8
|
|
|
9
|
-
from .types import StructuredType, Token, DisplayString, JsonDict
|
|
10
|
-
from .state import ParserState
|
|
11
9
|
from .errors import StructuredFieldError
|
|
10
|
+
from .state import ParserState
|
|
11
|
+
from .types import DisplayString, JsonDict, StructuredType, Token
|
|
12
12
|
|
|
13
13
|
SPACE = ord(b" ")
|
|
14
14
|
HTTP_OWS = set(b" \t")
|
|
@@ -115,7 +115,10 @@ def json_load(inobj: JsonDict) -> Any:
|
|
|
115
115
|
if objtype == "binary":
|
|
116
116
|
return base64.b32decode(inobj["value"])
|
|
117
117
|
if objtype == "date":
|
|
118
|
-
|
|
118
|
+
try:
|
|
119
|
+
return datetime.fromtimestamp(inobj["value"], tz=timezone.utc)
|
|
120
|
+
except (ValueError, OSError):
|
|
121
|
+
return int(inobj["value"])
|
|
119
122
|
if objtype == "displaystring":
|
|
120
123
|
return DisplayString(inobj["value"])
|
|
121
124
|
raise ValueError(f"Unknown object type - {inobj}")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: http-sf
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.1
|
|
4
4
|
Summary: Parse and serialise HTTP Structured Fields
|
|
5
5
|
Author-email: Mark Nottingham <mnot@mnot.net>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -13,6 +13,7 @@ Requires-Dist: typing_extensions
|
|
|
13
13
|
Provides-Extra: dev
|
|
14
14
|
Requires-Dist: mypy; extra == "dev"
|
|
15
15
|
Requires-Dist: black; extra == "dev"
|
|
16
|
+
Requires-Dist: isort; extra == "dev"
|
|
16
17
|
Requires-Dist: pylint; extra == "dev"
|
|
17
18
|
Requires-Dist: pytest; extra == "dev"
|
|
18
19
|
Requires-Dist: pytest-md; extra == "dev"
|
|
@@ -19,7 +19,7 @@ dependencies = [
|
|
|
19
19
|
]
|
|
20
20
|
|
|
21
21
|
[project.optional-dependencies]
|
|
22
|
-
dev = ["mypy", "black", "pylint", "pytest", "pytest-md", "validate-pyproject", "build"]
|
|
22
|
+
dev = ["mypy", "black", "isort", "pylint", "pytest", "pytest-md", "validate-pyproject", "build"]
|
|
23
23
|
|
|
24
24
|
[project.urls]
|
|
25
25
|
homepage = "https://github.com/mnot/http-sf"
|
|
@@ -40,6 +40,7 @@ version = {attr = "http_sf.__version__"}
|
|
|
40
40
|
[tool.setuptools.packages.find]
|
|
41
41
|
where = ["."]
|
|
42
42
|
include = ["http_sf", "http_sf.*"]
|
|
43
|
+
exclude = ["build*", "dist*"]
|
|
43
44
|
namespaces = false
|
|
44
45
|
|
|
45
46
|
[tool.setuptools]
|
|
@@ -80,3 +81,6 @@ min-public-methods=1
|
|
|
80
81
|
|
|
81
82
|
[tool.pylint.similarities]
|
|
82
83
|
min-similarity-lines = 10
|
|
84
|
+
|
|
85
|
+
[tool.isort]
|
|
86
|
+
profile = "black"
|
|
@@ -54,7 +54,11 @@ def run_suite(suite_name: str, suite: List) -> None:
|
|
|
54
54
|
if ser_success:
|
|
55
55
|
suite_passed += 1
|
|
56
56
|
else:
|
|
57
|
-
|
|
57
|
+
if test.get("can_fail", False):
|
|
58
|
+
print(f"{WARN} * {test['name']}: SERIALISE FAIL (non-critical){ENDC}")
|
|
59
|
+
suite_passed += 1
|
|
60
|
+
else:
|
|
61
|
+
print(f"{FAIL} * {test['name']}: SERIALISE FAIL{ENDC}")
|
|
58
62
|
print(f" - expected: {ser_expected}")
|
|
59
63
|
print(f" - got: ['{serialised}']")
|
|
60
64
|
if ser_fail_reason:
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests that all parse errors raise StructuredFieldError, never bare ValueError
|
|
3
|
+
or OSError, regardless of input. Covers cases where the SF integer range
|
|
4
|
+
permits values that downstream Python APIs reject.
|
|
5
|
+
"""
|
|
6
|
+
import sys
|
|
7
|
+
from http_sf import parse, StructuredFieldError
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def test_date_out_of_range():
|
|
11
|
+
print("Testing out-of-range Date values raise StructuredFieldError...")
|
|
12
|
+
|
|
13
|
+
# Max SF integer — year ~31 million, far outside datetime's year 1-9999 range
|
|
14
|
+
try:
|
|
15
|
+
parse(b"@999999999999999", tltype="item")
|
|
16
|
+
print(" Test 1: FAIL (no exception raised)")
|
|
17
|
+
sys.exit(1)
|
|
18
|
+
except StructuredFieldError:
|
|
19
|
+
print(" Test 1: OK (large positive timestamp)")
|
|
20
|
+
except (ValueError, OSError) as e:
|
|
21
|
+
print(f" Test 1: FAIL (wrong exception type {type(e).__name__}: {e})")
|
|
22
|
+
sys.exit(1)
|
|
23
|
+
|
|
24
|
+
# Min SF integer — far before year 1
|
|
25
|
+
try:
|
|
26
|
+
parse(b"@-999999999999999", tltype="item")
|
|
27
|
+
print(" Test 2: FAIL (no exception raised)")
|
|
28
|
+
sys.exit(1)
|
|
29
|
+
except StructuredFieldError:
|
|
30
|
+
print(" Test 2: OK (large negative timestamp)")
|
|
31
|
+
except (ValueError, OSError) as e:
|
|
32
|
+
print(f" Test 2: FAIL (wrong exception type {type(e).__name__}: {e})")
|
|
33
|
+
sys.exit(1)
|
|
34
|
+
|
|
35
|
+
print("All Date out-of-range tests passed.")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_decimal_too_long():
|
|
39
|
+
print("Testing overlong decimal integer part raises StructuredFieldError...")
|
|
40
|
+
|
|
41
|
+
# 14 digits before decimal point — exceeds the 12-digit SF limit
|
|
42
|
+
try:
|
|
43
|
+
parse(b"12345678901234.1", tltype="item")
|
|
44
|
+
print(" Test 1: FAIL (no exception raised)")
|
|
45
|
+
sys.exit(1)
|
|
46
|
+
except StructuredFieldError:
|
|
47
|
+
print(" Test 1: OK (14-digit integer part)")
|
|
48
|
+
except ValueError as e:
|
|
49
|
+
print(f" Test 1: FAIL (wrong exception type ValueError: {e})")
|
|
50
|
+
sys.exit(1)
|
|
51
|
+
|
|
52
|
+
# Exactly at the boundary — 13 digits before decimal (also invalid per spec)
|
|
53
|
+
try:
|
|
54
|
+
parse(b"1234567890123.1", tltype="item")
|
|
55
|
+
print(" Test 2: FAIL (no exception raised)")
|
|
56
|
+
sys.exit(1)
|
|
57
|
+
except StructuredFieldError:
|
|
58
|
+
print(" Test 2: OK (13-digit integer part)")
|
|
59
|
+
except ValueError as e:
|
|
60
|
+
print(f" Test 2: FAIL (wrong exception type ValueError: {e})")
|
|
61
|
+
sys.exit(1)
|
|
62
|
+
|
|
63
|
+
print("All Decimal too-long tests passed.")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
if __name__ == "__main__":
|
|
67
|
+
test_date_out_of_range()
|
|
68
|
+
print()
|
|
69
|
+
test_decimal_too_long()
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|