sqlglot.helper API documentation

Edit on GitHub

# sqlglot.helper

View Source

      1from __future__ import annotations
      2
      3import inspect
      4import logging
      5import re
      6import sys
      7import typing as t
      8from collections.abc import Collection
      9from contextlib import contextmanager
     10from copy import copy
     11from enum import Enum
     12
     13if t.TYPE_CHECKING:
     14    from sqlglot import exp
     15    from sqlglot.expressions import Expression
     16
     17    T = t.TypeVar("T")
     18    E = t.TypeVar("E", bound=Expression)
     19
     20CAMEL_CASE_PATTERN = re.compile("(?<!^)(?=[A-Z])")
     21PYTHON_VERSION = sys.version_info[:2]
     22logger = logging.getLogger("sqlglot")
     23
     24
     25class AutoName(Enum):
     26    """This is used for creating enum classes where `auto()` is the string form of the corresponding value's name."""
     27
     28    def _generate_next_value_(name, _start, _count, _last_values):  # type: ignore
     29        return name
     30
     31
     32def seq_get(seq: t.Sequence[T], index: int) -> t.Optional[T]:
     33    """Returns the value in `seq` at position `index`, or `None` if `index` is out of bounds."""
     34    try:
     35        return seq[index]
     36    except IndexError:
     37        return None
     38
     39
     40@t.overload
     41def ensure_list(value: t.Collection[T]) -> t.List[T]:
     42    ...
     43
     44
     45@t.overload
     46def ensure_list(value: T) -> t.List[T]:
     47    ...
     48
     49
     50def ensure_list(value):
     51    """
     52    Ensures that a value is a list, otherwise casts or wraps it into one.
     53
     54    Args:
     55        value: the value of interest.
     56
     57    Returns:
     58        The value cast as a list if it's a list or a tuple, or else the value wrapped in a list.
     59    """
     60    if value is None:
     61        return []
     62    if isinstance(value, (list, tuple)):
     63        return list(value)
     64
     65    return [value]
     66
     67
     68@t.overload
     69def ensure_collection(value: t.Collection[T]) -> t.Collection[T]:
     70    ...
     71
     72
     73@t.overload
     74def ensure_collection(value: T) -> t.Collection[T]:
     75    ...
     76
     77
     78def ensure_collection(value):
     79    """
     80    Ensures that a value is a collection (excluding `str` and `bytes`), otherwise wraps it into a list.
     81
     82    Args:
     83        value: the value of interest.
     84
     85    Returns:
     86        The value if it's a collection, or else the value wrapped in a list.
     87    """
     88    if value is None:
     89        return []
     90    return (
     91        value if isinstance(value, Collection) and not isinstance(value, (str, bytes)) else [value]
     92    )
     93
     94
     95def csv(*args, sep: str = ", ") -> str:
     96    """
     97    Formats any number of string arguments as CSV.
     98
     99    Args:
    100        args: the string arguments to format.
    101        sep: the argument separator.
    102
    103    Returns:
    104        The arguments formatted as a CSV string.
    105    """
    106    return sep.join(arg for arg in args if arg)
    107
    108
    109def subclasses(
    110    module_name: str,
    111    classes: t.Type | t.Tuple[t.Type, ...],
    112    exclude: t.Type | t.Tuple[t.Type, ...] = (),
    113) -> t.List[t.Type]:
    114    """
    115    Returns all subclasses for a collection of classes, possibly excluding some of them.
    116
    117    Args:
    118        module_name: the name of the module to search for subclasses in.
    119        classes: class(es) we want to find the subclasses of.
    120        exclude: class(es) we want to exclude from the returned list.
    121
    122    Returns:
    123        The target subclasses.
    124    """
    125    return [
    126        obj
    127        for _, obj in inspect.getmembers(
    128            sys.modules[module_name],
    129            lambda obj: inspect.isclass(obj) and issubclass(obj, classes) and obj not in exclude,
    130        )
    131    ]
    132
    133
    134def apply_index_offset(expressions: t.List[t.Optional[E]], offset: int) -> t.List[t.Optional[E]]:
    135    """
    136    Applies an offset to a given integer literal expression.
    137
    138    Args:
    139        expressions: the expression the offset will be applied to, wrapped in a list.
    140        offset: the offset that will be applied.
    141
    142    Returns:
    143        The original expression with the offset applied to it, wrapped in a list. If the provided
    144        `expressions` argument contains more than one expressions, it's returned unaffected.
    145    """
    146    if not offset or len(expressions) != 1:
    147        return expressions
    148
    149    expression = expressions[0]
    150
    151    if expression and expression.is_int:
    152        expression = expression.copy()
    153        logger.warning("Applying array index offset (%s)", offset)
    154        expression.args["this"] = str(int(expression.this) + offset)  # type: ignore
    155        return [expression]
    156
    157    return expressions
    158
    159
    160def camel_to_snake_case(name: str) -> str:
    161    """Converts `name` from camelCase to snake_case and returns the result."""
    162    return CAMEL_CASE_PATTERN.sub("_", name).upper()
    163
    164
    165def while_changing(expression: Expression, func: t.Callable[[Expression], E]) -> E:
    166    """
    167    Applies a transformation to a given expression until a fix point is reached.
    168
    169    Args:
    170        expression: the expression to be transformed.
    171        func: the transformation to be applied.
    172
    173    Returns:
    174        The transformed expression.
    175    """
    176    while True:
    177        for n, *_ in reversed(tuple(expression.walk())):
    178            n._hash = hash(n)
    179        start = hash(expression)
    180        expression = func(expression)
    181
    182        for n, *_ in expression.walk():
    183            n._hash = None
    184        if start == hash(expression):
    185            break
    186    return expression
    187
    188
    189def tsort(dag: t.Dict[T, t.List[T]]) -> t.List[T]:
    190    """
    191    Sorts a given directed acyclic graph in topological order.
    192
    193    Args:
    194        dag: the graph to be sorted.
    195
    196    Returns:
    197        A list that contains all of the graph's nodes in topological order.
    198    """
    199    result = []
    200
    201    def visit(node: T, visited: t.Set[T]) -> None:
    202        if node in result:
    203            return
    204        if node in visited:
    205            raise ValueError("Cycle error")
    206
    207        visited.add(node)
    208
    209        for dep in dag.get(node, []):
    210            visit(dep, visited)
    211
    212        visited.remove(node)
    213        result.append(node)
    214
    215    for node in dag:
    216        visit(node, set())
    217
    218    return result
    219
    220
    221def open_file(file_name: str) -> t.TextIO:
    222    """Open a file that may be compressed as gzip and return it in universal newline mode."""
    223    with open(file_name, "rb") as f:
    224        gzipped = f.read(2) == b"\x1f\x8b"
    225
    226    if gzipped:
    227        import gzip
    228
    229        return gzip.open(file_name, "rt", newline="")
    230
    231    return open(file_name, encoding="utf-8", newline="")
    232
    233
    234@contextmanager
    235def csv_reader(read_csv: exp.ReadCSV) -> t.Any:
    236    """
    237    Returns a csv reader given the expression `READ_CSV(name, ['delimiter', '|', ...])`.
    238
    239    Args:
    240        read_csv: a `ReadCSV` function call
    241
    242    Yields:
    243        A python csv reader.
    244    """
    245    args = read_csv.expressions
    246    file = open_file(read_csv.name)
    247
    248    delimiter = ","
    249    args = iter(arg.name for arg in args)
    250    for k, v in zip(args, args):
    251        if k == "delimiter":
    252            delimiter = v
    253
    254    try:
    255        import csv as csv_
    256
    257        yield csv_.reader(file, delimiter=delimiter)
    258    finally:
    259        file.close()
    260
    261
    262def find_new_name(taken: t.Collection[str], base: str) -> str:
    263    """
    264    Searches for a new name.
    265
    266    Args:
    267        taken: a collection of taken names.
    268        base: base name to alter.
    269
    270    Returns:
    271        The new, available name.
    272    """
    273    if base not in taken:
    274        return base
    275
    276    i = 2
    277    new = f"{base}_{i}"
    278    while new in taken:
    279        i += 1
    280        new = f"{base}_{i}"
    281
    282    return new
    283
    284
    285def object_to_dict(obj: t.Any, **kwargs) -> t.Dict:
    286    """Returns a dictionary created from an object's attributes."""
    287    return {**{k: copy(v) for k, v in vars(obj).copy().items()}, **kwargs}
    288
    289
    290def split_num_words(
    291    value: str, sep: str, min_num_words: int, fill_from_start: bool = True
    292) -> t.List[t.Optional[str]]:
    293    """
    294    Perform a split on a value and return N words as a result with `None` used for words that don't exist.
    295
    296    Args:
    297        value: the value to be split.
    298        sep: the value to use to split on.
    299        min_num_words: the minimum number of words that are going to be in the result.
    300        fill_from_start: indicates that if `None` values should be inserted at the start or end of the list.
    301
    302    Examples:
    303        >>> split_num_words("db.table", ".", 3)
    304        [None, 'db', 'table']
    305        >>> split_num_words("db.table", ".", 3, fill_from_start=False)
    306        ['db', 'table', None]
    307        >>> split_num_words("db.table", ".", 1)
    308        ['db', 'table']
    309
    310    Returns:
    311        The list of words returned by `split`, possibly augmented by a number of `None` values.
    312    """
    313    words = value.split(sep)
    314    if fill_from_start:
    315        return [None] * (min_num_words - len(words)) + words
    316    return words + [None] * (min_num_words - len(words))
    317
    318
    319def is_iterable(value: t.Any) -> bool:
    320    """
    321    Checks if the value is an iterable, excluding the types `str` and `bytes`.
    322
    323    Examples:
    324        >>> is_iterable([1,2])
    325        True
    326        >>> is_iterable("test")
    327        False
    328
    329    Args:
    330        value: the value to check if it is an iterable.
    331
    332    Returns:
    333        A `bool` value indicating if it is an iterable.
    334    """
    335    return hasattr(value, "__iter__") and not isinstance(value, (str, bytes))
    336
    337
    338def flatten(values: t.Iterable[t.Iterable[t.Any] | t.Any]) -> t.Iterator[t.Any]:
    339    """
    340    Flattens an iterable that can contain both iterable and non-iterable elements. Objects of
    341    type `str` and `bytes` are not regarded as iterables.
    342
    343    Examples:
    344        >>> list(flatten([[1, 2], 3, {4}, (5, "bla")]))
    345        [1, 2, 3, 4, 5, 'bla']
    346        >>> list(flatten([1, 2, 3]))
    347        [1, 2, 3]
    348
    349    Args:
    350        values: the value to be flattened.
    351
    352    Yields:
    353        Non-iterable elements in `values`.
    354    """
    355    for value in values:
    356        if is_iterable(value):
    357            yield from flatten(value)
    358        else:
    359            yield value
    360
    361
    362def count_params(function: t.Callable) -> int:
    363    """
    364    Returns the number of formal parameters expected by a function, without counting "self"
    365    and "cls", in case of instance and class methods, respectively.
    366    """
    367    count = function.__code__.co_argcount
    368    return count - 1 if inspect.ismethod(function) else count
    369
    370
    371def dict_depth(d: t.Dict) -> int:
    372    """
    373    Get the nesting depth of a dictionary.
    374
    375    For example:
    376        >>> dict_depth(None)
    377        0
    378        >>> dict_depth({})
    379        1
    380        >>> dict_depth({"a": "b"})
    381        1
    382        >>> dict_depth({"a": {}})
    383        2
    384        >>> dict_depth({"a": {"b": {}}})
    385        3
    386
    387    Args:
    388        d (dict): dictionary
    389
    390    Returns:
    391        int: depth
    392    """
    393    try:
    394        return 1 + dict_depth(next(iter(d.values())))
    395    except AttributeError:
    396        # d doesn't have attribute "values"
    397        return 0
    398    except StopIteration:
    399        # d.values() returns an empty sequence
    400        return 1
    401
    402
    403def first(it: t.Iterable[T]) -> T:
    404    """Returns the first element from an iterable.
    405
    406    Useful for sets.
    407    """
    408    return next(i for i in it)
    409
    410
    411def should_identify(text: str, identify: str | bool) -> bool:
    412    """Checks if text should be identified given an identify option.
    413
    414    Args:
    415        text: the text to check.
    416        identify: "always" | True - always returns true, "safe" - true if no upper case
    417
    418    Returns:
    419        Whether or not a string should be identified.
    420    """
    421    if identify is True or identify == "always":
    422        return True
    423    if identify == "safe":
    424        return not any(char.isupper() for char in text)
    425    return False

