none-shall-parse 0.6.2__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,267 @@
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
+ from typing import Any
10
+
11
+ _control_chars = "".join(
12
+ map(chr, itertools.chain(range(0x00, 0x20), range(0x7F, 0xA0)))
13
+ )
14
+ _re_control_char = re.compile("[%s]" % re.escape(_control_chars))
15
+ _re_combine_whitespace = re.compile(r"\s+")
16
+
17
+
18
+ def slugify(value: object, allow_unicode: bool = False) -> str:
19
+ """
20
+ Maps directly to Django's slugify function.
21
+ Convert to ASCII if 'allow_unicode' is False. Convert spaces or repeated
22
+ dashes to single dashes. Remove characters that aren't alphanumerics,
23
+ underscores, or hyphens. Convert to lowercase. Also strip leading and
24
+ trailing whitespace, dashes, and underscores.
25
+ """
26
+ value = str(value)
27
+ if allow_unicode:
28
+ value = unicodedata.normalize("NFKC", value)
29
+ else:
30
+ value = (
31
+ unicodedata.normalize("NFKD", value)
32
+ .encode("ascii", "ignore")
33
+ .decode("ascii")
34
+ )
35
+ value = re.sub(r"[^\w\s-]", "", value.lower())
36
+ return re.sub(r"[-\s]+", "-", value).strip("-_")
37
+
38
+
39
+ def random_16():
40
+ return "".join(random.choices(string.ascii_letters + string.digits, k=16))
41
+
42
+
43
+ def to_human_string(s: Any) -> tuple[Any | str, bool]:
44
+ """
45
+ Cleans up a string by removing extra whitespace and control characters.
46
+
47
+ Removes unnecessary whitespace and control characters from the input string.
48
+ This function is designed to validate and clean user-provided string input
49
+ while preserving input that does not require modification. It returns the
50
+ cleaned string along with a boolean indicating whether changes were made
51
+ to the original string.
52
+
53
+ Parameters:
54
+ s : any
55
+ The input value to clean and validate. If it is not a string, it is
56
+ returned unchanged with a modification flag of False.
57
+
58
+ Returns:
59
+ tuple
60
+ A tuple where the first element is the cleaned string (or the original
61
+ input if it is not a string), and the second element is a boolean
62
+ indicating whether the string was modified.
63
+ """
64
+ if not isinstance(s, str):
65
+ return s, False
66
+
67
+ c = _re_combine_whitespace.sub(" ", s).strip()
68
+ clean_string = _re_control_char.sub("", c)
69
+ if clean_string == s:
70
+ return s, False
71
+ return clean_string, True
72
+
73
+
74
+ def is_quoted_string(s: str, strip: bool = False) -> tuple[bool, str]:
75
+ """
76
+ Checks if a given string is enclosed in quotes and optionally strips the quotes.
77
+
78
+ The function determines whether a given string starts and ends with matching quotes,
79
+ either single quotes (') or double quotes ("). If the string is quoted and
80
+ the `strip` parameter is set to True, it removes the enclosing quotes and returns
81
+ the unquoted string.
82
+
83
+ Parameters:
84
+ s : str
85
+ The input string to check and possibly process.
86
+ strip : bool, optional
87
+ Indicates whether to remove the enclosing quotes if the string is quoted.
88
+ Defaults to False.
89
+
90
+ Returns:
91
+ tuple[bool, str]
92
+ A tuple where the first element is a boolean indicating whether the string
93
+ is quoted, and the second element is the original string or the stripped
94
+ version if `strip` is True.
95
+ """
96
+ is_quoted = False
97
+ result = s
98
+ if not isinstance(s, str):
99
+ return is_quoted, result
100
+
101
+ if s[0] == s[-1]:
102
+ if s[0] in ['"', "'"]:
103
+ is_quoted = True
104
+ if strip:
105
+ if s[0] == "'":
106
+ result = s.strip("'")
107
+ elif s[0] == '"':
108
+ result = s.strip('"')
109
+ return is_quoted, result
110
+
111
+
112
+ def is_numeric_string(s: str, convert: bool = False) -> tuple[bool, str | int | float]:
113
+ """
114
+ Checks if the given string represents a numeric value and optionally converts it.
115
+
116
+ This function determines if the provided string represents a numeric value.
117
+ If the input is numeric and the `convert` flag is set to True, it returns
118
+ the numeric value converted to either an integer (if the float represents
119
+ an integer) or a float. If the input is not numeric, it returns the original
120
+ input string.
121
+
122
+ Parameters
123
+ ----------
124
+ s : str
125
+ The input string to check.
126
+ convert : bool, optional
127
+ A flag indicating whether to convert the numeric string to a numeric
128
+ type (default is False).
129
+
130
+ Returns
131
+ -------
132
+ tuple
133
+ A tuple containing a boolean and the result:
134
+ - A boolean indicating whether the input string is numeric or not.
135
+ - The numeric value if `convert` is True and the string is numeric;
136
+ otherwise, the original string.
137
+ """
138
+ is_numeric = False
139
+ result = s
140
+ f = None
141
+ if not isinstance(s, str):
142
+ return is_numeric, result
143
+ try:
144
+ f = float(s)
145
+ is_numeric = True
146
+ except ValueError:
147
+ is_numeric = False
148
+
149
+ if is_numeric and convert:
150
+ result = int(f) if f.is_integer() else f
151
+
152
+ return is_numeric, result
153
+
154
+
155
+ def custom_slug(s: str) -> str:
156
+ # Remove all non-word characters (everything except numbers and letters)
157
+ s = re.sub(r"[^\w\s]", "", s)
158
+
159
+ # Replace all runs of whitespace with a single dash
160
+ s = re.sub(r"\s+", "_", s)
161
+
162
+ return s
163
+
164
+
165
+ def b64_encode(s: str | bytes) -> str:
166
+ """
167
+ Encodes a string or bytes into its Base64 representation.
168
+
169
+ This function takes an input, either a string or bytes, and encodes it
170
+ into its Base64 representation. If the input is a string, it is first
171
+ encoded into bytes using UTF-8 encoding. The resulting Base64 encoded
172
+ value is returned as a string.
173
+
174
+ Args:
175
+ s: The input to encode, which can be either a string or bytes.
176
+
177
+ Returns:
178
+ The Base64 encoded representation of the input as a string.
179
+ """
180
+ if isinstance(s, str):
181
+ s = s.encode("utf-8")
182
+ return base64.b64encode(s).decode("utf-8")
183
+
184
+
185
+ def b64_decode(s: str) -> bytes:
186
+ """
187
+ Decodes a Base64 encoded string to its original binary format.
188
+
189
+ This function takes a Base64 encoded string and decodes it to its
190
+ original bytes form. Base64 encoding may omit padding characters, so
191
+ the function ensures the input is properly padded before decoding.
192
+
193
+ Parameters:
194
+ s: str
195
+ A Base64 encoded string that needs to be decoded.
196
+
197
+ Returns:
198
+ bytes
199
+ The decoded binary data.
200
+
201
+ Raises:
202
+ ValueError
203
+ If the input string contains invalid Base64 characters.
204
+ """
205
+ pad = "=" * (-len(s) % 4)
206
+ return base64.b64decode(s + pad)
207
+
208
+
209
+ def calc_hash(*args: Any) -> str:
210
+ """
211
+ Calculate and return a SHA-1 hash for the given arguments.
212
+
213
+ This function joins the provided arguments into a single string, encodes it
214
+ using UTF-16, and calculates the SHA-1 hash of the resulting bytes.
215
+
216
+ Args:
217
+ *args: A variable number of arguments to include in the hash.
218
+
219
+ Returns:
220
+ str: The computed SHA-1 hash as a hexadecimal string.
221
+ """
222
+ s = "_".join(map(str, args))
223
+ return hashlib.sha1(s.encode("utf-16")).hexdigest()
224
+
225
+
226
+ def generate_random_password(n: int = 10) -> str:
227
+ """
228
+ Generates a random password meeting specific criteria for complexity. The
229
+ function ensures the password contains at least one lowercase letter, one
230
+ uppercase letter, and at least three numeric digits. The length of the
231
+ password can be customized using the 'n' parameter.
232
+
233
+ Parameters:
234
+ n (int): Length of the password to be generated. Default is 10.
235
+
236
+ Returns:
237
+ str: A randomly generated password that meets the specified criteria.
238
+ """
239
+ alphabet = string.ascii_letters + string.digits
240
+ while True:
241
+ password = "".join(secrets.choice(alphabet) for i in range(n))
242
+ if (
243
+ any(c.islower() for c in password)
244
+ and any(c.isupper() for c in password)
245
+ and sum(c.isdigit() for c in password) >= 3
246
+ ):
247
+ break
248
+ return password
249
+
250
+
251
+ def generate_crypto_password(n: int = 32) -> str:
252
+ """
253
+ Generates a cryptographically secure password string.
254
+
255
+ This function uses the `secrets` module to generate a cryptographically
256
+ secure random string with a specified length. The default length of
257
+ the password is 32 characters.
258
+
259
+ Parameters:
260
+ n: int, optional
261
+ Length of the password string. Defaults to 32.
262
+
263
+ Returns:
264
+ str
265
+ A cryptographically secure randomly generated password string.
266
+ """
267
+ return secrets.token_urlsafe(n)
@@ -0,0 +1,41 @@
1
+ from datetime import datetime, date
2
+ from typing import Protocol, Sequence, Tuple, Union, TypeVar
3
+
4
+ from pendulum import DateTime, Date
5
+
6
+
7
+ class StringLike(Protocol):
8
+ """
9
+ Protocol that defines the expected behavior for string-like objects.
10
+
11
+ This protocol specifies the methods and properties that an object
12
+ must implement to be considered string-like. It defines basic
13
+ string operations such as getting the string representation and
14
+ length, string concatenation, containment checks, and several
15
+ commonly used string manipulation methods. Objects adhering to
16
+ this protocol can mimic the behavior of standard Python strings.
17
+ """
18
+
19
+ def __str__(self) -> str: ...
20
+ def __len__(self) -> int: ...
21
+ def __add__(self, other: str) -> str: ...
22
+ def __contains__(self, item: str) -> bool: ...
23
+
24
+ # Most commonly used string methods
25
+ def upper(self) -> str: ...
26
+ def lower(self) -> str: ...
27
+ def strip(self) -> str: ...
28
+ def startswith(self, prefix: str) -> bool: ...
29
+ def endswith(self, suffix: str) -> bool: ...
30
+ def replace(self, old: str, new: str) -> str: ...
31
+
32
+
33
+ ChoicesType = Sequence[Tuple[Union[int, str], StringLike]]
34
+
35
+ DateLike = Union[Date, date]
36
+
37
+ DateTimeLike = Union[DateTime, datetime]
38
+
39
+ DateTimeOrDateLike = Union[DateTimeLike, DateLike]
40
+
41
+ T = TypeVar("T")
@@ -0,0 +1,66 @@
1
+ Metadata-Version: 2.3
2
+ Name: none-shall-parse
3
+ Version: 0.6.2
4
+ Summary: Trinity Shared Python utilities.
5
+ Author: Andries Niemandt, Jan Badenhorst
6
+ Author-email: Andries Niemandt <andries.niemandt@trintel.co.za>, Jan Badenhorst <jan@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: pendulum
12
+ Requires-Dist: pytest>=8.0.0 ; extra == 'dev'
13
+ Requires-Dist: ruff>=0.12.3 ; extra == 'dev'
14
+ Requires-Dist: isort ; extra == 'dev'
15
+ Requires-Dist: flake8 ; extra == 'dev'
16
+ Requires-Dist: uv-build ; extra == 'dev'
17
+ Requires-Python: >=3.12
18
+ Project-URL: Homepage, https://github.com/trinity-telecomms/tpu
19
+ Project-URL: Issues, https://github.com/trinity-telecomms/tpu/issues
20
+ Provides-Extra: dev
21
+ Description-Content-Type: text/markdown
22
+
23
+ # none-shall-parse
24
+
25
+ A collection of shared utilities for Trinity projects.
26
+
27
+ Originally intended to be parsing utilities only, this grew to include
28
+ other useful functions.
29
+
30
+ Named for its author Andries Niemandt — whose surname loosely
31
+ translates to "none". Combined this with our parsing intentions
32
+ to create a name which nods to the Black Knight in Monty Python's Holy Grail.
33
+ https://www.youtube.com/watch?v=zKhEw7nD9C4
34
+
35
+ ## Installation
36
+
37
+ Using `uv`:
38
+
39
+ ```bash
40
+ uv add none-shall-parse
41
+ ```
42
+
43
+ Using `pip` with `uv`:
44
+
45
+ ```bash
46
+ uv pip install none-shall-parse
47
+ ```
48
+
49
+ Using `pip`:
50
+
51
+ ```bash
52
+ pip install none-shall-parse
53
+ ```
54
+
55
+ ## Development Quick Start
56
+
57
+ #### To build and publish to pypi:
58
+
59
+ Update the version in the `pyproject.toml` file, then:
60
+ ```bash
61
+ uv sync --upgrade --all-extras --all-groups
62
+ pytest
63
+ rm -rf dist/ build/ *.egg-info/
64
+ uv build
65
+ uv publish
66
+ ```
@@ -0,0 +1,10 @@
1
+ none_shall_parse/__init__.py,sha256=ekNMvJaRzR-SOv2bQAOuH86ImPHXGpIj2NbeCO45GOc,3897
2
+ none_shall_parse/dates.py,sha256=lbdfw6DCq5EvmUIHliZK8yMkouqDNvY0jNWMWrTt9Zk,28216
3
+ none_shall_parse/imeis.py,sha256=20pONoUhLKomZxAJegqSSjG72hZjYs60r8IcaRt-15M,6770
4
+ none_shall_parse/lists.py,sha256=DhbiElDBYTVss1ivDytCCwbUKpKpaCIy4orv8KOK-4M,1765
5
+ none_shall_parse/parse.py,sha256=77bXZAtwFksRwuZ9Ax0lPxEjFpyjkQBqRa5mBc1WkF4,6843
6
+ none_shall_parse/strings.py,sha256=F7491CJAHJjL7vdEGwoH_4S6PjaovYUS_yzVGJ-bYIE,8463
7
+ none_shall_parse/types.py,sha256=WAgILMtW2_fm9MBpUuQvq68yXYBNd3rnSoQk70ibOd4,1320
8
+ none_shall_parse-0.6.2.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
9
+ none_shall_parse-0.6.2.dist-info/METADATA,sha256=a9CfMCC9wVb8gw4NoI964u9y2S_FibwMOAzpwPqGreo,1701
10
+ none_shall_parse-0.6.2.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: uv 0.8.24
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any