import asyncio import logging from aiohttp import web from contextvars import ContextVar from datetime import date, datetime from inspect import signature from typing import Callable, Dict, Tuple, Type from server import config logger = logging.getLogger("app") passed_headers = ContextVar("passed_headers") def pass_headers(request: web.Request): """Filter request headers and store them in context variable.""" _excluded = set(config.EXCLUDED_HEADERS or []) passed = {k: request.headers.get(k) for k in request.headers.keys() if k.lower() not in _excluded} passed_headers.set(passed) def retry(count: int, excepted: Tuple[Type[Exception]], delay: int = 0, allowed: bool = True): """Retry decorator. Args: count: number of retries in case of catching exceptions from specified 'excepted' tuple. excepted: tuple of exceptions to catch and retry after, another exceptions will be raised without retrying. delay: delay between retries. allowed: flag of using retries logic. """ def wrapper(f: Callable): async def wrapped(*args, **kwargs): if not allowed: return await f(*args, **kwargs) for i in range(count + 1): try: return await f(*args, **kwargs) except excepted as exc: if i == count: raise logger.warning(f"{f} got {exc}: retrying {i}/{count} after {delay} sec.\n") await asyncio.sleep(delay) wrapped.__signature__ = signature(f) return wrapped return wrapper def prepare_list_arg(name: str, required: bool = True, make_unique: bool = True, result_type: Type = list): """Decorator to make list argument items unique and not none. If the list is empty and required then return empty list without calling the function. Args: name: List kwarg name. required: If the list is required (to be not empty). make_unique: Remove list arg duplicates. result_type: Result type (list or dict). """ def wrapper(f: Callable): async def wrapped(*args, **kwargs): if name in kwargs: if make_unique and isinstance(kwargs[name], list): kwargs[name] = [i for i in set(kwargs[name]) if i] if required and not kwargs[name]: return result_type() return await f(*args, **kwargs) return wrapped return wrapper def str_to_date(value: date or str or None) -> date or None: """Convert string to date. Args: value: Date as string or None. Returns: Date or None. """ if value is None or isinstance(value, date): return value return datetime.strptime(value[:10], "%Y-%m-%d").date() def date_response(f: Callable): """Parse response date.""" async def wrapped(*args, **kwargs): response = await f(*args, **kwargs) if not response: return None if isinstance(response, (list, tuple)): return [str_to_date(i) for i in response] return str_to_date(response) return wrapped def prepare_list_arg_for_names( names: list, params: Dict, required: bool = False, make_unique: bool = True, result_type: Type = list ): for name in names: if name in params and isinstance(params[name], list): if make_unique: params[name] = [i for i in set(params[name]) if i] if required and not params[name]: params[name] = result_type() return params