Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/starlette/datastructures.py: 31%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

465 statements  

1from __future__ import annotations 

2 

3import ipaddress 

4from collections.abc import ItemsView, Iterable, Iterator, KeysView, Mapping, MutableMapping, Sequence, ValuesView 

5from operator import itemgetter 

6from shlex import shlex 

7from typing import Any, BinaryIO, Literal, NamedTuple, TypeVar, cast 

8from urllib.parse import SplitResult, parse_qsl, urlencode, urlsplit 

9 

10from starlette._utils import parse_host_header 

11from starlette.concurrency import run_in_threadpool 

12from starlette.types import Scope 

13 

14 

15class Address(NamedTuple): 

16 host: str 

17 port: int 

18 

19 

20_KeyType = TypeVar("_KeyType") 

21# Mapping keys are invariant but their values are covariant since 

22# you can only read them 

23# that is, you can't do `Mapping[str, Animal]()["fido"] = Dog()` 

24_CovariantValueType = TypeVar("_CovariantValueType", covariant=True) 

25 

26 

27class URL: 

28 def __init__( 

29 self, 

30 url: str = "", 

31 scope: Scope | None = None, 

32 **components: Any, 

33 ) -> None: 

34 if scope is not None: 

35 assert not url, 'Cannot set both "url" and "scope".' 

36 assert not components, 'Cannot set both "scope" and "**components".' 

37 scheme = scope.get("scheme", "http") 

38 server = scope.get("server", None) 

39 path = scope["path"].replace("?", "%3F").replace("#", "%23").replace("\\", "%5C") 

40 query_string = scope.get("query_string", b"") 

41 

42 host_header = None 

43 for key, value in scope["headers"]: 

44 if key == b"host": 

45 host_header = value.decode("latin-1") 

46 break 

47 

48 parsed_host = parse_host_header(host_header) 

49 if parsed_host is not None and parsed_host.is_valid_port: 

50 netloc = parsed_host.authority 

51 elif server is not None: 

52 host, port = server 

53 if ":" in host and not host.startswith("["): 

54 host = f"[{host}]" 

55 default_port = {"http": 80, "https": 443, "ws": 80, "wss": 443}[scheme] 

56 netloc = host if port == default_port else f"{host}:{port}" 

57 else: 

58 netloc = None 

59 

60 query = query_string.decode() 

61 if netloc is not None: 

62 url = SplitResult(scheme=scheme, netloc=netloc, path=path, query=query, fragment="").geturl() 

63 else: 

64 if path.startswith("//"): 

65 # Without a host, a leading `//` would be parsed as the authority. 

66 path = "/%2F" + path[2:] 

67 url = f"{path}?{query}" if query else path 

68 elif components: 

69 assert not url, 'Cannot set both "url" and "**components".' 

70 url = URL("").replace(**components).components.geturl() 

71 

72 self._url = url 

73 

74 @property 

75 def components(self) -> SplitResult: 

76 if not hasattr(self, "_components"): 

77 self._components = urlsplit(self._url) 

78 return self._components 

79 

80 @property 

81 def scheme(self) -> str: 

82 return self.components.scheme 

83 

84 @property 

85 def netloc(self) -> str: 

86 return self.components.netloc 

87 

88 @property 

89 def path(self) -> str: 

90 return self.components.path 

91 

92 @property 

93 def query(self) -> str: 

94 return self.components.query 

95 

96 @property 

97 def fragment(self) -> str: 

98 return self.components.fragment 

99 

100 @property 

101 def username(self) -> None | str: 

102 return self.components.username 

103 

104 @property 

105 def password(self) -> None | str: 

106 return self.components.password 

107 

108 @property 

109 def hostname(self) -> None | str: 

110 return self.components.hostname 

111 

112 @property 

113 def port(self) -> int | None: 

114 return self.components.port 

115 

116 @property 

117 def is_secure(self) -> bool: 

118 return self.scheme in ("https", "wss") 

119 

120 def replace(self, **kwargs: Any) -> URL: 

121 if "username" in kwargs or "password" in kwargs or "hostname" in kwargs or "port" in kwargs: 

122 hostname = kwargs.pop("hostname", None) 

123 port = kwargs.pop("port", self.port) 

124 username = kwargs.pop("username", self.username) 

125 password = kwargs.pop("password", self.password) 

126 

