mirror of
				https://github.com/yt-dlp/yt-dlp.git
				synced 2025-10-30 22:25:19 +00:00 
			
		
		
		
	 db7b054a61
			
		
	
	db7b054a61
	
	
	
		
			
			Preference functions that take a request and a request handler instance can be registered to prioritize different request handlers per request. Authored by: coletdjnz Co-authored-by: pukkandan <pukkandan.ytdlp@gmail.com>
		
			
				
	
	
		
			565 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			565 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| from __future__ import annotations
 | |
| 
 | |
| import abc
 | |
| import copy
 | |
| import enum
 | |
| import functools
 | |
| import io
 | |
| import typing
 | |
| import urllib.parse
 | |
| import urllib.request
 | |
| import urllib.response
 | |
| from collections.abc import Iterable, Mapping
 | |
| from email.message import Message
 | |
| from http import HTTPStatus
 | |
| 
 | |
| from ._helper import make_ssl_context, wrap_request_errors
 | |
| from .exceptions import (
 | |
|     NoSupportingHandlers,
 | |
|     RequestError,
 | |
|     TransportError,
 | |
|     UnsupportedRequest,
 | |
| )
 | |
| from ..compat.types import NoneType
 | |
| from ..cookies import YoutubeDLCookieJar
 | |
| from ..utils import (
 | |
|     bug_reports_message,
 | |
|     classproperty,
 | |
|     deprecation_warning,
 | |
|     error_to_str,
 | |
|     update_url_query,
 | |
| )
 | |
| from ..utils.networking import HTTPHeaderDict, normalize_url
 | |
| 
 | |
| 
 | |
| def register_preference(*handlers: type[RequestHandler]):
 | |
|     assert all(issubclass(handler, RequestHandler) for handler in handlers)
 | |
| 
 | |
|     def outer(preference: Preference):
 | |
|         @functools.wraps(preference)
 | |
|         def inner(handler, *args, **kwargs):
 | |
|             if not handlers or isinstance(handler, handlers):
 | |
|                 return preference(handler, *args, **kwargs)
 | |
|             return 0
 | |
|         _RH_PREFERENCES.add(inner)
 | |
|         return inner
 | |
|     return outer
 | |
| 
 | |
| 
 | |
| class RequestDirector:
 | |
|     """RequestDirector class
 | |
| 
 | |
|     Helper class that, when given a request, forward it to a RequestHandler that supports it.
 | |
| 
 | |
|     Preference functions in the form of func(handler, request) -> int
 | |
|     can be registered into the `preferences` set. These are used to sort handlers
 | |
|     in order of preference.
 | |
| 
 | |
|     @param logger: Logger instance.
 | |
|     @param verbose: Print debug request information to stdout.
 | |
|     """
 | |
| 
 | |
|     def __init__(self, logger, verbose=False):
 | |
|         self.handlers: dict[str, RequestHandler] = {}
 | |
|         self.preferences: set[Preference] = set()
 | |
|         self.logger = logger  # TODO(Grub4k): default logger
 | |
|         self.verbose = verbose
 | |
| 
 | |
|     def close(self):
 | |
|         for handler in self.handlers.values():
 | |
|             handler.close()
 | |
| 
 | |
|     def add_handler(self, handler: RequestHandler):
 | |
|         """Add a handler. If a handler of the same RH_KEY exists, it will overwrite it"""
 | |
|         assert isinstance(handler, RequestHandler), 'handler must be a RequestHandler'
 | |
|         self.handlers[handler.RH_KEY] = handler
 | |
| 
 | |
|     def _get_handlers(self, request: Request) -> list[RequestHandler]:
 | |
|         """Sorts handlers by preference, given a request"""
 | |
|         preferences = {
 | |
|             rh: sum(pref(rh, request) for pref in self.preferences)
 | |
|             for rh in self.handlers.values()
 | |
|         }
 | |
|         self._print_verbose('Handler preferences for this request: %s' % ', '.join(
 | |
|             f'{rh.RH_NAME}={pref}' for rh, pref in preferences.items()))
 | |
|         return sorted(self.handlers.values(), key=preferences.get, reverse=True)
 | |
| 
 | |
|     def _print_verbose(self, msg):
 | |
|         if self.verbose:
 | |
|             self.logger.stdout(f'director: {msg}')
 | |
| 
 | |
|     def send(self, request: Request) -> Response:
 | |
