none-shall-parse 0.1.0__tar.gz

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,47 @@
1
+ Metadata-Version: 2.3
2
+ Name: none-shall-parse
3
+ Version: 0.1.0
4
+ Summary: Trinity Shared Python utilities.
5
+ Author: Jan Badenhorst, Andries Niemandt
6
+ Author-email: Jan Badenhorst <jan.badenhorst@trintel.co.za>, Andries Niemandt <andries.niemandt@trintel.co.za>
7
+ License: MIT
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Dist: pytest>=8.0.0 ; extra == 'dev'
12
+ Requires-Dist: ruff>=0.12.3 ; extra == 'dev'
13
+ Requires-Dist: isort ; extra == 'dev'
14
+ Requires-Dist: flake8 ; extra == 'dev'
15
+ Requires-Dist: uv-build ; extra == 'dev'
16
+ Requires-Dist: sphinx ; extra == 'docs'
17
+ Requires-Dist: sphinx-rtd-theme ; extra == 'docs'
18
+ Requires-Python: >=3.12
19
+ Project-URL: Homepage, https://github.com/trinity-telecomms/tpu
20
+ Project-URL: Issues, https://github.com/trinity-telecomms/tpu/issues
21
+ Provides-Extra: dev
22
+ Provides-Extra: docs
23
+ Description-Content-Type: text/markdown
24
+
25
+ # none_shall_parse
26
+
27
+ Trinity Shared Python utilities.
28
+
29
+ ## Installation
30
+
31
+ Using `uv`:
32
+
33
+ ```bash
34
+ uv add git+https://bitbucket.org/trintel/py_tsu/src/v0.1.0/
35
+ ```
36
+
37
+ Using `pip` with `uv`:
38
+
39
+ ```bash
40
+ uv pip install git+https://bitbucket.org/trintel/py_tsu/src/v0.1.0/
41
+ ```
42
+
43
+ Using `pip`:
44
+
45
+ ```bash
46
+ pip install git+https://bitbucket.org/trintel/py_tsu/src/v0.1.0/
47
+ ```
@@ -0,0 +1,23 @@
1
+ # none_shall_parse
2
+
3
+ Trinity Shared Python utilities.
4
+
5
+ ## Installation
6
+
7
+ Using `uv`:
8
+
9
+ ```bash
10
+ uv add git+https://bitbucket.org/trintel/py_tsu/src/v0.1.0/
11
+ ```
12
+
13
+ Using `pip` with `uv`:
14
+
15
+ ```bash
16
+ uv pip install git+https://bitbucket.org/trintel/py_tsu/src/v0.1.0/
17
+ ```
18
+
19
+ Using `pip`:
20
+
21
+ ```bash
22
+ pip install git+https://bitbucket.org/trintel/py_tsu/src/v0.1.0/
23
+ ```
@@ -0,0 +1,53 @@
1
+ [build-system]
2
+ requires = ["uv_build >= 0.7.19, <0.9.0"]
3
+ build-backend = "uv_build"
4
+
5
+ [project]
6
+ name = "none-shall-parse"
7
+ version = "0.1.0"
8
+ description = "Trinity Shared Python utilities."
9
+ readme = "README.md"
10
+ authors = [
11
+ { name = "Jan Badenhorst", email = "jan.badenhorst@trintel.co.za" },
12
+ { name = "Andries Niemandt", email = "andries.niemandt@trintel.co.za" }
13
+ ]
14
+ license = {text = "MIT"}
15
+ classifiers = [
16
+ "Programming Language :: Python :: 3",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Operating System :: OS Independent",
19
+ ]
20
+ requires-python = ">=3.12"
21
+ dependencies = []
22
+
23
+ [project.optional-dependencies]
24
+ dev = [
25
+ "pytest>=8.0.0",
26
+ "ruff>=0.12.3",
27
+ "isort",
28
+ "flake8",
29
+ "uv-build"
30
+ ]
31
+ docs = [
32
+ "sphinx",
33
+ "sphinx-rtd-theme",
34
+ ]
35
+
36
+ [project.urls]
37
+ Homepage = "https://github.com/trinity-telecomms/tpu"
38
+ Issues = "https://github.com/trinity-telecomms/tpu/issues"
39
+
40
+ [dependency-groups]
41
+ lint = [
42
+ "ruff>=0.12.3",
43
+ ]
44
+ test = [
45
+ "pytest>=8.0.0",
46
+ "pytest-cov>=4.0.0",
47
+ "pytest-mock>=3.12.0",
48
+ "responses>=0.24.0",
49
+ ]
50
+
51
+ [tool.pytest.ini_options]
52
+ testpaths = ["tests"]
53
+ python_files = ["test_*.py", "*_test.py"]
@@ -0,0 +1,8 @@
1
+ """Trinity Shared Python utilities.
2
+
3
+ A collection of shared utilities for Trinity projects.
4
+ """
5
+
6
+ __version__ = "0.1.0"
7
+ __author__ = "Jan Badenhorst, Andries Niemandt"
8
+ __email__ = "jan@trintel.co.za"
@@ -0,0 +1,49 @@
1
+ import collections
2
+ from typing import Iterable, Generator, Any, TypeVar, List
3
+
4
+ T = TypeVar('T')
5
+
6
+
7
+ def flatten(some_list: Iterable) -> Generator[Any, None, None]:
8
+ """
9
+ Flattens a nested iterable into a one-dimensional generator.
10
+
11
+ This function takes an iterable, which may contain nested iterables,
12
+ and returns a generator that yields each element in a flattened order.
13
+ Strings and bytes are treated as atomic elements and will not be traversed
14
+ further as nested iterables.
15
+
16
+ :param some_list: A potentially nested iterable to be flattened.
17
+ :type some_list: Iterable
18
+ :return: A generator that yields elements from the input iterable in a
19
+ flattened order.
20
+ :rtype: Generator[Any, None, None]
21
+ """
22
+ for el in some_list:
23
+ if isinstance(el, collections.Iterable) and not isinstance(el, (str, bytes)):
24
+ yield from flatten(el)
25
+ else:
26
+ yield el
27
+
28
+
29
+ def safe_list_get(lst: List[T], idx: int, default: T) -> T:
30
+ """
31
+ Retrieve an element from a list by its index or return a default value if the index
32
+ is out of range. This function ensures no IndexError is raised during the retrieval
33
+ process by providing a fallback value.
34
+
35
+ :param lst: The list from which the element is to be retrieved.
36
+ :type lst: List[T]
37
+ :param idx: The index of the element to retrieve from the list.
38
+ :type idx: int
39
+ :param default: The fallback value to be returned if the index is out of range.
40
+ :type default: T
41
+ :return: The element at the specified index, or the default value
42
+ if the index is out of range.
43
+ :rtype: T
44
+ """
45
+ try:
46
+ return lst[idx]
47
+ except IndexError:
48
+ return default
49
+
@@ -0,0 +1,197 @@
1
+ from typing import Any, Sequence, Tuple, Callable
2
+ from typing import Union
3
+
4
+ from .strings import slugify
5
+
6
+ ChoicesType = Sequence[Tuple[Union[int, str], str]]
7
+
8
+ _true_set = {'yes', 'true', 't', 'y', '1'}
9
+ _false_set = {'no', 'false', 'f', 'n', '0'}
10
+
11
+
12
+ def str_to_bool(v: Any, raise_exc: bool = False) -> bool | None:
13
+ """
14
+ Convert a string representation of a boolean into a boolean value.
15
+
16
+ This function takes a string input and attempts to interpret it as a boolean
17
+ value based on predefined sets of true and false representations. It can
18
+ also raise an exception for invalid inputs if specified.
19
+
20
+ :param v: The string value to be interpreted as boolean.
21
+ :type v: str
22
+ :param raise_exc: Determines whether to raise an exception for invalid inputs.
23
+ :type raise_exc: bool
24
+ :return: A boolean value interpreted from the input string or None if invalid.
25
+ :rtype: bool | None
26
+ :raises ValueError: If the input string is invalid and `raise_exc` is True.
27
+ """
28
+ if isinstance(v, str):
29
+ v = v.lower()
30
+ if v in _true_set:
31
+ return True
32
+ if v in _false_set:
33
+ return False
34
+
35
+ if raise_exc:
36
+ raise ValueError('Expected "%s"' % '", "'.join(_true_set | _false_set))
37
+ return None
38
+
39
+
40
+ def is_true(v: Any) -> bool:
41
+ """
42
+ Determines whether the given value evaluates to a boolean `True`.
43
+
44
+ The function checks if the input value can be converted to a boolean
45
+ representation of `True` using the helper function `str_to_bool`.
46
+
47
+ :param v: The value to be evaluated for boolean truthiness
48
+ :type v: Any
49
+ :return: `True` if the value evaluates to boolean `True`, otherwise `False`
50
+ :rtype: bool
51
+ """
52
+ return str_to_bool(v) is True
53
+
54
+
55
+ def is_false(v: Any) -> bool:
56
+ """
57
+ Determines if the given value evaluates to a boolean False.
58
+
59
+ This function utilizes the `str_to_bool` conversion to determine whether
60
+ the input value corresponds to a boolean `False`. It is particularly
61
+ useful for interpreting string-based representations of boolean values.
62
+
63
+ :param v: The value to be evaluated.
64
+ :type v: Any
65
+ :return: True if the value evaluates to False, otherwise False.
66
+ :rtype: bool
67
+ """
68
+ return str_to_bool(v) is False
69
+
70
+
71
+ def str_to_strs_list(s: str | None) -> list[str]:
72
+ """
73
+ Parses a given string into an array of strings. The input string is split based on
74
+ commas or newline characters. Each resulting element is stripped of leading and
75
+ trailing whitespace, and empty items are excluded from the result. If the input
76
+ string is None, an empty list is returned.
77
+
78
+ :param s: The input string to be parsed. May be None.
79
+ :type s: str | None
80
+ :return: A list of non-empty, trimmed strings extracted from the input.
81
+ :rtype: list[str]
82
+ """
83
+ return (
84
+ []
85
+ if s is None
86
+ else [e.strip() for e in s.replace("\n", ",").split(",") if e.strip()]
87
+ )
88
+
89
+
90
+ def int_to_bool(v: int | float) -> bool:
91
+ """
92
+ Given an integer, convert it to bool.
93
+ If the integer is 1, return True, otherwise, return False.
94
+
95
+ Will raise an exception if the provided value cannot be cast to an integer.
96
+ :param v:
97
+ :exceptions: ValueError, TypeError
98
+ :return: True or False
99
+ """
100
+ return int(v) == 1
101
+
102
+
103
+ def int_or_none(s: int | float | str | None) -> int | None:
104
+ """
105
+ Parses the input value and attempts to convert it into an integer. If the
106
+ input is invalid (such as being non-numeric), `None` is returned. If the
107
+ input is `-1` or `None`, it also returns `None`. Otherwise, the integer
108
+ value of the input is returned.
109
+
110
+ :param s: The input value to be parsed. Can be of type `int`, `float`, `str`,
111
+ or `None`.
112
+ :return: Returns the integer value of the input if successful. If the input
113
+ is invalid, equal to `-1`, or `None`, it returns `None`.
114
+ """
115
+ if s is None:
116
+ return None
117
+ try:
118
+ if int(s) == -1:
119
+ return None
120
+ else:
121
+ return int(s)
122
+ except ValueError:
123
+ return None
124
+
125
+
126
+ def choices_code_to_string(
127
+ choices: ChoicesType, default: str | None = None,
128
+ to_slug: bool = False) -> Callable[[Union[int, str]], str | None]:
129
+ """
130
+ Converts a code to a corresponding string representation based on provided choices.
131
+ The function allows optional fallback to a default value and can slugify the resulting string
132
+ if required.
133
+
134
+ :param choices: A mapping of codes to string representations.
135
+ :type choices: ChoicesType
136
+
137
+ :param default: An optional default string to be returned if the code is not found in the choices.
138
+ :type default: str | None
139
+
140
+ :param to_slug: Specifies whether the resulting string should be slugified. If True, the string
141
+ representation is converted to a slug with hyphens replaced by underscores.
142
+ :type to_slug: bool
143
+
144
+ :return: A callable function that takes an input (code) and returns the corresponding string
145
+ representation or the default value. If to_slug is True, it returns the slugified version
146
+ instead.
147
+ :rtype: Callable[[Union[int, str]], str | None]
148
+ """
149
+ dict_map = dict(choices)
150
+
151
+ def f(code):
152
+ return dict_map.get(code, default)
153
+
154
+ def s(code):
155
+ return slugify(dict_map.get(code, default)).replace("-", "_")
156
+
157
+ return s if to_slug else f
158
+
159
+
160
+ def choices_string_to_code(
161
+ choices: ChoicesType, default: Any = None,
162
+ to_lower: bool = False) -> Callable[[str], Union[int, str, None]]:
163
+ """
164
+ Converts a dictionary of choices into a callable function that maps input strings
165
+ to their corresponding codes. This helper function is particularly useful for handling
166
+ mappings where string keys need to be converted into codes, while optionally allowing
167
+ the input to be case-insensitive.
168
+
169
+ :param choices: A dictionary-like object or sequence of tuples representing the choices.
170
+ :param default: Optional value returned if the input string does not match any key. Defaults to None.
171
+ :param to_lower: A boolean indicating whether to convert keys in the choice dictionary
172
+ and input string to lowercase for case-insensitive mapping. Defaults to False.
173
+ :return: A callable function that accepts a string input and returns the corresponding code from
174
+ the dictionary, or the default value if the input does not match.
175
+ """
176
+ if to_lower:
177
+ dict_map = {v.lower(): k for k, v in dict(choices).items()}
178
+ else:
179
+ dict_map = {v: k for k, v in dict(choices).items()}
180
+
181
+ def f(word):
182
+ return dict_map.get(word, default)
183
+
184
+ return f
185
+
186
+
187
+ def none_or_empty(s=None):
188
+ """
189
+ Check if the given thing is not empty and not None
190
+ :param s:
191
+ :return:
192
+ """
193
+ if s is None:
194
+ return True
195
+ if s.strip() == "":
196
+ return True
197
+ return False
@@ -0,0 +1,131 @@
1
+ import base64
2
+ import hashlib
3
+ import itertools
4
+ import random
5
+ import re
6
+ import secrets
7
+ import string
8
+ import unicodedata
9
+
10
+ _control_chars = "".join(
11
+ map(chr, itertools.chain(range(0x00, 0x20), range(0x7F, 0xA0))))
12
+ _re_control_char = re.compile("[%s]" % re.escape(_control_chars))
13
+ _re_combine_whitespace = re.compile(r"\s+")
14
+
15
+
16
+ def slugify(value, allow_unicode=False):
17
+ """
18
+ Maps directly to Django's slugify function.
19
+ Convert to ASCII if 'allow_unicode' is False. Convert spaces or repeated
20
+ dashes to single dashes. Remove characters that aren't alphanumerics,
21
+ underscores, or hyphens. Convert to lowercase. Also strip leading and
22
+ trailing whitespace, dashes, and underscores.
23
+ """
24
+ value = str(value)
25
+ if allow_unicode:
26
+ value = unicodedata.normalize("NFKC", value)
27
+ else:
28
+ value = (
29
+ unicodedata.normalize("NFKD", value)
30
+ .encode("ascii", "ignore")
31
+ .decode("ascii")
32
+ )
33
+ value = re.sub(r"[^\w\s-]", "", value.lower())
34
+ return re.sub(r"[-\s]+", "-", value).strip("-_")
35
+
36
+
37
+ def random_16():
38
+ return "".join(random.choices(string.ascii_letters + string.digits, k=16))
39
+
40
+
41
+ def to_human_string(s):
42
+ """
43
+ This replaces all tabs and newlines with spaces and removes all non-printing
44
+ control characters.
45
+ """
46
+ if not isinstance(s, str):
47
+ return s, False
48
+
49
+ c = _re_combine_whitespace.sub(" ", s).strip()
50
+ clean_string = _re_control_char.sub("", c)
51
+ if clean_string == s:
52
+ return s, False
53
+ return clean_string, True
54
+
55
+
56
+ def is_quoted_string(s, strip=False):
57
+ is_quoted = False
58
+ result = s
59
+ if not isinstance(s, str):
60
+ return is_quoted, result
61
+
62
+ if s[0] == s[-1]:
63
+ if s[0] in ['"', "'"]:
64
+ is_quoted = True
65
+ if strip:
66
+ if s[0] == "'":
67
+ result = s.strip("'")
68
+ elif s[0] == '"':
69
+ result = s.strip('"')
70
+ return is_quoted, result
71
+
72
+
73
+ def is_numeric_string(s, convert=False):
74
+ is_numeric = False
75
+ result = s
76
+ f = None
77
+ if not isinstance(s, str):
78
+ return is_numeric, result
79
+ try:
80
+ f = float(s)
81
+ is_numeric = True
82
+ except ValueError:
83
+ is_numeric = False
84
+
85
+ if is_numeric and convert:
86
+ result = int(f) if f.is_integer() else f
87
+
88
+ return is_numeric, result
89
+
90
+
91
+ def custom_slug(s):
92
+ # Remove all non-word characters (everything except numbers and letters)
93
+ s = re.sub(r"[^\w\s]", "", s)
94
+
95
+ # Replace all runs of whitespace with a single dash
96
+ s = re.sub(r"\s+", "_", s)
97
+
98
+ return s
99
+
100
+
101
+ def b64_encode(s):
102
+ return base64.b64encode(s).decode("utf-8").strip("=")
103
+
104
+
105
+ def b64_decode(s):
106
+ pad = "=" * (-len(s) % 4)
107
+ return base64.b64decode(s + pad)
108
+
109
+
110
+ def calc_hash(*args):
111
+ """
112
+ Calculate a hash over the set of arguments.
113
+ This is useful to compare models against each other for equality
114
+ if the assumption is that if some combination of fields are equal, then
115
+ the models represent equal ideas.
116
+ """
117
+ s = '_'.join(map(str, args))
118
+ return hashlib.sha1(s.encode("utf-16")).hexdigest()
119
+
120
+
121
+ def generate_random_password(n=10):
122
+ alphabet = string.ascii_letters + string.digits
123
+ while True:
124
+ password = "".join(secrets.choice(alphabet) for i in range(n))
125
+ if (
126
+ any(c.islower() for c in password)
127
+ and any(c.isupper() for c in password)
128
+ and sum(c.isdigit() for c in password) >= 3
129
+ ):
130
+ break
131
+ return password