http-fields 0.1.1__py3-none-any.whl

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 (132) hide show
  1. http_fields/__init__.py +193 -0
  2. http_fields/accept.py +37 -0
  3. http_fields/acceptcharset.py +48 -0
  4. http_fields/acceptencoding.py +42 -0
  5. http_fields/acceptlanguage.py +47 -0
  6. http_fields/acceptranges.py +35 -0
  7. http_fields/accesscontrol.py +197 -0
  8. http_fields/age.py +34 -0
  9. http_fields/allow.py +46 -0
  10. http_fields/altsvc.py +70 -0
  11. http_fields/authbases.py +79 -0
  12. http_fields/authenticationinfo.py +19 -0
  13. http_fields/authorization.py +23 -0
  14. http_fields/cachecontrol.py +119 -0
  15. http_fields/cachestatus.py +12 -0
  16. http_fields/connection.py +35 -0
  17. http_fields/contentdigest.py +19 -0
  18. http_fields/contentdisposition.py +66 -0
  19. http_fields/contentencoding.py +45 -0
  20. http_fields/contentlanguage.py +38 -0
  21. http_fields/contentlength.py +34 -0
  22. http_fields/contentlocation.py +24 -0
  23. http_fields/contentrange.py +74 -0
  24. http_fields/contenttype.py +104 -0
  25. http_fields/cookie.py +38 -0
  26. http_fields/date.py +17 -0
  27. http_fields/dateheader.py +32 -0
  28. http_fields/entitytaglist.py +39 -0
  29. http_fields/etag.py +46 -0
  30. http_fields/expect.py +52 -0
  31. http_fields/expires.py +17 -0
  32. http_fields/forwarded.py +36 -0
  33. http_fields/fromheader.py +42 -0
  34. http_fields/header.py +250 -0
  35. http_fields/host.py +44 -0
  36. http_fields/ifmatch.py +24 -0
  37. http_fields/ifmodifiedsince.py +17 -0
  38. http_fields/ifnonematch.py +24 -0
  39. http_fields/ifrange.py +49 -0
  40. http_fields/ifunmodifiedsince.py +17 -0
  41. http_fields/lastmodified.py +17 -0
  42. http_fields/link.py +35 -0
  43. http_fields/location.py +24 -0
  44. http_fields/maxforwards.py +32 -0
  45. http_fields/origin.py +42 -0
  46. http_fields/parsedobjs.py +62 -0
  47. http_fields/prefer.py +64 -0
  48. http_fields/priority.py +55 -0
  49. http_fields/productlistheader.py +33 -0
  50. http_fields/proxyauthenticate.py +17 -0
  51. http_fields/proxyauthenticationinfo.py +17 -0
  52. http_fields/proxyauthorization.py +17 -0
  53. http_fields/proxystatus.py +12 -0
  54. http_fields/py.typed +0 -0
  55. http_fields/range.py +37 -0
  56. http_fields/referer.py +24 -0
  57. http_fields/retryafter.py +45 -0
  58. http_fields/server.py +18 -0
  59. http_fields/setcookie.py +419 -0
  60. http_fields/stricttransportsecurity.py +96 -0
  61. http_fields/structuredfields.py +356 -0
  62. http_fields/structuredheaders.py +90 -0
  63. http_fields/te.py +36 -0
  64. http_fields/trailer.py +44 -0
  65. http_fields/upgrade.py +35 -0
  66. http_fields/uriheader.py +36 -0
  67. http_fields/useragent.py +18 -0
  68. http_fields/vary.py +40 -0
  69. http_fields/via.py +35 -0
  70. http_fields/visitors/__init__.py +1 -0
  71. http_fields/visitors/rfc3986.py +195 -0
  72. http_fields/visitors/rfc6265.py +62 -0
  73. http_fields/visitors/rfc6266.py +234 -0
  74. http_fields/visitors/rfc7230.py +80 -0
  75. http_fields/visitors/rfc7239.py +43 -0
  76. http_fields/visitors/rfc7240.py +77 -0
  77. http_fields/visitors/rfc7838.py +77 -0
  78. http_fields/visitors/rfc8288.py +56 -0
  79. http_fields/visitors/rfc9110/__init__.py +117 -0
  80. http_fields/visitors/rfc9110/_base.py +71 -0
  81. http_fields/visitors/rfc9110/accept.py +135 -0
  82. http_fields/visitors/rfc9110/acceptcharset.py +26 -0
  83. http_fields/visitors/rfc9110/acceptencoding.py +76 -0
  84. http_fields/visitors/rfc9110/acceptlanguage.py +55 -0
  85. http_fields/visitors/rfc9110/acceptranges.py +13 -0
  86. http_fields/visitors/rfc9110/allow.py +13 -0
  87. http_fields/visitors/rfc9110/authenticationinfo.py +19 -0
  88. http_fields/visitors/rfc9110/authorization.py +77 -0
  89. http_fields/visitors/rfc9110/authparam.py +40 -0
  90. http_fields/visitors/rfc9110/authscheme.py +17 -0
  91. http_fields/visitors/rfc9110/challenge.py +57 -0
  92. http_fields/visitors/rfc9110/comment.py +99 -0
  93. http_fields/visitors/rfc9110/connection.py +13 -0
  94. http_fields/visitors/rfc9110/contentlanguage.py +21 -0
  95. http_fields/visitors/rfc9110/contentlength.py +9 -0
  96. http_fields/visitors/rfc9110/contentrange.py +70 -0
  97. http_fields/visitors/rfc9110/contenttype.py +52 -0
  98. http_fields/visitors/rfc9110/date.py +12 -0
  99. http_fields/visitors/rfc9110/entitytag.py +66 -0
  100. http_fields/visitors/rfc9110/etag.py +12 -0
  101. http_fields/visitors/rfc9110/field.py +15 -0
  102. http_fields/visitors/rfc9110/host.py +32 -0
  103. http_fields/visitors/rfc9110/httpdate.py +167 -0
  104. http_fields/visitors/rfc9110/ifmatch.py +24 -0
  105. http_fields/visitors/rfc9110/ifmodifiedsince.py +12 -0
  106. http_fields/visitors/rfc9110/ifnonematch.py +26 -0
  107. http_fields/visitors/rfc9110/ifunmodifiedsince.py +12 -0
  108. http_fields/visitors/rfc9110/lastmodified.py +14 -0
  109. http_fields/visitors/rfc9110/location.py +9 -0
  110. http_fields/visitors/rfc9110/parameters.py +124 -0
  111. http_fields/visitors/rfc9110/product.py +39 -0
  112. http_fields/visitors/rfc9110/quotedstring.py +66 -0
  113. http_fields/visitors/rfc9110/range.py +100 -0
  114. http_fields/visitors/rfc9110/rangeunit.py +17 -0
  115. http_fields/visitors/rfc9110/retryafter.py +20 -0
  116. http_fields/visitors/rfc9110/server.py +16 -0
  117. http_fields/visitors/rfc9110/te.py +83 -0
  118. http_fields/visitors/rfc9110/token.py +18 -0
  119. http_fields/visitors/rfc9110/token68.py +18 -0
  120. http_fields/visitors/rfc9110/type.py +17 -0
  121. http_fields/visitors/rfc9110/upgrade.py +57 -0
  122. http_fields/visitors/rfc9110/useragent.py +16 -0
  123. http_fields/visitors/rfc9110/vary.py +10 -0
  124. http_fields/visitors/rfc9110/via.py +86 -0
  125. http_fields/visitors/rfc9110/weight.py +40 -0
  126. http_fields/visitors/rfc9110/wwwauthenticate.py +21 -0
  127. http_fields/visitors/rfc9111.py +92 -0
  128. http_fields/wwwauthenticate.py +24 -0
  129. http_fields-0.1.1.dist-info/METADATA +88 -0
  130. http_fields-0.1.1.dist-info/RECORD +132 -0
  131. http_fields-0.1.1.dist-info/WHEEL +4 -0
  132. http_fields-0.1.1.dist-info/licenses/LICENSE.txt +16 -0
