import inspect import time from contextlib import contextmanager from datetime import date, datetime from typing import Any, List, Optional, Tuple def deep_update(original_dict: dict, update_dict: dict) -> dict: """Extend data in original dict from update_dict. Args: original_dict: Original dict to update. update_dict: Dict with new content to insert into original. Returns: Updated original dict. """ for key, value in update_dict.items(): original_value = original_dict.get(key) if isinstance(original_value, list): original_dict[key] = original_value + value elif isinstance(original_value, dict): original_dict[key] = deep_update(original_value, value) else: original_dict[key] = value return original_dict def silent_date_parser( date_string: Optional[str], format: str = "%Y-%m-%d", skipped: Tuple[Exception] = (ValueError,) ) -> Optional[date]: """Parse date from date-string to particular format skipping selected exceptions. Returns date in successful case; returns None if date is None or in case of having exceptions. Raises if exception is not listed in skipped. """ _date = None try: _date = (date_string or None) and datetime.strptime(date_string, format).date() except skipped: pass return _date def sort_limit_dict(data: dict, limit: int) -> List[tuple]: """Sort dict by values and limit number of items. Args: data: Dict. limit: Items count limit. Returns: List of key/value pairs. """ return list(sorted(data.items(), key=lambda x: (-x[1], x[0])))[:limit] def get_arg( args: tuple, kwargs: dict, args_spec: inspect.FullArgSpec, arg_name: str, from_default: bool = False ) -> Any: """Get arg value by name from args or kwargs. Args: args: Function args. kwargs: Function kwargs. args_spec: Function args spec. arg_name: Function arg name. from_default: Allow get value from args defaults. Returns: Arg value. """ if arg_name in kwargs: return kwargs[arg_name] index = args_spec.args.index(arg_name) if len(args) > index: return args[index] if not from_default: return None return args_spec.defaults[index - len(args_spec.args)] def set_arg( args: tuple, kwargs: dict, args_spec: inspect.FullArgSpec, arg_name: str, value: Any, filter_kwargs: bool = False ) -> Tuple[tuple, dict]: """Set arg value by name to args or kwargs. Args: args: Function args. kwargs: Function kwargs. args_spec: Function args spec. arg_name: Function arg name. value: New arg value. filter_kwargs: Filter kwargs that does not exists in args spec. Returns: Modified args and kwargs. """ if filter_kwargs: modified_kwargs = {k: v for k, v in kwargs.items() if k in args_spec.args} else: modified_kwargs = dict(kwargs) index = args_spec.args.index(arg_name) if arg_name in args_spec.args else -1 if arg_name in kwargs or index < 0 or len(args) <= index: modified_kwargs[arg_name] = value return args, modified_kwargs modified_args = list(args) modified_args[index] = value return tuple(modified_args), modified_kwargs @contextmanager def timer(name): start = time.time() try: yield finally: finish = time.time() print(f"{name} finished in {finish - start} at {time.strftime('%Y:%d:%m %H:%M:%S', time.gmtime(finish))}")