127 if hostname is None: 

128 netloc = self.netloc 

129 _, _, hostname = netloc.rpartition("@") 

130 

131 if hostname and hostname[-1] != "]": 

132 hostname = hostname.rsplit(":", 1)[0] 

133 elif ":" in hostname and not hostname.startswith("["): 

134 try: 

135 ipaddress.IPv6Address(hostname) 

136 except ValueError: 

137 pass 

138 else: 

139 hostname = f"[{hostname}]" 

140 

141 netloc = hostname 

142 if port is not None: 

143 netloc += f":{port}" 

144 if username is not None: 

145 userpass = username 

146 if password is not None: 

147 userpass += f":{password}" 

148 netloc = f"{userpass}@{netloc}" 

149 

150 kwargs["netloc"] = netloc 

151 

152 components = self.components._replace(**kwargs) 

153 return self.__class__(components.geturl()) 

154 

155 def include_query_params(self, **kwargs: Any) -> URL: 

156 params = MultiDict(parse_qsl(self.query, keep_blank_values=True)) 

157 params.update({str(key): str(value) for key, value in kwargs.items()}) 

158 query = urlencode(params.multi_items()) 

159 return self.replace(query=query) 

160 

161 def replace_query_params(self, **kwargs: Any) -> URL: 

162 query = urlencode([(str(key), str(value)) for key, value in kwargs.items()]) 

163 return self.replace(query=query) 

164 

165 def remove_query_params(self, keys: str | Sequence[str]) -> URL: 

166 if isinstance(keys, str): 

167 keys = [keys] 

168 params = MultiDict(parse_qsl(self.query, keep_blank_values=True)) 

169 for key in keys: 

170 params.pop(key, None) 

171 query = urlencode(params.multi_items()) 

172 return self.replace(query=query) 

173 

174 def __eq__(self, other: Any) -> bool: 

175 return str(self) == str(other) 

176 

177 def __str__(self) -> str: 

178 return self._url 

179 

180 def __repr__(self) -> str: 

181 url = str(self) 

182 if self.password: 

183 url = str(self.replace(password="********")) 

184 return f"{self.__class__.__name__}({repr(url)})" 

185 

186 

187class URLPath(str): 

188 """ 

189 A URL path string that may also hold an associated protocol and/or host. 

190 Used by the routing to return `url_path_for` matches. 

191 """ 

192 

193 def __new__(cls, path: str, protocol: Literal["http", "websocket", ""] = "", host: str = "") -> URLPath: 

194 assert protocol in ("http", "websocket", "") 

195 path = path.replace("\\", "%5C") 

196 if path.startswith("//"): 

197 # A leading `//` would be parsed as the authority. 

198 path = "/%2F" + path[2:] 

199 return str.__new__(cls, path) 

200 

201 def __init__(self, path: str, protocol: Literal["http", "websocket", ""] = "", host: str = "") -> None: 

202 self.protocol = protocol 

203 self.host = host 

204 

205 def make_absolute_url(self, base_url: str | URL) -> URL: 

206 if isinstance(base_url, str): 

207 base_url = URL(base_url) 

208 if self.protocol: 

209 scheme = { 

210 "http": {True: "https", False: "http"}, 

211 "websocket": {True: "wss", False: "ws"}, 

212 }[self.protocol][base_url.is_secure] 

213 else: 

214 scheme = base_url.scheme 

215 

216 netloc = self.host or base_url.netloc 

217 path = base_url.path.rstrip("/") + str(self) 

218 return URL(scheme=scheme, netloc=netloc, path=path) 

219 

220 

221class Secret: 

222 """ 

223 Holds a string value that should not be revealed in tracebacks etc. 

224 You should cast the value to `str` at the point it is required. 

225 """ 

226 

227 def __init__(self, value: str): 

228 self._value = value 

229 

230 def __repr__(self) -> str: 

231 class_name = self.__class__.__name__ 

232 return f"{class_name}('**********')" 

233 

234 def __str__(self) -> str: 

235 return self._value 

236 

237 def __bool__(self) -> bool: 

238 return bool(self._value) 

239 

240 

241class CommaSeparatedStrings(Sequence[str]): 

242 def __init__(self, value: str | Sequence[str]): 

243 if isinstance(value, str): 

244 splitter = shlex(value, posix=True) 

245 splitter.whitespace = "," 