|         """
 | |
|         Passes a request onto a suitable RequestHandler
 | |
|         """
 | |
|         if not self.handlers:
 | |
|             raise RequestError('No request handlers configured')
 | |
| 
 | |
|         assert isinstance(request, Request)
 | |
| 
 | |
|         unexpected_errors = []
 | |
|         unsupported_errors = []
 | |
|         for handler in self._get_handlers(request):
 | |
|             self._print_verbose(f'Checking if "{handler.RH_NAME}" supports this request.')
 | |
|             try:
 | |
|                 handler.validate(request)
 | |
|             except UnsupportedRequest as e:
 | |
|                 self._print_verbose(
 | |
|                     f'"{handler.RH_NAME}" cannot handle this request (reason: {error_to_str(e)})')
 | |
|                 unsupported_errors.append(e)
 | |
|                 continue
 | |
| 
 | |
|             self._print_verbose(f'Sending request via "{handler.RH_NAME}"')
 | |
|             try:
 | |
|                 response = handler.send(request)
 | |
|             except RequestError:
 | |
|                 raise
 | |
|             except Exception as e:
 | |
|                 self.logger.error(
 | |
|                     f'[{handler.RH_NAME}] Unexpected error: {error_to_str(e)}{bug_reports_message()}',
 | |
|                     is_error=False)
 | |
|                 unexpected_errors.append(e)
 | |
|                 continue
 | |
| 
 | |
|             assert isinstance(response, Response)
 | |
|             return response
 | |
| 
 | |
|         raise NoSupportingHandlers(unsupported_errors, unexpected_errors)
 | |
| 
 | |
| 
 | |
| _REQUEST_HANDLERS = {}
 | |
| 
 | |
| 
 | |
| def register_rh(handler):
 | |
|     """Register a RequestHandler class"""
 | |
|     assert issubclass(handler, RequestHandler), f'{handler} must be a subclass of RequestHandler'
 | |
|     assert handler.RH_KEY not in _REQUEST_HANDLERS, f'RequestHandler {handler.RH_KEY} already registered'
 | |
|     _REQUEST_HANDLERS[handler.RH_KEY] = handler
 | |
|     return handler
 | |
| 
 | |
| 
 | |
| class Features(enum.Enum):
 | |
|     ALL_PROXY = enum.auto()
 | |
|     NO_PROXY = enum.auto()
 | |
| 
 | |
| 
 | |
| class RequestHandler(abc.ABC):
 | |
| 
 | |
|     """Request Handler class
 | |
| 
 | |
|     Request handlers are class that, given a Request,
 | |
|     process the request from start to finish and return a Response.
 | |
| 
 | |
|     Concrete subclasses need to redefine the _send(request) method,
 | |
|     which handles the underlying request logic and returns a Response.
 | |
| 
 | |
|     RH_NAME class variable may contain a display name for the RequestHandler.
 | |
|     By default, this is generated from the class name.
 | |
| 
 | |
|     The concrete request handler MUST have "RH" as the suffix in the class name.
 | |
| 
 | |
|     All exceptions raised by a RequestHandler should be an instance of RequestError.
 | |
|     Any other exception raised will be treated as a handler issue.
 | |
| 
 | |
|     If a Request is not supported by the handler, an UnsupportedRequest
 | |
|     should be raised with a reason.
 | |
| 
 | |
|     By default, some checks are done on the request in _validate() based on the following class variables:
 | |
|     - `_SUPPORTED_URL_SCHEMES`: a tuple of supported url schemes.
 | |
|         Any Request with an url scheme not in this list will raise an UnsupportedRequest.
 | |
| 
 | |
|     - `_SUPPORTED_PROXY_SCHEMES`: a tuple of support proxy url schemes. Any Request that contains
 | |
|         a proxy url with an url scheme not in this list will raise an UnsupportedRequest.
 | |
| 
 | |
|     - `_SUPPORTED_FEATURES`: a tuple of supported features, as defined in Features enum.
 | |
| 
 | |
|     The above may be set to None to disable the checks.
 | |
| 
 | |
|     Parameters:
 | |
|     @param logger: logger instance
 | |
|     @param headers: HTTP Headers to include when sending requests.
 | |
|     @param cookiejar: Cookiejar to use for requests.
 | |
|     @param timeout: Socket timeout to use when sending requests.
 | |
|     @param proxies: Proxies to use for sending requests.
 | |
|     @param source_address: Client-side IP address to bind to for requests.
 | |
|     @param verbose: Print debug request and traffic information to stdout.
 | |
|     @param prefer_system_certs: Whether to prefer system certificates over other means (e.g. certifi).
 | |
|     @param client_cert: SSL client certificate configuration.
 | |
|             dict with {client_certificate, client_certificate_key, client_certificate_password}
 | |
|     @param verify: Verify SSL certificates
 | |
|     @param legacy_ssl_support: Enable legacy SSL options such as legacy server connect and older cipher support.
 | |
| 
 | |
|     Some configuration options may be available for individual Requests too. In this case,
 | |
|     either the Request configuration option takes precedence or they are merged.
 | |
| 
 | |
|     Requests may have additional optional parameters defined as extensions.
 | |
|      RequestHandler subclasses may choose to support custom extensions.
 | |
| 
 | |
|     If an extension is supported, subclasses should extend _check_extensions(extensions)
 | |
|     to pop and validate the extension.
 | |
|     - Extensions left in `extensions` are treated as unsupported and UnsupportedRequest will be raised.
 | |
| 
 | |
|     The following extensions are defined for RequestHandler:
 | |
|     - `cookiejar`: Cookiejar to use for this request.
 | |
|     - `timeout`: socket timeout to use for this request.
 | |
|     To enable these, add extensions.pop('<extension>', None) to _check_extensions
 | |
| 
 | |
|     Apart from the url protocol, proxies dict may contain the following keys:
 | |
|     - `all`: proxy to use for all protocols. Used as a fallback if no proxy is set for a specific protocol.
 | |
|     - `no`: comma seperated list of hostnames (optionally with port) to not use a proxy for.
 | |
|     Note: a RequestHandler may not support these, as defined in `_SUPPORTED_FEATURES`.
 | |
| 
 | |
|     """
 | |
