import inspect import logging import re import sys import typing as t from contextlib import contextmanager from copy import copy from enum import Enum CAMEL_CASE_PATTERN = re.compile("(? t.List[t.Optional[str]]: """ Perform a split on a value and return N words as a result with None used for words that don't exist. Args: value: The value to be split sep: The value to use to split on min_num_words: The minimum number of words that are going to be in the result fill_from_start: Indicates that if None values should be inserted at the start or end of the list Examples: >>> split_num_words("db.table", ".", 3) [None, 'db', 'table'] >>> split_num_words("db.table", ".", 3, fill_from_start=False) ['db', 'table', None] >>> split_num_words("db.table", ".", 1) ['db', 'table'] """ words = value.split(sep) if fill_from_start: return [None] * (min_num_words - len(words)) + words return words + [None] * (min_num_words - len(words)) def is_iterable(value: t.Any) -> bool: """ Checks if the value is an iterable but does not include strings and bytes Examples: >>> is_iterable([1,2]) True >>> is_iterable("test") False Args: value: The value to check if it is an interable Returns: Bool indicating if it is an iterable """ return hasattr(value, "__iter__") and not isinstance(value, (str, bytes)) def flatten(values: t.Iterable[t.Union[t.Iterable[t.Any], t.Any]]) -> t.Generator[t.Any, None, None]: """ Flattens a list that can contain both iterables and non-iterable elements Examples: >>> list(flatten([[1, 2], 3])) [1, 2, 3] >>> list(flatten([1, 2, 3])) [1, 2, 3] Args: values: The value to be flattened Returns: Yields non-iterable elements (not including str or byte as iterable) """ for value in values: if is_iterable(value): yield from flatten(value) else: yield value