← requests / src/requests/cookies.py
| 1 | """ |
| 2 | requests.cookies |
| 3 | ~~~~~~~~~~~~~~~~ |
| 4 | |
| 5 | Compatibility code to be able to use `http.cookiejar.CookieJar` with requests. |
| 6 | |
| 7 | requests.utils imports from here, so be careful with imports. |
| 8 | """ |
| 9 | |
| 10 | from __future__ import annotations |
| 11 | |
| 12 | import calendar |
| 13 | import copy |
| 14 | import time |
| 15 | from collections.abc import Iterator, MutableMapping |
| 16 | from http.cookiejar import Cookie, CookieJar, CookiePolicy |
| 17 | from typing import TYPE_CHECKING, Any, TypeVar, overload |
| 18 | |
| 19 | from ._internal_utils import to_native_string |
| 20 | from ._types import is_prepared as _is_prepared |
| 21 | from .compat import Morsel, cookielib, urlparse, urlunparse |
| 22 | |
| 23 | if TYPE_CHECKING: |
| 24 | from _typeshed import SupportsKeysAndGetItem |
| 25 | |
| 26 | from .models import PreparedRequest |
| 27 | |
| 28 | import threading |
| 29 | |
| 30 | |
| 31 | class MockRequest: |
| 32 | """Wraps a `requests.PreparedRequest` to mimic a `urllib2.Request`. |
| 33 | |
| 34 | The code in `http.cookiejar.CookieJar` expects this interface in order to correctly |
| 35 | manage cookie policies, i.e., determine whether a cookie can be set, given the |
| 36 | domains of the request and the cookie. |
| 37 | |
| 38 | The original request object is read-only. The client is responsible for collecting |
| 39 | the new headers via `get_new_headers()` and interpreting them appropriately. You |
| 40 | probably want `get_cookie_header`, defined below. |
| 41 | """ |
| 42 | |
| 43 | type: str |
| 44 | |
| 45 | def __init__(self, request: PreparedRequest) -> None: |
| 46 | assert _is_prepared(request) |
| 47 | self._r = request |
| 48 | self._new_headers: dict[str, str] = {} |
| 49 | self.type = urlparse(self._r.url).scheme |
| 50 | |
| 51 | def get_type(self) -> str: |
| 52 | return self.type |
| 53 | |
| 54 | def get_host(self) -> str: |
| 55 | return urlparse(self._r.url).netloc |
| 56 | |
| 57 | def get_origin_req_host(self) -> str: |
| 58 | return self.get_host() |
| 59 | |
| 60 | def get_full_url(self) -> str: |
| 61 | # Only return the response's URL if the user hadn't set the Host |
| 62 | # header |
| 63 | if not self._r.headers.get("Host"): |
| 64 | return self._r.url |
| 65 | # If they did set it, retrieve it and reconstruct the expected domain |
| 66 | host = to_native_string(self._r.headers["Host"], encoding="utf-8") |
| 67 | parsed = urlparse(self._r.url) |
| 68 | # Reconstruct the URL as we expect it |
| 69 | return urlunparse( |
| 70 | [ |
| 71 | parsed.scheme, |
| 72 | host, |
| 73 | parsed.path, |
| 74 | parsed.params, |
| 75 | parsed.query, |
| 76 | parsed.fragment, |
| 77 | ] |
| 78 | ) |
| 79 | |
| 80 | def is_unverifiable(self) -> bool: |
| 81 | return True |
| 82 | |
| 83 | def has_header(self, name: str) -> bool: |
| 84 | return name in self._r.headers or name in self._new_headers |
| 85 | |
| 86 | def get_header(self, name: str, default: str | None = None) -> str | None: |
| 87 | return self._r.headers.get(name, self._new_headers.get(name, default)) # type: ignore[return-value] |
| 88 | |
| 89 | def add_header(self, key: str, val: str) -> None: |
| 90 | """cookiejar has no legitimate use for this method; add it back if you find one.""" |
| 91 | raise NotImplementedError( |
| 92 | "Cookie headers should be added with add_unredirected_header()" |
| 93 | ) |
| 94 | |
| 95 | def add_unredirected_header(self, name: str, value: str) -> None: |
| 96 | self._new_headers[name] = value |
| 97 | |
| 98 | def get_new_headers(self) -> dict[str, str]: |
| 99 | return self._new_headers |
| 100 | |
| 101 | @property |
| 102 | def unverifiable(self) -> bool: |
| 103 | return self.is_unverifiable() |
| 104 | |
| 105 | @property |
| 106 | def origin_req_host(self) -> str: |
| 107 | return self.get_origin_req_host() |
| 108 | |
| 109 | @property |
| 110 | def host(self) -> str: |
| 111 | return self.get_host() |
| 112 | |
| 113 | |
| 114 | class MockResponse: |
| 115 | """Wraps a `httplib.HTTPMessage` to mimic a `urllib.addinfourl`. |
| 116 | |
| 117 | ...what? Basically, expose the parsed HTTP headers from the server response |
| 118 | the way `http.cookiejar` expects to see them. |
| 119 | """ |
| 120 | |
| 121 | def __init__(self, headers: Any) -> None: |
| 122 | """Make a MockResponse for `cookiejar` to read. |
| 123 | |
| 124 | :param headers: a httplib.HTTPMessage or analogous carrying the headers |
| 125 | """ |
| 126 | self._headers = headers |
| 127 | |
| 128 | def info(self) -> Any: |
| 129 | return self._headers |
| 130 | |
| 131 | def getheaders(self, name: str) -> Any: |
| 132 | self._headers.getheaders(name) |
| 133 | |
| 134 | |
| 135 | def extract_cookies_to_jar( |
| 136 | jar: CookieJar, request: PreparedRequest, response: Any |
| 137 | ) -> None: |
| 138 | """Extract the cookies from the response into a CookieJar. |
| 139 | |
| 140 | :param jar: http.cookiejar.CookieJar (not necessarily a RequestsCookieJar) |
| 141 | :param request: our own requests.Request object |
| 142 | :param response: urllib3.HTTPResponse object |
| 143 | """ |
| 144 | if not (hasattr(response, "_original_response") and response._original_response): |
| 145 | return |
| 146 | # the _original_response field is the wrapped httplib.HTTPResponse object, |
| 147 | req = MockRequest(request) |
| 148 | # pull out the HTTPMessage with the headers and put it in the mock: |
| 149 | res = MockResponse(response._original_response.msg) |
| 150 | jar.extract_cookies(res, req) # type: ignore[arg-type] |
| 151 | |
| 152 | |
| 153 | def get_cookie_header(jar: CookieJar, request: PreparedRequest) -> str | None: |
| 154 | """ |
| 155 | Produce an appropriate Cookie header string to be sent with `request`, or None. |
| 156 | |
| 157 | :rtype: str |
| 158 | """ |
| 159 | r = MockRequest(request) |
| 160 | jar.add_cookie_header(r) # type: ignore[arg-type] |
| 161 | return r.get_new_headers().get("Cookie") |
| 162 | |
| 163 | |
| 164 | def remove_cookie_by_name( |
| 165 | cookiejar: CookieJar, name: str, domain: str | None = None, path: str | None = None |
| 166 | ) -> None: |
| 167 | """Unsets a cookie by name, by default over all domains and paths. |
| 168 | |
| 169 | Wraps CookieJar.clear(), is O(n). |
| 170 | """ |
| 171 | clearables: list[tuple[str, str, str]] = [] |
| 172 | for cookie in cookiejar: |
| 173 | if cookie.name != name: |
| 174 | continue |
| 175 | if domain is not None and domain != cookie.domain: |
| 176 | continue |
| 177 | if path is not None and path != cookie.path: |
| 178 | continue |
| 179 | clearables.append((cookie.domain, cookie.path, cookie.name)) |
| 180 | |
| 181 | for domain, path, name in clearables: |
| 182 | cookiejar.clear(domain, path, name) |
| 183 | |
| 184 | |
| 185 | class CookieConflictError(RuntimeError): |
| 186 | """There are two cookies that meet the criteria specified in the cookie jar. |
| 187 | Use .get and .set and include domain and path args in order to be more specific. |
| 188 | """ |
| 189 | |
| 190 | |
| 191 | class RequestsCookieJar(CookieJar, MutableMapping[str, str | None]): # type: ignore[misc] |
| 192 | """Compatibility class; is a http.cookiejar.CookieJar, but exposes a dict |
| 193 | interface. |
| 194 | |
| 195 | This is the CookieJar we create by default for requests and sessions that |
| 196 | don't specify one, since some clients may expect response.cookies and |
| 197 | session.cookies to support dict operations. |
| 198 | |
| 199 | Requests does not use the dict interface internally; it's just for |
| 200 | compatibility with external client code. All requests code should work |
| 201 | out of the box with externally provided instances of ``CookieJar``, e.g. |
| 202 | ``LWPCookieJar`` and ``FileCookieJar``. |
| 203 | |
| 204 | Unlike a regular CookieJar, this class is pickleable. |
| 205 | |
| 206 | .. warning:: dictionary operations that are normally O(1) may be O(n). |
| 207 | """ |
| 208 | |
| 209 | _policy: CookiePolicy |
| 210 | |
| 211 | def get( # type: ignore[override] |
| 212 | self, |
| 213 | name: str, |
| 214 | default: str | None = None, |
| 215 | domain: str | None = None, |
| 216 | path: str | None = None, |
| 217 | ) -> str | None: |
| 218 | """Dict-like get() that also supports optional domain and path args in |
| 219 | order to resolve naming collisions from using one cookie jar over |
| 220 | multiple domains. |
| 221 | |
| 222 | .. warning:: operation is O(n), not O(1). |
| 223 | """ |
| 224 | try: |
| 225 | return self._find_no_duplicates(name, domain, path) |
| 226 | except KeyError: |
| 227 | return default |
| 228 | |
| 229 | def set( |
| 230 | self, name: str, value: str | Morsel[dict[str, str]] | None, **kwargs: Any |
| 231 | ) -> Cookie | None: |
| 232 | """Dict-like set() that also supports optional domain and path args in |
| 233 | order to resolve naming collisions from using one cookie jar over |
| 234 | multiple domains. |
| 235 | """ |
| 236 | # support client code that unsets cookies by assignment of a None value: |
| 237 | if value is None: |
| 238 | remove_cookie_by_name( |
| 239 | self, name, domain=kwargs.get("domain"), path=kwargs.get("path") |
| 240 | ) |
| 241 | return |
| 242 | |
| 243 | if isinstance(value, Morsel): |
| 244 | c = morsel_to_cookie(value) |
| 245 | else: |
| 246 | c = create_cookie(name, value, **kwargs) |
| 247 | self.set_cookie(c) |
| 248 | return c |
| 249 | |
| 250 | def iterkeys(self) -> Iterator[str]: |
| 251 | """Dict-like iterkeys() that returns an iterator of names of cookies |
| 252 | from the jar. |
| 253 | |
| 254 | .. seealso:: itervalues() and iteritems(). |
| 255 | """ |
| 256 | for cookie in iter(self): |
| 257 | yield cookie.name |
| 258 | |
| 259 | def keys(self) -> list[str]: # type: ignore[override] |
| 260 | """Dict-like keys() that returns a list of names of cookies from the |
| 261 | jar. |
| 262 | |
| 263 | .. seealso:: values() and items(). |
| 264 | """ |
| 265 | return list(self.iterkeys()) |
| 266 | |
| 267 | def itervalues(self) -> Iterator[str | None]: |
| 268 | """Dict-like itervalues() that returns an iterator of values of cookies |
| 269 | from the jar. |
| 270 | |
| 271 | .. seealso:: iterkeys() and iteritems(). |
| 272 | """ |
| 273 | for cookie in iter(self): |
| 274 | yield cookie.value |
| 275 | |
| 276 | def values(self) -> list[str | None]: # type: ignore[override] |
| 277 | """Dict-like values() that returns a list of values of cookies from the |
| 278 | jar. |
| 279 | |
| 280 | .. seealso:: keys() and items(). |
| 281 | """ |
| 282 | return list(self.itervalues()) |
| 283 | |
| 284 | def iteritems(self) -> Iterator[tuple[str, str | None]]: |
| 285 | """Dict-like iteritems() that returns an iterator of name-value tuples |
| 286 | from the jar. |
| 287 | |
| 288 | .. seealso:: iterkeys() and itervalues(). |
| 289 | """ |
| 290 | for cookie in iter(self): |
| 291 | yield cookie.name, cookie.value |
| 292 | |
| 293 | def items(self) -> list[tuple[str, str | None]]: # type: ignore[override] |
| 294 | """Dict-like items() that returns a list of name-value tuples from the |
| 295 | jar. Allows client-code to call ``dict(RequestsCookieJar)`` and get a |
| 296 | vanilla python dict of key value pairs. |
| 297 | |
| 298 | .. seealso:: keys() and values(). |
| 299 | """ |
| 300 | return list(self.iteritems()) |
| 301 | |
| 302 | def list_domains(self) -> list[str]: |
| 303 | """Utility method to list all the domains in the jar.""" |
| 304 | domains: list[str] = [] |
| 305 | for cookie in iter(self): |
| 306 | if cookie.domain not in domains: |
| 307 | domains.append(cookie.domain) |
| 308 | return domains |
| 309 | |
| 310 | def list_paths(self) -> list[str]: |
| 311 | """Utility method to list all the paths in the jar.""" |
| 312 | paths: list[str] = [] |
| 313 | for cookie in iter(self): |
| 314 | if cookie.path not in paths: |
| 315 | paths.append(cookie.path) |
| 316 | return paths |
| 317 | |
| 318 | def multiple_domains(self) -> bool: |
| 319 | """Returns True if there are multiple domains in the jar. |
| 320 | Returns False otherwise. |
| 321 | |
| 322 | :rtype: bool |
| 323 | """ |
| 324 | domains: list[str] = [] |
| 325 | for cookie in iter(self): |
| 326 | if cookie.domain is not None and cookie.domain in domains: # type: ignore[reportUnnecessaryComparison] # defensive check |
| 327 | return True |
| 328 | domains.append(cookie.domain) |
| 329 | return False # there is only one domain in jar |
| 330 | |
| 331 | def get_dict( |
| 332 | self, domain: str | None = None, path: str | None = None |
| 333 | ) -> dict[str, str | None]: |
| 334 | """Takes as an argument an optional domain and path and returns a plain |
| 335 | old Python dict of name-value pairs of cookies that meet the |
| 336 | requirements. |
| 337 | |
| 338 | :rtype: dict |
| 339 | """ |
| 340 | dictionary: dict[str, str | None] = {} |
| 341 | for cookie in iter(self): |
| 342 | if (domain is None or cookie.domain == domain) and ( |
| 343 | path is None or cookie.path == path |
| 344 | ): |
| 345 | dictionary[cookie.name] = cookie.value |
| 346 | return dictionary |
| 347 | |
| 348 | def __iter__(self) -> Iterator[Cookie]: # type: ignore[override] |
| 349 | """RequestCookieJar's __iter__ comes from CookieJar not MutableMapping.""" |
| 350 | return super().__iter__() |
| 351 | |
| 352 | def __contains__(self, name: object) -> bool: |
| 353 | try: |
| 354 | return super().__contains__(name) |
| 355 | except CookieConflictError: |
| 356 | return True |
| 357 | |
| 358 | def __getitem__(self, name: str) -> str | None: |
| 359 | """Dict-like __getitem__() for compatibility with client code. Throws |
| 360 | exception if there are more than one cookie with name. In that case, |
| 361 | use the more explicit get() method instead. |
| 362 | |
| 363 | .. warning:: operation is O(n), not O(1). |
| 364 | """ |
| 365 | return self._find_no_duplicates(name) |
| 366 | |
| 367 | def __setitem__( |
| 368 | self, name: str, value: str | Morsel[dict[str, str]] | None |
| 369 | ) -> None: |
| 370 | """Dict-like __setitem__ for compatibility with client code. Throws |
| 371 | exception if there is already a cookie of that name in the jar. In that |
| 372 | case, use the more explicit set() method instead. |
| 373 | """ |
| 374 | self.set(name, value) |
| 375 | |
| 376 | def __delitem__(self, name: str) -> None: |
| 377 | """Deletes a cookie given a name. Wraps ``http.cookiejar.CookieJar``'s |
| 378 | ``remove_cookie_by_name()``. |
| 379 | """ |
| 380 | remove_cookie_by_name(self, name) |
| 381 | |
| 382 | def set_cookie(self, cookie: Cookie, *args: Any, **kwargs: Any) -> None: |
| 383 | if ( |
| 384 | (value := cookie.value) is not None |
| 385 | and value.startswith('"') |
| 386 | and value.endswith('"') |
| 387 | ): |
| 388 | cookie.value = value.replace('\\"', "") |
| 389 | return super().set_cookie(cookie, *args, **kwargs) |
| 390 | |
| 391 | def update( # type: ignore[override] |
| 392 | self, other: CookieJar | SupportsKeysAndGetItem[str, str] |
| 393 | ) -> None: |
| 394 | """Updates this jar with cookies from another CookieJar or dict-like""" |
| 395 | if isinstance(other, cookielib.CookieJar): |
| 396 | for cookie in other: |
| 397 | self.set_cookie(copy.copy(cookie)) |
| 398 | else: |
| 399 | super().update(other) |
| 400 | |
| 401 | def _find( |
| 402 | self, name: str, domain: str | None = None, path: str | None = None |
| 403 | ) -> str | None: |
| 404 | """Requests uses this method internally to get cookie values. |
| 405 | |
| 406 | If there are conflicting cookies, _find arbitrarily chooses one. |
| 407 | See _find_no_duplicates if you want an exception thrown if there are |
| 408 | conflicting cookies. |
| 409 | |
| 410 | :param name: a string containing name of cookie |
| 411 | :param domain: (optional) string containing domain of cookie |
| 412 | :param path: (optional) string containing path of cookie |
| 413 | :return: cookie.value |
| 414 | """ |
| 415 | for cookie in iter(self): |
| 416 | if cookie.name == name: |
| 417 | if domain is None or cookie.domain == domain: |
| 418 | if path is None or cookie.path == path: |
| 419 | return cookie.value |
| 420 | |
| 421 | raise KeyError(f"name={name!r}, domain={domain!r}, path={path!r}") |
| 422 | |
| 423 | def _find_no_duplicates( |
| 424 | self, name: str, domain: str | None = None, path: str | None = None |
| 425 | ) -> str: |
| 426 | """Both ``__get_item__`` and ``get`` call this function: it's never |
| 427 | used elsewhere in Requests. |
| 428 | |
| 429 | :param name: a string containing name of cookie |
| 430 | :param domain: (optional) string containing domain of cookie |
| 431 | :param path: (optional) string containing path of cookie |
| 432 | :raises KeyError: if cookie is not found |
| 433 | :raises CookieConflictError: if there are multiple cookies |
| 434 | that match name and optionally domain and path |
| 435 | :return: cookie.value |
| 436 | """ |
| 437 | toReturn = None |
| 438 | for cookie in iter(self): |
| 439 | if cookie.name == name: |
| 440 | if domain is None or cookie.domain == domain: |
| 441 | if path is None or cookie.path == path: |
| 442 | if toReturn is not None: |
| 443 | # if there are multiple cookies that meet passed in criteria |
| 444 | raise CookieConflictError( |
| 445 | f"There are multiple cookies with name, {name!r}" |
| 446 | ) |
| 447 | # we will eventually return this as long as no cookie conflict |
| 448 | toReturn = cookie.value |
| 449 | |
| 450 | if toReturn is not None: |
| 451 | return toReturn |
| 452 | raise KeyError(f"name={name!r}, domain={domain!r}, path={path!r}") |
| 453 | |
| 454 | def __getstate__(self) -> dict[str, Any]: |
| 455 | """Unlike a normal CookieJar, this class is pickleable.""" |
| 456 | state = self.__dict__.copy() |
| 457 | # remove the unpickleable RLock object |
| 458 | state.pop("_cookies_lock") |
| 459 | return state |
| 460 | |
| 461 | def __setstate__(self, state: dict[str, Any]) -> None: |
| 462 | """Unlike a normal CookieJar, this class is pickleable.""" |
| 463 | self.__dict__.update(state) |
| 464 | if "_cookies_lock" not in self.__dict__: |
| 465 | self._cookies_lock = threading.RLock() |
| 466 | |
| 467 | def copy(self) -> RequestsCookieJar: |
| 468 | """Return a copy of this RequestsCookieJar.""" |
| 469 | new_cj = RequestsCookieJar() |
| 470 | new_cj.set_policy(self.get_policy()) |
| 471 | new_cj.update(self) |
| 472 | return new_cj |
| 473 | |
| 474 | def get_policy(self) -> CookiePolicy: |
| 475 | """Return the CookiePolicy instance used.""" |
| 476 | return self._policy |
| 477 | |
| 478 | |
| 479 | def _copy_cookie_jar(jar: CookieJar | None) -> CookieJar | None: # type: ignore[reportUnusedFunction] # cross-module usage in models.py |
| 480 | if jar is None: |
| 481 | return None |
| 482 | |
| 483 | if copy_method := getattr(jar, "copy", None): |
| 484 | # We're dealing with an instance of RequestsCookieJar |
| 485 | return copy_method() |
| 486 | # We're dealing with a generic CookieJar instance |
| 487 | new_jar = copy.copy(jar) |
| 488 | new_jar.clear() |
| 489 | for cookie in jar: |
| 490 | new_jar.set_cookie(copy.copy(cookie)) |
| 491 | return new_jar |
| 492 | |
| 493 | |
| 494 | def create_cookie(name: str, value: str, **kwargs: Any) -> Cookie: |
| 495 | """Make a cookie from underspecified parameters. |
| 496 | |
| 497 | By default, the pair of `name` and `value` will be set for the domain '' |
| 498 | and sent on every request (this is sometimes called a "supercookie"). |
| 499 | """ |
| 500 | result: dict[str, Any] = { |
| 501 | "version": 0, |
| 502 | "name": name, |
| 503 | "value": value, |
| 504 | "port": None, |
| 505 | "domain": "", |
| 506 | "path": "/", |
| 507 | "secure": False, |
| 508 | "expires": None, |
| 509 | "discard": True, |
| 510 | "comment": None, |
| 511 | "comment_url": None, |
| 512 | "rest": {"HttpOnly": None}, |
| 513 | "rfc2109": False, |
| 514 | } |
| 515 | |
| 516 | badargs = set(kwargs) - set(result) |
| 517 | if badargs: |
| 518 | raise TypeError( |
| 519 | f"create_cookie() got unexpected keyword arguments: {list(badargs)}" |
| 520 | ) |
| 521 | |
| 522 | result.update(kwargs) |
| 523 | result["port_specified"] = bool(result["port"]) |
| 524 | result["domain_specified"] = bool(result["domain"]) |
| 525 | result["domain_initial_dot"] = result["domain"].startswith(".") |
| 526 | result["path_specified"] = bool(result["path"]) |
| 527 | |
| 528 | return cookielib.Cookie(**result) |
| 529 | |
| 530 | |
| 531 | def morsel_to_cookie(morsel: Morsel[Any]) -> Cookie: |
| 532 | """Convert a Morsel object into a Cookie containing the one k/v pair.""" |
| 533 | |
| 534 | expires: int | None = None |
| 535 | if morsel["max-age"]: |
| 536 | try: |
| 537 | expires = int(time.time() + int(morsel["max-age"])) |
| 538 | except ValueError: |
| 539 | raise TypeError(f"max-age: {morsel['max-age']} must be integer") |
| 540 | elif morsel["expires"]: |
| 541 | time_template = "%a, %d-%b-%Y %H:%M:%S GMT" |
| 542 | expires = calendar.timegm(time.strptime(morsel["expires"], time_template)) |
| 543 | return create_cookie( |
| 544 | comment=morsel["comment"], |
| 545 | comment_url=bool(morsel["comment"]), |
| 546 | discard=False, |
| 547 | domain=morsel["domain"], |
| 548 | expires=expires, |
| 549 | name=morsel.key, |
| 550 | path=morsel["path"], |
| 551 | port=None, |
| 552 | rest={"HttpOnly": morsel["httponly"]}, |
| 553 | rfc2109=False, |
| 554 | secure=bool(morsel["secure"]), |
| 555 | value=morsel.value, |
| 556 | version=morsel["version"] or 0, |
| 557 | ) |
| 558 | |
| 559 | |
| 560 | _CookieJarT = TypeVar("_CookieJarT", bound=CookieJar) |
| 561 | |
| 562 | |
| 563 | @overload |
| 564 | def cookiejar_from_dict( |
| 565 | cookie_dict: dict[str, str] | None, |
| 566 | cookiejar: None = None, |
| 567 | overwrite: bool = True, |
| 568 | ) -> RequestsCookieJar: ... |
| 569 | |
| 570 | |
| 571 | @overload |
| 572 | def cookiejar_from_dict( |
| 573 | cookie_dict: dict[str, str] | None, |
| 574 | cookiejar: _CookieJarT, |
| 575 | overwrite: bool = True, |
| 576 | ) -> _CookieJarT: ... |
| 577 | |
| 578 | |
| 579 | def cookiejar_from_dict( |
| 580 | cookie_dict: dict[str, str] | None, |
| 581 | cookiejar: CookieJar | None = None, |
| 582 | overwrite: bool = True, |
| 583 | ) -> CookieJar: |
| 584 | """Returns a CookieJar from a key/value dictionary. |
| 585 | |
| 586 | :param cookie_dict: Dict of key/values to insert into CookieJar. |
| 587 | :param cookiejar: (optional) A cookiejar to add the cookies to. |
| 588 | :param overwrite: (optional) If False, will not replace cookies |
| 589 | already in the jar with new ones. |
| 590 | :rtype: CookieJar |
| 591 | """ |
| 592 | if cookiejar is None: |
| 593 | cookiejar = RequestsCookieJar() |
| 594 | |
| 595 | if cookie_dict is not None: |
| 596 | names_from_jar = [cookie.name for cookie in cookiejar] |
| 597 | for name in cookie_dict: |
| 598 | if overwrite or (name not in names_from_jar): |
| 599 | cookiejar.set_cookie(create_cookie(name, cookie_dict[name])) |
| 600 | |
| 601 | return cookiejar |
| 602 | |
| 603 | |
| 604 | def merge_cookies( |
| 605 | cookiejar: CookieJar, cookies: dict[str, str] | CookieJar | None |
| 606 | ) -> CookieJar: |
| 607 | """Add cookies to cookiejar and returns a merged CookieJar. |
| 608 | |
| 609 | :param cookiejar: CookieJar object to add the cookies to. |
| 610 | :param cookies: Dictionary or CookieJar object to be added. |
| 611 | :rtype: CookieJar |
| 612 | """ |
| 613 | if not isinstance(cookiejar, cookielib.CookieJar): # type: ignore[reportUnnecessaryIsInstance] # runtime guard |
| 614 | raise ValueError("You can only merge into CookieJar") |
| 615 | |
| 616 | if isinstance(cookies, dict): |
| 617 | cookiejar = cookiejar_from_dict(cookies, cookiejar=cookiejar, overwrite=False) |
| 618 | elif isinstance(cookies, cookielib.CookieJar): |
| 619 | if update_method := getattr(cookiejar, "update", None): |
| 620 | update_method(cookies) |
| 621 | else: |
| 622 | for cookie_in_jar in cookies: |
| 623 | cookiejar.set_cookie(cookie_in_jar) |
| 624 | |
| 625 | return cookiejar |
| 626 |