""" Core data models: URL, Headers, Cookies, Request, Response. These are the central data types that everything else in the library references. """ import json as _json from exceptions import HTTPStatusError class URL: def __init__(self, url: str): self.raw = url self.scheme, _, rest = url.partition("://") self.host, _, self.path = rest.partition("/") self.path = "/" + self.path def copy_with(self, **kwargs) -> "URL": return URL(kwargs.get("url", self.raw)) def __str__(self): return self.raw def __repr__(self): return f"URL({self.raw!r})" class Headers: def __init__(self, headers=None): self._store = {} for k, v in (headers or {}).items(): self._store[k.lower()] = v def get(self, key: str, default=None): return self._store.get(key.lower(), default) def items(self): return self._store.items() def __setitem__(self, key, value): self._store[key.lower()] = value def __getitem__(self, key): return self._store[key.lower()] def __contains__(self, key): return key.lower() in self._store class Cookies: def __init__(self, cookies=None): self._jar = dict(cookies or {}) def set(self, name: str, value: str, domain: str = "") -> None: self._jar[name] = value def get(self, name: str, default=None): return self._jar.get(name, default) def delete(self, name: str) -> None: self._jar.pop(name, None) def clear(self) -> None: self._jar.clear() def items(self): return self._jar.items() class Request: def __init__(self, method: str, url, *, headers=None, content=None, cookies=None): self.method = method.upper() self.url = URL(url) if isinstance(url, str) else url self.headers = Headers(headers) self.content = content or b"" self.cookies = Cookies(cookies) def __repr__(self): return f"" class Response: def __init__(self, status_code: int, *, headers=None, content=None, request=None): self.status_code = status_code self.headers = Headers(headers) self.content = content or b"" self.request = request @property def text(self) -> str: return self.content.decode("utf-8", errors="replace") def json(self): return _json.loads(self.content) def read(self) -> bytes: return self.content @property def is_success(self) -> bool: return 200 <= self.status_code < 300 @property def is_error(self) -> bool: return self.status_code >= 400 def raise_for_status(self) -> None: if self.is_error: message = f"{self.status_code} Error" raise HTTPStatusError(message, request=self.request, response=self) @property def cookies(self) -> Cookies: jar = Cookies() for header in self.headers.get("set-cookie", "").split(","): if "=" in header: name, _, value = header.strip().partition("=") jar.set(name.strip(), value.split(";")[0].strip()) return jar def __repr__(self): return f""