class AutoName(enum.Enum): View Source

    26class AutoName(Enum):
    27    """This is used for creating enum classes where `auto()` is the string form of the corresponding value's name."""
    28
    29    def _generate_next_value_(name, _start, _count, _last_values):  # type: ignore
    30        return name

This is used for creating enum classes where `auto()` is the string form of the corresponding value's name.

##### Inherited Members

enum.Enum
  name
  value

def seq_get(seq: Sequence[~T], index: int) -> Optional[~T]: View Source

    33def seq_get(seq: t.Sequence[T], index: int) -> t.Optional[T]:
    34    """Returns the value in `seq` at position `index`, or `None` if `index` is out of bounds."""
    35    try:
    36        return seq[index]
    37    except IndexError:
    38        return None

Returns the value in `seq` at position `index`, or `None` if `index` is out of bounds.

def ensure_list(value): View Source

    51def ensure_list(value):
    52    """
    53    Ensures that a value is a list, otherwise casts or wraps it into one.
    54
    55    Args:
    56        value: the value of interest.
    57
    58    Returns:
    59        The value cast as a list if it's a list or a tuple, or else the value wrapped in a list.
    60    """
    61    if value is None:
    62        return []
    63    if isinstance(value, (list, tuple)):
    64        return list(value)
    65
    66    return [value]