246 splitter.whitespace_split = True 

247 self._items = [item.strip() for item in splitter] 

248 else: 

249 self._items = list(value) 

250 

251 def __len__(self) -> int: 

252 return len(self._items) 

253 

254 def __getitem__(self, index: int | slice) -> Any: 

255 return self._items[index] 

256 

257 def __iter__(self) -> Iterator[str]: 

258 return iter(self._items) 

259 

260 def __repr__(self) -> str: 

261 class_name = self.__class__.__name__ 

262 items = [item for item in self] 

263 return f"{class_name}({items!r})" 

264 

265 def __str__(self) -> str: 

266 return ", ".join(repr(item) for item in self) 

267 

268 

269class ImmutableMultiDict(Mapping[_KeyType, _CovariantValueType]): 

270 _dict: dict[_KeyType, _CovariantValueType] 

271 

272 def __init__( 

273 self, 

274 *args: ImmutableMultiDict[_KeyType, _CovariantValueType] 

275 | Mapping[_KeyType, _CovariantValueType] 

276 | Iterable[tuple[_KeyType, _CovariantValueType]], 

277 **kwargs: Any, 

278 ) -> None: 

279 assert len(args) < 2, "Too many arguments." 

280 

281 value: Any = args[0] if args else [] 

282 if kwargs: 

283 value = ImmutableMultiDict(value).multi_items() + ImmutableMultiDict(kwargs).multi_items() 

284 

285 if not value: 

286 _items: list[tuple[Any, Any]] = [] 

287 elif hasattr(value, "multi_items"): 

288 value = cast(ImmutableMultiDict[_KeyType, _CovariantValueType], value) 

289 _items = list(value.multi_items()) 

290 elif hasattr(value, "items"): 

291 value = cast(Mapping[_KeyType, _CovariantValueType], value) 

292 _items = list(value.items()) 

293 else: 

294 value = cast("list[tuple[Any, Any]]", value) 

295 _items = list(value) 

296 

297 self._dict = {k: v for k, v in _items} 

298 self._list = _items 

299 

300 def getlist(self, key: Any) -> list[_CovariantValueType]: 

301 return [item_value for item_key, item_value in self._list if item_key == key] 

302 

303 def keys(self) -> KeysView[_KeyType]: 

304 return self._dict.keys() 

305 

306 def values(self) -> ValuesView[_CovariantValueType]: 

307 return self._dict.values() 

308 

309 def items(self) -> ItemsView[_KeyType, _CovariantValueType]: 

310 return self._dict.items() 

311 

312 def multi_items(self) -> list[tuple[_KeyType, _CovariantValueType]]: 

313 return list(self._list) 

314 

315 def __getitem__(self, key: _KeyType) -> _CovariantValueType: 

316 return self._dict[key] 

317 

318 def __contains__(self, key: Any) -> bool: 

319 return key in self._dict 

320 

321 def __iter__(self) -> Iterator[_KeyType]: 

322 return iter(self.keys()) 

323 

324 def __len__(self) -> int: 

325 return len(self._dict) 

326 

327 def __eq__(self, other: Any) -> bool: 

328 if not isinstance(other, self.__class__): 

329 return False 

330 return sorted(self._list, key=itemgetter(0)) == sorted(other._list, key=itemgetter(0)) 

331 

332 def __repr__(self) -> str: 

333 class_name = self.__class__.__name__ 

334 items = self.multi_items() 

335 return f"{class_name}({items!r})" 

336 

337 

338class MultiDict(ImmutableMultiDict[Any, Any]): 

339 def __setitem__(self, key: Any, value: Any) -> None: 

340 self.setlist(key, [value]) 

341 

342 def __delitem__(self, key: Any) -> None: 

343 self._list = [(k, v) for k, v in self._list if k != key] 

344 del self._dict[key] 

345 

346 def pop(self, key: Any, default: Any = None) -> Any: 

347 self._list = [(k, v) for k, v in self._list if k != key] 

348 return self._dict.pop(key, default) 

349 

350 def popitem(self) -> tuple[Any, Any]: 

351 key, value = self._dict.popitem() 

352 self._list = [(k, v) for k, v in self._list if k != key] 

353 return key, value 

354 

355 def poplist(self, key: Any) -> list[Any]: 

356 values = [v for k, v in self._list if k == key] 

