extended-data-types 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,68 @@
1
+ """This module provides utilities for string and value matching.
2
+
3
+ It includes functions to partially match strings and to compare non-empty values
4
+ for equality, handling different data types including strings, mappings, and lists.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import Any, Mapping
10
+
11
+ from .json_utils import encode_json
12
+ from .state_utils import is_nothing
13
+
14
+
15
+ def is_partial_match(
16
+ a: str | None,
17
+ b: str | None,
18
+ check_prefix_only: bool = False,
19
+ ) -> bool:
20
+ """Checks if two strings partially match.
21
+
22
+ Args:
23
+ a (str | None): The first string.
24
+ b (str | None): The second string.
25
+ check_prefix_only (bool): Whether to check only the prefix.
26
+
27
+ Returns:
28
+ bool: True if there is a partial match, False otherwise.
29
+ """
30
+ if is_nothing(a) or is_nothing(b):
31
+ return False
32
+
33
+ a = a.casefold() if a is not None else ""
34
+ b = b.casefold() if b is not None else ""
35
+
36
+ if check_prefix_only:
37
+ return a.startswith(b) or b.startswith(a)
38
+
39
+ return a in b or b in a
40
+
41
+
42
+ def is_non_empty_match(a: Any, b: Any) -> bool:
43
+ """Checks if two non-empty values match.
44
+
45
+ Args:
46
+ a (Any): The first value.
47
+ b (Any): The second value.
48
+
49
+ Returns:
50
+ bool: True if the values match, False otherwise.
51
+ """
52
+ if is_nothing(a) or is_nothing(b):
53
+ return False
54
+
55
+ if not isinstance(a, type(b)):
56
+ return False
57
+
58
+ if isinstance(a, str):
59
+ a = a.casefold()
60
+ b = b.casefold()
61
+ elif isinstance(a, Mapping):
62
+ a = encode_json(a, sort_keys=True)
63
+ b = encode_json(b, sort_keys=True)
64
+ elif isinstance(a, list):
65
+ a.sort()
66
+ b.sort()
67
+
68
+ return bool(a == b)
File without changes
@@ -0,0 +1,54 @@
1
+ """This module contains utility functions for splitting lists and dictionaries by the type of their items.
2
+
3
+ It includes functions `split_list_by_type` and `split_dict_by_type` which categorize elements
4
+ based on their type and return defaultdict of type list or dict respectively.
5
+
6
+ Functions:
7
+ - split_list_by_type: Splits a list by the type of its items.
8
+ - split_dict_by_type: Splits a dictionary by the type of its values.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from collections import defaultdict
14
+ from typing import Any
15
+
16
+ from .type_utils import typeof
17
+
18
+
19
+ def split_list_by_type(
20
+ input_list: list[Any], primitive_only: bool = False
21
+ ) -> defaultdict[type, list[Any]]:
22
+ """Split a list by the type of its items, with an option to categorize by primitive types only.
23
+
24
+ Args:
25
+ input_list (List[Any]): The list to split.
26
+ primitive_only (bool): If True, categorize items based on primitive types (int, float, str, etc.)
27
+ rather than their exact type.
28
+
29
+ Returns:
30
+ DefaultDict[type, List[Any]]: A defaultdict storing lists of elements categorized by their type.
31
+ """
32
+ result: defaultdict[type, list[Any]] = defaultdict(list)
33
+ for item in input_list:
34
+ result[typeof(item, primitive_only=primitive_only)].append(item)
35
+ return result
36
+
37
+
38
+ def split_dict_by_type(
39
+ input_dict: dict[Any, Any], primitive_only: bool = False
40
+ ) -> defaultdict[type, dict[Any, Any]]:
41
+ """Split a dictionary by the type of its values, with an option to categorize by primitive types only.
42
+
43
+ Args:
44
+ input_dict (Dict[Any, Any]): The dictionary to split.
45
+ primitive_only (bool): If True, categorize values based on primitive types (int, float, str, etc.)
46
+ rather than their exact type.
47
+
48
+ Returns:
49
+ DefaultDict[type, Dict[Any, Any]]: A defaultdict storing dictionaries of elements categorized by their type.
50
+ """
51
+ result: defaultdict[type, dict[Any, Any]] = defaultdict(dict)
52
+ for key, value in input_dict.items():
53
+ result[typeof(value, primitive_only=primitive_only)][key] = value
54
+ return result
@@ -0,0 +1,87 @@
1
+ """This module provides utilities for inspecting the call stack and methods of classes.
2
+
3
+ It includes functions to get the caller's name, filter methods, and retrieve available
4
+ methods and their docstrings for a class.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import sys
10
+
11
+ from inspect import getmembers, ismethod
12
+ from typing import Any
13
+
14
+
15
+ def get_caller() -> str:
16
+ """Gets the name of the caller function.
17
+
18
+ Returns:
19
+ str: The name of the caller function.
20
+ """
21
+ return sys._getframe(2).f_code.co_name # noqa: SLF001
22
+
23
+
24
+ def get_unique_signature(obj: Any, delim: str = "/") -> str:
25
+ """Generate a unique signature for an object based on its class and module.
26
+
27
+ Args:
28
+ obj (Any): The object to generate a signature for.
29
+ delim (str): The delimiter to use between the module and class names. Defaults to "/".
30
+
31
+ Returns:
32
+ str: A unique signature string for the object.
33
+ """
34
+ return str(obj.__class__.__module__) + delim + str(obj.__class__.__name__)
35
+
36
+
37
+ def filter_methods(methods: list[str]) -> list[str]:
38
+ """Filters out private methods from a list of method names.
39
+
40
+ Args:
41
+ methods (list[str]): The list of method names to filter.
42
+
43
+ Returns:
44
+ list[str]: The filtered list of method names.
45
+ """
46
+ return [method for method in methods if not method.startswith("_")]
47
+
48
+
49
+ def get_available_methods(cls: type[Any]) -> dict[str, str | None]:
50
+ """Gets available methods and their docstrings for a class.
51
+
52
+ An "available method" is a public method that:
53
+ - Does not contain '__' in its name.
54
+ - Belongs to the same module as the class.
55
+ - Does not have 'NOPARSE' in its docstring.
56
+
57
+ Args:
58
+ cls (type[Any]): The class to inspect.
59
+
60
+ Returns:
61
+ dict[str, str | None]: A dictionary of method names and their docstrings.
62
+ """
63
+ module_name = cls.__class__.__module__
64
+ methods = getmembers(cls, ismethod)
65
+
66
+ return {
67
+ method_name: method_signature.__doc__
68
+ for method_name, method_signature in methods
69
+ if "__" not in method_name
70
+ and method_signature.__self__.__class__.__module__ == module_name
71
+ and "NOPARSE" not in (method_signature.__doc__ or "")
72
+ }
73
+
74
+
75
+ def current_python_version_is_at_least(minor: int, major: int = 3) -> bool:
76
+ """Checks if the current Python version is at least the specified version.
77
+
78
+ Args:
79
+ minor (int): The minimum minor version.
80
+ major (int, optional): The minimum major version. Defaults to 3.
81
+
82
+ Returns:
83
+ bool: True if the current Python version is at least the specified version, False otherwise.
84
+ """
85
+ return (sys.version_info.major > major) or (
86
+ sys.version_info.major == major and sys.version_info.minor >= minor
87
+ )
@@ -0,0 +1,169 @@
1
+ """The `state_utils` module provides utility functions for handling and evaluating data structure "emptiness".
2
+
3
+ It includes functions to determine whether a value is considered "nothing", to extract
4
+ non-empty values, and to assess the state of provided arguments and keyword arguments.
5
+
6
+ Functions:
7
+ - is_nothing: Determines if a value is considered "nothing" (e.g., None, empty string, empty list, empty dict).
8
+ - are_nothing: Checks if all provided values (both positional and keyword arguments) are "nothing".
9
+ - all_non_empty: Returns all non-empty values from provided arguments and keyword arguments.
10
+ - all_non_empty_in_list: Returns a list of non-empty values from the provided list.
11
+ - all_non_empty_in_dict: Returns a dictionary of non-empty values from the provided dictionary.
12
+ - first_non_empty: Retrieves the first non-empty value from a set of provided values.
13
+ - any_non_empty: Retrieves the first non-empty value from a mapping for a given set of keys.
14
+ - yield_non_empty: Yields non-empty values from a mapping for a given set of keys.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ from typing import Any, Generator
20
+
21
+
22
+ def is_nothing(v: Any) -> bool:
23
+ """Checks if a value is considered 'nothing'.
24
+
25
+ Args:
26
+ v (Any): The value to check.
27
+
28
+ Returns:
29
+ bool: True if the value is considered 'nothing', False otherwise.
30
+ """
31
+ if v in [None, "", {}, []]:
32
+ return True
33
+
34
+ if str(v) == "" or str(v).isspace():
35
+ return True
36
+
37
+ if isinstance(v, (list, set)):
38
+ v = [vv for vv in v if vv not in [None, "", {}, []]]
39
+ if len(v) == 0:
40
+ return True
41
+
42
+ return False
43
+
44
+
45
+ def are_nothing(*args: Any, **kwargs: Any) -> bool:
46
+ """Checks if all provided values (both args and kwargs) are considered 'nothing'.
47
+
48
+ Args:
49
+ args (Any): Positional arguments to check.
50
+ kwargs (Any): Keyword arguments to check.
51
+
52
+ Returns:
53
+ bool: True if all values are considered 'nothing', False otherwise.
54
+ """
55
+ non_empty = all_non_empty(*args, **kwargs)
56
+
57
+ if non_empty is None:
58
+ return True
59
+
60
+ if isinstance(non_empty, (list, dict)):
61
+ return len(non_empty) == 0
62
+
63
+ if isinstance(non_empty, tuple):
64
+ list_part, dict_part = non_empty
65
+ return len(list_part) == 0 and len(dict_part) == 0
66
+
67
+ return False
68
+
69
+
70
+ def all_non_empty(
71
+ *args: Any, **kwargs: Any
72
+ ) -> list[Any] | dict[str, Any] | tuple[list[Any], dict[str, Any]] | None:
73
+ """Returns all non-empty values from the provided args and kwargs.
74
+
75
+ Args:
76
+ args (Any): Positional arguments to check.
77
+ kwargs (Any): Keyword arguments to check.
78
+
79
+ Returns:
80
+ Union[List[Any], Dict[str, Any], Tuple[List[Any], Dict[str, Any]], None]:
81
+ A list, dict, tuple of list
82
+ """
83
+ if len(args) == 0 and len(kwargs) == 0:
84
+ return None
85
+
86
+ if len(args) == 0:
87
+ return all_non_empty_in_dict(dict(kwargs))
88
+
89
+ results = all_non_empty_in_list(list(args))
90
+ if len(kwargs) == 0:
91
+ return results
92
+
93
+ return results, all_non_empty_in_dict(dict(kwargs))
94
+
95
+
96
+ def all_non_empty_in_list(input_list: list[Any]) -> list[Any]:
97
+ """Returns a list of all non-empty values from the input list.
98
+
99
+ Args:
100
+ input_list (List[Any]): A list of items to check for emptiness.
101
+
102
+ Returns:
103
+ List: A list of non-empty values.
104
+ """
105
+ return [item for item in input_list if not is_nothing(item)]
106
+
107
+
108
+ def all_non_empty_in_dict(input_dict: dict[Any, Any]) -> dict[Any, Any]:
109
+ """Returns a dictionary of all non-empty values from the input dictionary.
110
+
111
+ Args:
112
+ input_dict (Dict[Any, Any]): A dictionary of items to check for emptiness.
113
+
114
+ Returns:
115
+ Dict: A dictionary of non-empty values.
116
+ """
117
+ return {key: value for key, value in input_dict.items() if not is_nothing(value)}
118
+
119
+
120
+ def first_non_empty(*vals: Any) -> Any:
121
+ """Returns the first non-empty value.
122
+
123
+ Args:
124
+ vals (Any): The values to check.
125
+
126
+ Returns:
127
+ Any: The first non-empty value, or None if all are 'nothing'.
128
+ """
129
+ non_empty_vals = all_non_empty(*vals)
130
+ return (
131
+ non_empty_vals[0]
132
+ if isinstance(non_empty_vals, list) and non_empty_vals
133
+ else None
134
+ )
135
+
136
+
137
+ def any_non_empty(m: dict[Any, Any], *keys: Any) -> dict[Any, Any]:
138
+ """Returns the first non-empty value from a mapping for the given keys.
139
+
140
+ Args:
141
+ m (Dict): The mapping to check.
142
+ keys (Any): The keys to check.
143
+
144
+ Returns:
145
+ Dict[Any, Any]: A mapping containing the first non-empty value.
146
+ """
147
+ for k in keys:
148
+ v = m.get(k)
149
+ if not is_nothing(v):
150
+ return {k: v}
151
+ return {}
152
+
153
+
154
+ def yield_non_empty(
155
+ m: dict[Any, Any], *keys: Any
156
+ ) -> Generator[dict[Any, Any], None, None]:
157
+ """Yields non-empty values from a mapping for the given keys.
158
+
159
+ Args:
160
+ m (Dict): The mapping to check.
161
+ keys (Any): The keys to check.
162
+
163
+ Yields:
164
+ Generator[Dict[Any, Any], None, None]: A generator yielding non-empty values.
165
+ """
166
+ for k in keys:
167
+ v = m.get(k)
168
+ if not is_nothing(v):
169
+ yield {k: v}
@@ -0,0 +1,161 @@
1
+ """This module provides utilities for string manipulation and validation.
2
+
3
+ It includes functions to sanitize keys, truncate messages, manipulate character
4
+ cases, validate URLs, convert camelCase to TitleCase, and convert string representations
5
+ of truth to boolean values.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import inflection
11
+ import validators
12
+
13
+ from .stack_utils import current_python_version_is_at_least
14
+
15
+
16
+ def sanitize_key(key: str, delim: str = "_") -> str:
17
+ """Sanitizes a key by replacing non-alphanumeric characters with a delimiter.
18
+
19
+ Args:
20
+ key (str): The key to sanitize.
21
+ delim (str): The delimiter to replace non-alphanumeric characters with. Defaults to "_".
22
+
23
+ Returns:
24
+ str: The sanitized key.
25
+ """
26
+ return "".join(x if (x.isalnum() or x == delim) else delim for x in key)
27
+
28
+
29
+ def truncate(msg: str, max_length: int, ender: str = "...") -> str:
30
+ """Truncates a message to a maximum length, appending an ender if truncated.
31
+
32
+ Args:
33
+ msg (str): The message to truncate.
34
+ max_length (int): The maximum length of the truncated message.
35
+ ender (str): The string to append to the truncated message. Defaults to "...".
36
+
37
+ Returns:
38
+ str: The truncated message.
39
+ """
40
+ if len(msg) <= max_length:
41
+ return msg
42
+ return msg[: max_length - len(ender)] + ender
43
+
44
+
45
+ def lower_first_char(inp: str) -> str:
46
+ """Converts the first character of a string to lowercase.
47
+
48
+ Args:
49
+ inp (str): The input string.
50
+
51
+ Returns:
52
+ str: The string with the first character in lowercase.
53
+ """
54
+ return inp[:1].lower() + inp[1:] if inp else ""
55
+
56
+
57
+ def upper_first_char(inp: str) -> str:
58
+ """Converts the first character of a string to uppercase.
59
+
60
+ Args:
61
+ inp (str): The input string.
62
+
63
+ Returns:
64
+ str: The string with the first character in uppercase.
65
+ """
66
+ return inp[:1].upper() + inp[1:] if inp else ""
67
+
68
+
69
+ def is_url(url: str) -> bool:
70
+ """Checks if the given string is a valid URL.
71
+
72
+ Args:
73
+ url (str): The string to check.
74
+
75
+ Returns:
76
+ bool: True if the file path is a valid URL, False otherwise.
77
+ """
78
+ return validators.url(url.strip()) is True
79
+
80
+
81
+ def titleize_name(name: str) -> str:
82
+ """Converts a camelCase name to a TitleCase name.
83
+
84
+ Args:
85
+ name (str): The camelCase name.
86
+
87
+ Returns:
88
+ str: The TitleCase name.
89
+ """
90
+ return inflection.titleize(inflection.underscore(name))
91
+
92
+
93
+ def strtobool(val: str | bool | None, raise_on_error: bool = False) -> bool | None:
94
+ """Converts a string representation of truth to boolean.
95
+
96
+ Args:
97
+ val (str | bool | None): The value to convert.
98
+ raise_on_error (bool): Whether to raise an error on invalid value. Defaults to False.
99
+
100
+ Returns:
101
+ bool | None: The converted boolean value, or None if invalid and raise_on_error is False.
102
+ """
103
+ if isinstance(val, bool) or val is None:
104
+ return val
105
+
106
+ if isinstance(val, str):
107
+ val = val.lower()
108
+ if val in ("y", "yes", "t", "true", "on", "1"):
109
+ return True
110
+ if val in ("n", "no", "f", "false", "off", "0"):
111
+ return False
112
+
113
+ if raise_on_error:
114
+ error_msg = f"invalid truth value {val!r}"
115
+ raise ValueError(error_msg)
116
+
117
+ return None
118
+
119
+
120
+ def removeprefix(string: str, prefix: str) -> str:
121
+ """Removes the specified prefix from the string if present.
122
+
123
+ For Python versions less than 3.9, the function mimics the behavior of
124
+ str.removeprefix.
125
+
126
+ Args:
127
+ string (str): The string from which to remove the prefix.
128
+ prefix (str): The prefix to remove.
129
+
130
+ Returns:
131
+ str: The string with the prefix removed if it was present, otherwise the original string.
132
+ """
133
+ if current_python_version_is_at_least(9):
134
+ return string.removeprefix(prefix)
135
+
136
+ if prefix and string.startswith(prefix):
137
+ string = string[len(prefix) :]
138
+
139
+ return string
140
+
141
+
142
+ def removesuffix(string: str, suffix: str) -> str:
143
+ """Removes the specified suffix from the string if present.
144
+
145
+ For Python versions less than 3.9, the function mimics the behavior of
146
+ str.removesuffix.
147
+
148
+ Args:
149
+ string (str): The string from which to remove the suffix.
150
+ suffix (str): The suffix to remove.
151
+
152
+ Returns:
153
+ str: The string with the suffix removed if it was present, otherwise the original string.
154
+ """
155
+ if current_python_version_is_at_least(9):
156
+ return string.removesuffix(suffix)
157
+
158
+ if suffix and string.endswith(suffix):
159
+ string = string[: -len(suffix)]
160
+
161
+ return string
@@ -0,0 +1,67 @@
1
+ """Type Utilities.
2
+
3
+ This module provides utility functions related to Python types, specifically for retrieving
4
+ default values based on the type. It includes functions for getting default values for common
5
+ types like lists, dictionaries, and strings, and for determining whether to return primitive
6
+ or exact types of a given value.
7
+
8
+ Functions:
9
+ - get_default_value_for_type: Returns the default value for a given type.
10
+ - get_primitive_type_for_instance_type: Returns the primitive type for a given value.
11
+ - typeof: Returns the type (or primitive type) of a given value.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ from typing import Any
17
+
18
+
19
+ def get_default_value_for_type(input_type: type) -> Any:
20
+ """Returns the default value for a given type.
21
+
22
+ Args:
23
+ input_type (type): The type to get the default value for.
24
+
25
+ Returns:
26
+ Any: The default value for the given type.
27
+ """
28
+ if input_type is list:
29
+ return []
30
+ if input_type is dict:
31
+ return {}
32
+ if input_type is str:
33
+ return ""
34
+ return None
35
+
36
+
37
+ def get_primitive_type_for_instance_type(value: Any) -> type:
38
+ """Gets the primitive type for a given value.
39
+
40
+ Args:
41
+ value (Any): The value to match.
42
+
43
+ Returns:
44
+ type: The primitive type for the given value.
45
+ """
46
+ if isinstance(value, (bool, int, float, str, bytes, bytearray)):
47
+ return type(value)
48
+ if isinstance(value, (list, tuple)):
49
+ return list
50
+ if isinstance(value, dict):
51
+ return dict
52
+ if isinstance(value, (set, frozenset)):
53
+ return set
54
+ return type(None) if value is None else object
55
+
56
+
57
+ def typeof(item: Any, primitive_only: bool = False) -> type:
58
+ """Determines either the primitive or exact type of a given value.
59
+
60
+ Args:
61
+ item (Any): The value to determine the type of.
62
+ primitive_only (bool): Whether to return the primitive type.
63
+
64
+ Returns:
65
+ type: The type (or primitive type) of the value.
66
+ """
67
+ return get_primitive_type_for_instance_type(item) if primitive_only else type(item)
@@ -0,0 +1,31 @@
1
+ """This module provides utilities for handling YAML data.
2
+
3
+ It includes custom loaders, dumpers, and utility functions for working with YAML.
4
+ """
5
+
6
+ from .constructors import yaml_construct_pairs, yaml_construct_undefined
7
+ from .dumpers import PureDumper
8
+ from .loaders import PureLoader
9
+ from .representers import (
10
+ yaml_represent_pairs,
11
+ yaml_represent_tagged,
12
+ yaml_str_representer,
13
+ )
14
+ from .tag_classes import YamlPairs, YamlTagged
15
+ from .utils import decode_yaml, encode_yaml, is_yaml_data
16
+
17
+
18
+ __all__ = [
19
+ "YamlTagged",
20
+ "YamlPairs",
21
+ "PureLoader",
22
+ "PureDumper",
23
+ "decode_yaml",
24
+ "encode_yaml",
25
+ "is_yaml_data",
26
+ "yaml_construct_undefined",
27
+ "yaml_construct_pairs",
28
+ "yaml_represent_tagged",
29
+ "yaml_represent_pairs",
30
+ "yaml_str_representer",
31
+ ]
@@ -0,0 +1,58 @@
1
+ """This module provides constructors for custom YAML tags and types.
2
+
3
+ It includes functions to construct undefined YAML tags and YAML pairs.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from typing import Any
9
+
10
+ from yaml import MappingNode, SafeLoader, ScalarNode, SequenceNode
11
+
12
+ from .tag_classes import YamlPairs, YamlTagged
13
+
14
+
15
+ def yaml_construct_undefined(
16
+ loader: SafeLoader,
17
+ node: ScalarNode | SequenceNode | MappingNode,
18
+ ) -> YamlTagged:
19
+ """Construct a YAML tagged object for undefined tags.
20
+
21
+ Args:
22
+ loader (SafeLoader): The YAML loader.
23
+ node (ScalarNode | SequenceNode | MappingNode): The YAML node.
24
+
25
+ Returns:
26
+ YamlTagged: The constructed YAML tagged object.
27
+ """
28
+ value: Any
29
+ if isinstance(node, ScalarNode):
30
+ value = loader.construct_scalar(node)
31
+ elif isinstance(node, SequenceNode):
32
+ value = loader.construct_sequence(node)
33
+ elif isinstance(node, MappingNode):
34
+ value = loader.construct_mapping(node)
35
+ else:
36
+ node_type = type(node).__name__
37
+ raise TypeError(f"Unexpected node type: {node_type}") # noqa: TRY003, EM102
38
+ return YamlTagged(node.tag, value)
39
+
40
+
41
+ def yaml_construct_pairs(
42
+ loader: SafeLoader,
43
+ node: MappingNode,
44
+ ) -> dict[Any, Any] | YamlPairs:
45
+ """Construct YAML pairs.
46
+
47
+ Args:
48
+ loader (SafeLoader): The YAML loader.
49
+ node (MappingNode): The YAML mapping node.
50
+
51
+ Returns:
52
+ Union[Dict[Any, Any], YamlPairs]: The constructed YAML pairs.
53
+ """
54
+ value: list[tuple[Any, Any]] = loader.construct_pairs(node) # type: ignore[no-untyped-call]
55
+ try:
56
+ return dict(value)
57
+ except TypeError:
58
+ return YamlPairs(value)