Ensures that a value is a list, otherwise casts or wraps it into one.

###### Arguments:

- value: the value of interest.

###### Returns:

> The value cast as a list if it's a list or a tuple, or else the value wrapped in a list.

def ensure_collection(value): View Source

    79def ensure_collection(value):
    80    """
    81    Ensures that a value is a collection (excluding `str` and `bytes`), otherwise wraps it into a list.
    82
    83    Args:
    84        value: the value of interest.
    85
    86    Returns:
    87        The value if it's a collection, or else the value wrapped in a list.
    88    """
    89    if value is None:
    90        return []
    91    return (
    92        value if isinstance(value, Collection) and not isinstance(value, (str, bytes)) else [value]
    93    )

Ensures that a value is a collection (excluding `str` and `bytes`), otherwise wraps it into a list.

###### Arguments:

- value: the value of interest.

###### Returns:

> The value if it's a collection, or else the value wrapped in a list.

def csv(*args, sep: str = ', ') -> str: View Source

     96def csv(*args, sep: str = ", ") -> str:
     97    """
     98    Formats any number of string arguments as CSV.
     99
    100    Args:
    101        args: the string arguments to format.
    102        sep: the argument separator.
    103
    104    Returns:
    105        The arguments formatted as a CSV string.
    106    """
    107    return sep.join(arg for arg in args if arg)