357 self.pop(key) 

358 return values 

359 

360 def clear(self) -> None: 

361 self._dict.clear() 

362 self._list.clear() 

363 

364 def setdefault(self, key: Any, default: Any = None) -> Any: 

365 if key not in self: 

366 self._dict[key] = default 

367 self._list.append((key, default)) 

368 

369 return self[key] 

370 

371 def setlist(self, key: Any, values: list[Any]) -> None: 

372 if not values: 

373 self.pop(key, None) 

374 else: 

375 existing_items = [(k, v) for (k, v) in self._list if k != key] 

376 self._list = existing_items + [(key, value) for value in values] 

377 self._dict[key] = values[-1] 

378 

379 def append(self, key: Any, value: Any) -> None: 

380 self._list.append((key, value)) 

381 self._dict[key] = value 

382 

383 def update( 

384 self, 

385 *args: MultiDict | Mapping[Any, Any] | list[tuple[Any, Any]], 

386 **kwargs: Any, 

387 ) -> None: 

388 value = MultiDict(*args, **kwargs) 

389 existing_items = [(k, v) for (k, v) in self._list if k not in value.keys()] 

390 self._list = existing_items + value.multi_items() 

391 self._dict.update(value) 

392 

393 

394class QueryParams(ImmutableMultiDict[str, str]): 

395 """ 

396 An immutable multidict. 

397 """ 

398 

399 def __init__( 

400 self, 

401 *args: ImmutableMultiDict[Any, Any] | Mapping[Any, Any] | list[tuple[Any, Any]] | str | bytes, 

402 **kwargs: Any, 

403 ) -> None: 

404 assert len(args) < 2, "Too many arguments." 

405 

406 value = args[0] if args else [] 

407 

408 if isinstance(value, str): 

409 super().__init__(parse_qsl(value, keep_blank_values=True), **kwargs) 

410 elif isinstance(value, bytes): 

411 super().__init__(parse_qsl(value.decode("latin-1"), keep_blank_values=True), **kwargs) 

412 else: 

413 super().__init__(*args, **kwargs) # type: ignore[arg-type] 

414 self._list = [(str(k), str(v)) for k, v in self._list] 

415 self._dict = {str(k): str(v) for k, v in self._dict.items()} 

416 

417 def __str__(self) -> str: 

418 return urlencode(self._list) 

419 

420 def __repr__(self) -> str: 

421 class_name = self.__class__.__name__ 

422 query_string = str(self) 

423 return f"{class_name}({query_string!r})" 

424 

425 

426class UploadFile: 

427 """ 

428 An uploaded file included as part of the request data. 

429 """ 

430 

431 def __init__( 

432 self, 

433 file: BinaryIO, 

434 *, 

435 size: int | None = None, 

436 filename: str | None = None, 

437 headers: Headers | None = None, 

438 ) -> None: 

439 self.filename = filename 

440 self.file = file 

441 self.size = size 

442 self.headers = headers or Headers() 

443 

444 # Capture max size from SpooledTemporaryFile if one is provided. This slightly speeds up future checks. 

445 # Note 0 means unlimited mirroring SpooledTemporaryFile's __init__ 

446 self._max_mem_size = getattr(self.file, "_max_size", 0) 

447 

448 @property 

449 def content_type(self) -> str | None: 

450 return self.headers.get("content-type", None) 

451 

452 @property 

453 def _in_memory(self) -> bool: 

454 # check for SpooledTemporaryFile._rolled 

455 rolled_to_disk = getattr(self.file, "_rolled", True) 

456 return not rolled_to_disk 

457 

458 def _will_roll(self, size_to_add: int) -> bool: 

459 # If we're not in_memory then we will always roll 

460 if not self._in_memory: 

461 return True 

462 

463 # Check for SpooledTemporaryFile._max_size 

464 future_size = self.file.tell() + size_to_add 

465 return bool(future_size > self._max_mem_size) if self._max_mem_size else False 

466 

467 async def write(self, data: bytes) -> None: 

468 new_data_len = len(data) 

469 if self.size is not None: 

470 self.size += new_data_len 

471 

472 if self._will_roll(new_data_len): 

473 await run_in_threadpool(self.file.write, data) 

474 else: 

475 self.file.write(data) 

476 

477 async def read(self, size: int = -1) -> bytes: 

478 if self._in_memory: 