| 
 | |
|     _SUPPORTED_URL_SCHEMES = ()
 | |
|     _SUPPORTED_PROXY_SCHEMES = ()
 | |
|     _SUPPORTED_FEATURES = ()
 | |
| 
 | |
|     def __init__(
 | |
|         self, *,
 | |
|         logger,  # TODO(Grub4k): default logger
 | |
|         headers: HTTPHeaderDict = None,
 | |
|         cookiejar: YoutubeDLCookieJar = None,
 | |
|         timeout: float | int | None = None,
 | |
|         proxies: dict = None,
 | |
|         source_address: str = None,
 | |
|         verbose: bool = False,
 | |
|         prefer_system_certs: bool = False,
 | |
|         client_cert: dict[str, str | None] = None,
 | |
|         verify: bool = True,
 | |
|         legacy_ssl_support: bool = False,
 | |
|         **_,
 | |
|     ):
 | |
| 
 | |
|         self._logger = logger
 | |
|         self.headers = headers or {}
 | |
|         self.cookiejar = cookiejar if cookiejar is not None else YoutubeDLCookieJar()
 | |
|         self.timeout = float(timeout or 20)
 | |
|         self.proxies = proxies or {}
 | |
|         self.source_address = source_address
 | |
|         self.verbose = verbose
 | |
|         self.prefer_system_certs = prefer_system_certs
 | |
|         self._client_cert = client_cert or {}
 | |
|         self.verify = verify
 | |
|         self.legacy_ssl_support = legacy_ssl_support
 | |
|         super().__init__()
 | |
| 
 | |
|     def _make_sslcontext(self):
 | |
|         return make_ssl_context(
 | |
|             verify=self.verify,
 | |
|             legacy_support=self.legacy_ssl_support,
 | |
|             use_certifi=not self.prefer_system_certs,
 | |
|             **self._client_cert,
 | |
|         )
 | |
| 
 | |
|     def _merge_headers(self, request_headers):
 | |
|         return HTTPHeaderDict(self.headers, request_headers)
 | |
| 
 | |
|     def _check_url_scheme(self, request: Request):
 | |
|         scheme = urllib.parse.urlparse(request.url).scheme.lower()
 | |
|         if self._SUPPORTED_URL_SCHEMES is not None and scheme not in self._SUPPORTED_URL_SCHEMES:
 | |
|             raise UnsupportedRequest(f'Unsupported url scheme: "{scheme}"')
 | |
|         return scheme  # for further processing
 | |
| 
 | |
|     def _check_proxies(self, proxies):
 | |
|         for proxy_key, proxy_url in proxies.items():
 | |
|             if proxy_url is None:
 | |