Formats any number of string arguments as CSV.

###### Arguments:

- args: the string arguments to format.
- sep: the argument separator.

###### Returns:

> The arguments formatted as a CSV string.

def subclasses( module_name: str, classes: Union[Type, Tuple[Type, ...]], exclude: Union[Type, Tuple[Type, ...]] = ()) -> List[Type]: View Source

    110def subclasses(
    111    module_name: str,
    112    classes: t.Type | t.Tuple[t.Type, ...],
    113    exclude: t.Type | t.Tuple[t.Type, ...] = (),
    114) -> t.List[t.Type]:
    115    """
    116    Returns all subclasses for a collection of classes, possibly excluding some of them.
    117
    118    Args:
    119        module_name: the name of the module to search for subclasses in.
    120        classes: class(es) we want to find the subclasses of.
    121        exclude: class(es) we want to exclude from the returned list.
    122
    123    Returns:
    124        The target subclasses.
    125    """
    126    return [
    127        obj
    128        for _, obj in inspect.getmembers(
    129            sys.modules[module_name],
    130            lambda obj: inspect.isclass(obj) and issubclass(obj, classes) and obj not in exclude,
    131        )
    132    ]

Returns all subclasses for a collection of classes, possibly excluding some of them.

###### Arguments:

- module_name: the name of the module to search for subclasses in.
- classes: class(es) we want to find the subclasses of.
- exclude: class(es) we want to exclude from the returned list.

###### Returns:

> The target subclasses.

