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
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(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.
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.
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.
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.
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.
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.
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.
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.
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 ofNone
values.
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.
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
.
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.
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
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.
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.