|                 continue
 | |
|             if proxy_key == 'no':
 | |
|                 if self._SUPPORTED_FEATURES is not None and Features.NO_PROXY not in self._SUPPORTED_FEATURES:
 | |
|                     raise UnsupportedRequest('"no" proxy is not supported')
 | |
|                 continue
 | |
|             if (
 | |
|                 proxy_key == 'all'
 | |
|                 and self._SUPPORTED_FEATURES is not None
 | |
|                 and Features.ALL_PROXY not in self._SUPPORTED_FEATURES
 | |
|             ):
 | |
|                 raise UnsupportedRequest('"all" proxy is not supported')
 | |
| 
 | |
|             # Unlikely this handler will use this proxy, so ignore.
 | |
|             # This is to allow a case where a proxy may be set for a protocol
 | |
|             # for one handler in which such protocol (and proxy) is not supported by another handler.
 | |
|             if self._SUPPORTED_URL_SCHEMES is not None and proxy_key not in (*self._SUPPORTED_URL_SCHEMES, 'all'):
 | |
|                 continue
 | |
| 
 | |
|             if self._SUPPORTED_PROXY_SCHEMES is None:
 | |
|                 # Skip proxy scheme checks
 | |
|                 continue
 | |
| 
 | |
|             try:
 | |
|                 if urllib.request._parse_proxy(proxy_url)[0] is None:
 | |
|                     # Scheme-less proxies are not supported
 | |
|                     raise UnsupportedRequest(f'Proxy "{proxy_url}" missing scheme')
 | |
|             except ValueError as e:
 | |
|                 # parse_proxy may raise on some invalid proxy urls such as "/a/b/c"
 | |
|                 raise UnsupportedRequest(f'Invalid proxy url "{proxy_url}": {e}')
 | |
| 
 | |
|             scheme = urllib.parse.urlparse(proxy_url).scheme.lower()
 | |
|             if scheme not in self._SUPPORTED_PROXY_SCHEMES:
 | |
|                 raise UnsupportedRequest(f'Unsupported proxy type: "{scheme}"')
 | |
| 
 | |
|     def _check_extensions(self, extensions):
 | |
|         """Check extensions for unsupported extensions. Subclasses should extend this."""
 | |
|         assert isinstance(extensions.get('cookiejar'), (YoutubeDLCookieJar, NoneType))
 | |
|         assert isinstance(extensions.get('timeout'), (float, int, NoneType))
 | |
| 
 | |
|     def _validate(self, request):
 | |
|         self._check_url_scheme(request)
 | |
|         self._check_proxies(request.proxies or self.proxies)
 | |
|         extensions = request.extensions.copy()
 | |
|         self._check_extensions(extensions)
 | |
|         if extensions:
 | |
|             # TODO: add support for optional extensions
 | |
|             raise UnsupportedRequest(f'Unsupported extensions: {", ".join(extensions.keys())}')
 | |
| 
 | |
|     @wrap_request_errors
 | |
|     def validate(self, request: Request):
 | |
|         if not isinstance(request, Request):
 | |
|             raise TypeError('Expected an instance of Request')
 | |
|         self._validate(request)
 | |
| 
 | |
|     @wrap_request_errors
 | |
|     def send(self, request: Request) -> Response:
 | |
|         if not isinstance(request, Request):
 | |
|             raise TypeError('Expected an instance of Request')
 | |
|         return self._send(request)
 | |
| 
 | |
|     @abc.abstractmethod
 | |
|     def _send(self, request: Request):
 | |
|         """Handle a request from start to finish. Redefine in subclasses."""
 | |
|         pass
 | |
| 
 | |
|     def close(self):
 | |
|         pass
 | |
| 
 | |
|     @classproperty
 | |
|     def RH_NAME(cls):
 | |
|         return cls.__name__[:-2]
 | |
| 
 | |
|     @classproperty
 | |
|     def RH_KEY(cls):
 | |
|         assert cls.__name__.endswith('RH'), 'RequestHandler class names must end with "RH"'
 | |
|         return cls.__name__[:-2]
 | |
| 
 | |
|     def __enter__(self):
 | |
|         return self
 | |
| 
 | |
|     def __exit__(self, *args):
 | |
|         self.close()
 | |
| 
 | |
| 
 | |
| class Request:
 | |