def apply_index_offset(expressions: List[Optional[~E]], offset: int) -> List[Optional[~E]]: View Source

    135def apply_index_offset(expressions: t.List[t.Optional[E]], offset: int) -> t.List[t.Optional[E]]:
    136    """
    137    Applies an offset to a given integer literal expression.
    138
    139    Args:
    140        expressions: the expression the offset will be applied to, wrapped in a list.
    141        offset: the offset that will be applied.
    142
    143    Returns:
    144        The original expression with the offset applied to it, wrapped in a list. If the provided
    145        `expressions` argument contains more than one expressions, it's returned unaffected.
    146    """
    147    if not offset or len(expressions) != 1:
    148        return expressions
    149
    150    expression = expressions[0]
    151
    152    if expression and expression.is_int:
    153        expression = expression.copy()
    154        logger.warning("Applying array index offset (%s)", offset)
    155        expression.args["this"] = str(int(expression.this) + offset)  # type: ignore
    156        return [expression]
    157
    158    return expressions

Applies an offset to a given integer literal expression.

###### Arguments:

- expressions: the expression the offset will be applied to, wrapped in a list.
- offset: the offset that will be applied.

###### Returns:

> The original expression with the offset applied to it, wrapped in a list. If the provided `expressions` argument contains more than one expressions, it's returned unaffected.

def camel_to_snake_case(name: str) -> str: View Source

    161def camel_to_snake_case(name: str) -> str:
    162    """Converts `name` from camelCase to snake_case and returns the result."""
    163    return CAMEL_CASE_PATTERN.sub("_", name).upper()

Converts `name` from camelCase to snake_case and returns the result.

def while_changing( expression: sqlglot.expressions.Expression, func: Callable[[sqlglot.expressions.Expression], ~E]) -> ~E: View Source

    166def while_changing(expression: Expression, func: t.Callable[[Expression], E]) -> E:
    167    """
    168    Applies a transformation to a given expression until a fix point is reached.
    169
    170    Args:
    171        expression: the expression to be transformed.
    172        func: the transformation to be applied.
    173
    174    Returns:
    175        The transformed expression.
    176    """
    177    while True:
    178        for n, *_ in reversed(tuple(expression.walk())):
    179            n._hash = hash(n)
    180        start = hash(expression)
    181        expression = func(expression)
    182
    183        for n, *_ in expression.walk():
    184            n._hash = None
    185        if start == hash(expression):
    186            break
    187    return expression

Applies a transformation to a given expression until a fix point is reached.

###### Arguments:

- expression: the expression to be transformed.
- func: the transformation to be applied.

###### Returns:

> The transformed expression.

def tsort(dag: Dict[~T, List[~T]]) -> List[~T]: View Source

    190def tsort(dag: t.Dict[T, t.List[T]]) -> t.List[T]:
    191    """
    192    Sorts a given directed acyclic graph in topological order.
    193
    194    Args:
    195        dag: the graph to be sorted.
    196
    197    Returns:
    198        A list that contains all of the graph's nodes in topological order.
    199    """
    200    result = []
    201
    202    def visit(node: T, visited: t.Set[T]) -> None:
    203        if node in result:
    204            return
    205        if node in visited:
    206            raise ValueError("Cycle error")
    207
    208        visited.add(node)
    209
    210        for dep in dag.get(node, []):
    211            visit(dep, visited)
    212
    213        visited.remove(node)
    214        result.append(node)
    215
    216    for node in dag:
    217        visit(node, set())
    218
    219    return result

Sorts a given directed acyclic graph in topological order.

###### Arguments:

- dag: the graph to be sorted.

###### Returns:

> A list that contains all of the graph's nodes in topological order.

def open_file(file_name: str) -> <class 'TextIO'>: View Source

    222def open_file(file_name: str) -> t.TextIO:
    223    """Open a file that may be compressed as gzip and return it in universal newline mode."""
    224    with open(file_name, "rb") as f:
    225        gzipped = f.read(2) == b"\x1f\x8b"
    226
    227    if gzipped:
    228        import gzip
    229
    230        return gzip.open(file_name, "rt", newline="")
    231
    232    return open(file_name, encoding="utf-8", newline="")

Open a file that may be compressed as gzip and return it in universal newline mode.

