helpers.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780
  1. """Various helper functions"""
  2. import asyncio
  3. import base64
  4. import binascii
  5. import cgi
  6. import datetime
  7. import functools
  8. import inspect
  9. import netrc
  10. import os
  11. import platform
  12. import re
  13. import sys
  14. import time
  15. import warnings
  16. import weakref
  17. from collections import namedtuple
  18. from contextlib import suppress
  19. from math import ceil
  20. from pathlib import Path
  21. from types import TracebackType
  22. from typing import (
  23. Any,
  24. Callable,
  25. Dict,
  26. Generator,
  27. Generic,
  28. Iterable,
  29. Iterator,
  30. List,
  31. Mapping,
  32. Optional,
  33. Pattern,
  34. Set,
  35. Tuple,
  36. Type,
  37. TypeVar,
  38. Union,
  39. cast,
  40. )
  41. from urllib.parse import quote
  42. from urllib.request import getproxies
  43. import async_timeout
  44. import attr
  45. from multidict import MultiDict, MultiDictProxy
  46. from typing_extensions import Protocol
  47. from yarl import URL
  48. from . import hdrs
  49. from .log import client_logger, internal_logger
  50. from .typedefs import PathLike # noqa
  51. __all__ = ("BasicAuth", "ChainMapProxy")
  52. PY_36 = sys.version_info >= (3, 6)
  53. PY_37 = sys.version_info >= (3, 7)
  54. PY_38 = sys.version_info >= (3, 8)
  55. if not PY_37:
  56. import idna_ssl
  57. idna_ssl.patch_match_hostname()
  58. try:
  59. from typing import ContextManager
  60. except ImportError:
  61. from typing_extensions import ContextManager
  62. def all_tasks(
  63. loop: Optional[asyncio.AbstractEventLoop] = None,
  64. ) -> Set["asyncio.Task[Any]"]:
  65. tasks = list(asyncio.Task.all_tasks(loop))
  66. return {t for t in tasks if not t.done()}
  67. if PY_37:
  68. all_tasks = getattr(asyncio, "all_tasks")
  69. _T = TypeVar("_T")
  70. _S = TypeVar("_S")
  71. sentinel = object() # type: Any
  72. NO_EXTENSIONS = bool(os.environ.get("AIOHTTP_NO_EXTENSIONS")) # type: bool
  73. # N.B. sys.flags.dev_mode is available on Python 3.7+, use getattr
  74. # for compatibility with older versions
  75. DEBUG = getattr(sys.flags, "dev_mode", False) or (
  76. not sys.flags.ignore_environment and bool(os.environ.get("PYTHONASYNCIODEBUG"))
  77. ) # type: bool
  78. CHAR = {chr(i) for i in range(0, 128)}
  79. CTL = {chr(i) for i in range(0, 32)} | {
  80. chr(127),
  81. }
  82. SEPARATORS = {
  83. "(",
  84. ")",
  85. "<",
  86. ">",
  87. "@",
  88. ",",
  89. ";",
  90. ":",
  91. "\\",
  92. '"',
  93. "/",
  94. "[",
  95. "]",
  96. "?",
  97. "=",
  98. "{",
  99. "}",
  100. " ",
  101. chr(9),
  102. }
  103. TOKEN = CHAR ^ CTL ^ SEPARATORS
  104. class noop:
  105. def __await__(self) -> Generator[None, None, None]:
  106. yield
  107. class BasicAuth(namedtuple("BasicAuth", ["login", "password", "encoding"])):
  108. """Http basic authentication helper."""
  109. def __new__(
  110. cls, login: str, password: str = "", encoding: str = "latin1"
  111. ) -> "BasicAuth":
  112. if login is None:
  113. raise ValueError("None is not allowed as login value")
  114. if password is None:
  115. raise ValueError("None is not allowed as password value")
  116. if ":" in login:
  117. raise ValueError('A ":" is not allowed in login (RFC 1945#section-11.1)')
  118. return super().__new__(cls, login, password, encoding)
  119. @classmethod
  120. def decode(cls, auth_header: str, encoding: str = "latin1") -> "BasicAuth":
  121. """Create a BasicAuth object from an Authorization HTTP header."""
  122. try:
  123. auth_type, encoded_credentials = auth_header.split(" ", 1)
  124. except ValueError:
  125. raise ValueError("Could not parse authorization header.")
  126. if auth_type.lower() != "basic":
  127. raise ValueError("Unknown authorization method %s" % auth_type)
  128. try:
  129. decoded = base64.b64decode(
  130. encoded_credentials.encode("ascii"), validate=True
  131. ).decode(encoding)
  132. except binascii.Error:
  133. raise ValueError("Invalid base64 encoding.")
  134. try:
  135. # RFC 2617 HTTP Authentication
  136. # https://www.ietf.org/rfc/rfc2617.txt
  137. # the colon must be present, but the username and password may be
  138. # otherwise blank.
  139. username, password = decoded.split(":", 1)
  140. except ValueError:
  141. raise ValueError("Invalid credentials.")
  142. return cls(username, password, encoding=encoding)
  143. @classmethod
  144. def from_url(cls, url: URL, *, encoding: str = "latin1") -> Optional["BasicAuth"]:
  145. """Create BasicAuth from url."""
  146. if not isinstance(url, URL):
  147. raise TypeError("url should be yarl.URL instance")
  148. if url.user is None:
  149. return None
  150. return cls(url.user, url.password or "", encoding=encoding)
  151. def encode(self) -> str:
  152. """Encode credentials."""
  153. creds = (f"{self.login}:{self.password}").encode(self.encoding)
  154. return "Basic %s" % base64.b64encode(creds).decode(self.encoding)
  155. def strip_auth_from_url(url: URL) -> Tuple[URL, Optional[BasicAuth]]:
  156. auth = BasicAuth.from_url(url)
  157. if auth is None:
  158. return url, None
  159. else:
  160. return url.with_user(None), auth
  161. def netrc_from_env() -> Optional[netrc.netrc]:
  162. """Attempt to load the netrc file from the path specified by the env-var
  163. NETRC or in the default location in the user's home directory.
  164. Returns None if it couldn't be found or fails to parse.
  165. """
  166. netrc_env = os.environ.get("NETRC")
  167. if netrc_env is not None:
  168. netrc_path = Path(netrc_env)
  169. else:
  170. try:
  171. home_dir = Path.home()
  172. except RuntimeError as e: # pragma: no cover
  173. # if pathlib can't resolve home, it may raise a RuntimeError
  174. client_logger.debug(
  175. "Could not resolve home directory when "
  176. "trying to look for .netrc file: %s",
  177. e,
  178. )
  179. return None
  180. netrc_path = home_dir / (
  181. "_netrc" if platform.system() == "Windows" else ".netrc"
  182. )
  183. try:
  184. return netrc.netrc(str(netrc_path))
  185. except netrc.NetrcParseError as e:
  186. client_logger.warning("Could not parse .netrc file: %s", e)
  187. except OSError as e:
  188. # we couldn't read the file (doesn't exist, permissions, etc.)
  189. if netrc_env or netrc_path.is_file():
  190. # only warn if the environment wanted us to load it,
  191. # or it appears like the default file does actually exist
  192. client_logger.warning("Could not read .netrc file: %s", e)
  193. return None
  194. @attr.s(auto_attribs=True, frozen=True, slots=True)
  195. class ProxyInfo:
  196. proxy: URL
  197. proxy_auth: Optional[BasicAuth]
  198. def proxies_from_env() -> Dict[str, ProxyInfo]:
  199. proxy_urls = {k: URL(v) for k, v in getproxies().items() if k in ("http", "https")}
  200. netrc_obj = netrc_from_env()
  201. stripped = {k: strip_auth_from_url(v) for k, v in proxy_urls.items()}
  202. ret = {}
  203. for proto, val in stripped.items():
  204. proxy, auth = val
  205. if proxy.scheme == "https":
  206. client_logger.warning("HTTPS proxies %s are not supported, ignoring", proxy)
  207. continue
  208. if netrc_obj and auth is None:
  209. auth_from_netrc = None
  210. if proxy.host is not None:
  211. auth_from_netrc = netrc_obj.authenticators(proxy.host)
  212. if auth_from_netrc is not None:
  213. # auth_from_netrc is a (`user`, `account`, `password`) tuple,
  214. # `user` and `account` both can be username,
  215. # if `user` is None, use `account`
  216. *logins, password = auth_from_netrc
  217. login = logins[0] if logins[0] else logins[-1]
  218. auth = BasicAuth(cast(str, login), cast(str, password))
  219. ret[proto] = ProxyInfo(proxy, auth)
  220. return ret
  221. def current_task(
  222. loop: Optional[asyncio.AbstractEventLoop] = None,
  223. ) -> "Optional[asyncio.Task[Any]]":
  224. if PY_37:
  225. return asyncio.current_task(loop=loop)
  226. else:
  227. return asyncio.Task.current_task(loop=loop)
  228. def get_running_loop(
  229. loop: Optional[asyncio.AbstractEventLoop] = None,
  230. ) -> asyncio.AbstractEventLoop:
  231. if loop is None:
  232. loop = asyncio.get_event_loop()
  233. if not loop.is_running():
  234. warnings.warn(
  235. "The object should be created within an async function",
  236. DeprecationWarning,
  237. stacklevel=3,
  238. )
  239. if loop.get_debug():
  240. internal_logger.warning(
  241. "The object should be created within an async function", stack_info=True
  242. )
  243. return loop
  244. def isasyncgenfunction(obj: Any) -> bool:
  245. func = getattr(inspect, "isasyncgenfunction", None)
  246. if func is not None:
  247. return func(obj)
  248. else:
  249. return False
  250. @attr.s(auto_attribs=True, frozen=True, slots=True)
  251. class MimeType:
  252. type: str
  253. subtype: str
  254. suffix: str
  255. parameters: "MultiDictProxy[str]"
  256. @functools.lru_cache(maxsize=56)
  257. def parse_mimetype(mimetype: str) -> MimeType:
  258. """Parses a MIME type into its components.
  259. mimetype is a MIME type string.
  260. Returns a MimeType object.
  261. Example:
  262. >>> parse_mimetype('text/html; charset=utf-8')
  263. MimeType(type='text', subtype='html', suffix='',
  264. parameters={'charset': 'utf-8'})
  265. """
  266. if not mimetype:
  267. return MimeType(
  268. type="", subtype="", suffix="", parameters=MultiDictProxy(MultiDict())
  269. )
  270. parts = mimetype.split(";")
  271. params = MultiDict() # type: MultiDict[str]
  272. for item in parts[1:]:
  273. if not item:
  274. continue
  275. key, value = cast(
  276. Tuple[str, str], item.split("=", 1) if "=" in item else (item, "")
  277. )
  278. params.add(key.lower().strip(), value.strip(' "'))
  279. fulltype = parts[0].strip().lower()
  280. if fulltype == "*":
  281. fulltype = "*/*"
  282. mtype, stype = (
  283. cast(Tuple[str, str], fulltype.split("/", 1))
  284. if "/" in fulltype
  285. else (fulltype, "")
  286. )
  287. stype, suffix = (
  288. cast(Tuple[str, str], stype.split("+", 1)) if "+" in stype else (stype, "")
  289. )
  290. return MimeType(
  291. type=mtype, subtype=stype, suffix=suffix, parameters=MultiDictProxy(params)
  292. )
  293. def guess_filename(obj: Any, default: Optional[str] = None) -> Optional[str]:
  294. name = getattr(obj, "name", None)
  295. if name and isinstance(name, str) and name[0] != "<" and name[-1] != ">":
  296. return Path(name).name
  297. return default
  298. def content_disposition_header(
  299. disptype: str, quote_fields: bool = True, **params: str
  300. ) -> str:
  301. """Sets ``Content-Disposition`` header.
  302. disptype is a disposition type: inline, attachment, form-data.
  303. Should be valid extension token (see RFC 2183)
  304. params is a dict with disposition params.
  305. """
  306. if not disptype or not (TOKEN > set(disptype)):
  307. raise ValueError("bad content disposition type {!r}" "".format(disptype))
  308. value = disptype
  309. if params:
  310. lparams = []
  311. for key, val in params.items():
  312. if not key or not (TOKEN > set(key)):
  313. raise ValueError(
  314. "bad content disposition parameter" " {!r}={!r}".format(key, val)
  315. )
  316. qval = quote(val, "") if quote_fields else val
  317. lparams.append((key, '"%s"' % qval))
  318. if key == "filename":
  319. lparams.append(("filename*", "utf-8''" + qval))
  320. sparams = "; ".join("=".join(pair) for pair in lparams)
  321. value = "; ".join((value, sparams))
  322. return value
  323. class _TSelf(Protocol):
  324. _cache: Dict[str, Any]
  325. class reify(Generic[_T]):
  326. """Use as a class method decorator. It operates almost exactly like
  327. the Python `@property` decorator, but it puts the result of the
  328. method it decorates into the instance dict after the first call,
  329. effectively replacing the function it decorates with an instance
  330. variable. It is, in Python parlance, a data descriptor.
  331. """
  332. def __init__(self, wrapped: Callable[..., _T]) -> None:
  333. self.wrapped = wrapped
  334. self.__doc__ = wrapped.__doc__
  335. self.name = wrapped.__name__
  336. def __get__(self, inst: _TSelf, owner: Optional[Type[Any]] = None) -> _T:
  337. try:
  338. try:
  339. return inst._cache[self.name]
  340. except KeyError:
  341. val = self.wrapped(inst)
  342. inst._cache[self.name] = val
  343. return val
  344. except AttributeError:
  345. if inst is None:
  346. return self
  347. raise
  348. def __set__(self, inst: _TSelf, value: _T) -> None:
  349. raise AttributeError("reified property is read-only")
  350. reify_py = reify
  351. try:
  352. from ._helpers import reify as reify_c
  353. if not NO_EXTENSIONS:
  354. reify = reify_c # type: ignore
  355. except ImportError:
  356. pass
  357. _ipv4_pattern = (
  358. r"^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}"
  359. r"(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"
  360. )
  361. _ipv6_pattern = (
  362. r"^(?:(?:(?:[A-F0-9]{1,4}:){6}|(?=(?:[A-F0-9]{0,4}:){0,6}"
  363. r"(?:[0-9]{1,3}\.){3}[0-9]{1,3}$)(([0-9A-F]{1,4}:){0,5}|:)"
  364. r"((:[0-9A-F]{1,4}){1,5}:|:)|::(?:[A-F0-9]{1,4}:){5})"
  365. r"(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.){3}"
  366. r"(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])|(?:[A-F0-9]{1,4}:){7}"
  367. r"[A-F0-9]{1,4}|(?=(?:[A-F0-9]{0,4}:){0,7}[A-F0-9]{0,4}$)"
  368. r"(([0-9A-F]{1,4}:){1,7}|:)((:[0-9A-F]{1,4}){1,7}|:)|(?:[A-F0-9]{1,4}:){7}"
  369. r":|:(:[A-F0-9]{1,4}){7})$"
  370. )
  371. _ipv4_regex = re.compile(_ipv4_pattern)
  372. _ipv6_regex = re.compile(_ipv6_pattern, flags=re.IGNORECASE)
  373. _ipv4_regexb = re.compile(_ipv4_pattern.encode("ascii"))
  374. _ipv6_regexb = re.compile(_ipv6_pattern.encode("ascii"), flags=re.IGNORECASE)
  375. def _is_ip_address(
  376. regex: Pattern[str], regexb: Pattern[bytes], host: Optional[Union[str, bytes]]
  377. ) -> bool:
  378. if host is None:
  379. return False
  380. if isinstance(host, str):
  381. return bool(regex.match(host))
  382. elif isinstance(host, (bytes, bytearray, memoryview)):
  383. return bool(regexb.match(host))
  384. else:
  385. raise TypeError("{} [{}] is not a str or bytes".format(host, type(host)))
  386. is_ipv4_address = functools.partial(_is_ip_address, _ipv4_regex, _ipv4_regexb)
  387. is_ipv6_address = functools.partial(_is_ip_address, _ipv6_regex, _ipv6_regexb)
  388. def is_ip_address(host: Optional[Union[str, bytes, bytearray, memoryview]]) -> bool:
  389. return is_ipv4_address(host) or is_ipv6_address(host)
  390. def next_whole_second() -> datetime.datetime:
  391. """Return current time rounded up to the next whole second."""
  392. return datetime.datetime.now(datetime.timezone.utc).replace(
  393. microsecond=0
  394. ) + datetime.timedelta(seconds=0)
  395. _cached_current_datetime = None # type: Optional[int]
  396. _cached_formatted_datetime = ""
  397. def rfc822_formatted_time() -> str:
  398. global _cached_current_datetime
  399. global _cached_formatted_datetime
  400. now = int(time.time())
  401. if now != _cached_current_datetime:
  402. # Weekday and month names for HTTP date/time formatting;
  403. # always English!
  404. # Tuples are constants stored in codeobject!
  405. _weekdayname = ("Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun")
  406. _monthname = (
  407. "", # Dummy so we can use 1-based month numbers
  408. "Jan",
  409. "Feb",
  410. "Mar",
  411. "Apr",
  412. "May",
  413. "Jun",
  414. "Jul",
  415. "Aug",
  416. "Sep",
  417. "Oct",
  418. "Nov",
  419. "Dec",
  420. )
  421. year, month, day, hh, mm, ss, wd, *tail = time.gmtime(now)
  422. _cached_formatted_datetime = "%s, %02d %3s %4d %02d:%02d:%02d GMT" % (
  423. _weekdayname[wd],
  424. day,
  425. _monthname[month],
  426. year,
  427. hh,
  428. mm,
  429. ss,
  430. )
  431. _cached_current_datetime = now
  432. return _cached_formatted_datetime
  433. def _weakref_handle(info): # type: ignore
  434. ref, name = info
  435. ob = ref()
  436. if ob is not None:
  437. with suppress(Exception):
  438. getattr(ob, name)()
  439. def weakref_handle(ob, name, timeout, loop): # type: ignore
  440. if timeout is not None and timeout > 0:
  441. when = loop.time() + timeout
  442. if timeout >= 5:
  443. when = ceil(when)
  444. return loop.call_at(when, _weakref_handle, (weakref.ref(ob), name))
  445. def call_later(cb, timeout, loop): # type: ignore
  446. if timeout is not None and timeout > 0:
  447. when = loop.time() + timeout
  448. if timeout > 5:
  449. when = ceil(when)
  450. return loop.call_at(when, cb)
  451. class TimeoutHandle:
  452. """ Timeout handle """
  453. def __init__(
  454. self, loop: asyncio.AbstractEventLoop, timeout: Optional[float]
  455. ) -> None:
  456. self._timeout = timeout
  457. self._loop = loop
  458. self._callbacks = (
  459. []
  460. ) # type: List[Tuple[Callable[..., None], Tuple[Any, ...], Dict[str, Any]]]
  461. def register(
  462. self, callback: Callable[..., None], *args: Any, **kwargs: Any
  463. ) -> None:
  464. self._callbacks.append((callback, args, kwargs))
  465. def close(self) -> None:
  466. self._callbacks.clear()
  467. def start(self) -> Optional[asyncio.Handle]:
  468. timeout = self._timeout
  469. if timeout is not None and timeout > 0:
  470. when = self._loop.time() + timeout
  471. if timeout >= 5:
  472. when = ceil(when)
  473. return self._loop.call_at(when, self.__call__)
  474. else:
  475. return None
  476. def timer(self) -> "BaseTimerContext":
  477. if self._timeout is not None and self._timeout > 0:
  478. timer = TimerContext(self._loop)
  479. self.register(timer.timeout)
  480. return timer
  481. else:
  482. return TimerNoop()
  483. def __call__(self) -> None:
  484. for cb, args, kwargs in self._callbacks:
  485. with suppress(Exception):
  486. cb(*args, **kwargs)
  487. self._callbacks.clear()
  488. class BaseTimerContext(ContextManager["BaseTimerContext"]):
  489. pass
  490. class TimerNoop(BaseTimerContext):
  491. def __enter__(self) -> BaseTimerContext:
  492. return self
  493. def __exit__(
  494. self,
  495. exc_type: Optional[Type[BaseException]],
  496. exc_val: Optional[BaseException],
  497. exc_tb: Optional[TracebackType],
  498. ) -> None:
  499. return
  500. class TimerContext(BaseTimerContext):
  501. """ Low resolution timeout context manager """
  502. def __init__(self, loop: asyncio.AbstractEventLoop) -> None:
  503. self._loop = loop
  504. self._tasks = [] # type: List[asyncio.Task[Any]]
  505. self._cancelled = False
  506. def __enter__(self) -> BaseTimerContext:
  507. task = current_task(loop=self._loop)
  508. if task is None:
  509. raise RuntimeError(
  510. "Timeout context manager should be used " "inside a task"
  511. )
  512. if self._cancelled:
  513. task.cancel()
  514. raise asyncio.TimeoutError from None
  515. self._tasks.append(task)
  516. return self
  517. def __exit__(
  518. self,
  519. exc_type: Optional[Type[BaseException]],
  520. exc_val: Optional[BaseException],
  521. exc_tb: Optional[TracebackType],
  522. ) -> Optional[bool]:
  523. if self._tasks:
  524. self._tasks.pop()
  525. if exc_type is asyncio.CancelledError and self._cancelled:
  526. raise asyncio.TimeoutError from None
  527. return None
  528. def timeout(self) -> None:
  529. if not self._cancelled:
  530. for task in set(self._tasks):
  531. task.cancel()
  532. self._cancelled = True
  533. class CeilTimeout(async_timeout.timeout):
  534. def __enter__(self) -> async_timeout.timeout:
  535. if self._timeout is not None:
  536. self._task = current_task(loop=self._loop)
  537. if self._task is None:
  538. raise RuntimeError(
  539. "Timeout context manager should be used inside a task"
  540. )
  541. now = self._loop.time()
  542. delay = self._timeout
  543. when = now + delay
  544. if delay > 5:
  545. when = ceil(when)
  546. self._cancel_handler = self._loop.call_at(when, self._cancel_task)
  547. return self
  548. class HeadersMixin:
  549. ATTRS = frozenset(["_content_type", "_content_dict", "_stored_content_type"])
  550. _content_type = None # type: Optional[str]
  551. _content_dict = None # type: Optional[Dict[str, str]]
  552. _stored_content_type = sentinel
  553. def _parse_content_type(self, raw: str) -> None:
  554. self._stored_content_type = raw
  555. if raw is None:
  556. # default value according to RFC 2616
  557. self._content_type = "application/octet-stream"
  558. self._content_dict = {}
  559. else:
  560. self._content_type, self._content_dict = cgi.parse_header(raw)
  561. @property
  562. def content_type(self) -> str:
  563. """The value of content part for Content-Type HTTP header."""
  564. raw = self._headers.get(hdrs.CONTENT_TYPE) # type: ignore
  565. if self._stored_content_type != raw:
  566. self._parse_content_type(raw)
  567. return self._content_type # type: ignore
  568. @property
  569. def charset(self) -> Optional[str]:
  570. """The value of charset part for Content-Type HTTP header."""
  571. raw = self._headers.get(hdrs.CONTENT_TYPE) # type: ignore
  572. if self._stored_content_type != raw:
  573. self._parse_content_type(raw)
  574. return self._content_dict.get("charset") # type: ignore
  575. @property
  576. def content_length(self) -> Optional[int]:
  577. """The value of Content-Length HTTP header."""
  578. content_length = self._headers.get(hdrs.CONTENT_LENGTH) # type: ignore
  579. if content_length is not None:
  580. return int(content_length)
  581. else:
  582. return None
  583. def set_result(fut: "asyncio.Future[_T]", result: _T) -> None:
  584. if not fut.done():
  585. fut.set_result(result)
  586. def set_exception(fut: "asyncio.Future[_T]", exc: BaseException) -> None:
  587. if not fut.done():
  588. fut.set_exception(exc)
  589. class ChainMapProxy(Mapping[str, Any]):
  590. __slots__ = ("_maps",)
  591. def __init__(self, maps: Iterable[Mapping[str, Any]]) -> None:
  592. self._maps = tuple(maps)
  593. def __init_subclass__(cls) -> None:
  594. raise TypeError(
  595. "Inheritance class {} from ChainMapProxy "
  596. "is forbidden".format(cls.__name__)
  597. )
  598. def __getitem__(self, key: str) -> Any:
  599. for mapping in self._maps:
  600. try:
  601. return mapping[key]
  602. except KeyError:
  603. pass
  604. raise KeyError(key)
  605. def get(self, key: str, default: Any = None) -> Any:
  606. return self[key] if key in self else default
  607. def __len__(self) -> int:
  608. # reuses stored hash values if possible
  609. return len(set().union(*self._maps)) # type: ignore
  610. def __iter__(self) -> Iterator[str]:
  611. d = {} # type: Dict[str, Any]
  612. for mapping in reversed(self._maps):
  613. # reuses stored hash values if possible
  614. d.update(mapping)
  615. return iter(d)
  616. def __contains__(self, key: object) -> bool:
  617. return any(key in m for m in self._maps)
  618. def __bool__(self) -> bool:
  619. return any(self._maps)
  620. def __repr__(self) -> str:
  621. content = ", ".join(map(repr, self._maps))
  622. return f"ChainMapProxy({content})"