|     """
 | |
|     Represents a request to be made.
 | |
|     Partially backwards-compatible with urllib.request.Request.
 | |
| 
 | |
|     @param url: url to send. Will be sanitized.
 | |
|     @param data: payload data to send. Must be bytes, iterable of bytes, a file-like object or None
 | |
|     @param headers: headers to send.
 | |
|     @param proxies: proxy dict mapping of proto:proxy to use for the request and any redirects.
 | |
|     @param query: URL query parameters to update the url with.
 | |
|     @param method: HTTP method to use. If no method specified, will use POST if payload data is present else GET
 | |
|     @param extensions: Dictionary of Request extensions to add, as supported by handlers.
 | |
|     """
 | |
| 
 | |
|     def __init__(
 | |
|             self,
 | |
|             url: str,
 | |
|             data: RequestData = None,
 | |
|             headers: typing.Mapping = None,
 | |
|             proxies: dict = None,
 | |
|             query: dict = None,
 | |
|             method: str = None,
 | |
|             extensions: dict = None
 | |
|     ):
 | |
| 
 | |
|         self._headers = HTTPHeaderDict()
 | |
|         self._data = None
 | |
| 
 | |
|         if query:
 | |
|             url = update_url_query(url, query)
 | |
| 
 | |
|         self.url = url
 | |
|         self.method = method
 | |
|         if headers:
 | |
|             self.headers = headers
 | |
|         self.data = data  # note: must be done after setting headers
 | |
|         self.proxies = proxies or {}
 | |
|         self.extensions = extensions or {}
 | |
| 
 | |
|     @property
 | |
|     def url(self):
 | |
|         return self._url
 | |
| 
 | |
|     @url.setter
 | |
|     def url(self, url):
 | |
|         if not isinstance(url, str):
 | |
|             raise TypeError('url must be a string')
 | |
|         elif url.startswith('//'):
 | |
|             url = 'http:' + url
 | |
|         self._url = normalize_url(url)
 | |
| 
 | |
|     @property
 | |
|     def method(self):
 | |
|         return self._method or ('POST' if self.data is not None else 'GET')
 | |
| 
 | |
|     @method.setter
 | |
|     def method(self, method):
 | |
|         if method is None:
 | |
|             self._method = None
 | |
|         elif isinstance(method, str):
 | |
|             self._method = method.upper()
 | |
|         else:
 | |
|             raise TypeError('method must be a string')
 | |
| 
 | |
|     @property
 | |
|     def data(self):
 | |
|         return self._data
 | |
| 
 | |
|     @data.setter
 | |
|     def data(self, data: RequestData):
 | |
|         # Try catch some common mistakes
 | |
|         if data is not None and (
 | |
|             not isinstance(data, (bytes, io.IOBase, Iterable)) or isinstance(data, (str, Mapping))
 | |
|         ):
 | |
|             raise TypeError('data must be bytes, iterable of bytes, or a file-like object')
 | |
| 
 | |
|         if data == self._data and self._data is None:
 | |
|             self.headers.pop('Content-Length', None)
 | |
| 
 | |
|         # https://docs.python.org/3/library/urllib.request.html#urllib.request.Request.data
 | |
|         if data != self._data:
 | |
|             if self._data is not None:
 | |
|                 self.headers.pop('Content-Length', None)
 | |
|             self._data = data
 | |
| 
 | |
|         if self._data is None:
 | |
|             self.headers.pop('Content-Type', None)
 | |
| 
 | |
|         if 'Content-Type' not in self.headers and self._data is not None:
 | |
|             self.headers['Content-Type'] = 'application/x-www-form-urlencoded'
 | |
| 
 | |
|     @property
 | |
|     def headers(self) -> HTTPHeaderDict:
 | |
|         return self._headers
 | |
| 
 | |
|     @headers.setter
 | |
|     def headers(self, new_headers: Mapping):
 | |
|         """Replaces headers of the request. If not a CaseInsensitiveDict, it will be converted to one."""
 | |
|         if isinstance(new_headers, HTTPHeaderDict):
 | |
|             self._headers = new_headers
 | |
|         elif isinstance(new_headers, Mapping):
 | |
|             self._headers = HTTPHeaderDict(new_headers)
 | |
|         else:
 | |
|             raise TypeError('headers must be a mapping')
 | |
| 
 | |
|     def update(self, url=None, data=None, headers=None, query=None):
 | |
|         self.data = data if data is not None else self.data
 | |
|         self.headers.update(headers or {})
 | |
|         self.url = update_url_query(url or self.url, query or {})
 | |
| 
 | |
