none-shall-parse 0.2.2__tar.gz → 0.3.1__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: none-shall-parse
3
- Version: 0.2.2
3
+ Version: 0.3.1
4
4
  Summary: Trinity Shared Python utilities.
5
5
  Author: Andries Niemandt, Jan Badenhorst
6
6
  Author-email: Andries Niemandt <andries.niemandt@trintel.co.za>, Jan Badenhorst <jan@trintel.co.za>
@@ -53,7 +53,7 @@ pip install none-shall-parse
53
53
 
54
54
  ## Development Quick Start
55
55
 
56
- #### To build an publish to pypi:
56
+ #### To build and publish to pypi:
57
57
 
58
58
  Update the version in the `pyproject.toml` file, then:
59
59
  ```bash
@@ -32,7 +32,7 @@ pip install none-shall-parse
32
32
 
33
33
  ## Development Quick Start
34
34
 
35
- #### To build an publish to pypi:
35
+ #### To build and publish to pypi:
36
36
 
37
37
  Update the version in the `pyproject.toml` file, then:
38
38
  ```bash
@@ -4,7 +4,7 @@ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "none-shall-parse"
7
- version = "0.2.2"
7
+ version = "0.3.1"
8
8
  description = "Trinity Shared Python utilities."
9
9
  readme = "README.md"
