Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/scrapy/http/headers.py: 35%
74 statements
« prev ^ index » next coverage.py v7.4.1, created at 2024-02-07 06:38 +0000
« prev ^ index » next coverage.py v7.4.1, created at 2024-02-07 06:38 +0000
1from __future__ import annotations
3from collections.abc import Mapping
4from typing import (
5 TYPE_CHECKING,
6 Any,
7 AnyStr,
8 Dict,
9 Iterable,
10 List,
11 Optional,
12 Tuple,
13 Union,
14 cast,
15)
17from w3lib.http import headers_dict_to_raw
19from scrapy.utils.datatypes import CaseInsensitiveDict, CaselessDict
20from scrapy.utils.python import to_unicode
22if TYPE_CHECKING:
23 # typing.Self requires Python 3.11
24 from typing_extensions import Self
27_RawValueT = Union[bytes, str, int]
30# isn't fully compatible typing-wise with either dict or CaselessDict,
31# but it needs refactoring anyway, see also https://github.com/scrapy/scrapy/pull/5146
32class Headers(CaselessDict):
33 """Case insensitive http headers dictionary"""
35 def __init__(
36 self,
37 seq: Union[Mapping[AnyStr, Any], Iterable[Tuple[AnyStr, Any]], None] = None,
38 encoding: str = "utf-8",
39 ):
40 self.encoding: str = encoding
41 super().__init__(seq)
43 def update( # type: ignore[override]
44 self, seq: Union[Mapping[AnyStr, Any], Iterable[Tuple[AnyStr, Any]]]
45 ) -> None:
46 seq = seq.items() if isinstance(seq, Mapping) else seq
47 iseq: Dict[bytes, List[bytes]] = {}
48 for k, v in seq:
49 iseq.setdefault(self.normkey(k), []).extend(self.normvalue(v))
50 super().update(iseq)
52 def normkey(self, key: AnyStr) -> bytes: # type: ignore[override]
53 """Normalize key to bytes"""
54 return self._tobytes(key.title())
56 def normvalue(self, value: Union[_RawValueT, Iterable[_RawValueT]]) -> List[bytes]:
57 """Normalize values to bytes"""
58 _value: Iterable[_RawValueT]
59 if value is None:
60 _value = []
61 elif isinstance(value, (str, bytes)):
62 _value = [value]
63 elif hasattr(value, "__iter__"):
64 _value = value
65 else:
66 _value = [value]
68 return [self._tobytes(x) for x in _value]
70 def _tobytes(self, x: _RawValueT) -> bytes:
71 if isinstance(x, bytes):
72 return x
73 if isinstance(x, str):
74 return x.encode(self.encoding)
75 if isinstance(x, int):
76 return str(x).encode(self.encoding)
77 raise TypeError(f"Unsupported value type: {type(x)}")
79 def __getitem__(self, key: AnyStr) -> Optional[bytes]:
80 try:
81 return cast(List[bytes], super().__getitem__(key))[-1]
82 except IndexError:
83 return None
85 def get(self, key: AnyStr, def_val: Any = None) -> Optional[bytes]:
86 try:
87 return cast(List[bytes], super().get(key, def_val))[-1]
88 except IndexError:
89 return None
91 def getlist(self, key: AnyStr, def_val: Any = None) -> List[bytes]:
92 try:
93 return cast(List[bytes], super().__getitem__(key))
94 except KeyError:
95 if def_val is not None:
96 return self.normvalue(def_val)
97 return []
99 def setlist(self, key: AnyStr, list_: Iterable[_RawValueT]) -> None:
100 self[key] = list_
102 def setlistdefault(
103 self, key: AnyStr, default_list: Iterable[_RawValueT] = ()
104 ) -> Any:
105 return self.setdefault(key, default_list)
107 def appendlist(self, key: AnyStr, value: Iterable[_RawValueT]) -> None:
108 lst = self.getlist(key)
109 lst.extend(self.normvalue(value))
110 self[key] = lst
112 def items(self) -> Iterable[Tuple[bytes, List[bytes]]]: # type: ignore[override]
113 return ((k, self.getlist(k)) for k in self.keys())
115 def values(self) -> List[Optional[bytes]]: # type: ignore[override]
116 return [self[k] for k in self.keys()]
118 def to_string(self) -> bytes:
119 # cast() can be removed if the headers_dict_to_raw() hint is improved
120 return cast(bytes, headers_dict_to_raw(self))
122 def to_unicode_dict(self) -> CaseInsensitiveDict:
123 """Return headers as a CaseInsensitiveDict with str keys
124 and str values. Multiple values are joined with ','.
125 """
126 return CaseInsensitiveDict(
127 (
128 to_unicode(key, encoding=self.encoding),
129 to_unicode(b",".join(value), encoding=self.encoding),
130 )
131 for key, value in self.items()
132 )
134 def __copy__(self) -> Self:
135 return self.__class__(self)
137 copy = __copy__