|     def copy(self):
 | |
|         return self.__class__(
 | |
|             url=self.url,
 | |
|             headers=copy.deepcopy(self.headers),
 | |
|             proxies=copy.deepcopy(self.proxies),
 | |
|             data=self._data,
 | |
|             extensions=copy.copy(self.extensions),
 | |
|             method=self._method,
 | |
|         )
 | |
| 
 | |
| 
 | |
| HEADRequest = functools.partial(Request, method='HEAD')
 | |
| PUTRequest = functools.partial(Request, method='PUT')
 | |
| 
 | |
| 
 | |
| class Response(io.IOBase):
 | |
|     """
 | |
|     Base class for HTTP response adapters.
 | |
| 
 | |
|     By default, it provides a basic wrapper for a file-like response object.
 | |
| 
 | |
|     Interface partially backwards-compatible with addinfourl and http.client.HTTPResponse.
 | |
| 
 | |
|     @param fp: Original, file-like, response.
 | |
|     @param url: URL that this is a response of.
 | |
|     @param headers: response headers.
 | |
|     @param status: Response HTTP status code. Default is 200 OK.
 | |
|     @param reason: HTTP status reason. Will use built-in reasons based on status code if not provided.
 | |
|     """
 | |
| 
 | |
|     def __init__(
 | |
|             self,
 | |
|             fp: typing.IO,
 | |
|             url: str,
 | |
|             headers: Mapping[str, str],
 | |
|             status: int = 200,
 | |
|             reason: str = None):
 | |
| 
 | |
|         self.fp = fp
 | |
|         self.headers = Message()
 | |
|         for name, value in headers.items():
 | |
|             self.headers.add_header(name, value)
 | |
|         self.status = status
 | |
|         self.url = url
 | |
|         try:
 | |
|             self.reason = reason or HTTPStatus(status).phrase
 | |
|         except ValueError:
 | |
|             self.reason = None
 | |
| 
 | |
|     def readable(self):
 | |
|         return self.fp.readable()
 | |
| 
 | |
|     def read(self, amt: int = None) -> bytes:
 | |
|         # Expected errors raised here should be of type RequestError or subclasses.
 | |
|         # Subclasses should redefine this method with more precise error handling.
 | |
|         try:
 | |
|             return self.fp.read(amt)
 | |
|         except Exception as e:
 | |
|             raise TransportError(cause=e) from e
 | |
| 
 | |
|     def close(self):
 | |
|         self.fp.close()
 | |
|         return super().close()
 | |
| 
 | |
|     def get_header(self, name, default=None):
 | |
|         """Get header for name.
 | |
|         If there are multiple matching headers, return all seperated by comma."""
 | |
|         headers = self.headers.get_all(name)
 | |
|         if not headers:
 | |
|             return default
 | |
|         if name.title() == 'Set-Cookie':
 | |
|             # Special case, only get the first one
 | |
|             # https://www.rfc-editor.org/rfc/rfc9110.html#section-5.3-4.1
 | |
|             return headers[0]
 | |
|         return ', '.join(headers)
 | |
| 
 | |
|     # The following methods are for compatability reasons and are deprecated
 | |
|     @property
 | |
|     def code(self):
 | |
|         deprecation_warning('Response.code is deprecated, use Response.status', stacklevel=2)
 | |
|         return self.status
 | |
| 
 | |
|     def getcode(self):
 | |
|         deprecation_warning('Response.getcode() is deprecated, use Response.status', stacklevel=2)
 | |
|         return self.status
 | |
| 
 | |
|     def geturl(self):
 | |
|         deprecation_warning('Response.geturl() is deprecated, use Response.url', stacklevel=2)
 | |
|         return self.url
 | |
| 
 | |
|     def info(self):
 | |
|         deprecation_warning('Response.info() is deprecated, use Response.headers', stacklevel=2)
 | |
|         return self.headers
 | |
| 
 | |
|     def getheader(self, name, default=None):
 | |
|         deprecation_warning('Response.getheader() is deprecated, use Response.get_header', stacklevel=2)
 | |
|         return self.get_header(name, default)
 | |
| 
 | |
| 
 | |
| if typing.TYPE_CHECKING:
 | |
|     RequestData = bytes | Iterable[bytes] | typing.IO | None
 | |
|     Preference = typing.Callable[[RequestHandler, Request], int]
 | |
| 
 | |
| _RH_PREFERENCES: set[Preference] = set()
 |