479 return self.file.read(size) 

480 return await run_in_threadpool(self.file.read, size) 

481 

482 async def seek(self, offset: int) -> None: 

483 if self._in_memory: 

484 self.file.seek(offset) 

485 else: 

486 await run_in_threadpool(self.file.seek, offset) 

487 

488 async def close(self) -> None: 

489 if self._in_memory: 

490 self.file.close() 

491 else: 

492 await run_in_threadpool(self.file.close) 

493 

494 def __repr__(self) -> str: 

495 return f"{self.__class__.__name__}(filename={self.filename!r}, size={self.size!r}, headers={self.headers!r})" 

496 

497 

498class FormData(ImmutableMultiDict[str, UploadFile | str]): 

499 """ 

500 An immutable multidict, containing both file uploads and text input. 

501 """ 

502 

503 def __init__( 

504 self, 

505 *args: FormData | Mapping[str, str | UploadFile] | list[tuple[str, str | UploadFile]], 

506 **kwargs: str | UploadFile, 

507 ) -> None: 

508 super().__init__(*args, **kwargs) 

509 

510 async def close(self) -> None: 

511 for key, value in self.multi_items(): 

512 if isinstance(value, UploadFile): 

513 await value.close() 

514 

515 

516class Headers(Mapping[str, str]): 

517 """ 

518 An immutable, case-insensitive multidict. 

519 """ 

520 

521 def __init__( 

522 self, 

523 headers: Mapping[str, str] | None = None, 

524 raw: list[tuple[bytes, bytes]] | None = None, 

525 scope: MutableMapping[str, Any] | None = None, 

526 ) -> None: 

527 self._list: list[tuple[bytes, bytes]] = [] 

528 if headers is not None: 

529 assert raw is None, 'Cannot set both "headers" and "raw".' 

530 assert scope is None, 'Cannot set both "headers" and "scope".' 

531 self._list = [(key.lower().encode("latin-1"), value.encode("latin-1")) for key, value in headers.items()] 

532 elif raw is not None: 

533 assert scope is None, 'Cannot set both "raw" and "scope".' 

534 self._list = raw 

535 elif scope is not None: 

536 # scope["headers"] isn't necessarily a list 

537 # it might be a tuple or other iterable 

538 self._list = scope["headers"] = list(scope["headers"]) 

539 

540 @property 

541 def raw(self) -> list[tuple[bytes, bytes]]: 

542 return list(self._list) 

543 

544 def keys(self) -> list[str]: # type: ignore[override] 

545 return [key.decode("latin-1") for key, value in self._list] 

546 

547 def values(self) -> list[str]: # type: ignore[override] 

548 return [value.decode("latin-1") for key, value in self._list] 

549 

550 def items(self) -> list[tuple[str, str]]: # type: ignore[override] 

551 return [(key.decode("latin-1"), value.decode("latin-1")) for key, value in self._list] 

552 

553 def getlist(self, key: str) -> list[str]: 

554 get_header_key = key.lower().encode("latin-1") 

555 return [item_value.decode("latin-1") for item_key, item_value in self._list if item_key == get_header_key] 

556 

557 def mutablecopy(self) -> MutableHeaders: 

558 return MutableHeaders(raw=self._list[:]) 

559 

560 def __getitem__(self, key: str) -> str: 

561 get_header_key = key.lower().encode("latin-1") 

562 for header_key, header_value in self._list: 

563 if header_key == get_header_key: 

564 return header_value.decode("latin-1") 

565 raise KeyError(key) 

566 

567 def __contains__(self, key: Any) -> bool: 

568 get_header_key = key.lower().encode("latin-1") 

569 for header_key, header_value in self._list: 

570 if header_key == get_header_key: 

571 return True 

572 return False 

573 

574 def __iter__(self) -> Iterator[Any]: 

575 return iter(self.keys()) 

576 

577 def __len__(self) -> int: 

578 return len(self._list) 

579 

580 def __eq__(self, other: Any) -> bool: 

581 if not isinstance(other, Headers): 

582 return False 

583 return sorted(self._list) == sorted(other._list) 

584 

585 def __repr__(self) -> str: 

586 class_name = self.__class__.__name__ 

587 as_dict = dict(self.items()) 

588 if len(as_dict) == len(self): 

589 return f"{class_name}({as_dict!r})" 

