Edit on GitHub

sqlglot.helper

  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):
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]:
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):
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):
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:
 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]:
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]]:
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:
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:
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]:
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'>:
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:
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:
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:
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]]:
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:
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]:
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:
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:
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:
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:
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.