@contextmanager
def csv_reader(read_csv: sqlglot.expressions.ReadCSV) -> Any: View Source

    235@contextmanager
    236def csv_reader(read_csv: exp.ReadCSV) -> t.Any:
    237    """
    238    Returns a csv reader given the expression `READ_CSV(name, ['delimiter', '|', ...])`.
    239
    240    Args:
    241        read_csv: a `ReadCSV` function call
    242
    243    Yields:
    244        A python csv reader.
    245    """
    246    args = read_csv.expressions
    247    file = open_file(read_csv.name)
    248
    249    delimiter = ","
    250    args = iter(arg.name for arg in args)
    251    for k, v in zip(args, args):
    252        if k == "delimiter":
    253            delimiter = v
    254
    255    try:
    256        import csv as csv_
    257
    258        yield csv_.reader(file, delimiter=delimiter)
    259    finally:
    260        file.close()

Returns a csv reader given the expression `READ_CSV(name, ['delimiter', '|', ...])`.

###### Arguments:

- read_csv: a `ReadCSV` function call

###### Yields:

> A python csv reader.

def find_new_name(taken: Collection[str], base: str) -> str: View Source

    263def find_new_name(taken: t.Collection[str], base: str) -> str:
    264    """
    265    Searches for a new name.
    266
    267    Args:
    268        taken: a collection of taken names.
    269        base: base name to alter.
    270
    271    Returns:
    272        The new, available name.
    273    """
    274    if base not in taken:
    275        return base
    276
    277    i = 2
    278    new = f"{base}_{i}"
    279    while new in taken:
    280        i += 1
    281        new = f"{base}_{i}"
    282
    283    return new

Searches for a new name.

###### Arguments:

- taken: a collection of taken names.
- base: base name to alter.

###### Returns:

> The new, available name.

def object_to_dict(obj: Any, **kwargs) -> Dict: View Source

    286def object_to_dict(obj: t.Any, **kwargs) -> t.Dict:
    287    """Returns a dictionary created from an object's attributes."""
    288    return {**{k: copy(v) for k, v in vars(obj).copy().items()}, **kwargs}

Returns a dictionary created from an object's attributes.

def split_num_words( value: str, sep: str, min_num_words: int, fill_from_start: bool = True) -> List[Optional[str]]: View Source

    291def split_num_words(
    292    value: str, sep: str, min_num_words: int, fill_from_start: bool = True
    293) -> t.List[t.Optional[str]]:
    294    """
    295    Perform a split on a value and return N words as a result with `None` used for words that don't exist.
    296
    297    Args:
    298        value: the value to be split.
    299        sep: the value to use to split on.
    300        min_num_words: the minimum number of words that are going to be in the result.
    301        fill_from_start: indicates that if `None` values should be inserted at the start or end of the list.
    302
    303    Examples:
    304        >>> split_num_words("db.table", ".", 3)
    305        [None, 'db', 'table']
    306        >>> split_num_words("db.table", ".", 3, fill_from_start=False)
    307        ['db', 'table', None]
    308        >>> split_num_words("db.table", ".", 1)
    309        ['db', 'table']
    310
    311    Returns:
    312        The list of words returned by `split`, possibly augmented by a number of `None` values.
    313    """
    314    words = value.split(sep)
    315    if fill_from_start:
    316        return [None] * (min_num_words - len(words)) + words
    317    return words + [None] * (min_num_words - len(words))

Perform a split on a value and return N words as a result with `None` used for words that don't exist.

###### Arguments:

- 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']

###### Returns:

> The list of words returned by `split`, possibly augmented by a number of `None` values.

def is_iterable(value: Any) -> bool: View Source

    320def is_iterable(value: t.Any) -> bool:
    321    """
    322    Checks if the value is an iterable, excluding the types `str` and `bytes`.
    323
    324    Examples:
    325        >>> is_iterable([1,2])
    326        True
    327        >>> is_iterable("test")
    328        False
    329
    330    Args:
    331        value: the value to check if it is an iterable.
    332
    333    Returns:
    334        A `bool` value indicating if it is an iterable.
    335    """
    336    return hasattr(value, "__iter__") and not isinstance(value, (str, bytes))

Checks if the value is an iterable, excluding the types `str` and `bytes`.

###### Examples:

>     >>> is_iterable([1,2])
>     True
>     >>> is_iterable("test")
>     False

###### Arguments:

