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( 135 this: exp.Expression, 136 expressions: t.List[t.Optional[E]], 137 offset: int, 138) -> t.List[t.Optional[E]]: 139 """ 140 Applies an offset to a given integer literal expression. 141 142 Args: 143 this: the target of the index 144 expressions: the expression the offset will be applied to, wrapped in a list. 145 offset: the offset that will be applied. 146 147 Returns: 148 The original expression with the offset applied to it, wrapped in a list. If the provided 149 `expressions` argument contains more than one expressions, it's returned unaffected. 150 """ 151 if not offset or len(expressions) != 1: 152 return expressions 153 154 expression = expressions[0] 155 156 from sqlglot import exp 157 from sqlglot.optimizer.annotate_types import annotate_types 158 from sqlglot.optimizer.simplify import simplify 159 160 if not this.type: 161 annotate_types(this) 162 163 if t.cast(exp.DataType, this.type).this not in ( 164 exp.DataType.Type.UNKNOWN, 165 exp.DataType.Type.ARRAY, 166 ): 167 return expressions 168 169 if expression: 170 if not expression.type: 171 annotate_types(expression) 172 if t.cast(exp.DataType, expression.type).this in exp.DataType.INTEGER_TYPES: 173 logger.warning("Applying array index offset (%s)", offset) 174 expression = simplify( 175 exp.Add(this=expression.copy(), expression=exp.Literal.number(offset)) 176 ) 177 return [expression] 178 179 return expressions 180 181 182def camel_to_snake_case(name: str) -> str: 183 """Converts `name` from camelCase to snake_case and returns the result.""" 184 return CAMEL_CASE_PATTERN.sub("_", name).upper() 185 186 187def while_changing(expression: Expression, func: t.Callable[[Expression], E]) -> E: 188 """ 189 Applies a transformation to a given expression until a fix point is reached. 190 191 Args: 192 expression: the expression to be transformed. 193 func: the transformation to be applied. 194 195 Returns: 196 The transformed expression. 197 """ 198 while True: 199 for n, *_ in reversed(tuple(expression.walk())): 200 n._hash = hash(n) 201 start = hash(expression) 202 expression = func(expression) 203 204 for n, *_ in expression.walk(): 205 n._hash = None 206 if start == hash(expression): 207 break 208 return expression 209 210 211def tsort(dag: t.Dict[T, t.List[T]]) -> t.List[T]: 212 """ 213 Sorts a given directed acyclic graph in topological order. 214 215 Args: 216 dag: the graph to be sorted. 217 218 Returns: 219 A list that contains all of the graph's nodes in topological order. 220 """ 221 result = [] 222 223 def visit(node: T, visited: t.Set[T]) -> None: 224 if node in result: 225 return 226 if node in visited: 227 raise ValueError("Cycle error") 228 229 visited.add(node) 230 231 for dep in dag.get(node, []): 232 visit(dep, visited) 233 234 visited.remove(node) 235 result.append(node) 236 237 for node in dag: 238 visit(node, set()) 239 240 return result 241 242 243def open_file(file_name: str) -> t.TextIO: 244 """Open a file that may be compressed as gzip and return it in universal newline mode.""" 245 with open(file_name, "rb") as f: 246 gzipped = f.read(2) == b"\x1f\x8b" 247 248 if gzipped: 249 import gzip 250 251 return gzip.open(file_name, "rt", newline="") 252 253 return open(file_name, encoding="utf-8", newline="") 254 255 256@contextmanager 257def csv_reader(read_csv: exp.ReadCSV) -> t.Any: 258 """ 259 Returns a csv reader given the expression `READ_CSV(name, ['delimiter', '|', ...])`. 260 261 Args: 262 read_csv: a `ReadCSV` function call 263 264 Yields: 265 A python csv reader. 266 """ 267 args = read_csv.expressions 268 file = open_file(read_csv.name) 269 270 delimiter = "," 271 args = iter(arg.name for arg in args) 272 for k, v in zip(args, args): 273 if k == "delimiter": 274 delimiter = v 275 276 try: 277 import csv as csv_ 278 279 yield csv_.reader(file, delimiter=delimiter) 280 finally: 281 file.close() 282 283 284def find_new_name(taken: t.Collection[str], base: str) -> str: 285 """ 286 Searches for a new name. 287 288 Args: 289 taken: a collection of taken names. 290 base: base name to alter. 291 292 Returns: 293 The new, available name. 294 """ 295 if base not in taken: 296 return base 297 298 i = 2 299 new = f"{base}_{i}" 300 while new in taken: 301 i += 1 302 new = f"{base}_{i}" 303 304 return new 305 306 307def object_to_dict(obj: t.Any, **kwargs) -> t.Dict: 308 """Returns a dictionary created from an object's attributes.""" 309 return {**{k: copy(v) for k, v in vars(obj).copy().items()}, **kwargs} 310 311 312def split_num_words( 313 value: str, sep: str, min_num_words: int, fill_from_start: bool = True 314) -> t.List[t.Optional[str]]: 315 """ 316 Perform a split on a value and return N words as a result with `None` used for words that don't exist. 317 318 Args: 319 value: the value to be split. 320 sep: the value to use to split on. 321 min_num_words: the minimum number of words that are going to be in the result. 322 fill_from_start: indicates that if `None` values should be inserted at the start or end of the list. 323 324 Examples: 325 >>> split_num_words("db.table", ".", 3) 326 [None, 'db', 'table'] 327 >>> split_num_words("db.table", ".", 3, fill_from_start=False) 328 ['db', 'table', None] 329 >>> split_num_words("db.table", ".", 1) 330 ['db', 'table'] 331 332 Returns: 333 The list of words returned by `split`, possibly augmented by a number of `None` values. 334 """ 335 words = value.split(sep) 336 if fill_from_start: 337 return [None] * (min_num_words - len(words)) + words 338 return words + [None] * (min_num_words - len(words)) 339 340 341def is_iterable(value: t.Any) -> bool: 342 """ 343 Checks if the value is an iterable, excluding the types `str` and `bytes`. 344 345 Examples: 346 >>> is_iterable([1,2]) 347 True 348 >>> is_iterable("test") 349 False 350 351 Args: 352 value: the value to check if it is an iterable. 353 354 Returns: 355 A `bool` value indicating if it is an iterable. 356 """ 357 return hasattr(value, "__iter__") and not isinstance(value, (str, bytes)) 358 359 360def flatten(values: t.Iterable[t.Iterable[t.Any] | t.Any]) -> t.Iterator[t.Any]: 361 """ 362 Flattens an iterable that can contain both iterable and non-iterable elements. Objects of 363 type `str` and `bytes` are not regarded as iterables. 364 365 Examples: 366 >>> list(flatten([[1, 2], 3, {4}, (5, "bla")])) 367 [1, 2, 3, 4, 5, 'bla'] 368 >>> list(flatten([1, 2, 3])) 369 [1, 2, 3] 370 371 Args: 372 values: the value to be flattened. 373 374 Yields: 375 Non-iterable elements in `values`. 376 """ 377 for value in values: 378 if is_iterable(value): 379 yield from flatten(value) 380 else: 381 yield value 382 383 384def count_params(function: t.Callable) -> int: 385 """ 386 Returns the number of formal parameters expected by a function, without counting "self" 387 and "cls", in case of instance and class methods, respectively. 388 """ 389 count = function.__code__.co_argcount 390 return count - 1 if inspect.ismethod(function) else count 391 392 393def dict_depth(d: t.Dict) -> int: 394 """ 395 Get the nesting depth of a dictionary. 396 397 For example: 398 >>> dict_depth(None) 399 0 400 >>> dict_depth({}) 401 1 402 >>> dict_depth({"a": "b"}) 403 1 404 >>> dict_depth({"a": {}}) 405 2 406 >>> dict_depth({"a": {"b": {}}}) 407 3 408 409 Args: 410 d (dict): dictionary 411 412 Returns: 413 int: depth 414 """ 415 try: 416 return 1 + dict_depth(next(iter(d.values()))) 417 except AttributeError: 418 # d doesn't have attribute "values" 419 return 0 420 except StopIteration: 421 # d.values() returns an empty sequence 422 return 1 423 424 425def first(it: t.Iterable[T]) -> T: 426 """Returns the first element from an iterable. 427 428 Useful for sets. 429 """ 430 return next(i for i in it) 431 432 433def should_identify(text: str, identify: str | bool) -> bool: 434 """Checks if text should be identified given an identify option. 435 436 Args: 437 text: the text to check. 438 identify: "always" | True - always returns true, "safe" - true if no upper case 439 440 Returns: 441 Whether or not a string should be identified. 442 """ 443 if identify is True or identify == "always": 444 return True 445 if identify == "safe": 446 return not any(char.isupper() for char in text) 447 return False
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
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.
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.
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.
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.
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.
135def apply_index_offset( 136 this: exp.Expression, 137 expressions: t.List[t.Optional[E]], 138 offset: int, 139) -> t.List[t.Optional[E]]: 140 """ 141 Applies an offset to a given integer literal expression. 142 143 Args: 144 this: the target of the index 145 expressions: the expression the offset will be applied to, wrapped in a list. 146 offset: the offset that will be applied. 147 148 Returns: 149 The original expression with the offset applied to it, wrapped in a list. If the provided 150 `expressions` argument contains more than one expressions, it's returned unaffected. 151 """ 152 if not offset or len(expressions) != 1: 153 return expressions 154 155 expression = expressions[0] 156 157 from sqlglot import exp 158 from sqlglot.optimizer.annotate_types import annotate_types 159 from sqlglot.optimizer.simplify import simplify 160 161 if not this.type: 162 annotate_types(this) 163 164 if t.cast(exp.DataType, this.type).this not in ( 165 exp.DataType.Type.UNKNOWN, 166 exp.DataType.Type.ARRAY, 167 ): 168 return expressions 169 170 if expression: 171 if not expression.type: 172 annotate_types(expression) 173 if t.cast(exp.DataType, expression.type).this in exp.DataType.INTEGER_TYPES: 174 logger.warning("Applying array index offset (%s)", offset) 175 expression = simplify( 176 exp.Add(this=expression.copy(), expression=exp.Literal.number(offset)) 177 ) 178 return [expression] 179 180 return expressions
Applies an offset to a given integer literal expression.
Arguments:
- this: the target of the index
- 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.
183def camel_to_snake_case(name: str) -> str: 184 """Converts `name` from camelCase to snake_case and returns the result.""" 185 return CAMEL_CASE_PATTERN.sub("_", name).upper()
Converts name
from camelCase to snake_case and returns the result.
188def while_changing(expression: Expression, func: t.Callable[[Expression], E]) -> E: 189 """ 190 Applies a transformation to a given expression until a fix point is reached. 191 192 Args: 193 expression: the expression to be transformed. 194 func: the transformation to be applied. 195 196 Returns: 197 The transformed expression. 198 """ 199 while True: 200 for n, *_ in reversed(tuple(expression.walk())): 201 n._hash = hash(n) 202 start = hash(expression) 203 expression = func(expression) 204 205 for n, *_ in expression.walk(): 206 n._hash = None 207 if start == hash(expression): 208 break 209 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.
212def tsort(dag: t.Dict[T, t.List[T]]) -> t.List[T]: 213 """ 214 Sorts a given directed acyclic graph in topological order. 215 216 Args: 217 dag: the graph to be sorted. 218 219 Returns: 220 A list that contains all of the graph's nodes in topological order. 221 """ 222 result = [] 223 224 def visit(node: T, visited: t.Set[T]) -> None: 225 if node in result: 226 return 227 if node in visited: 228 raise ValueError("Cycle error") 229 230 visited.add(node) 231 232 for dep in dag.get(node, []): 233 visit(dep, visited) 234 235 visited.remove(node) 236 result.append(node) 237 238 for node in dag: 239 visit(node, set()) 240 241 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.
244def open_file(file_name: str) -> t.TextIO: 245 """Open a file that may be compressed as gzip and return it in universal newline mode.""" 246 with open(file_name, "rb") as f: 247 gzipped = f.read(2) == b"\x1f\x8b" 248 249 if gzipped: 250 import gzip 251 252 return gzip.open(file_name, "rt", newline="") 253 254 return open(file_name, encoding="utf-8", newline="")
Open a file that may be compressed as gzip and return it in universal newline mode.
257@contextmanager 258def csv_reader(read_csv: exp.ReadCSV) -> t.Any: 259 """ 260 Returns a csv reader given the expression `READ_CSV(name, ['delimiter', '|', ...])`. 261 262 Args: 263 read_csv: a `ReadCSV` function call 264 265 Yields: 266 A python csv reader. 267 """ 268 args = read_csv.expressions 269 file = open_file(read_csv.name) 270 271 delimiter = "," 272 args = iter(arg.name for arg in args) 273 for k, v in zip(args, args): 274 if k == "delimiter": 275 delimiter = v 276 277 try: 278 import csv as csv_ 279 280 yield csv_.reader(file, delimiter=delimiter) 281 finally: 282 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.
285def find_new_name(taken: t.Collection[str], base: str) -> str: 286 """ 287 Searches for a new name. 288 289 Args: 290 taken: a collection of taken names. 291 base: base name to alter. 292 293 Returns: 294 The new, available name. 295 """ 296 if base not in taken: 297 return base 298 299 i = 2 300 new = f"{base}_{i}" 301 while new in taken: 302 i += 1 303 new = f"{base}_{i}" 304 305 return new
Searches for a new name.
Arguments:
- taken: a collection of taken names.
- base: base name to alter.
Returns:
The new, available name.
308def object_to_dict(obj: t.Any, **kwargs) -> t.Dict: 309 """Returns a dictionary created from an object's attributes.""" 310 return {**{k: copy(v) for k, v in vars(obj).copy().items()}, **kwargs}
Returns a dictionary created from an object's attributes.
313def split_num_words( 314 value: str, sep: str, min_num_words: int, fill_from_start: bool = True 315) -> t.List[t.Optional[str]]: 316 """ 317 Perform a split on a value and return N words as a result with `None` used for words that don't exist. 318 319 Args: 320 value: the value to be split. 321 sep: the value to use to split on. 322 min_num_words: the minimum number of words that are going to be in the result. 323 fill_from_start: indicates that if `None` values should be inserted at the start or end of the list. 324 325 Examples: 326 >>> split_num_words("db.table", ".", 3) 327 [None, 'db', 'table'] 328 >>> split_num_words("db.table", ".", 3, fill_from_start=False) 329 ['db', 'table', None] 330 >>> split_num_words("db.table", ".", 1) 331 ['db', 'table'] 332 333 Returns: 334 The list of words returned by `split`, possibly augmented by a number of `None` values. 335 """ 336 words = value.split(sep) 337 if fill_from_start: 338 return [None] * (min_num_words - len(words)) + words 339 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 ofNone
values.
342def is_iterable(value: t.Any) -> bool: 343 """ 344 Checks if the value is an iterable, excluding the types `str` and `bytes`. 345 346 Examples: 347 >>> is_iterable([1,2]) 348 True 349 >>> is_iterable("test") 350 False 351 352 Args: 353 value: the value to check if it is an iterable. 354 355 Returns: 356 A `bool` value indicating if it is an iterable. 357 """ 358 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.
361def flatten(values: t.Iterable[t.Iterable[t.Any] | t.Any]) -> t.Iterator[t.Any]: 362 """ 363 Flattens an iterable that can contain both iterable and non-iterable elements. Objects of 364 type `str` and `bytes` are not regarded as iterables. 365 366 Examples: 367 >>> list(flatten([[1, 2], 3, {4}, (5, "bla")])) 368 [1, 2, 3, 4, 5, 'bla'] 369 >>> list(flatten([1, 2, 3])) 370 [1, 2, 3] 371 372 Args: 373 values: the value to be flattened. 374 375 Yields: 376 Non-iterable elements in `values`. 377 """ 378 for value in values: 379 if is_iterable(value): 380 yield from flatten(value) 381 else: 382 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
.
385def count_params(function: t.Callable) -> int: 386 """ 387 Returns the number of formal parameters expected by a function, without counting "self" 388 and "cls", in case of instance and class methods, respectively. 389 """ 390 count = function.__code__.co_argcount 391 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.
394def dict_depth(d: t.Dict) -> int: 395 """ 396 Get the nesting depth of a dictionary. 397 398 For example: 399 >>> dict_depth(None) 400 0 401 >>> dict_depth({}) 402 1 403 >>> dict_depth({"a": "b"}) 404 1 405 >>> dict_depth({"a": {}}) 406 2 407 >>> dict_depth({"a": {"b": {}}}) 408 3 409 410 Args: 411 d (dict): dictionary 412 413 Returns: 414 int: depth 415 """ 416 try: 417 return 1 + dict_depth(next(iter(d.values()))) 418 except AttributeError: 419 # d doesn't have attribute "values" 420 return 0 421 except StopIteration: 422 # d.values() returns an empty sequence 423 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
426def first(it: t.Iterable[T]) -> T: 427 """Returns the first element from an iterable. 428 429 Useful for sets. 430 """ 431 return next(i for i in it)
Returns the first element from an iterable.
Useful for sets.
434def should_identify(text: str, identify: str | bool) -> bool: 435 """Checks if text should be identified given an identify option. 436 437 Args: 438 text: the text to check. 439 identify: "always" | True - always returns true, "safe" - true if no upper case 440 441 Returns: 442 Whether or not a string should be identified. 443 """ 444 if identify is True or identify == "always": 445 return True 446 if identify == "safe": 447 return not any(char.isupper() for char in text) 448 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.