590 return f"{class_name}(raw={self.raw!r})" 

591 

592 

593class MutableHeaders(Headers): 

594 def __setitem__(self, key: str, value: str) -> None: 

595 """ 

596 Set the header `key` to `value`, removing any duplicate entries. 

597 Retains insertion order. 

598 """ 

599 set_key = key.lower().encode("latin-1") 

600 set_value = value.encode("latin-1") 

601 

602 found_indexes: list[int] = [] 

603 for idx, (item_key, item_value) in enumerate(self._list): 

604 if item_key == set_key: 

605 found_indexes.append(idx) 

606 

607 for idx in reversed(found_indexes[1:]): 

608 del self._list[idx] 

609 

610 if found_indexes: 

611 idx = found_indexes[0] 

612 self._list[idx] = (set_key, set_value) 

613 else: 

614 self._list.append((set_key, set_value)) 

615 

616 def __delitem__(self, key: str) -> None: 

617 """ 

618 Remove the header `key`. 

619 """ 

620 del_key = key.lower().encode("latin-1") 

621 

622 pop_indexes: list[int] = [] 

623 for idx, (item_key, item_value) in enumerate(self._list): 

624 if item_key == del_key: 

625 pop_indexes.append(idx) 

626 

627 for idx in reversed(pop_indexes): 

628 del self._list[idx] 

629 

630 def __ior__(self, other: Mapping[str, str]) -> MutableHeaders: 

631 if not isinstance(other, Mapping): 

632 raise TypeError(f"Expected a mapping but got {other.__class__.__name__}") 

633 self.update(other) 

634 return self 

635 

636 def __or__(self, other: Mapping[str, str]) -> MutableHeaders: 

637 if not isinstance(other, Mapping): 

638 raise TypeError(f"Expected a mapping but got {other.__class__.__name__}") 

639 new = self.mutablecopy() 

640 new.update(other) 

641 return new 

642 

643 @property 

644 def raw(self) -> list[tuple[bytes, bytes]]: 

645 return self._list 

646 

647 def setdefault(self, key: str, value: str) -> str: 

648 """ 

649 If the header `key` does not exist, then set it to `value`. 

650 Returns the header value. 

651 """ 

652 set_key = key.lower().encode("latin-1") 

653 set_value = value.encode("latin-1") 

654 

655 for idx, (item_key, item_value) in enumerate(self._list): 

656 if item_key == set_key: 

657 return item_value.decode("latin-1") 

658 self._list.append((set_key, set_value)) 

659 return value 

660 

661 def update(self, other: Mapping[str, str]) -> None: 

662 for key, val in other.items(): 

663 self[key] = val 

664 

665 def append(self, key: str, value: str) -> None: 

666 """ 

667 Append a header, preserving any duplicate entries. 

668 """ 

669 append_key = key.lower().encode("latin-1") 

670 append_value = value.encode("latin-1") 

671 self._list.append((append_key, append_value)) 

672 

673 def add_vary_header(self, vary: str) -> None: 

674 self["vary"] = ", ".join([*self.getlist("vary"), vary]) 

675 

676 

677class State: 

678 """ 

679 An object that can be used to store arbitrary state. 

680 

681 Used for `request.state` and `app.state`. 

682 """ 

683 

684 _state: dict[str, Any] 

685 

686 def __init__(self, state: dict[str, Any] | None = None): 

687 if state is None: 

688 state = {} 

689 super().__setattr__("_state", state) 

690 

691 def __setattr__(self, key: Any, value: Any) -> None: 

692 self._state[key] = value 

693 

694 def __getattr__(self, key: Any) -> Any: 

695 try: 

696 return self._state[key] 

697 except KeyError: 

698 message = "'{}' object has no attribute '{}'" 

699 raise AttributeError(message.format(self.__class__.__name__, key)) 

700 

701 def __delattr__(self, key: Any) -> None: 

702 del self._state[key] 

703 

704 def __getitem__(self, key: str) -> Any: 

705 return self._state[key] 

706 

707 def __setitem__(self, key: str, value: Any) -> None: 

708 self._state[key] = value 

709 

710 def __delitem__(self, key: str) -> None: 

711 del self._state[key] 

712 

713 def __iter__(self) -> Iterator[str]: 

714 return iter(self._state) 

715 

716 def __len__(self) -> int: 

717 return len(self._state)