@@ -0,0 +1,77 @@
1
+ from dataclasses import dataclass
2
+
3
+ from abnf import Node, NodeVisitor
4
+ from abnf.grammars import rfc7838
5
+
6
+ from http_fields.parsedobjs import ParsedStr
7
+ from http_fields.visitors.rfc9110.parameters import (
8
+ Param,
9
+ ParamsInput,
10
+ as_params,
11
+ param_from_node,
12
+ )
13
+
14
+ __all__ = ["AltAuthority", "AltSvcVisitor", "AltValue", "ProtocolId"]
15
+
16
+
17
+ class ProtocolId(ParsedStr):
18
+ """An RFC 7838 protocol-id (a percent-encoded ALPN protocol name). Self-validating."""
19
+
20
+ parser = rfc7838.Rule("protocol-id")
21
+
22
+
23
+ class AltAuthority(ParsedStr):
24
+ """An RFC 7838 alt-authority: a quoted ``host:port`` (the enclosing quotes are part of
25
+ the value). Self-validating."""
26
+
27
+ parser = rfc7838.Rule("alt-authority")
28
+
29
+
30
+ @dataclass(frozen=True)
31
+ class AltValue:
32
+ """One RFC 7838 alt-value: a protocol-id, an alt-authority, and parameters."""
33
+
34
+ protocol_id: ProtocolId
35
+ authority: AltAuthority
36
+ params: tuple[Param, ...] = ()
37
+
38
+ def __init__(
39
+ self,
40
+ protocol_id: str,
41
+ authority: str,
42
+ params: ParamsInput = (),
43
+ ) -> None:
44
+ # Each part self-validates; an existing leaf/Param passes through unchanged.
45
+ object.__setattr__(self, "protocol_id", ProtocolId(protocol_id))
46
+ object.__setattr__(self, "authority", AltAuthority(authority))
47
+ object.__setattr__(self, "params", as_params(params))
48
+
49
+ def __str__(self) -> str:
50
+ out = f"{self.protocol_id}={self.authority}"
51
+ for p in self.params:
52
+ out += f"; {p}"
53
+ return out
54
+
55
+
56
+ class AltSvcVisitor(NodeVisitor):
57
+ @staticmethod
58
+ def visit_alternative(node: Node) -> tuple[str, str]:
59
+ # protocol-id "=" alt-authority
60
+ return (node.children[0].value, node.children[-1].value)
61
+
62
+ def visit_alt_value(self, node: Node) -> AltValue:
63
+ protocol_id = authority = ""
64
+ params: list[Param] = []
65
+ for child in node.children:
66
+ if child.name == "alternative":
67
+ protocol_id, authority = self.visit_alternative(child)
68
+ elif child.name == "parameter":
69
+ params.append(param_from_node(child))
70
+ return AltValue(
71
+ ProtocolId(protocol_id, parse=False),
72
+ AltAuthority(authority, parse=False),
73
+ params,
74
+ )
75
+
76
+ def visit_alt_svc(self, node: Node) -> list[AltValue]:
77
+ return [self.visit_alt_value(c) for c in node.children if c.name == "alt-value"]
@@ -0,0 +1,56 @@
1
+ from dataclasses import dataclass
2
+
3
+ from abnf import Node, NodeVisitor
4
+ from abnf.grammars import rfc8288
5
+
6
+ from http_fields.parsedobjs import ParsedStr
7
+ from http_fields.visitors.rfc9110.parameters import (
8
+ Param,
9
+ ParamsInput,
10
+ as_params,
11
+ param_from_node,
12
+ )
13
+
14
+ __all__ = ["LinkValue", "LinkVisitor", "URIReference"]
15
+
16
+
17
+ class URIReference(ParsedStr):
18
+ """An RFC 3986 URI-Reference, as used by RFC 8288's link target. Self-validating."""
19
+
20
+ parser = rfc8288.Rule("URI-Reference")
21
+
22
+
23
+ @dataclass(frozen=True)
24
+ class LinkValue:
25
+ """One RFC 8288 link-value: a target URI reference plus link-params."""
26
+
27
+ target: URIReference
28
+ params: tuple[Param, ...] = ()
29
+
30
+ def __init__(self, target: str, params: ParamsInput = ()) -> None:
31
+ # target and each Param self-validate; an existing leaf/Param passes through.
32
+ object.__setattr__(self, "target", URIReference(target))
33
+ object.__setattr__(self, "params", as_params(params))
34
+
35
+ def __str__(self) -> str:
36
+ out = f"<{self.target}>"
37
+ for p in self.params:
38
+ out += f"; {p}"
39
+ return out
40
+
41
+
42
+ class LinkVisitor(NodeVisitor):
43
+ def visit_link_value(self, node: Node) -> LinkValue:
44
+ target = ""
45
+ params: list[Param] = []
46
+ for child in node.children:
47
+ if child.name == "URI-Reference":
48
+ target = child.value
49
+ elif child.name == "link-param":
50
+ params.append(param_from_node(child))
51
+ return LinkValue(URIReference(target, parse=False), params)
52
+
53
+ def visit_link(self, node: Node) -> list[LinkValue]:
54
+ return [
55
+ self.visit_link_value(c) for c in node.children if c.name == "link-value"
56
+ ]
@@ -0,0 +1,117 @@
1
+ """Visitors and dataclasses from RFC9110."""
2
+
3
+ # There are many classes and functions, so manageabilty has led to
4
+ # many modules,
5
+
6
+ from http_fields.visitors.rfc9110._base import imf_fixdate
7
+ from http_fields.visitors.rfc9110.accept import AcceptType, AcceptVisitor
8
+ from http_fields.visitors.rfc9110.acceptcharset import AcceptCharsetVisitor
9
+ from http_fields.visitors.rfc9110.acceptencoding import (
10
+ AcceptEncodingVisitor,
11
+ WeightedCoding,
12
+ )
13
+ from http_fields.visitors.rfc9110.acceptlanguage import (
14
+ AcceptLanguageVisitor,
15
+ WeightedLanguageRange,
16
+ )
17
+ from http_fields.visitors.rfc9110.acceptranges import AcceptRangesVisitor
18
+ from http_fields.visitors.rfc9110.allow import AllowVisitor
19
+ from http_fields.visitors.rfc9110.authenticationinfo import (
20
+ AuthenticationInfoVisitor,
21
+ ProxyAuthenticationInfoVisitor,
22
+ )
23
+ from http_fields.visitors.rfc9110.authorization import (
24
+ AuthorizationVisitor,
25
+ AuthParamCredentials,
26
+ ProxyAuthorizationVisitor,
27
+ TokenCredentials,
28
+ )
29
+ from http_fields.visitors.rfc9110.authparam import AuthParam
30
+ from http_fields.visitors.rfc9110.challenge import AuthParamChallenge, TokenChallenge
31
+ from http_fields.visitors.rfc9110.comment import Comment
32
+ from http_fields.visitors.rfc9110.connection import ConnectionVisitor
33
+ from http_fields.visitors.rfc9110.contentlength import ContentLengthVisitor
34
+ from http_fields.visitors.rfc9110.contentrange import ContentRangeVisitor
35
+ from http_fields.visitors.rfc9110.contenttype import ContentTypeVisitor, MediaType
36
+ from http_fields.visitors.rfc9110.date import DateVisitor
37
+ from http_fields.visitors.rfc9110.entitytag import EntityTag
38
+ from http_fields.visitors.rfc9110.etag import ETagVisitor
39
+ from http_fields.visitors.rfc9110.field import FieldName, FieldValue
40
+ from http_fields.visitors.rfc9110.host import Hostname, HostVisitor
41
+ from http_fields.visitors.rfc9110.httpdate import HttpDateVisitor
42
+ from http_fields.visitors.rfc9110.ifmatch import IfMatchVisitor
43
+ from http_fields.visitors.rfc9110.ifmodifiedsince import IfModifiedSinceVisitor
44
+ from http_fields.visitors.rfc9110.ifnonematch import IfNoneMatchVisitor
45
+ from http_fields.visitors.rfc9110.ifunmodifiedsince import IfUnmodifiedSinceVisitor
46
+ from http_fields.visitors.rfc9110.lastmodified import LastModifiedVisitor
47
+ from http_fields.visitors.rfc9110.location import LocationVisitor
48
+ from http_fields.visitors.rfc9110.parameters import Parameter
49
+ from http_fields.visitors.rfc9110.product import Product
50
+ from http_fields.visitors.rfc9110.quotedstring import QuotedString, QuotedStringVisitor
51
+ from http_fields.visitors.rfc9110.rangeunit import RangeUnit
52
+ from http_fields.visitors.rfc9110.retryafter import RetryAfterVisitor
53
+ from http_fields.visitors.rfc9110.server import ServerVisitor
54
+ from http_fields.visitors.rfc9110.token import Token, TokenVisitor
55
+ from http_fields.visitors.rfc9110.useragent import UserAgentVisitor
56
+ from http_fields.visitors.rfc9110.vary import VaryVisitor
57
+ from http_fields.visitors.rfc9110.weight import Weight
58
+ from http_fields.visitors.rfc9110.wwwauthenticate import (
59
+ ProxyAuthenticateVisitor,
60
+ WWWAuthenticateVisitor,
61
+ )
62
+
63
+ __all__ = [
64
+ "imf_fixdate",
65
+ "AcceptCharsetVisitor",
66
+ "AcceptEncodingVisitor",
67
+ "AcceptLanguageVisitor",
68
+ "AcceptRangesVisitor",
69
+ "AcceptType",
70
+ "AcceptVisitor",
71
+ "AllowVisitor",
72
+ "AuthenticationInfoVisitor",
73
+ "AuthorizationVisitor",
74
+ "AuthParam",
75
+ "AuthParamCredentials",
76
+ "AuthParamChallenge",
77
+ "Comment",
78
+ "ConnectionVisitor",
79
+ "ContentLengthVisitor",
80
+ "ContentRangeVisitor",
81
+ "ContentTypeVisitor",
82
+ "DateVisitor",
83
+ "EntityTag",
84
+ "ETagVisitor",
85
+ "FieldName",
86
+ "FieldValue",
87
+ "HostVisitor",
88
+ "Hostname",
89
+ "HttpDateVisitor",
90
+ "IfMatchVisitor",
91
+ "IfModifiedSinceVisitor",
92
+ "IfNoneMatchVisitor",
93
+ "IfUnmodifiedSinceVisitor",
94
+ "LastModifiedVisitor",
95
+ "LocationVisitor",
96
+ "MediaType",
97
+ "Parameter",
98
+ "Product",
99
+ "ProxyAuthenticateVisitor",
100
+ "ProxyAuthenticationInfoVisitor",
101
+ "ProxyAuthorizationVisitor",
102
+ "QuotedString",
103
+ "QuotedStringVisitor",
104
+ "RangeUnit",
105
+ "RetryAfterVisitor",
106
+ "ServerVisitor",
107
+ "Token",
108
+ "TokenChallenge",
109
+ "TokenCredentials",
110
+ "TokenVisitor",
111
+ "UserAgentVisitor",
112
+ "VaryVisitor",
113
+ "Weight",
114
+ "WeightedCoding",
115
+ "WeightedLanguageRange",
116
+ "WWWAuthenticateVisitor",
117
+ ]
@@ -0,0 +1,71 @@
1
+ """A few functions used here and there in this package."""
2
+
3
+ from collections.abc import Iterator
4
+ from datetime import datetime, timezone
5
+ from typing import TypeVar, cast
6
+
7
+ S = TypeVar("S")
8
+ T = TypeVar("T")
9
+
10
+
11
+ def transform(
12
+ items: Iterator[S | T], type_s: type[S], type_t: type[T]
13
+ ) -> Iterator[tuple[S, T | None]]:
14
+ """Pair each ``type_s`` item with the optional ``type_t`` item that follows it.
15
+
16
+ Iterative (not recursive) so an arbitrarily long stream — e.g. a very long
17
+ ``Accept`` header — cannot exhaust the call stack.
18
+ """
19
+ pending: S | None = None
20
+ for item in items:
21
+ if isinstance(item, type_t):
22
+ if pending is None: # pragma: no cover - a type_t with no preceding type_s
23
+ raise AssertionError()
24
+ yield (pending, cast(T, item))
25
+ pending = None
26
+ elif isinstance(item, type_s):
27
+ if pending is not None:
28
+ yield (pending, None)
29
+ pending = item
30
+ else: # pragma: no cover
31
+ raise AssertionError()
32
+ if pending is not None:
33
+ yield (pending, None)
34
+
35
+
36
+ def imf_fixdate(pydate: datetime):
37
+ """
38
+ Converts a datetime object to a fixdate string.
39
+ """
40
+
41
+ # HTTP dates are expressed in GMT/UTC. Convert tz-aware datetimes to UTC so the
42
+ # printed wall-clock fields match the "GMT" label; naive datetimes are assumed
43
+ # to already be UTC.
44
+ if pydate.tzinfo is not None:
45
+ pydate = pydate.astimezone(timezone.utc)
46
+
47
+ day_name = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
48
+ month_name = [
49
+ "Jan",
50
+ "Feb",
51
+ "Mar",
52
+ "Apr",
53
+ "May",
54
+ "Jun",
55
+ "Jul",
56
+ "Aug",
57
+ "Sep",
58
+ "Oct",
59
+ "Nov",
60
+ "Dec",
61
+ ]
62
+ fixdate_fmt = "%s, %02d %s %04d %02d:%02d:%02d GMT"
63
+ return fixdate_fmt % (
64
+ day_name[pydate.weekday()],
65
+ pydate.day,
66
+ month_name[pydate.month - 1],
67
+ pydate.year,
68
+ pydate.hour,
69
+ pydate.minute,
70
+ pydate.second,
71
+ )
@@ -0,0 +1,135 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+
5
+ from abnf import Node, NodeVisitor
6
+
7
+ import http_fields.visitors.rfc9110._base as base
8
+ import http_fields.visitors.rfc9110.parameters as parameters
9
+ import http_fields.visitors.rfc9110.token as token
10
+ import http_fields.visitors.rfc9110.type as type_grammar
11
+ import http_fields.visitors.rfc9110.weight as weight
12
+
13
+ # NB: do not alias the "type" grammar module as `type` -- it would shadow the
14
+ # builtin, which a frozen dataclass's generated __setattr__ (`type(self)`) needs.
15
+ Parameter = parameters.Parameter
16
+ ParametersVisitor = parameters.ParametersVisitor
17
+ SubtypeVisitor = type_grammar.SubtypeVisitor
18
+ Token = token.Token
19
+ TypeVisitor = type_grammar.TypeVisitor
20
+ Weight = weight.Weight
21
+ WeightVisitor = weight.WeightVisitor
22
+ as_qvalue = weight.as_qvalue
23
+
24
+
25
+ @dataclass(frozen=True)
26
+ class MediaRange:
27
+ type: Token
28
+ subtype: Token
29
+ params: tuple[Parameter, ...]
30
+
31
+ def __init__(self, type: str, subtype: str, params: list[Parameter] | None = None):
32
+ object.__setattr__(self, "type", Token(type))
33
+ object.__setattr__(self, "subtype", Token(subtype))
34
+ object.__setattr__(self, "params", tuple(params) if params else ())
35
+
36
+ def __str__(self) -> str:
37
+ return f"{self.type}/{self.subtype}" + (
38
+ (";" + ";".join(str(p) for p in self.params)) if self.params else ""
39
+ )
40
+
41
+
42
+ class MediaRangeVisitor(NodeVisitor):
43
+ visit_type = TypeVisitor()
44
+ visit_subtype = SubtypeVisitor()
45
+ visit_parameters = ParametersVisitor()
46
+
47
+ def visit_media_range(self, node: Node) -> MediaRange:
48
+ items = filter(None, map(self.visit, node.children))
49
+ item = next(items)
50
+ if item == "*/*":
51
+ type, subtype = tuple([Token(x) for x in item.split("/")])
52
+ # and anything else must be a param.
53
+ else:
54
+ type = item
55
+ item = next(items)
56
+ subtype = Token("*") if item == "/*" else item
57
+ params: list[Parameter] = next(items, [])
58
+ return MediaRange(type, subtype, params)
59
+
60
+ @staticmethod
61
+ def visit_literal(node: Node):
62
+ return node.value if node.value in ["*/*", "/*"] else None
63
+
64
+
65
+ @dataclass(frozen=True)
66
+ class AcceptType:
67
+ type: Token
68
+ subtype: Token
69
+ params: tuple[Parameter, ...] = ()
70
+ weight: Weight | None = None
71
+
72
+ def __init__(
73
+ self,
74
+ type: str,
75
+ subtype: str,
76
+ *,
77
+ params: list[tuple[str, str] | Parameter] | None = None,
78
+ weight: float | Weight | None = None,
79
+ ) -> None:
80
+ _params = (
81
+ [Parameter(*p) if isinstance(p, tuple) else p for p in params]
82
+ if params
83
+ else []
84
+ )
85
+ resolved_weight: Weight | None
86
+ if isinstance(weight, (float, Weight)):
87
+ resolved_weight = (
88
+ Weight(qvalue=weight) if isinstance(weight, float) else weight
89
+ )
90
+ resolved_params = tuple(_params)
91
+ elif _params:
92
+ # sometimes, weight was captured by parser as a parameter because abnf
93
+ # backtracking is somewhat arbitrary. A trailing "q" parameter is a weight
94
+ # only if its value is an unquoted, in-range qvalue (0..1); a quoted or
95
+ # out-of-range "q" is an ordinary parameter.
96
+ w = _params[-1]
97
+ qvalue = as_qvalue(w.value) if w.name == "q" else None
98
+ if qvalue is not None:
99
+ resolved_weight = Weight(qvalue=qvalue)
100
+ resolved_params = tuple(_params[:-1])
101
+ else:
102
+ resolved_weight = None
103
+ resolved_params = tuple(_params)
104
+ else:
105
+ resolved_weight = None
106
+ resolved_params = tuple(_params)
107
+ object.__setattr__(self, "type", Token(type))
108
+ object.__setattr__(self, "subtype", Token(subtype))
109
+ object.__setattr__(self, "params", resolved_params)
110
+ object.__setattr__(self, "weight", resolved_weight)
111
+
112
+ def __str__(self):
113
+ return (
114
+ f"{self.type}/{self.subtype}"
115
+ + ((";" + ";".join(str(p) for p in self.params)) if self.params else "")
116
+ + (";" + str(self.weight) if self.weight else "")
117
+ )
118
+
119
+
120
+ class AcceptVisitor(NodeVisitor):
121
+ visit_media_range = MediaRangeVisitor()
122
+ visit_weight = WeightVisitor()
123
+
124
+ def visit_accept(self, node: Node):
125
+ items = filter(None, map(self.visit, node.children))
126
+ accept_types = [
127
+ AcceptType(
128
+ media_range.type,
129
+ media_range.subtype,
130
+ params=media_range.params,
131
+ weight=weight,
132
+ )
133
+ for media_range, weight in base.transform(items, MediaRange, Weight)
134
+ ]
135
+ return accept_types
@@ -0,0 +1,26 @@
1
+ from abnf import Node, NodeVisitor
2
+
3
+ import http_fields.visitors.rfc9110._base as base
4
+ import http_fields.visitors.rfc9110.token as token
5
+ import http_fields.visitors.rfc9110.weight as weight
6
+
7
+ Token = token.Token
8
+ TokenVisitor = token.TokenVisitor
9
+ Weight = weight.Weight
10
+ WeightVisitor = weight.WeightVisitor
11
+
12
+
13
+ class AcceptCharsetVisitor(NodeVisitor):
14
+ visit_token = TokenVisitor()
15
+ visit_weight = WeightVisitor()
16
+
17
+ def visit_accept_charset(self, node: Node):
18
+ items = filter(None, map(self.visit, node.children))
19
+ charsets = list(base.transform(items, Token, Weight))
20
+ return charsets
21
+
22
+ # the accept-charset value "*" is matched by the RFC 9110 rule 'token', so
23
+ # the parser will usually recognize it as a token in the alternation 'token / "*".
24
+ # we have this method just in case, but skip testing it.
25
+ def visit_literal(self, node: Node): # pragma: no cover
26
+ return Token("*") if node.value == "*" else None
@@ -0,0 +1,76 @@
1
+ from collections.abc import Callable
2
+ from dataclasses import dataclass
3
+ from typing import Any
4
+
5
+ from abnf import Node
6
+ from abnf import NodeVisitor as _NodeVisitor
7
+ from abnf.grammars import rfc9110
8
+
9
+ import http_fields.visitors.rfc9110._base as base
10
+ import http_fields.visitors.rfc9110.token as token
11
+ import http_fields.visitors.rfc9110.weight as weight
12
+ from http_fields.parsedobjs import ParsedStr
13
+
14
+ Token = token.Token
15
+ TokenVisitor = token.TokenVisitor
16
+ Weight = weight.Weight
17
+ WeightVisitor = weight.WeightVisitor
18
+
19
+
20
+ class NodeVisitor(_NodeVisitor):
21
+ def visit_children(self, node: Node, *, f: Callable[[Any], bool] | None = None):
22
+ for child in node.children:
23
+ result = self.visit(child)
24
+ if (f and f(result)) or result is not None:
25
+ yield result
26
+
27
+
28
+ class Coding(ParsedStr):
29
+ """Represents an RFC 9110 codings thing."""
30
+
31
+ parser = rfc9110.Rule("codings")
32
+
33
+
34
+ @dataclass(frozen=True)
35
+ class WeightedCoding:
36
+ coding: Coding
37
+ weight: Weight | None
38
+
39
+ def __init__(self, coding: str, weight: float | (Weight | None) = None):
40
+ object.__setattr__(self, "coding", Coding(coding))
41
+ if isinstance(weight, Weight):
42
+ object.__setattr__(self, "weight", weight)
43
+ elif isinstance(weight, (int, float)) and not isinstance(weight, bool):
44
+ # Accept any real number, including 0 ("not acceptable"); a bare
45
+ # None means no weight was specified.
46
+ object.__setattr__(self, "weight", Weight(float(weight)))
47
+ else:
48
+ object.__setattr__(self, "weight", None)
49
+
50
+ def __str__(self):
51
+ return str(self.coding) + (f";{str(self.weight)}" if self.weight else "")
52
+
53
+
54
+ class AcceptEncodingVisitor(NodeVisitor):
55
+ visit_weight = WeightVisitor()
56
+ visit_token = TokenVisitor()
57
+
58
+ @staticmethod
59
+ def visit_literal(node: Node):
60
+ return node.value if node.value in {"identity", "*"} else None
61
+
62
+ def visit_content_coding(self, node: Node):
63
+ item: Token = next(filter(None, map(self.visit, node.children)))
64
+ return Coding(item)
65
+
66
+ def visit_codings(self, node: Node):
67
+ item: Coding = next(filter(None, map(self.visit, node.children)))
68
+ return item
69
+
70
+ def visit_accept_encoding(self, node: Node):
71
+ # items = filter(None, map(self.visit, node.children))
72
+ items = self.visit_children(node)
73
+ return [
74
+ WeightedCoding(enc, weight)
75
+ for enc, weight in base.transform(items, Coding, Weight)
76
+ ]
@@ -0,0 +1,55 @@
1
+ from dataclasses import dataclass
2
+
3
+ from abnf import Node, NodeVisitor
4
+ from abnf.grammars import rfc9110
5
+
6
+ import http_fields.visitors.rfc9110._base as base
7
+ import http_fields.visitors.rfc9110.token as token
8
+ import http_fields.visitors.rfc9110.weight as weight
9
+ from http_fields.parsedobjs import ParsedStr
10
+
11
+ __all__ = ["WeightedLanguageRange", "AcceptLanguageVisitor"]
12
+
13
+ Token = token.Token
14
+ Weight = weight.Weight
15
+ WeightVisitor = weight.WeightVisitor
16
+
17
+
18
+ class LanguageRange(ParsedStr):
19
+ """Represents an RFC 9110 language-range."""
20
+
21
+ parser = rfc9110.Rule("language-range")
22
+
23
+
24
+ @dataclass(frozen=True)
25
+ class WeightedLanguageRange:
26
+ language_range: LanguageRange
27
+ weight: Weight | None
28
+
29
+ def __init__(self, language_range: str, weight: float | Weight | None = None):
30
+ object.__setattr__(self, "language_range", LanguageRange(language_range))
31
+ if isinstance(weight, Weight):
32
+ object.__setattr__(self, "weight", weight)
33
+ elif isinstance(weight, (int, float)) and not isinstance(weight, bool):
34
+ # a numeric 0 is a valid weight ("not acceptable"); only None means unset.
35
+ object.__setattr__(self, "weight", Weight(float(weight)))
36
+ else:
37
+ object.__setattr__(self, "weight", None)
38
+
39
+ def __str__(self) -> str:
40
+ return str(self.language_range) + (f";{self.weight}" if self.weight else "")
41
+
42
+
43
+ class AcceptLanguageVisitor(NodeVisitor):
44
+ visit_weight = WeightVisitor()
45
+
46
+ @staticmethod
47
+ def visit_language_range(node: Node):
48
+ return LanguageRange(node.value)
49
+
50
+ def visit_accept_language(self, node: Node):
51
+ items = filter(None, map(self.visit, node.children))
52
+ return [
53
+ WeightedLanguageRange(enc, weight)
54
+ for enc, weight in base.transform(items, LanguageRange, weight.Weight)
55
+ ]
@@ -0,0 +1,13 @@
1
+ from abnf import Node, NodeVisitor
2
+
3
+ import http_fields.visitors.rfc9110.rangeunit as rangeunit
4
+
5
+
6
+ class AcceptRangesVisitor(NodeVisitor):
7
+ visit_range_unit = rangeunit.RangeUnitVisitor()
8
+
9
+ def visit_acceptable_ranges(self, node: Node) -> list[rangeunit.RangeUnit]:
10
+ return list(filter(None, map(self.visit, node.children)))
11
+
12
+ def visit_accept_ranges(self, node: Node) -> list[rangeunit.RangeUnit]:
13
+ return next(filter(None, map(self.visit, node.children)))
@@ -0,0 +1,13 @@
1
+ from abnf import Node, NodeVisitor
2
+
3
+ import http_fields.visitors.rfc9110.token as token
4
+
5
+
6
+ class AllowVisitor(NodeVisitor):
7
+ visit_token = token.TokenVisitor()
8
+
9
+ def visit_allow(self, node: Node) -> list[token.Token]:
10
+ return list(filter(None, map(self.visit, node.children)))
11
+
12
+ def visit_method(self, node: Node) -> token.Token:
13
+ return next(filter(None, map(self.visit, node.children)))
@@ -0,0 +1,19 @@
1
+ from abnf import Node, NodeVisitor
2
+
3
+ from http_fields.visitors.rfc9110.authparam import AuthParam, AuthParamVisitor
4
+
5
+
6
+ class AuthenticationInfoVisitor(NodeVisitor):
7
+ visit_auth_param = AuthParamVisitor()
8
+
9
+ def visit_authentication_info(self, node: Node) -> list[AuthParam]:
10
+ items = filter(None, map(self.visit, node.children))
11
+ return list(items)
12
+
13
+
14
+ class ProxyAuthenticationInfoVisitor(NodeVisitor):
15
+ visit_auth_param = AuthParamVisitor()
16
+
17
+ def visit_proxy_authentication_info(self, node: Node) -> list[AuthParam]:
18
+ items = filter(None, map(self.visit, node.children))
19
+ return list(items)