10
10
  authors = [
@@ -12,4 +12,4 @@ https://www.youtube.com/watch?v=zKhEw7nD9C4
12
12
  """
13
13
 
14
14
  __author__ = "Andries Niemandt, Jan Badenhorst"
15
- __email__ = "andries.niemandt@trintel.co.za, jan@trintel.co.za"
15
+ __email__ = "andries.niemandt@trintel.co.za, jan@trintel.co.za"
@@ -0,0 +1,212 @@
1
+ from typing import Union
2
+
3
+ LUHN_DOUBLES = [0, 2, 4, 6, 8, 1, 3, 5, 7, 9]
4
+
5
+
6
+ def get_luhn_digit(n: Union[str, int]) -> int:
7
+ """
8
+ Calculates the Luhn checksum digit for a given number.
9
+
10
+ The function processes a number using the Luhn algorithm. It computes
11
+ the Luhn checksum digit based on the provided digits, ensuring that the
12
+ resulting number adheres to the Luhn standard. The method is useful for
13
+ validating numerical identifiers like credit card numbers or IMEIs.
14
+
15
+ Parameters:
16
+ n (str | int): A number represented as a string or integer that
17
+ requires Luhn checksum digit calculation.
18
+
19
+ Returns:
20
+ int: The Luhn checksum digit as an integer.
21
+ """
22
+ chars = [int(ch) for ch in str(n)]
23
+ firsts = [ch for ch in chars[0::2]]
24
+ doubles = [LUHN_DOUBLES[ch] for ch in chars[1::2]]
25
+ check = 10 - divmod(sum((sum(firsts), sum(doubles))), 10)[1]
26
+ return divmod(check, 10)[1]
27
+
28
+
29
+ def is_valid_luhn(n: Union[str, int]) -> bool:
30
+ """
31
+ Determines if a given number, represented as a string or integer, adheres
32
+ to the Luhn algorithm.
33
+
34
+ The Luhn algorithm, also known as the mod 10 algorithm, is a simple checksum
35
+ formula used to validate identification numbers such as credit card numbers.
36
+
37
+ Parameters:
38
+ n : Union[str, int]
39
+ The input number to be validated. It can be provided as a string or an integer.
40
+
41
+ Returns:
42
+ bool
43
+ Returns True if the input number satisfies the Luhn algorithm; otherwise, False.
44
+ """
45
+ n = "".join(
46
+ [
47
+ e
48
+ for e in n
49
+ if e
50
+ in [
51
+ 0,
52
+ 1,
53
+ 2,
54
+ 3,
55
+ 4,
56
+ 5,
57
+ 6,
58
+ 7,
59
+ 8,
60
+ 9,
61
+ "0",
62
+ "1",
63
+ "2",
64
+ "3",
65
+ "4",
66
+ "5",
67
+ "6",
68
+ "7",
69
+ "8",
70
+ "9",
71
+ ]
72
+ ]
73
+ )
74
+ chars = [int(ch) for ch in str(n)][::-1] # Reversed Digits
75
+ firsts = [ch for ch in chars[0::2]]
76
+ doubles = [LUHN_DOUBLES[ch] for ch in chars[1::2]]
77
+ final = sum((sum(firsts), sum(doubles)))
78
+ return divmod(final, 10)[1] == 0
79
+
80
+
81
+ def is_valid_imei(n: Union[str, int]) -> bool:
82
+ """
83
+ Determines whether the given number is a valid IMEI (International Mobile
84
+ Equipment Identity) number.
85
+
86
+ An IMEI number is a 15-digit unique identifier for a mobile device. This function
87
+ first checks that the length of the input is 15 characters and then validates
88
+ it using the Luhn algorithm.
89
+
90
+ Parameters:
91
+ n: Union[str, int]
92
+ The number to be checked, represented as a string or integer.
93
+
94
+ Returns:
95
+ bool
96
+ True if the given number is a valid IMEI, otherwise False.
97
+ """
98
+ return len(str(n)) == 15 and is_valid_luhn(n)
99
+
100
+
101
+ def normalize_imei(c: Union[str, int]) -> str:
102
+ """
103
+ Normalizes the given IMEI number by extracting the first 14 digits and appending
104
+ the calculated Luhn check digit to make it a valid IMEI.
105
+
106
+ The IMEI (International Mobile Equipment Identity) is a unique identifier
107
+ typically consisting of 15 digits. This function ensures that the provided
108
+ IMEI-like input is converted into a valid IMEI format by calculating and appending
109
+ the appropriate check digit.
110
+
111
+ Parameters:
112
+ c: Union[str, int]
113
+ The input IMEI or a value resembling an IMEI. It can be provided as a string
114
+ or an integer.
115
+
116
+ Returns:
117
+ str
118
+ A 15-digit valid IMEI as a string.
119
+
120
+ Raises:
121
+ Exception
122
+ Raises any exceptions occurring internally within the `get_luhn_digit` function
123
+ if the calculation of the check digit fails.
124
+
125
+ Notes:
126
+ This function assumes the presence of the `get_luhn_digit` function for Luhn
127
+ digit calculation.
128
+ """
129
+ t = str(c)[:14]
130
+ check_digit = get_luhn_digit(t)
131
+ return "%s%s" % (t, check_digit)
132
+
133
+
134
+ def get_tac_from_imei(n: Union[str, int]) -> tuple[bool, str]:
135
+ """
136
+ Determines the validity of an IMEI number and extracts its TAC if valid.
137
+
138
+ This function checks whether a provided IMEI (International Mobile Equipment
139
+ Identity) number is valid based on IMEI validation rules. If the given IMEI
140
+ is valid, the function also extracts and returns the TAC (Type Allocation
141
+ Code), which corresponds to the first 8 digits of the IMEI.
142
+
143
+ Parameters:
144
+ n (str): The IMEI number to be validated and processed.
145
+
146
+ Returns:
147
+ tuple: A tuple containing a boolean indicating whether the IMEI is valid
148
+ and a string representing the TAC if valid or a placeholder if invalid.
149
+ """
150
+ tac = "Not a Valid IMEI"
151
+ is_valid = is_valid_imei(n)
152
+ if not is_valid:
153
+ return False, tac
154
+ else:
155
+ tac = str(n)[:8]
156
+ return True, tac
157
+
158
+
159
+ def decrement_imei(n: Union[str, int]) -> tuple[bool, str]:
160
+ """
161
+ Decrements the given IMEI number by one and normalizes it.
162
+
163
+ This function validates the provided IMEI number. If it is a valid IMEI, the
164
+ function decrements the first 14 digits by one and computes the new IMEI
165
+ checksum to generate a normalized IMEI. If the provided IMEI is not valid,
166
+ it returns a failure status and an error message.
167
+
168
+ Parameters:
169
+ n: int
170
+ The IMEI number to be validated and decremented.
171
+
172
+ Returns:
173
+ tuple[bool, str]
174
+ A tuple where the first element is a boolean indicating whether the
175
+ operation was successful, and the second element is the resulting IMEI
176
+ or an error message if the input was invalid.
177
+ """
178
+ result = "Not a Valid IMEI"
179
+ is_valid = is_valid_imei(n)
180
+ if not is_valid:
181
+ return False, result
182
+ else:
183
+ result = normalize_imei(int(str(n)[:14]) - 1)
184
+ return True, result
185
+
186
+
187
+ def increment_imei(n: Union[str, int]) -> tuple[bool, str]:
188
+ """
189
+ Determines if a given IMEI number is valid and increments it by 1 if valid.
190
+
191
+ This function first checks if the provided IMEI number is valid using the
192
+ is_valid_imei function. If the input is a valid IMEI, it increments the IMEI
193
+ value by 1 while retaining only the first 14 digits. If the input is not valid,
194
+ it returns a predefined invalid result.
195
+
196
+ Parameters:
197
+ n: int
198
+ IMEI number to be validated and potentially incremented.
199
+
200
+ Returns:
201
+ tuple[bool, str]
202
+ A tuple where the first element is a boolean indicating whether the operation
203
+ was successful, and the second element is a string containing the incremented
204
+ IMEI number if valid or an error message if not valid.
205
+ """
206
+ result = "Not a Valid IMEI"
207
+ is_valid = is_valid_imei(n)
208
+ if not is_valid:
209
+ return False, result
210
+ else:
211
+ result = normalize_imei(int(str(n)[:14]) + 1)
212
+ return True, result
@@ -1,7 +1,7 @@
1
1
  import collections
2
- from typing import Iterable, Generator, Any, TypeVar, List
2
+ from typing import Iterable, Generator, Any, List
3
3
 
4
- T = TypeVar('T')
4
+ from .types import T
5
5
 
6
6
 
7
7
  def flatten(some_list: Iterable) -> Generator[Any, None, None]:
@@ -46,4 +46,3 @@ def safe_list_get(lst: List[T], idx: int, default: T) -> T:
46
46
  return lst[idx]
47
47
  except IndexError:
48
48
  return default
49
-
@@ -1,12 +1,11 @@
1
- from typing import Any, Sequence, Tuple, Callable
1
+ from typing import Callable, Any
2
2
  from typing import Union
3
3
 
4
4
  from .strings import slugify
5
+ from .types import ChoicesType, StringLike
5
6
 
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'}
7
+ _true_set = {"yes", "true", "t", "y", "1"}
8
+ _false_set = {"no", "false", "f", "n", "0"}
10
9
 
11
10
 
12
11
  def str_to_bool(v: Any, raise_exc: bool = False) -> bool | None:
@@ -124,8 +123,8 @@ def int_or_none(s: int | float | str | None) -> int | None:
124
123
 
125
124
 
126
125
  def choices_code_to_string(
127
- choices: ChoicesType, default: str | None = None,
128
- to_slug: bool = False) -> Callable[[Union[int, str]], str | None]:
126
+ choices: ChoicesType, default: str | None = None, to_slug: bool = False
127
+ ) -> Callable[[Union[int, StringLike]], StringLike | None]:
129
128
  """
130
129
  Converts a code to a corresponding string representation based on provided choices.
131
130
  The function allows optional fallback to a default value and can slugify the resulting string
@@ -158,8 +157,8 @@ def choices_code_to_string(
158
157
 
159
158
 
160
159
  def choices_string_to_code(
161
- choices: ChoicesType, default: Any = None,
162
- to_lower: bool = False) -> Callable[[str], Union[int, str, None]]:
160
+ choices: ChoicesType, default: Any = None, to_lower: bool = False
161
+ ) -> Callable[[str], Union[int, str, None]]:
163
162
  """
164
163
  Converts a dictionary of choices into a callable function that maps input strings
165
164
  to their corresponding codes. This helper function is particularly useful for handling
@@ -0,0 +1,248 @@
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
@@ -0,0 +1,32 @@
1
+ from typing import Protocol, Sequence, Tuple, Union, TypeVar
2
+
3
+
4
+ class StringLike(Protocol):
5
+ """
6
+ Protocol that defines the expected behavior for string-like objects.
7
+
8
+ This protocol specifies the methods and properties that an object
9
+ must implement to be considered string-like. It defines basic
10
+ string operations such as getting the string representation and
11
+ length, string concatenation, containment checks, and several
12
+ commonly used string manipulation methods. Objects adhering to
13
+ this protocol can mimic the behavior of standard Python strings.
14
+ """
15
+
16
+ def __str__(self) -> str: ...
17
+ def __len__(self) -> int: ...
18
+ def __add__(self, other: str) -> str: ...
19
+ def __contains__(self, item: str) -> bool: ...
20
+
21
+ # Most commonly used string methods
22
+ def upper(self) -> str: ...
23
+ def lower(self) -> str: ...
24
+ def strip(self) -> str: ...
25
+ def startswith(self, prefix: str) -> bool: ...
26
+ def endswith(self, suffix: str) -> bool: ...
27
+ def replace(self, old: str, new: str) -> str: ...
28
+
29
+
30
+ ChoicesType = Sequence[Tuple[Union[int, str], StringLike]]
31
+
32
+ T = TypeVar("T")
@@ -1,131 +0,0 @@
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