Request/Response ============================= Request - HTTP request. Response - HTTP response. Introduction ------------ In computer science, request-response or request-replica is one of the basic methods used by computers to communicate with each other on a network, in which the first computer sends a request for some data and the second one responds to the request. More specifically, it is a messaging pattern in which a requester sends a request message to a responder system, which receives and processes the request, ultimately returning a message in response. This is similar to a telephone call, in which the caller must wait for the recipient to pick up the phone before anything can be discussed. Request ------- **Request** is a request that contains data for interaction between the client and the API: base URL, endpoint, method to use, headers, etc. .. code:: python class Request: """ This class describes a request. """ def __init__(self, environ: dict, settings: Settings): """ Constructs a new instance. :param environ: The environ :type environ: dict """ self.environ: dict = environ self.settings: Settings = settings self.method: str = self.environ["REQUEST_METHOD"] self.path: str = self.environ["PATH_INFO"] self.GET: dict = self._build_get_params_dict(self.environ["QUERY_STRING"]) self.POST: dict = self._build_post_params_dict(self.environ["wsgi.input"].read()) self.user_agent: str = self.environ["HTTP_USER_AGENT"] self.extra: dict = {} logger.debug(f"New request created: {self.method} {self.path}") def __getattr__(self, item: Any) -> Union[Any, None]: """ Magic method for get attrs (from extra) :param item: The item :type item: Any :returns: Item from self.extra or None :rtype: Union[Any, None] """ return self.extra.get(item, None) def _build_get_params_dict(self, raw_params: str): """ Builds a get parameters dictionary. :param raw_params: The raw parameters :type raw_params: str """ return parse_qs(raw_params) def _build_post_params_dict(self, raw_params: bytes): """ Builds a post parameters dictionary. :param raw_params: The raw parameters :type raw_params: bytes """ return parse_qs(raw_params.decode()) Request requires the following arguments to create: - environ (dictionary) - web environment (generated by gunicorn) - settings (pyechonext.config.Settings dataclass object) Request has the following public attributes: - environ (dictionary) - web environment - settings (pyechonext.config.Settings dataclass object) - method (string) - http method - path (string) - path - GET (dictionary) - get request parameters - POST (dictionary) - post request parameters - user_agent (string) - User-Agent - extra (dictionary) - additional parameters (for example for middleware) Request also has the following methods: - ``__getattr__`` - magic descriptor method for getting attributes (to get elements from the extra attribute) - ``_build_get_params_dict`` - private method for parsing get request parameters - ``_build_post_params_dict`` - private method for parsing post request parameters Response -------- **Response** is a response that contains the data returned by the server, including content, status code, and headers. .. code:: python import json from typing import Dict, Iterable, Union, Any, List, Tuple, Optional from socks import method from loguru import logger from pyechonext.request import Request class Response: """ This dataclass describes a response. """ default_content_type: str = "text/html" default_charset: str = "UTF-8" unicode_errors: str = "strict" default_conditional_response: bool = False default_body_encoding: str = "UTF-8" def __init__( self, request: Request, use_i18n: bool = False, status_code: Optional[int] = 200, body: Optional[str] = None, headers: Optional[Dict[str, str]] = {}, content_type: Optional[str] = None, charset: Optional[str] = None, **kwargs, ): """ Constructs a new instance. :param request: The request :type request: Request :param use_i18n: The use i 18 n :type use_i18n: bool :param status_code: The status code :type status_code: int :param body: The body :type body: str :param headers: The headers :type headers: Dict[str, str] :param content_type: The content type :type content_type: str :param charset: The charset :type charset: str :param kwargs: The keywords arguments :type kwargs: dictionary """ if status_code == 200: self.status_code: str = "200 OK" else: self.status_code: str = str(status_code) if content_type is None: self.content_type: str = self.default_content_type else: self.content_type: str = content_type if charset is None: self.charset: str = self.default_charset else: self.charset: str = charset if body is not None: self.body: str = body else: self.body: str = "" self._headerslist: list = headers self._added_headers: list = [] self.request: Request = request self.extra: dict = {} self.use_i18n: bool = use_i18n self.i18n_kwargs = kwargs self._update_headers() def __getattr__(self, item: Any) -> Union[Any, None]: """ Magic method for get attrs (from extra) :param item: The item :type item: Any :returns: Item from self.extra or None :rtype: Union[Any, None] """ return self.extra.get(item, None) def _structuring_headers(self, environ): headers = { "Host": environ["HTTP_HOST"], "Accept": environ["HTTP_ACCEPT"], "User-Agent": environ["HTTP_USER_AGENT"], } for name, value in headers.items(): self._headerslist.append((name, value)) for header_tuple in self._added_headers: self._headerslist.append(header_tuple) def _update_headers(self) -> None: """ Sets the headers by environ. :param environ: The environ :type environ: dict """ self._headerslist = [ ("Content-Type", f"{self.content_type}; charset={self.charset}"), ("Content-Length", str(len(self.body))), ] def add_headers(self, headers: List[Tuple[str, str]]): """ Adds new headers. :param headers: The headers :type headers: List[Tuple[str, str]] """ for header in headers: self._added_headers.append(header) def _encode_body(self): """ Encodes a body. """ if self.content_type.split("/")[-1] == "json": self.body = str(self.json) try: self.body = self.body.encode("UTF-8") except AttributeError: self.body = str(self.body).encode("UTF-8") def __call__(self, environ: dict, start_response: method) -> Iterable: """ Makes the Response object callable. :param environ: The environ :type environ: dict :param start_response: The start response :type start_response: method :returns: response body :rtype: Iterable """ self._encode_body() self._update_headers() self._structuring_headers(environ) logger.debug( f"[{environ['REQUEST_METHOD']} {self.status_code}] Run response: {self.content_type}" ) start_response(status=self.status_code, headers=self._headerslist) return iter([self.body]) @property def json(self) -> dict: """ Parse request body as JSON. :returns: json body :rtype: dict """ if self.body: if self.content_type.split("/")[-1] == "json": return json.dumps(self.body) else: return json.dumps(self.body.decode("UTF-8")) return {} def __repr__(self): """ Returns a unambiguous string representation of the object (for debug...). :returns: String representation of the object. :rtype: str """ return f"<{self.__class__.__name__} at 0x{abs(id(self)):x} {self.status_code}>" Response has the following arguments: - request (request class object) - request - [optional] status_code (integer value) - response status code - [optional] body (string) - response body - [optional] headers (dictionary) - response headers - [optional] content_type (string) - response content type - [optional] charset (string) - response encoding - [optional] use_i18n (boolean value) - whether to use i18n (default False) Response has the following attributes: - status_code (string) - status code (default “200 OK”) - content_type (string) - content type (defaults to default_content_type) - charset (string) - encoding (defaults to default_charset) - body (string) - body of the answer (defaults to the empty string) - ``_headerslist`` (list) - private list of response headers - ``_added_headers`` (list) - private list of added response headers - request (request class object) - request - extra (dictionary) - additional parameters Response has the following methods: - ``__getattr__`` - magic descriptor method for getting attributes (to get elements from the extra attribute) - ``_structuring_headers`` - private method for structuring headers from the web environment - ``_update_headers`` - private method for updating (overwriting) header lists - ``add_headers`` - public method for adding headers - ``_encode_body`` - response body encoding - ``__call__`` - magic method, makes the Response object callable - ``json`` - class property for receiving the response body as json