class Rederict(BaseSiteModule): def __init__(self, location=None, code=302, headers=None, methods=None, url_modifier=None): if not location and not url_modifier: raise ValueError('must be passed "location" or "url_modifier"') self._code = 302 self._headers = AutoCFG(headers or {}) self._url_modifier = url_modifier if not url_modifier: self._headers.update(location=location) for method in (methods or HTTP_METHODS): setattr(self, method.lower(), self._req_handler) async def _req_handler(self, req): headers = dict(self._headers) if self._url_modifier: headers.update(location=self._url_modifier(req)) return Response( code=self._code, headers=headers, )
class Response: """ basic class for response """ def __init__(self, data=None, headers=None, code=200, cookies=None, http_version='HTTP/1.1'): if data is not None and not isinstance(data, (str, bytes)): raise TypeError('data must be str or bytes') if headers is not None and not isinstance(headers, dict): raise TypeError('headers must be dict') if not isinstance(code, int): raise TypeError('code must be int') # response data self._data = data or b'' # dict of headers: {'Content-Type': 'text/html'} self._headers = AutoCFG(headers or {}, key_modifier=lambda x: x.lower()) # HTTP status code: 200 self._code = code # dict of dicts for Set-Cookie header: # {'uid': {'value': '123', 'flags':['HttpOnly'], 'properties': {'Path': '/'}} self._cookies = AutoCFG() if cookies: for ck in cookies: self.add_cookie(**ck) self._http_version = http_version async def _extra_prepare_data(self): return self.data async def _cache_n_zip(self, data): return data def __str__(self): return f'<Response: {self._code} {HTTP_CODE_MSG[self._code]}>' @property def code(self): return self._code @code.setter def code(self, code): if code not in HTTP_CODE_MSG: raise ValueError('Code must be in k2.utils.http.HTTP_CODE_MSG') self._code = code @property def http_version(self) -> str: return self._http_version @property def data(self): return self._data @property def cookies(self) -> AutoCFG: return self._cookies @property def headers(self) -> AutoCFG: return self._headers @data.setter def data(self, d): self._data = b'' if d is None else ( d.encode() if isinstance(d, str) else d) def add_headers(self, *args, **kwargs): if any(not isinstance(i, (dict, tuple)) for i in args): raise TypeError('HTTP-header must be tuple of dict') if any(not isinstance(kwargs[i], str) for i in kwargs): raise TypeError('HTTP-header value must be string') self._headers.update(*args, **kwargs) def add_cookie(self, name: str, value: str, *options, **kwoptions): """ name - cookie name value - value of cookie options - boolean properties like HttpOnly and Secure kwoptions - kev-value properties like Max-Age and Domain """ self._cookies[name] = { 'value': value, 'flags': set(options), 'properties': AutoCFG(kwoptions) } async def export(self) -> bytes: data = await self._cache_n_zip(data.encode() if isinstance( data := await self._extra_prepare_data(), str) else data) headers = self._headers.update_missing(STANDART_HEADERS) headers.update({'Content-Length': len(data)}) return b''.join([ '\r\n'.join([ f'{self.http_version} ' f'{204 if len(data) <= 0 and self.code in {200, 201} else self.code} ' f'{HTTP_CODE_MSG[self.code]}', *[ ''.join([key, ': ', str(value)]) for key, value in headers.items() if key and value ], *[ 'Set-Cookie: {}'.format('; '.join([ f'''{name}={quote(self._cookies[name]['value'])}''', *[ f'{key}={quote(str(val))}' for key, val in self._cookies[name]['properties'].items() ], *list(self._cookies[name]['flags']) ])) for name in self._cookies ], '\r\n', ]).encode(), data, ])