datastructures.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704
  1. from __future__ import annotations
  2. import re
  3. from collections.abc import ItemsView, Iterable, Iterator, KeysView, Mapping, MutableMapping, Sequence, ValuesView
  4. from shlex import shlex
  5. from typing import Any, BinaryIO, Literal, NamedTuple, TypeVar, cast
  6. from urllib.parse import SplitResult, parse_qsl, urlencode, urlsplit
  7. from starlette.concurrency import run_in_threadpool
  8. from starlette.types import Scope
  9. class Address(NamedTuple):
  10. host: str
  11. port: int
  12. _KeyType = TypeVar("_KeyType")
  13. # Mapping keys are invariant but their values are covariant since
  14. # you can only read them
  15. # that is, you can't do `Mapping[str, Animal]()["fido"] = Dog()`
  16. _CovariantValueType = TypeVar("_CovariantValueType", covariant=True)
  17. # Rejects Host header chars (/, ?, #, @, ...) that would let urlsplit produce a path differing from scope["path"].
  18. _HOST_RE = re.compile(r"^([a-z0-9.-]+|\[[a-f0-9]*:[a-f0-9.:]+\])(?::[0-9]+)?$", re.IGNORECASE)
  19. class URL:
  20. def __init__(
  21. self,
  22. url: str = "",
  23. scope: Scope | None = None,
  24. **components: Any,
  25. ) -> None:
  26. if scope is not None:
  27. assert not url, 'Cannot set both "url" and "scope".'
  28. assert not components, 'Cannot set both "scope" and "**components".'
  29. scheme = scope.get("scheme", "http")
  30. server = scope.get("server", None)
  31. path = scope["path"]
  32. query_string = scope.get("query_string", b"")
  33. host_header = None
  34. for key, value in scope["headers"]:
  35. if key == b"host":
  36. host_header = value.decode("latin-1")
  37. break
  38. if host_header is not None and _HOST_RE.fullmatch(host_header):
  39. netloc = host_header
  40. elif server is not None:
  41. host, port = server
  42. default_port = {"http": 80, "https": 443, "ws": 80, "wss": 443}[scheme]
  43. netloc = host if port == default_port else f"{host}:{port}"
  44. else:
  45. netloc = None
  46. query = query_string.decode()
  47. if netloc is not None:
  48. url = SplitResult(scheme=scheme, netloc=netloc, path=path, query=query, fragment="").geturl()
  49. else:
  50. url = f"{path}?{query}" if query else path
  51. elif components:
  52. assert not url, 'Cannot set both "url" and "**components".'
  53. url = URL("").replace(**components).components.geturl()
  54. self._url = url
  55. @property
  56. def components(self) -> SplitResult:
  57. if not hasattr(self, "_components"):
  58. self._components = urlsplit(self._url)
  59. return self._components
  60. @property
  61. def scheme(self) -> str:
  62. return self.components.scheme
  63. @property
  64. def netloc(self) -> str:
  65. return self.components.netloc
  66. @property
  67. def path(self) -> str:
  68. return self.components.path
  69. @property
  70. def query(self) -> str:
  71. return self.components.query
  72. @property
  73. def fragment(self) -> str:
  74. return self.components.fragment
  75. @property
  76. def username(self) -> None | str:
  77. return self.components.username
  78. @property
  79. def password(self) -> None | str:
  80. return self.components.password
  81. @property
  82. def hostname(self) -> None | str:
  83. return self.components.hostname
  84. @property
  85. def port(self) -> int | None:
  86. return self.components.port
  87. @property
  88. def is_secure(self) -> bool:
  89. return self.scheme in ("https", "wss")
  90. def replace(self, **kwargs: Any) -> URL:
  91. if "username" in kwargs or "password" in kwargs or "hostname" in kwargs or "port" in kwargs:
  92. hostname = kwargs.pop("hostname", None)
  93. port = kwargs.pop("port", self.port)
  94. username = kwargs.pop("username", self.username)
  95. password = kwargs.pop("password", self.password)
  96. if hostname is None:
  97. netloc = self.netloc
  98. _, _, hostname = netloc.rpartition("@")
  99. if hostname and hostname[-1] != "]":
  100. hostname = hostname.rsplit(":", 1)[0]
  101. netloc = hostname
  102. if port is not None:
  103. netloc += f":{port}"
  104. if username is not None:
  105. userpass = username
  106. if password is not None:
  107. userpass += f":{password}"
  108. netloc = f"{userpass}@{netloc}"
  109. kwargs["netloc"] = netloc
  110. components = self.components._replace(**kwargs)
  111. return self.__class__(components.geturl())
  112. def include_query_params(self, **kwargs: Any) -> URL:
  113. params = MultiDict(parse_qsl(self.query, keep_blank_values=True))
  114. params.update({str(key): str(value) for key, value in kwargs.items()})
  115. query = urlencode(params.multi_items())
  116. return self.replace(query=query)
  117. def replace_query_params(self, **kwargs: Any) -> URL:
  118. query = urlencode([(str(key), str(value)) for key, value in kwargs.items()])
  119. return self.replace(query=query)
  120. def remove_query_params(self, keys: str | Sequence[str]) -> URL:
  121. if isinstance(keys, str):
  122. keys = [keys]
  123. params = MultiDict(parse_qsl(self.query, keep_blank_values=True))
  124. for key in keys:
  125. params.pop(key, None)
  126. query = urlencode(params.multi_items())
  127. return self.replace(query=query)
  128. def __eq__(self, other: Any) -> bool:
  129. return str(self) == str(other)
  130. def __str__(self) -> str:
  131. return self._url
  132. def __repr__(self) -> str:
  133. url = str(self)
  134. if self.password:
  135. url = str(self.replace(password="********"))
  136. return f"{self.__class__.__name__}({repr(url)})"
  137. class URLPath(str):
  138. """
  139. A URL path string that may also hold an associated protocol and/or host.
  140. Used by the routing to return `url_path_for` matches.
  141. """
  142. def __new__(cls, path: str, protocol: Literal["http", "websocket", ""] = "", host: str = "") -> URLPath:
  143. assert protocol in ("http", "websocket", "")
  144. return str.__new__(cls, path)
  145. def __init__(self, path: str, protocol: Literal["http", "websocket", ""] = "", host: str = "") -> None:
  146. self.protocol = protocol
  147. self.host = host
  148. def make_absolute_url(self, base_url: str | URL) -> URL:
  149. if isinstance(base_url, str):
  150. base_url = URL(base_url)
  151. if self.protocol:
  152. scheme = {
  153. "http": {True: "https", False: "http"},
  154. "websocket": {True: "wss", False: "ws"},
  155. }[self.protocol][base_url.is_secure]
  156. else:
  157. scheme = base_url.scheme
  158. netloc = self.host or base_url.netloc
  159. path = base_url.path.rstrip("/") + str(self)
  160. return URL(scheme=scheme, netloc=netloc, path=path)
  161. class Secret:
  162. """
  163. Holds a string value that should not be revealed in tracebacks etc.
  164. You should cast the value to `str` at the point it is required.
  165. """
  166. def __init__(self, value: str):
  167. self._value = value
  168. def __repr__(self) -> str:
  169. class_name = self.__class__.__name__
  170. return f"{class_name}('**********')"
  171. def __str__(self) -> str:
  172. return self._value
  173. def __bool__(self) -> bool:
  174. return bool(self._value)
  175. class CommaSeparatedStrings(Sequence[str]):
  176. def __init__(self, value: str | Sequence[str]):
  177. if isinstance(value, str):
  178. splitter = shlex(value, posix=True)
  179. splitter.whitespace = ","
  180. splitter.whitespace_split = True
  181. self._items = [item.strip() for item in splitter]
  182. else:
  183. self._items = list(value)
  184. def __len__(self) -> int:
  185. return len(self._items)
  186. def __getitem__(self, index: int | slice) -> Any:
  187. return self._items[index]
  188. def __iter__(self) -> Iterator[str]:
  189. return iter(self._items)
  190. def __repr__(self) -> str:
  191. class_name = self.__class__.__name__
  192. items = [item for item in self]
  193. return f"{class_name}({items!r})"
  194. def __str__(self) -> str:
  195. return ", ".join(repr(item) for item in self)
  196. class ImmutableMultiDict(Mapping[_KeyType, _CovariantValueType]):
  197. _dict: dict[_KeyType, _CovariantValueType]
  198. def __init__(
  199. self,
  200. *args: ImmutableMultiDict[_KeyType, _CovariantValueType]
  201. | Mapping[_KeyType, _CovariantValueType]
  202. | Iterable[tuple[_KeyType, _CovariantValueType]],
  203. **kwargs: Any,
  204. ) -> None:
  205. assert len(args) < 2, "Too many arguments."
  206. value: Any = args[0] if args else []
  207. if kwargs:
  208. value = ImmutableMultiDict(value).multi_items() + ImmutableMultiDict(kwargs).multi_items()
  209. if not value:
  210. _items: list[tuple[Any, Any]] = []
  211. elif hasattr(value, "multi_items"):
  212. value = cast(ImmutableMultiDict[_KeyType, _CovariantValueType], value)
  213. _items = list(value.multi_items())
  214. elif hasattr(value, "items"):
  215. value = cast(Mapping[_KeyType, _CovariantValueType], value)
  216. _items = list(value.items())
  217. else:
  218. value = cast("list[tuple[Any, Any]]", value)
  219. _items = list(value)
  220. self._dict = {k: v for k, v in _items}
  221. self._list = _items
  222. def getlist(self, key: Any) -> list[_CovariantValueType]:
  223. return [item_value for item_key, item_value in self._list if item_key == key]
  224. def keys(self) -> KeysView[_KeyType]:
  225. return self._dict.keys()
  226. def values(self) -> ValuesView[_CovariantValueType]:
  227. return self._dict.values()
  228. def items(self) -> ItemsView[_KeyType, _CovariantValueType]:
  229. return self._dict.items()
  230. def multi_items(self) -> list[tuple[_KeyType, _CovariantValueType]]:
  231. return list(self._list)
  232. def __getitem__(self, key: _KeyType) -> _CovariantValueType:
  233. return self._dict[key]
  234. def __contains__(self, key: Any) -> bool:
  235. return key in self._dict
  236. def __iter__(self) -> Iterator[_KeyType]:
  237. return iter(self.keys())
  238. def __len__(self) -> int:
  239. return len(self._dict)
  240. def __eq__(self, other: Any) -> bool:
  241. if not isinstance(other, self.__class__):
  242. return False
  243. return sorted(self._list) == sorted(other._list)
  244. def __repr__(self) -> str:
  245. class_name = self.__class__.__name__
  246. items = self.multi_items()
  247. return f"{class_name}({items!r})"
  248. class MultiDict(ImmutableMultiDict[Any, Any]):
  249. def __setitem__(self, key: Any, value: Any) -> None:
  250. self.setlist(key, [value])
  251. def __delitem__(self, key: Any) -> None:
  252. self._list = [(k, v) for k, v in self._list if k != key]
  253. del self._dict[key]
  254. def pop(self, key: Any, default: Any = None) -> Any:
  255. self._list = [(k, v) for k, v in self._list if k != key]
  256. return self._dict.pop(key, default)
  257. def popitem(self) -> tuple[Any, Any]:
  258. key, value = self._dict.popitem()
  259. self._list = [(k, v) for k, v in self._list if k != key]
  260. return key, value
  261. def poplist(self, key: Any) -> list[Any]:
  262. values = [v for k, v in self._list if k == key]
  263. self.pop(key)
  264. return values
  265. def clear(self) -> None:
  266. self._dict.clear()
  267. self._list.clear()
  268. def setdefault(self, key: Any, default: Any = None) -> Any:
  269. if key not in self:
  270. self._dict[key] = default
  271. self._list.append((key, default))
  272. return self[key]
  273. def setlist(self, key: Any, values: list[Any]) -> None:
  274. if not values:
  275. self.pop(key, None)
  276. else:
  277. existing_items = [(k, v) for (k, v) in self._list if k != key]
  278. self._list = existing_items + [(key, value) for value in values]
  279. self._dict[key] = values[-1]
  280. def append(self, key: Any, value: Any) -> None:
  281. self._list.append((key, value))
  282. self._dict[key] = value
  283. def update(
  284. self,
  285. *args: MultiDict | Mapping[Any, Any] | list[tuple[Any, Any]],
  286. **kwargs: Any,
  287. ) -> None:
  288. value = MultiDict(*args, **kwargs)
  289. existing_items = [(k, v) for (k, v) in self._list if k not in value.keys()]
  290. self._list = existing_items + value.multi_items()
  291. self._dict.update(value)
  292. class QueryParams(ImmutableMultiDict[str, str]):
  293. """
  294. An immutable multidict.
  295. """
  296. def __init__(
  297. self,
  298. *args: ImmutableMultiDict[Any, Any] | Mapping[Any, Any] | list[tuple[Any, Any]] | str | bytes,
  299. **kwargs: Any,
  300. ) -> None:
  301. assert len(args) < 2, "Too many arguments."
  302. value = args[0] if args else []
  303. if isinstance(value, str):
  304. super().__init__(parse_qsl(value, keep_blank_values=True), **kwargs)
  305. elif isinstance(value, bytes):
  306. super().__init__(parse_qsl(value.decode("latin-1"), keep_blank_values=True), **kwargs)
  307. else:
  308. super().__init__(*args, **kwargs) # type: ignore[arg-type]
  309. self._list = [(str(k), str(v)) for k, v in self._list]
  310. self._dict = {str(k): str(v) for k, v in self._dict.items()}
  311. def __str__(self) -> str:
  312. return urlencode(self._list)
  313. def __repr__(self) -> str:
  314. class_name = self.__class__.__name__
  315. query_string = str(self)
  316. return f"{class_name}({query_string!r})"
  317. class UploadFile:
  318. """
  319. An uploaded file included as part of the request data.
  320. """
  321. def __init__(
  322. self,
  323. file: BinaryIO,
  324. *,
  325. size: int | None = None,
  326. filename: str | None = None,
  327. headers: Headers | None = None,
  328. ) -> None:
  329. self.filename = filename
  330. self.file = file
  331. self.size = size
  332. self.headers = headers or Headers()
  333. # Capture max size from SpooledTemporaryFile if one is provided. This slightly speeds up future checks.
  334. # Note 0 means unlimited mirroring SpooledTemporaryFile's __init__
  335. self._max_mem_size = getattr(self.file, "_max_size", 0)
  336. @property
  337. def content_type(self) -> str | None:
  338. return self.headers.get("content-type", None)
  339. @property
  340. def _in_memory(self) -> bool:
  341. # check for SpooledTemporaryFile._rolled
  342. rolled_to_disk = getattr(self.file, "_rolled", True)
  343. return not rolled_to_disk
  344. def _will_roll(self, size_to_add: int) -> bool:
  345. # If we're not in_memory then we will always roll
  346. if not self._in_memory:
  347. return True
  348. # Check for SpooledTemporaryFile._max_size
  349. future_size = self.file.tell() + size_to_add
  350. return bool(future_size > self._max_mem_size) if self._max_mem_size else False
  351. async def write(self, data: bytes) -> None:
  352. new_data_len = len(data)
  353. if self.size is not None:
  354. self.size += new_data_len
  355. if self._will_roll(new_data_len):
  356. await run_in_threadpool(self.file.write, data)
  357. else:
  358. self.file.write(data)
  359. async def read(self, size: int = -1) -> bytes:
  360. if self._in_memory:
  361. return self.file.read(size)
  362. return await run_in_threadpool(self.file.read, size)
  363. async def seek(self, offset: int) -> None:
  364. if self._in_memory:
  365. self.file.seek(offset)
  366. else:
  367. await run_in_threadpool(self.file.seek, offset)
  368. async def close(self) -> None:
  369. if self._in_memory:
  370. self.file.close()
  371. else:
  372. await run_in_threadpool(self.file.close)
  373. def __repr__(self) -> str:
  374. return f"{self.__class__.__name__}(filename={self.filename!r}, size={self.size!r}, headers={self.headers!r})"
  375. class FormData(ImmutableMultiDict[str, UploadFile | str]):
  376. """
  377. An immutable multidict, containing both file uploads and text input.
  378. """
  379. def __init__(
  380. self,
  381. *args: FormData | Mapping[str, str | UploadFile] | list[tuple[str, str | UploadFile]],
  382. **kwargs: str | UploadFile,
  383. ) -> None:
  384. super().__init__(*args, **kwargs)
  385. async def close(self) -> None:
  386. for key, value in self.multi_items():
  387. if isinstance(value, UploadFile):
  388. await value.close()
  389. class Headers(Mapping[str, str]):
  390. """
  391. An immutable, case-insensitive multidict.
  392. """
  393. def __init__(
  394. self,
  395. headers: Mapping[str, str] | None = None,
  396. raw: list[tuple[bytes, bytes]] | None = None,
  397. scope: MutableMapping[str, Any] | None = None,
  398. ) -> None:
  399. self._list: list[tuple[bytes, bytes]] = []
  400. if headers is not None:
  401. assert raw is None, 'Cannot set both "headers" and "raw".'
  402. assert scope is None, 'Cannot set both "headers" and "scope".'
  403. self._list = [(key.lower().encode("latin-1"), value.encode("latin-1")) for key, value in headers.items()]
  404. elif raw is not None:
  405. assert scope is None, 'Cannot set both "raw" and "scope".'
  406. self._list = raw
  407. elif scope is not None:
  408. # scope["headers"] isn't necessarily a list
  409. # it might be a tuple or other iterable
  410. self._list = scope["headers"] = list(scope["headers"])
  411. @property
  412. def raw(self) -> list[tuple[bytes, bytes]]:
  413. return list(self._list)
  414. def keys(self) -> list[str]: # type: ignore[override]
  415. return [key.decode("latin-1") for key, value in self._list]
  416. def values(self) -> list[str]: # type: ignore[override]
  417. return [value.decode("latin-1") for key, value in self._list]
  418. def items(self) -> list[tuple[str, str]]: # type: ignore[override]
  419. return [(key.decode("latin-1"), value.decode("latin-1")) for key, value in self._list]
  420. def getlist(self, key: str) -> list[str]:
  421. get_header_key = key.lower().encode("latin-1")
  422. return [item_value.decode("latin-1") for item_key, item_value in self._list if item_key == get_header_key]
  423. def mutablecopy(self) -> MutableHeaders:
  424. return MutableHeaders(raw=self._list[:])
  425. def __getitem__(self, key: str) -> str:
  426. get_header_key = key.lower().encode("latin-1")
  427. for header_key, header_value in self._list:
  428. if header_key == get_header_key:
  429. return header_value.decode("latin-1")
  430. raise KeyError(key)
  431. def __contains__(self, key: Any) -> bool:
  432. get_header_key = key.lower().encode("latin-1")
  433. for header_key, header_value in self._list:
  434. if header_key == get_header_key:
  435. return True
  436. return False
  437. def __iter__(self) -> Iterator[Any]:
  438. return iter(self.keys())
  439. def __len__(self) -> int:
  440. return len(self._list)
  441. def __eq__(self, other: Any) -> bool:
  442. if not isinstance(other, Headers):
  443. return False
  444. return sorted(self._list) == sorted(other._list)
  445. def __repr__(self) -> str:
  446. class_name = self.__class__.__name__
  447. as_dict = dict(self.items())
  448. if len(as_dict) == len(self):
  449. return f"{class_name}({as_dict!r})"
  450. return f"{class_name}(raw={self.raw!r})"
  451. class MutableHeaders(Headers):
  452. def __setitem__(self, key: str, value: str) -> None:
  453. """
  454. Set the header `key` to `value`, removing any duplicate entries.
  455. Retains insertion order.
  456. """
  457. set_key = key.lower().encode("latin-1")
  458. set_value = value.encode("latin-1")
  459. found_indexes: list[int] = []
  460. for idx, (item_key, item_value) in enumerate(self._list):
  461. if item_key == set_key:
  462. found_indexes.append(idx)
  463. for idx in reversed(found_indexes[1:]):
  464. del self._list[idx]
  465. if found_indexes:
  466. idx = found_indexes[0]
  467. self._list[idx] = (set_key, set_value)
  468. else:
  469. self._list.append((set_key, set_value))
  470. def __delitem__(self, key: str) -> None:
  471. """
  472. Remove the header `key`.
  473. """
  474. del_key = key.lower().encode("latin-1")
  475. pop_indexes: list[int] = []
  476. for idx, (item_key, item_value) in enumerate(self._list):
  477. if item_key == del_key:
  478. pop_indexes.append(idx)
  479. for idx in reversed(pop_indexes):
  480. del self._list[idx]
  481. def __ior__(self, other: Mapping[str, str]) -> MutableHeaders:
  482. if not isinstance(other, Mapping):
  483. raise TypeError(f"Expected a mapping but got {other.__class__.__name__}")
  484. self.update(other)
  485. return self
  486. def __or__(self, other: Mapping[str, str]) -> MutableHeaders:
  487. if not isinstance(other, Mapping):
  488. raise TypeError(f"Expected a mapping but got {other.__class__.__name__}")
  489. new = self.mutablecopy()
  490. new.update(other)
  491. return new
  492. @property
  493. def raw(self) -> list[tuple[bytes, bytes]]:
  494. return self._list
  495. def setdefault(self, key: str, value: str) -> str:
  496. """
  497. If the header `key` does not exist, then set it to `value`.
  498. Returns the header value.
  499. """
  500. set_key = key.lower().encode("latin-1")
  501. set_value = value.encode("latin-1")
  502. for idx, (item_key, item_value) in enumerate(self._list):
  503. if item_key == set_key:
  504. return item_value.decode("latin-1")
  505. self._list.append((set_key, set_value))
  506. return value
  507. def update(self, other: Mapping[str, str]) -> None:
  508. for key, val in other.items():
  509. self[key] = val
  510. def append(self, key: str, value: str) -> None:
  511. """
  512. Append a header, preserving any duplicate entries.
  513. """
  514. append_key = key.lower().encode("latin-1")
  515. append_value = value.encode("latin-1")
  516. self._list.append((append_key, append_value))
  517. def add_vary_header(self, vary: str) -> None:
  518. existing = self.get("vary")
  519. if existing is not None:
  520. vary = ", ".join([existing, vary])
  521. self["vary"] = vary
  522. class State:
  523. """
  524. An object that can be used to store arbitrary state.
  525. Used for `request.state` and `app.state`.
  526. """
  527. _state: dict[str, Any]
  528. def __init__(self, state: dict[str, Any] | None = None):
  529. if state is None:
  530. state = {}
  531. super().__setattr__("_state", state)
  532. def __setattr__(self, key: Any, value: Any) -> None:
  533. self._state[key] = value
  534. def __getattr__(self, key: Any) -> Any:
  535. try:
  536. return self._state[key]
  537. except KeyError:
  538. message = "'{}' object has no attribute '{}'"
  539. raise AttributeError(message.format(self.__class__.__name__, key))
  540. def __delattr__(self, key: Any) -> None:
  541. del self._state[key]
  542. def __getitem__(self, key: str) -> Any:
  543. return self._state[key]
  544. def __setitem__(self, key: str, value: Any) -> None:
  545. self._state[key] = value
  546. def __delitem__(self, key: str) -> None:
  547. del self._state[key]
  548. def __iter__(self) -> Iterator[str]:
  549. return iter(self._state)
  550. def __len__(self) -> int:
  551. return len(self._state)