- value: the value to check if it is an iterable.

###### Returns:

> A `bool` value indicating if it is an iterable.

def flatten(values: Iterable[Union[Iterable[Any], Any]]) -> Iterator[Any]: View Source

    339def flatten(values: t.Iterable[t.Iterable[t.Any] | t.Any]) -> t.Iterator[t.Any]:
    340    """
    341    Flattens an iterable that can contain both iterable and non-iterable elements. Objects of
    342    type `str` and `bytes` are not regarded as iterables.
    343
    344    Examples:
    345        >>> list(flatten([[1, 2], 3, {4}, (5, "bla")]))
    346        [1, 2, 3, 4, 5, 'bla']
    347        >>> list(flatten([1, 2, 3]))
    348        [1, 2, 3]
    349
    350    Args:
    351        values: the value to be flattened.
    352
    353    Yields:
    354        Non-iterable elements in `values`.
    355    """
    356    for value in values:
    357        if is_iterable(value):
    358            yield from flatten(value)
    359        else:
    360            yield value

Flattens an iterable that can contain both iterable and non-iterable elements. Objects of type `str` and `bytes` are not regarded as iterables.

###### Examples:

>     >>> list(flatten([[1, 2], 3, {4}, (5, "bla")]))
>     [1, 2, 3, 4, 5, 'bla']
>     >>> list(flatten([1, 2, 3]))
>     [1, 2, 3]

###### Arguments:

- values: the value to be flattened.

###### Yields:

> Non-iterable elements in `values`.

def count_params(function: Callable) -> int: View Source

    363def count_params(function: t.Callable) -> int:
    364    """
    365    Returns the number of formal parameters expected by a function, without counting "self"
    366    and "cls", in case of instance and class methods, respectively.
    367    """
    368    count = function.__code__.co_argcount
    369    return count - 1 if inspect.ismethod(function) else count

Returns the number of formal parameters expected by a function, without counting "self" and "cls", in case of instance and class methods, respectively.

def dict_depth(d: Dict) -> int: View Source

    372def dict_depth(d: t.Dict) -> int:
    373    """
    374    Get the nesting depth of a dictionary.
    375
    376    For example:
    377        >>> dict_depth(None)
    378        0
    379        >>> dict_depth({})
    380        1
    381        >>> dict_depth({"a": "b"})
    382        1
    383        >>> dict_depth({"a": {}})
    384        2
    385        >>> dict_depth({"a": {"b": {}}})
    386        3
    387
    388    Args:
    389        d (dict): dictionary
    390
    391    Returns:
    392        int: depth
    393    """
    394    try:
    395        return 1 + dict_depth(next(iter(d.values())))
    396    except AttributeError:
    397        # d doesn't have attribute "values"
    398        return 0
    399    except StopIteration:
    400        # d.values() returns an empty sequence
    401        return 1

Get the nesting depth of a dictionary.

###### For example:

>     >>> dict_depth(None)
>     0
>     >>> dict_depth({})
>     1
>     >>> dict_depth({"a": "b"})
>     1
>     >>> dict_depth({"a": {}})
>     2
>     >>> dict_depth({"a": {"b": {}}})
>     3

###### Arguments:

- d (dict): dictionary

###### Returns:

> int: depth

def first(it: Iterable[~T]) -> ~T: View Source

    404def first(it: t.Iterable[T]) -> T:
    405    """Returns the first element from an iterable.
    406
    407    Useful for sets.
    408    """
    409    return next(i for i in it)

Returns the first element from an iterable.

Useful for sets.

def should_identify(text: str, identify: str | bool) -> bool: View Source

    412def should_identify(text: str, identify: str | bool) -> bool:
    413    """Checks if text should be identified given an identify option.
    414
    415    Args:
    416        text: the text to check.
    417        identify: "always" | True - always returns true, "safe" - true if no upper case
    418
    419    Returns:
    420        Whether or not a string should be identified.
    421    """
    422    if identify is True or identify == "always":
    423        return True
    424    if identify == "safe":
    425        return not any(char.isupper() for char in text)
    426    return False

Checks if text should be identified given an identify option.

###### Arguments:

- text: the text to check.
- identify: "always" | True - always returns true, "safe" - true if no upper case

###### Returns:

> Whether or not a string should be identified.