hunterMakesPy 0.7.5__tar.gz → 0.8.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.
- {huntermakespy-0.7.5 → huntermakespy-0.8.1}/PKG-INFO +3 -3
- {huntermakespy-0.7.5 → huntermakespy-0.8.1}/hunterMakesPy/__init__.py +4 -2
- {huntermakespy-0.7.5 → huntermakespy-0.8.1}/hunterMakesPy/coping.py +6 -12
- {huntermakespy-0.7.5 → huntermakespy-0.8.1}/hunterMakesPy/dataStructures.py +100 -53
- {huntermakespy-0.7.5 → huntermakespy-0.8.1}/hunterMakesPy/filesystemToolkit.py +78 -23
- {huntermakespy-0.7.5 → huntermakespy-0.8.1}/hunterMakesPy/parseParameters.py +100 -79
- {huntermakespy-0.7.5 → huntermakespy-0.8.1}/hunterMakesPy/semiotics.py +3 -1
- {huntermakespy-0.7.5 → huntermakespy-0.8.1}/hunterMakesPy/tests/conftest.py +6 -6
- huntermakespy-0.8.1/hunterMakesPy/tests/conftestAnnex.py +22 -0
- {huntermakespy-0.7.5 → huntermakespy-0.8.1}/hunterMakesPy/tests/test_dataStructures.py +2 -2
- {huntermakespy-0.7.5 → huntermakespy-0.8.1}/hunterMakesPy/tests/test_theTypes.py +1 -2
- {huntermakespy-0.7.5 → huntermakespy-0.8.1}/hunterMakesPy/theTypes.py +5 -3
- {huntermakespy-0.7.5 → huntermakespy-0.8.1}/hunterMakesPy/theTypesCallableFunction.py +2 -12
- {huntermakespy-0.7.5 → huntermakespy-0.8.1}/hunterMakesPy.egg-info/PKG-INFO +3 -3
- {huntermakespy-0.7.5 → huntermakespy-0.8.1}/hunterMakesPy.egg-info/SOURCES.txt +1 -0
- {huntermakespy-0.7.5 → huntermakespy-0.8.1}/pyproject.toml +3 -3
- {huntermakespy-0.7.5 → huntermakespy-0.8.1}/LICENSE +0 -0
- {huntermakespy-0.7.5 → huntermakespy-0.8.1}/README.md +0 -0
- {huntermakespy-0.7.5 → huntermakespy-0.8.1}/hunterMakesPy/_theSSOT.py +0 -0
- {huntermakespy-0.7.5 → huntermakespy-0.8.1}/hunterMakesPy/py.typed +0 -0
- {huntermakespy-0.7.5 → huntermakespy-0.8.1}/hunterMakesPy/tests/__init__.py +0 -0
- {huntermakespy-0.7.5 → huntermakespy-0.8.1}/hunterMakesPy/tests/test_coping.py +0 -0
- {huntermakespy-0.7.5 → huntermakespy-0.8.1}/hunterMakesPy/tests/test_filesystemToolkit.py +0 -0
- {huntermakespy-0.7.5 → huntermakespy-0.8.1}/hunterMakesPy/tests/test_parseParameters.py +0 -0
- {huntermakespy-0.7.5 → huntermakespy-0.8.1}/hunterMakesPy.egg-info/dependency_links.txt +0 -0
- {huntermakespy-0.7.5 → huntermakespy-0.8.1}/hunterMakesPy.egg-info/requires.txt +0 -0
- {huntermakespy-0.7.5 → huntermakespy-0.8.1}/hunterMakesPy.egg-info/top_level.txt +0 -0
- {huntermakespy-0.7.5 → huntermakespy-0.8.1}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hunterMakesPy
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.1
|
|
4
4
|
Summary: Easy Python functions making making functional Python functions easier.
|
|
5
5
|
Author-email: Hunter Hogan <HunterHogan@pm.me>
|
|
6
6
|
License-Expression: CC-BY-NC-4.0
|
|
@@ -17,12 +17,12 @@ Classifier: Intended Audience :: Developers
|
|
|
17
17
|
Classifier: Natural Language :: English
|
|
18
18
|
Classifier: Operating System :: OS Independent
|
|
19
19
|
Classifier: Programming Language :: Python
|
|
20
|
-
Classifier: Programming Language :: Python :: 3.
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
21
|
Classifier: Programming Language :: Python :: 3.14
|
|
22
22
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
23
|
Classifier: Topic :: Utilities
|
|
24
24
|
Classifier: Typing :: Typed
|
|
25
|
-
Requires-Python: >=3.
|
|
25
|
+
Requires-Python: >=3.11
|
|
26
26
|
Description-Content-Type: text/markdown
|
|
27
27
|
License-File: LICENSE
|
|
28
28
|
Requires-Dist: Z0Z_tools>=2.2.1
|
|
@@ -69,11 +69,13 @@ References
|
|
|
69
69
|
from __future__ import annotations
|
|
70
70
|
|
|
71
71
|
from hunterMakesPy.semiotics import (
|
|
72
|
-
|
|
72
|
+
ansiColor as ansiColor, ansiColorReset as ansiColorReset, decreasing as decreasing, errorL33T as errorL33T, inclusive as inclusive,
|
|
73
|
+
oneIndexed as oneIndexed, zeroIndexed as zeroIndexed)
|
|
73
74
|
|
|
74
75
|
# isort: split
|
|
75
76
|
from hunterMakesPy.theTypes import (
|
|
76
|
-
CallableFunction as CallableFunction,
|
|
77
|
+
CallableFunction as CallableFunction, ConcurrencyLimit as ConcurrencyLimit, identifierDotAttribute as identifierDotAttribute,
|
|
78
|
+
Ordinals as Ordinals, 小于 as 小于, 文义 as 文义, 文件 as 文件)
|
|
77
79
|
|
|
78
80
|
# isort: split
|
|
79
81
|
from hunterMakesPy.coping import PackageSettings as PackageSettings, raiseIfNone as raiseIfNone
|
|
@@ -12,14 +12,17 @@ if TYPE_CHECKING:
|
|
|
12
12
|
from importlib.machinery import ModuleSpec
|
|
13
13
|
|
|
14
14
|
def getIdentifierPackagePACKAGING(identifierPackageFALLBACK: str) -> str:
|
|
15
|
-
|
|
15
|
+
#ruff: ignore[docstring-missing-returns]
|
|
16
|
+
"""Get package name from pyproject.toml or fallback to provided value."""
|
|
16
17
|
try:
|
|
17
18
|
return tomli_loads(Path('pyproject.toml').read_text(encoding='utf-8'))['project']['name']
|
|
18
|
-
|
|
19
|
+
#ruff: ignore[blind-except]
|
|
20
|
+
except Exception:
|
|
19
21
|
return identifierPackageFALLBACK
|
|
20
22
|
|
|
21
23
|
def getPathPackageINSTALLING(identifierPackage: str) -> Path:
|
|
22
|
-
|
|
24
|
+
#ruff: ignore[docstring-missing-returns]
|
|
25
|
+
"""Return the root directory of the installed package."""
|
|
23
26
|
try:
|
|
24
27
|
moduleSpecification: ModuleSpec | None = find_spec(identifierPackage)
|
|
25
28
|
if moduleSpecification and moduleSpecification.origin:
|
|
@@ -54,8 +57,6 @@ class PackageSettings:
|
|
|
54
57
|
|
|
55
58
|
```python
|
|
56
59
|
settings = PackageSettings(identifierPackageFALLBACK='cobraPy')
|
|
57
|
-
# Automatically discovers package name from pyproject.toml
|
|
58
|
-
# Resolves installation path from package identifier
|
|
59
60
|
```
|
|
60
61
|
|
|
61
62
|
Explicit configuration for specific deployment:
|
|
@@ -117,7 +118,6 @@ def raiseIfNone(expression: TypeSansNone | None, errorMessage: str | None = None
|
|
|
117
118
|
Basic usage with attribute access:
|
|
118
119
|
```python
|
|
119
120
|
annotation = raiseIfNone(ast_arg.annotation)
|
|
120
|
-
# Raises ValueError if ast_arg.annotation is None
|
|
121
121
|
```
|
|
122
122
|
|
|
123
123
|
Function return value validation:
|
|
@@ -130,7 +130,6 @@ def raiseIfNone(expression: TypeSansNone | None, errorMessage: str | None = None
|
|
|
130
130
|
|
|
131
131
|
listFiles = ['document.txt', 'image.png', 'data.csv']
|
|
132
132
|
filename = raiseIfNone(findFirstMatch(listFiles, '.txt'))
|
|
133
|
-
# Returns 'document.txt' when match exists
|
|
134
133
|
```
|
|
135
134
|
|
|
136
135
|
Dictionary value retrieval with custom message:
|
|
@@ -138,11 +137,6 @@ def raiseIfNone(expression: TypeSansNone | None, errorMessage: str | None = None
|
|
|
138
137
|
configurationMapping = {'host': 'localhost', 'port': 8080}
|
|
139
138
|
host = raiseIfNone(configurationMapping.get('host'),
|
|
140
139
|
"Configuration must include 'host' setting")
|
|
141
|
-
# Returns 'localhost' when key exists
|
|
142
|
-
|
|
143
|
-
# This would raise ValueError with custom message:
|
|
144
|
-
# database = raiseIfNone(configurationMapping.get('database'),
|
|
145
|
-
# "Configuration must include 'database' setting")
|
|
146
140
|
```
|
|
147
141
|
|
|
148
142
|
Thanks
|
|
@@ -1,41 +1,43 @@
|
|
|
1
1
|
"""Manipulate data structures with encoding, extraction, and merging utilities.
|
|
2
2
|
|
|
3
|
-
You can use this module to
|
|
4
|
-
|
|
3
|
+
You can use this module to encode integer arrays as compact Python expressions, extract strings
|
|
4
|
+
from nested data, normalize whitespace in representations of Python data, and merge mappings of
|
|
5
|
+
iterable values into dictionaries of lists.
|
|
5
6
|
|
|
6
7
|
Contents
|
|
7
8
|
--------
|
|
8
9
|
Functions
|
|
9
10
|
autoDecodingRLE
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
Encode an integer array as a compact Python expression.
|
|
12
|
+
removeExtraWhitespace
|
|
13
|
+
Remove whitespace after commas and inside parentheses or square brackets.
|
|
12
14
|
stringItUp
|
|
13
15
|
Convert every element in input data structures to strings.
|
|
14
16
|
updateExtendPolishDictionaryLists
|
|
15
|
-
Merge
|
|
17
|
+
Merge iterable values into lists with optional deduplication and sorting.
|
|
16
18
|
"""
|
|
17
19
|
from __future__ import annotations
|
|
18
20
|
|
|
19
21
|
from charset_normalizer import CharsetMatch
|
|
20
|
-
from
|
|
21
|
-
from humpy_cytoolz.dicttoolz import valmap
|
|
22
|
+
from humpy_cytoolz.dicttoolz import valmap as mapValues
|
|
22
23
|
from humpy_cytoolz.functoolz import compose
|
|
23
24
|
from types import FunctionType
|
|
24
|
-
from typing import cast,
|
|
25
|
+
from typing import cast, overload, TYPE_CHECKING
|
|
25
26
|
import charset_normalizer
|
|
26
27
|
import more_itertools
|
|
27
28
|
import re as regex
|
|
28
29
|
import sys
|
|
29
30
|
|
|
30
31
|
if TYPE_CHECKING:
|
|
31
|
-
from collections.abc import Iterator, Mapping
|
|
32
|
-
from hunterMakesPy import
|
|
32
|
+
from collections.abc import Callable, Iterable, Iterator, Mapping
|
|
33
|
+
from hunterMakesPy import 小于, 文义, 文件
|
|
33
34
|
from numpy import integer
|
|
34
35
|
from numpy.typing import NDArray
|
|
35
|
-
from typing import Any
|
|
36
|
+
from typing import Any, Literal
|
|
36
37
|
|
|
37
38
|
def removeExtraWhitespace(string: str) -> str:
|
|
38
|
-
|
|
39
|
+
#ruff: ignore[docstring-missing-returns]
|
|
40
|
+
"""Remove extra whitespace from string representation of Python data structures."""
|
|
39
41
|
commas: str = regex.sub(r',\s+', ',', string)
|
|
40
42
|
bracketsOpening: str = regex.sub(r'([\[\(])\s+', r'\1', commas)
|
|
41
43
|
return regex.sub(r'\s+([\]\)])', r'\1', bracketsOpening)
|
|
@@ -70,7 +72,8 @@ def autoDecodingRLE(arrayTarget: NDArray[integer[Any]], *, assumeAddSpaces: bool
|
|
|
70
72
|
"""
|
|
71
73
|
def sliceNDArrayToNestedLists(arraySlice: NDArray[integer[Any]]) -> Any:
|
|
72
74
|
def getLengthOption(optionAsStr: str) -> int:
|
|
73
|
-
|
|
75
|
+
#ruff: ignore[docstring-missing-returns]
|
|
76
|
+
"""`assumeAddSpaces` characters: `,` 1 space; `]*` 2 spaces."""
|
|
74
77
|
return assumeAddSpaces * (optionAsStr.count(',') + optionAsStr.count(']*') * 2) + len(optionAsStr)
|
|
75
78
|
|
|
76
79
|
if 1 < arraySlice.ndim:
|
|
@@ -92,7 +95,6 @@ def autoDecodingRLE(arrayTarget: NDArray[integer[Any]], *, assumeAddSpaces: bool
|
|
|
92
95
|
option2 = ImaSerious
|
|
93
96
|
option2AsStr: str | None = None
|
|
94
97
|
|
|
95
|
-
# alpha, potential function
|
|
96
98
|
option1AsStr: str = option1AsStr or removeExtraWhitespace(str(option1))
|
|
97
99
|
lengthOption1: int = getLengthOption(option1AsStr)
|
|
98
100
|
|
|
@@ -123,7 +125,6 @@ def autoDecodingRLE(arrayTarget: NDArray[integer[Any]], *, assumeAddSpaces: bool
|
|
|
123
125
|
option2 = malkovichAsList
|
|
124
126
|
option2AsStr = None
|
|
125
127
|
|
|
126
|
-
# beta, potential function
|
|
127
128
|
option1AsStr = option1AsStr or removeExtraWhitespace(str(option1))
|
|
128
129
|
lengthOption1 = getLengthOption(option1AsStr)
|
|
129
130
|
|
|
@@ -148,19 +149,20 @@ def autoDecodingRLE(arrayTarget: NDArray[integer[Any]], *, assumeAddSpaces: bool
|
|
|
148
149
|
|
|
149
150
|
patternRegex: regex.Pattern[str] = regex.compile(
|
|
150
151
|
r"(?<!rang)(?:"
|
|
151
|
-
#
|
|
152
|
+
#Pattern 1: Comma ahead, bracket behind
|
|
152
153
|
r"(?P<joinAhead>,)\((?P<malkovich>\d+),(?P<multiply>\d+)\)(?P<bracketBehind>])|"
|
|
153
|
-
#
|
|
154
|
+
#Pattern 2: Bracket or start ahead, comma behind
|
|
154
155
|
r"(?P<bracketOrStartAhead>\[|^.)\((?P<malkovichMalkovich>\d+),(?P<multiplyIDK>\d+)\)(?P<joinBehind>,)|"
|
|
155
|
-
#
|
|
156
|
+
#Pattern 3: Bracket ahead, bracket behind
|
|
156
157
|
r"(?P<bracketAhead>\[)\((?P<malkovichMalkovichMalkovich>\d+),(?P<multiply_whatever>\d+)\)(?P<bracketBehindBracketBehind>])|"
|
|
157
|
-
#
|
|
158
|
+
#Pattern 4: Comma ahead, comma behind
|
|
158
159
|
r"(?P<joinAheadJoinAhead>,)\((?P<malkovichMalkovichMalkovichMalkovich>\d+),(?P<multiplyOrSomething>\d+)\)(?P<joinBehindJoinBehind>,)"
|
|
159
160
|
r")"
|
|
160
161
|
)
|
|
161
162
|
|
|
162
163
|
def replacementByContext(match: regex.Match[str]) -> str:
|
|
163
|
-
|
|
164
|
+
#ruff: ignore[docstring-missing-returns]
|
|
165
|
+
"""Generate replacement string based on context patterns."""
|
|
164
166
|
elephino: dict[str, str | None] = match.groupdict()
|
|
165
167
|
joinAhead: str | None = elephino.get('joinAhead') or elephino.get('joinAheadJoinAhead')
|
|
166
168
|
malkovich: str | None = elephino.get('malkovich') or elephino.get('malkovichMalkovich') or elephino.get('malkovichMalkovichMalkovich') or elephino.get('malkovichMalkovichMalkovichMalkovich')
|
|
@@ -176,8 +178,8 @@ def autoDecodingRLE(arrayTarget: NDArray[integer[Any]], *, assumeAddSpaces: bool
|
|
|
176
178
|
arrayAsStr = patternRegex.sub(replacementByContext, arrayAsStr)
|
|
177
179
|
arrayAsStr = patternRegex.sub(replacementByContext, arrayAsStr)
|
|
178
180
|
|
|
179
|
-
|
|
180
|
-
#
|
|
181
|
+
#.replace `range(0,stop)` syntax with `range(stop)` syntax.
|
|
182
|
+
#Add unpack operator `*` for automatic decoding when evaluated.
|
|
181
183
|
return arrayAsStr.replace('range(0,', 'range(').replace('range', '*range')
|
|
182
184
|
|
|
183
185
|
def stringItUp(*scrapPile: Any) -> list[str]:
|
|
@@ -220,16 +222,18 @@ def stringItUp(*scrapPile: Any) -> list[str]:
|
|
|
220
222
|
elif isinstance(KitKat, (list, tuple, set, frozenset, range)):
|
|
221
223
|
for kit in KitKat: # pyright: ignore[reportUnknownVariableType]
|
|
222
224
|
drill(kit)
|
|
223
|
-
elif hasattr(KitKat, '__iter__'):
|
|
225
|
+
elif hasattr(KitKat, '__iter__'):
|
|
224
226
|
for kat in KitKat:
|
|
225
227
|
drill(kat)
|
|
226
228
|
else:
|
|
227
229
|
try:
|
|
228
|
-
|
|
230
|
+
#ruff: ignore[unnecessary-dunder-call]
|
|
231
|
+
sharingIsCaring: str = KitKat.__str__()
|
|
229
232
|
listStrungUp.append(sharingIsCaring)
|
|
230
233
|
except AttributeError:
|
|
231
234
|
pass
|
|
232
|
-
except TypeError:
|
|
235
|
+
except TypeError:
|
|
236
|
+
#"The error traceback provided indicates that there is an issue when calling the __str__ method on an object that does not have this method properly defined, leading to a TypeError."
|
|
233
237
|
pass
|
|
234
238
|
except:
|
|
235
239
|
message: str = (f"\nWoah! I received '{repr(KitKat)}'.\nTheir report card says, 'Plays well with others: Needs improvement.'\n")
|
|
@@ -242,55 +246,98 @@ def stringItUp(*scrapPile: Any) -> list[str]:
|
|
|
242
246
|
listStrungUp.append(repr(scrap))
|
|
243
247
|
return listStrungUp
|
|
244
248
|
|
|
245
|
-
|
|
249
|
+
@overload
|
|
250
|
+
def updateExtendPolishDictionaryLists(*dictionaryLists: Mapping[文件, Iterable[文义]], destroyDuplicates: bool = False, reorderLists: Literal[False] = False, killErroneousDataTypes: bool = False) -> dict[文件, list[文义]]: ...
|
|
246
251
|
|
|
247
|
-
|
|
248
|
-
def updateExtendPolishDictionaryLists(*dictionaryLists: Mapping[文件,
|
|
249
|
-
"""Merge multiple dictionaries with `list` values into a single dictionary with the `list` values merged.
|
|
252
|
+
@overload
|
|
253
|
+
def updateExtendPolishDictionaryLists(*dictionaryLists: Mapping[文件, Iterable[小于]], destroyDuplicates: bool = False, reorderLists: Literal[True], killErroneousDataTypes: bool = False) -> dict[文件, list[小于]]: ...
|
|
250
254
|
|
|
251
|
-
|
|
255
|
+
@overload
|
|
256
|
+
def updateExtendPolishDictionaryLists(*dictionaryLists: Mapping[文件, Iterable[小于]], destroyDuplicates: bool = False, reorderLists: bool, killErroneousDataTypes: bool = False) -> dict[文件, list[小于]]: ...
|
|
257
|
+
|
|
258
|
+
def updateExtendPolishDictionaryLists(*dictionaryLists: Mapping[文件, Iterable[文义]], destroyDuplicates: bool = False, reorderLists: bool = False, killErroneousDataTypes: bool = False) -> dict[文件, list[文义]]:
|
|
259
|
+
"""Merge mappings of iterable values into a dictionary of lists.
|
|
260
|
+
|
|
261
|
+
You can use this function to combine values under matching keys across `dictionaryLists`.
|
|
262
|
+
This function returns a new dictionary with new lists and can remove duplicate elements,
|
|
263
|
+
sort elements, or skip entries that raise `TypeError` during merging.
|
|
264
|
+
|
|
265
|
+
Iteration and Ordering
|
|
266
|
+
----------------------
|
|
267
|
+
This function consumes each value's iterable in mapping argument order. Within each value,
|
|
268
|
+
the list follows the iterable's order, which may be unpredictable for sets. Strings contribute
|
|
269
|
+
individual characters, and mapping values contribute their keys.
|
|
270
|
+
|
|
271
|
+
Deduplication uses `dict.fromkeys` [1] to preserve first occurrences. Sorting uses `sorted` [2]
|
|
272
|
+
and runs after deduplication. Every retained value becomes a list regardless of the input type.
|
|
252
273
|
|
|
253
274
|
Parameters
|
|
254
275
|
----------
|
|
255
|
-
*dictionaryLists :
|
|
256
|
-
|
|
257
|
-
|
|
276
|
+
*dictionaryLists : Mapping[文件, Iterable[文义]]
|
|
277
|
+
The mappings to merge, with hashable keys and iterable values. A single mapping receives
|
|
278
|
+
the same conversion and optional processing. No mappings produce an empty dictionary.
|
|
258
279
|
destroyDuplicates : bool = False
|
|
259
|
-
If `True`,
|
|
280
|
+
If `True`, remove duplicate elements from each merged list, preserving the first occurrence.
|
|
281
|
+
Elements must be hashable when this option is enabled.
|
|
260
282
|
reorderLists : bool = False
|
|
261
|
-
If `True`,
|
|
262
|
-
|
|
283
|
+
If `True`, sort each merged list in ascending order after optional deduplication.
|
|
284
|
+
Elements within each list must be mutually comparable.
|
|
263
285
|
killErroneousDataTypes : bool = False
|
|
264
|
-
If `True`,
|
|
265
|
-
|
|
286
|
+
If `True`, skip an entry when converting the entry's value to a list or merging the entry
|
|
287
|
+
raises `TypeError`. Successfully merged values for the same key remain in the result.
|
|
288
|
+
This option does not suppress `TypeError` from deduplication or sorting.
|
|
266
289
|
|
|
267
290
|
Returns
|
|
268
291
|
-------
|
|
269
|
-
ePluribusUnum : dict[文件, list[
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
292
|
+
ePluribusUnum : dict[文件, list[文义]]
|
|
293
|
+
The merged dictionary, with a list for every retained key. This function does not modify
|
|
294
|
+
the input mappings or their containers, but the lists retain references to the original elements.
|
|
295
|
+
|
|
296
|
+
Raises
|
|
297
|
+
------
|
|
298
|
+
TypeError
|
|
299
|
+
If a value cannot be iterated or an entry cannot be merged and `killErroneousDataTypes`
|
|
300
|
+
is `False`. Also raised for unhashable elements during deduplication or incomparable
|
|
301
|
+
elements during sorting, regardless of `killErroneousDataTypes`.
|
|
302
|
+
|
|
303
|
+
Examples
|
|
304
|
+
--------
|
|
305
|
+
The README merges server lists while preserving the first occurrence of each server name.
|
|
306
|
+
|
|
307
|
+
```python
|
|
308
|
+
from hunterMakesPy.dataStructures import updateExtendPolishDictionaryLists
|
|
309
|
+
|
|
310
|
+
merged = updateExtendPolishDictionaryLists(
|
|
311
|
+
{"servers": ["chicago"]},
|
|
312
|
+
{"servers": ["tokyo", "chicago"]},
|
|
313
|
+
destroyDuplicates=True,
|
|
314
|
+
)
|
|
315
|
+
# Returns {"servers": ["chicago", "tokyo"]}.
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
References
|
|
319
|
+
----------
|
|
320
|
+
[1] Python dictionaries and `dict.fromkeys`.
|
|
321
|
+
https://docs.python.org/3/library/stdtypes.html#dict.fromkeys
|
|
322
|
+
[2] Python `sorted`.
|
|
323
|
+
https://docs.python.org/3/library/functions.html#sorted
|
|
324
|
+
"""
|
|
325
|
+
ePluribusUnum: dict[文件, list[文义]] = {}
|
|
280
326
|
|
|
281
327
|
for dictionaryListTarget in dictionaryLists:
|
|
282
328
|
for keyName, keyValue in dictionaryListTarget.items():
|
|
283
329
|
try:
|
|
284
|
-
ImaList: list[
|
|
330
|
+
ImaList: list[文义] = list(keyValue)
|
|
285
331
|
ePluribusUnum.setdefault(keyName, []).extend(ImaList)
|
|
286
|
-
except TypeError as error:
|
|
332
|
+
except TypeError as error:
|
|
287
333
|
if killErroneousDataTypes:
|
|
288
334
|
continue
|
|
289
335
|
raise TypeError from error
|
|
290
336
|
|
|
291
337
|
if destroyDuplicates:
|
|
292
|
-
ePluribusUnum =
|
|
338
|
+
ePluribusUnum = mapValues(compose(list, dict.fromkeys), ePluribusUnum)
|
|
293
339
|
if reorderLists:
|
|
294
|
-
|
|
340
|
+
#=SIN= `sorted` is broken.
|
|
341
|
+
ePluribusUnum = mapValues(cast('Callable[[list[文义]], list[文义]]', sorted), ePluribusUnum)
|
|
295
342
|
|
|
296
343
|
return ePluribusUnum
|
|
@@ -1,41 +1,43 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Import Python identifiers and write or append text to files.
|
|
2
2
|
|
|
3
3
|
(AI generated docstring)
|
|
4
4
|
|
|
5
5
|
You can use this module to import `identifier` values from logical module paths or Python files,
|
|
6
|
-
create parent directories for output paths, and write text or formatted Python
|
|
7
|
-
text streams. The writing functions can normalize Python imports with
|
|
8
|
-
before the destination receives the final text.
|
|
6
|
+
create parent directories for output paths, append text to files, and write text or formatted Python
|
|
7
|
+
source to files and text streams. The writing functions can normalize Python imports with
|
|
8
|
+
`autoflake` [1] and `isort` [2] before the destination receives the final text.
|
|
9
9
|
|
|
10
10
|
Contents
|
|
11
11
|
--------
|
|
12
12
|
Functions
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
13
|
+
appendStringToHere
|
|
14
|
+
Append `this` to `pathFilename` without changing existing text.
|
|
15
|
+
importLogicalPath2Identifier
|
|
16
|
+
Import `identifier` from the module named by `logicalPathModule`.
|
|
17
|
+
importPathFilename2Identifier
|
|
18
|
+
Import `identifier` from the Python file at `pathFilename`.
|
|
19
|
+
makeDirectorySafely
|
|
20
|
+
Create parent directories for `pathFilename` when `pathFilename` is a filesystem path.
|
|
21
|
+
makeDirsSafely
|
|
22
|
+
Temporary alias for `makeDirectorySafely`.
|
|
23
|
+
writePython
|
|
24
|
+
Format `pythonSource` and write `pythonSource` to `pathFilename`.
|
|
25
|
+
writeStringToHere
|
|
26
|
+
Write `this` to `pathFilename`.
|
|
25
27
|
|
|
26
28
|
Variables
|
|
27
29
|
---------
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
settings_autoflakeDEFAULT
|
|
31
|
+
Default settings dictionary for Python source cleanup.
|
|
32
|
+
settings_isortDEFAULT
|
|
33
|
+
Default settings dictionary for Python import sorting.
|
|
32
34
|
|
|
33
35
|
References
|
|
34
36
|
----------
|
|
35
37
|
[1] autoflake
|
|
36
|
-
|
|
38
|
+
https://github.com/PyCQA/autoflake
|
|
37
39
|
[2] isort
|
|
38
|
-
|
|
40
|
+
https://pycqa.github.io/isort/
|
|
39
41
|
"""
|
|
40
42
|
from __future__ import annotations
|
|
41
43
|
|
|
@@ -55,6 +57,58 @@ if TYPE_CHECKING:
|
|
|
55
57
|
from types import ModuleType
|
|
56
58
|
from typing import Any
|
|
57
59
|
|
|
60
|
+
def appendStringToHere(this: str, pathFilename: PathLike[Any] | PurePath) -> Path:
|
|
61
|
+
"""Append `this` to the UTF-8 file at `pathFilename`.
|
|
62
|
+
|
|
63
|
+
(AI generated docstring)
|
|
64
|
+
|
|
65
|
+
You can use this function to add text to the end of a file while preserving existing text. This
|
|
66
|
+
function creates missing parent directories when possible and creates the file when needed.
|
|
67
|
+
This function returns the destination path after writing and closing the file.
|
|
68
|
+
|
|
69
|
+
Parameters
|
|
70
|
+
----------
|
|
71
|
+
this : str
|
|
72
|
+
The text to append. This function does not add a separator or trailing newline.
|
|
73
|
+
pathFilename : PathLike[Any] | PurePath
|
|
74
|
+
The destination file path. This function accepts a filesystem path rather than an open stream.
|
|
75
|
+
|
|
76
|
+
Returns
|
|
77
|
+
-------
|
|
78
|
+
destinationAppended : Path
|
|
79
|
+
The destination path as a `Path`, without resolving the path to an absolute path.
|
|
80
|
+
|
|
81
|
+
See Also
|
|
82
|
+
--------
|
|
83
|
+
`writeStringToHere`
|
|
84
|
+
Replace a file's contents or write to an open text stream.
|
|
85
|
+
|
|
86
|
+
File Writing
|
|
87
|
+
------------
|
|
88
|
+
This function prepares parent directories with `makeDirectorySafely` [1], then opens the file in
|
|
89
|
+
append mode with UTF-8 encoding and `newline=''` [2]. Newline characters in `this` are written
|
|
90
|
+
without translation. This function writes and flushes the text with `writeStringToHere` [3].
|
|
91
|
+
Directory creation suppresses `OSError`, but errors from opening or writing the file propagate.
|
|
92
|
+
|
|
93
|
+
Examples
|
|
94
|
+
--------
|
|
95
|
+
No invocation of this function exists elsewhere in the codebase.
|
|
96
|
+
|
|
97
|
+
References
|
|
98
|
+
----------
|
|
99
|
+
[1] `makeDirectorySafely`
|
|
100
|
+
|
|
101
|
+
[2] Python `open` modes, encoding, and newline handling.
|
|
102
|
+
https://docs.python.org/3/library/functions.html#open
|
|
103
|
+
[3] `writeStringToHere`
|
|
104
|
+
|
|
105
|
+
"""
|
|
106
|
+
pathFilename = Path(pathFilename)
|
|
107
|
+
makeDirectorySafely(pathFilename)
|
|
108
|
+
with pathFilename.open(encoding='utf-8', mode='a', newline='') as streamAppend:
|
|
109
|
+
writeStringToHere(this, streamAppend)
|
|
110
|
+
return pathFilename
|
|
111
|
+
|
|
58
112
|
def importLogicalPath2Identifier(logicalPathModule: identifierDotAttribute, identifier: str, packageIdentifierIfRelative: str | None = None) -> Any:
|
|
59
113
|
"""Import `identifier` from the module named by `logicalPathModule`.
|
|
60
114
|
|
|
@@ -82,6 +136,7 @@ def importLogicalPath2Identifier(logicalPathModule: identifierDotAttribute, iden
|
|
|
82
136
|
return getattr(moduleImported, identifier)
|
|
83
137
|
|
|
84
138
|
def importPathFilename2Identifier(pathFilename: PathLike[Any] | PurePath, identifier: str, moduleIdentifier: str | None = None) -> Any:
|
|
139
|
+
#ruff: ignore[docstring-extraneous-exception]
|
|
85
140
|
"""Import `identifier` from the Python file at `pathFilename`.
|
|
86
141
|
|
|
87
142
|
You can use this function to load a Python source file as a module and retrieve a named attribute
|
|
@@ -118,7 +173,7 @@ def importPathFilename2Identifier(pathFilename: PathLike[Any] | PurePath, identi
|
|
|
118
173
|
https://docs.python.org/3/library/importlib.html#importlib.util.spec_from_file_location
|
|
119
174
|
[2] `importlib.util.module_from_spec`
|
|
120
175
|
https://docs.python.org/3/library/importlib.html#importlib.util.module_from_spec
|
|
121
|
-
"""
|
|
176
|
+
"""
|
|
122
177
|
pathFilename = Path(pathFilename)
|
|
123
178
|
|
|
124
179
|
importlibSpecification: ModuleSpec | None = importlib.util.spec_from_file_location(moduleIdentifier or pathFilename.stem, pathFilename)
|
|
@@ -1,20 +1,13 @@
|
|
|
1
|
-
# ruff:
|
|
2
|
-
"""Validate
|
|
1
|
+
# ruff: file-ignore[raise-within-try]
|
|
2
|
+
"""Validate integer inputs, calculate worker counts, and interpret string parameters.
|
|
3
3
|
|
|
4
4
|
(AI generated docstring)
|
|
5
5
|
|
|
6
|
-
You can use this module to
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
The integer validation function accepts various numeric types including strings, floats,
|
|
12
|
-
complex numbers, and binary data, converting them to integers while detecting ambiguous
|
|
13
|
-
or incompatible values. The concurrency limit function interprets boolean, integer, and
|
|
14
|
-
float values to compute processor counts with support for absolute limits, fractional
|
|
15
|
-
allocation, and reserved-processor specifications. The string interpretation function
|
|
16
|
-
attempts to parse string values as True, False, or None to handle parameter type mismatches
|
|
17
|
-
gracefully.
|
|
6
|
+
You can use this module to convert integer-compatible inputs into lists of integers, calculate
|
|
7
|
+
worker counts from absolute or fractional limits, and interpret strings as `True`, `False`, or
|
|
8
|
+
`None`. Integer validation reports incompatible values with context about the caller's parameter.
|
|
9
|
+
Worker-count calculation uses the system CPU count from `multiprocessing` [1] by default and also
|
|
10
|
+
accepts numeric strings. Binary integer inputs use `charset_normalizer` [2] for text decoding.
|
|
18
11
|
|
|
19
12
|
Contents
|
|
20
13
|
--------
|
|
@@ -26,11 +19,15 @@ Functions
|
|
|
26
19
|
oopsieKwargsie
|
|
27
20
|
Interpret a string as True, False, or None to avoid exceptions.
|
|
28
21
|
|
|
22
|
+
Classes
|
|
23
|
+
ErrorMessageContext
|
|
24
|
+
Store value, type, and container information for integer-validation error messages.
|
|
25
|
+
|
|
29
26
|
References
|
|
30
27
|
----------
|
|
31
|
-
[1] multiprocessing
|
|
28
|
+
[1] Python `multiprocessing`.
|
|
32
29
|
https://docs.python.org/3/library/multiprocessing.html
|
|
33
|
-
[2] charset-normalizer
|
|
30
|
+
[2] charset-normalizer.
|
|
34
31
|
https://github.com/Ousret/charset_normalizer
|
|
35
32
|
|
|
36
33
|
"""
|
|
@@ -45,8 +42,8 @@ import sys
|
|
|
45
42
|
|
|
46
43
|
if TYPE_CHECKING:
|
|
47
44
|
from charset_normalizer.models import CharsetMatch
|
|
48
|
-
from collections.abc import
|
|
49
|
-
from hunterMakesPy.theTypes import
|
|
45
|
+
from collections.abc import Collection
|
|
46
|
+
from hunterMakesPy.theTypes import ConcurrencyLimit
|
|
50
47
|
from typing import Any
|
|
51
48
|
|
|
52
49
|
@dataclass
|
|
@@ -71,49 +68,78 @@ class ErrorMessageContext:
|
|
|
71
68
|
containerType: str | None = None
|
|
72
69
|
isElement: bool = False
|
|
73
70
|
|
|
74
|
-
def defineConcurrencyLimit(*, limit:
|
|
75
|
-
"""
|
|
71
|
+
def defineConcurrencyLimit(*, limit: ConcurrencyLimit, cpuTotal: int = multiprocessing.cpu_count()) -> int:
|
|
72
|
+
"""Calculate a worker count from an absolute, fractional, or reserved-CPU limit.
|
|
73
|
+
|
|
74
|
+
You can use this function to select a worker count from `limit` and `cpuTotal`. This function
|
|
75
|
+
accepts explicit counts, fractions of `cpuTotal`, counts to reserve, and boolean choices.
|
|
76
|
+
This function also accepts numeric strings and strings representing `True`, `False`, or `None`.
|
|
77
|
+
The result is at least one worker and is at most 61 workers on Windows.
|
|
78
|
+
|
|
79
|
+
String Parsing
|
|
80
|
+
--------------
|
|
81
|
+
This function first uses `oopsieKwargsie` [1] to interpret strings representing `True`, `False`,
|
|
82
|
+
or `None`, ignoring capitalization and surrounding whitespace. Other strings are converted
|
|
83
|
+
with `float`. An invalid numeric string raises `ValueError` with the conversion error as the cause.
|
|
84
|
+
|
|
85
|
+
Processor Counts
|
|
86
|
+
----------------
|
|
87
|
+
This function rounds floating-point counts whose absolute value is at least one before applying
|
|
88
|
+
the count rules. Rounding uses Python `round` [2], including rounding ties to the nearest even
|
|
89
|
+
integer. This function applies the following rules before enforcing the platform bounds.
|
|
90
|
+
|
|
91
|
+
- `None`, `False`, or zero select `cpuTotal`.
|
|
92
|
+
- `True` selects one worker.
|
|
93
|
+
- A positive count of at least one selects that count.
|
|
94
|
+
- A positive fraction below one selects `round(limit * cpuTotal)` workers.
|
|
95
|
+
- A negative fraction above negative one reserves `abs(round(limit * cpuTotal))` CPUs.
|
|
96
|
+
- A negative count of at most negative one reserves `abs(int(limit))` CPUs.
|
|
97
|
+
|
|
98
|
+
This function caps the result at 61 on Windows, matching the restriction for
|
|
99
|
+
`concurrent.futures.ProcessPoolExecutor` [3]. This function then enforces the minimum of one.
|
|
76
100
|
|
|
77
101
|
Parameters
|
|
78
102
|
----------
|
|
79
|
-
limit :
|
|
80
|
-
|
|
103
|
+
limit : ConcurrencyLimit
|
|
104
|
+
The worker-count specification. `None`, `False`, or zero select `cpuTotal`; `True` selects
|
|
105
|
+
one worker. Positive counts select workers to use, and negative counts select CPUs to reserve.
|
|
106
|
+
Fractions between negative one and one select proportions to reserve or use, respectively.
|
|
107
|
+
Strings are also accepted at runtime, although the type annotation excludes strings.
|
|
81
108
|
cpuTotal : int = multiprocessing.cpu_count()
|
|
82
|
-
The
|
|
109
|
+
The CPU count used for fractional limits and reserved-CPU calculations. The default is the
|
|
110
|
+
value of `multiprocessing.cpu_count` [4] evaluated when this module is imported.
|
|
83
111
|
|
|
84
112
|
Returns
|
|
85
113
|
-------
|
|
86
114
|
concurrencyLimit : int
|
|
87
|
-
The calculated
|
|
115
|
+
The calculated worker count, with a minimum of one and a Windows maximum of 61.
|
|
116
|
+
An explicit positive count can exceed `cpuTotal`.
|
|
88
117
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
CPUlimit: Limitation = None
|
|
94
|
-
```
|
|
118
|
+
Raises
|
|
119
|
+
------
|
|
120
|
+
ValueError
|
|
121
|
+
If a string does not represent a number, `True`, `False`, or `None`.
|
|
95
122
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
123
|
+
Examples
|
|
124
|
+
--------
|
|
125
|
+
The README selects three quarters of the default CPU count.
|
|
99
126
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
CPUlimit: bool | float | int | None
|
|
103
|
-
Whether and how to limit the the number of available processors used by the function. See notes for details.
|
|
127
|
+
```python
|
|
128
|
+
from hunterMakesPy.parseParameters import defineConcurrencyLimit
|
|
104
129
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
Limits on CPU usage, `CPUlimit`:
|
|
108
|
-
- `False`, `None`, or `0`: No limits on processor usage; uses all available processors. All other values will potentially limit processor usage.
|
|
109
|
-
- `True`: Yes, limit the processor usage; limits to 1 processor.
|
|
110
|
-
- `int >= 1`: The maximum number of available processors to use.
|
|
111
|
-
- `0 < float < 1`: The maximum number of processors to use expressed as a fraction of available processors.
|
|
112
|
-
- `-1 < float < 0`: The number of processors to *not* use expressed as a fraction of available processors.
|
|
113
|
-
- `int <= -1`: The number of available processors to *not* use.
|
|
114
|
-
- If the value of `CPUlimit` is a `float` greater than 1 or less than -1, the function truncates the value to an `int` with the same sign as the `float`.
|
|
115
|
-
```
|
|
130
|
+
workers = defineConcurrencyLimit(limit=0.75)
|
|
131
|
+
```
|
|
116
132
|
|
|
133
|
+
References
|
|
134
|
+
----------
|
|
135
|
+
[1] `oopsieKwargsie`
|
|
136
|
+
|
|
137
|
+
[2] Python `round`.
|
|
138
|
+
https://docs.python.org/3/library/functions.html#round
|
|
139
|
+
[3] Python `concurrent.futures.ProcessPoolExecutor`.
|
|
140
|
+
https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.ProcessPoolExecutor
|
|
141
|
+
[4] Python `multiprocessing.cpu_count`.
|
|
142
|
+
https://docs.python.org/3/library/multiprocessing.html#multiprocessing.cpu_count
|
|
117
143
|
"""
|
|
118
144
|
if isinstance(limit, str):
|
|
119
145
|
limitFromString: bool | str | None = oopsieKwargsie(limit)
|
|
@@ -142,7 +168,7 @@ def defineConcurrencyLimit(*, limit: Limitation, cpuTotal: int = multiprocessing
|
|
|
142
168
|
elif limit <= -1:
|
|
143
169
|
concurrencyLimit = cpuTotal - abs(int(limit))
|
|
144
170
|
|
|
145
|
-
#
|
|
171
|
+
#https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.ProcessPoolExecutor
|
|
146
172
|
if sys.platform == "win32":
|
|
147
173
|
concurrencyLimit = min(concurrencyLimit, 61)
|
|
148
174
|
return max(int(concurrencyLimit), 1)
|
|
@@ -195,7 +221,7 @@ def _constructErrorMessage(context: ErrorMessageContext, parameterName: str, par
|
|
|
195
221
|
|
|
196
222
|
return "".join(messageParts)
|
|
197
223
|
|
|
198
|
-
def intInnit(listInt_Allegedly:
|
|
224
|
+
def intInnit(listInt_Allegedly: Collection[Any], parameterName: str | None = None, parameterType: type[Any] | None = None) -> list[int]:
|
|
199
225
|
"""Validate and convert input values to a `list` of integers.
|
|
200
226
|
|
|
201
227
|
Accepts various numeric types and attempts to convert them into integers while providing descriptive error messages. This
|
|
@@ -204,7 +230,7 @@ def intInnit(listInt_Allegedly: Iterable[Any], parameterName: str | None = None,
|
|
|
204
230
|
|
|
205
231
|
Parameters
|
|
206
232
|
----------
|
|
207
|
-
listInt_Allegedly :
|
|
233
|
+
listInt_Allegedly : Collection[Any]
|
|
208
234
|
The input sequence that should contain integer-compatible values. Accepts integers, strings, floats, complex numbers, and
|
|
209
235
|
binary data. Rejects boolean values and non-integer numeric values.
|
|
210
236
|
parameterName : str | None = None
|
|
@@ -236,11 +262,12 @@ def intInnit(listInt_Allegedly: Iterable[Any], parameterName: str | None = None,
|
|
|
236
262
|
parameterType = parameterType or list
|
|
237
263
|
|
|
238
264
|
if not listInt_Allegedly:
|
|
239
|
-
message: str = f"I did not receive a value for {parameterName}
|
|
265
|
+
message: str = f"I did not receive a value for `{parameterName}`, but I must have it."
|
|
240
266
|
raise ValueError(message)
|
|
241
267
|
|
|
242
|
-
#
|
|
243
|
-
#
|
|
268
|
+
#Be nice, and assume the input container is valid and every element is valid.
|
|
269
|
+
#Nevertheless, this is a "fail-early" step, so reject ambiguity and try to induce errors now that could be catastrophic later.
|
|
270
|
+
#ruff: ignore[too-many-statements-in-try-clause]
|
|
244
271
|
try:
|
|
245
272
|
iter(listInt_Allegedly)
|
|
246
273
|
lengthInitial: int | None = None
|
|
@@ -250,19 +277,18 @@ def intInnit(listInt_Allegedly: Iterable[Any], parameterName: str | None = None,
|
|
|
250
277
|
listValidated: list[int] = []
|
|
251
278
|
|
|
252
279
|
for allegedInt in listInt_Allegedly:
|
|
253
|
-
|
|
254
280
|
errorMessageContext: ErrorMessageContext = ErrorMessageContext(
|
|
255
|
-
parameterValue=allegedInt
|
|
256
|
-
parameterValueType=type(allegedInt).__name__
|
|
257
|
-
isElement=True
|
|
281
|
+
parameterValue=allegedInt
|
|
282
|
+
, parameterValueType=type(allegedInt).__name__
|
|
283
|
+
, isElement=True
|
|
258
284
|
)
|
|
259
285
|
|
|
260
|
-
#
|
|
286
|
+
#Always rejected as ambiguous
|
|
261
287
|
if isinstance(allegedInt, bool):
|
|
262
288
|
raise TypeError(errorMessageContext)
|
|
263
289
|
|
|
264
|
-
#
|
|
265
|
-
#
|
|
290
|
+
#In this section, we know the Python type is not `int`, but maybe the value is clearly an integer.
|
|
291
|
+
#Through a series of conversions, allow data to cascade down into either an `int` or a meaningful error message.
|
|
266
292
|
|
|
267
293
|
if isinstance(allegedInt, (bytes, bytearray, memoryview)):
|
|
268
294
|
errorMessageContext.parameterValue = None # Don't expose potentially garbled binary data in error messages
|
|
@@ -289,29 +315,23 @@ def intInnit(listInt_Allegedly: Iterable[Any], parameterName: str | None = None,
|
|
|
289
315
|
|
|
290
316
|
listValidated.append(allegedInt)
|
|
291
317
|
|
|
292
|
-
if lengthInitial is not None and isinstance(listInt_Allegedly, Sized) and len(listInt_Allegedly) != lengthInitial:
|
|
318
|
+
if lengthInitial is not None and isinstance(listInt_Allegedly, Sized) and len(listInt_Allegedly) != lengthInitial: # pyright: ignore[reportUnnecessaryComparison]
|
|
293
319
|
raise RuntimeError((lengthInitial, len(listInt_Allegedly)))
|
|
294
320
|
|
|
295
|
-
except (TypeError, ValueError) as
|
|
296
|
-
if isinstance(
|
|
297
|
-
context: ErrorMessageContext =
|
|
321
|
+
except (TypeError, ValueError) as ERROR:
|
|
322
|
+
if isinstance(ERROR.args[0], ErrorMessageContext):
|
|
323
|
+
context: ErrorMessageContext = ERROR.args[0]
|
|
298
324
|
if not context.containerType:
|
|
299
325
|
context.containerType = type(listInt_Allegedly).__name__
|
|
300
326
|
message = _constructErrorMessage(context, parameterName, parameterType)
|
|
301
|
-
raise type(
|
|
302
|
-
#
|
|
303
|
-
raise
|
|
304
|
-
|
|
305
|
-
except RuntimeError as
|
|
306
|
-
lengthCurrent
|
|
307
|
-
lengthInitial, lengthCurrent =
|
|
308
|
-
|
|
309
|
-
f"The input sequence {parameterName} was modified during iteration. "
|
|
310
|
-
f"Initial length {lengthInitial}, current length {lengthCurrent}."
|
|
311
|
-
)
|
|
312
|
-
raise RuntimeError(
|
|
313
|
-
ERRORmessage
|
|
314
|
-
) from None
|
|
327
|
+
raise type(ERROR)(message) from None
|
|
328
|
+
#If it's not our Exception, don't molest it
|
|
329
|
+
raise ValueError from ERROR
|
|
330
|
+
|
|
331
|
+
except RuntimeError as ERROR:
|
|
332
|
+
lengthInitial, lengthCurrent = ERROR.args[0]
|
|
333
|
+
message = (f"`{parameterName}` was modified during iteration: {lengthInitial = }, current length {lengthCurrent = }.")
|
|
334
|
+
raise RuntimeError(message) from None
|
|
315
335
|
|
|
316
336
|
else:
|
|
317
337
|
return listValidated
|
|
@@ -338,6 +358,7 @@ def oopsieKwargsie(huh: Any) -> bool | str | None:
|
|
|
338
358
|
if not isinstance(huh, str):
|
|
339
359
|
try:
|
|
340
360
|
huh = str(huh)
|
|
361
|
+
#ruff: ignore[blind-except]
|
|
341
362
|
except BaseException:
|
|
342
363
|
return huh
|
|
343
364
|
formatted: str = huh.strip().title()
|
|
@@ -326,7 +326,7 @@ References
|
|
|
326
326
|
#======== Some colors for printing text to the terminal. ========
|
|
327
327
|
# Many values and options at https://gist.github.com/JBlond/2fea43a3049b38287e5e9cefc87b2124
|
|
328
328
|
# Many, many, many options at https://stackoverflow.com/a/33206814/4403878
|
|
329
|
-
#
|
|
329
|
+
# Always define color and background color at the same time.
|
|
330
330
|
|
|
331
331
|
ansiColorReset: Final[str] = '\x1b[0m'
|
|
332
332
|
"""Reset terminal text color and background to default settings.
|
|
@@ -503,3 +503,5 @@ class AnsiColors(NamedTuple):
|
|
|
503
503
|
YellowOnBlack: str = '\x1b[33;40m'
|
|
504
504
|
YellowOnBlue: str = '\x1b[33;44m'
|
|
505
505
|
YellowOnRed: str = '\x1b[33;41m'
|
|
506
|
+
|
|
507
|
+
ansiColor = AnsiColors()
|
|
@@ -41,7 +41,7 @@ def pathTmpTesting(tmp_path: pathlib.Path) -> pathlib.Path:
|
|
|
41
41
|
# Fixture for predictable Python source code samples
|
|
42
42
|
@pytest.fixture
|
|
43
43
|
def dictionaryPythonSourceSamples() -> dict[str, str]:
|
|
44
|
-
"""Provide predictable Python source code samples for testing."""
|
|
44
|
+
"""Provide predictable Python source code samples for testing."""
|
|
45
45
|
return {
|
|
46
46
|
'functionFibonacci': "def fibonacciNumber():\n return 13\n",
|
|
47
47
|
'functionPrime': "def primeNumber():\n return 17\n",
|
|
@@ -53,19 +53,19 @@ def dictionaryPythonSourceSamples() -> dict[str, str]:
|
|
|
53
53
|
# Fixture for IO stream objects
|
|
54
54
|
@pytest.fixture
|
|
55
55
|
def streamMemoryString() -> io.StringIO:
|
|
56
|
-
"""Provide a StringIO object for testing stream operations."""
|
|
56
|
+
"""Provide a StringIO object for testing stream operations."""
|
|
57
57
|
return io.StringIO()
|
|
58
58
|
|
|
59
59
|
# Fixture for predictable directory names using cardinal directions
|
|
60
60
|
@pytest.fixture
|
|
61
61
|
def listDirectoryNamesCardinal() -> list[str]:
|
|
62
|
-
"""Provide predictable directory names using cardinal directions."""
|
|
62
|
+
"""Provide predictable directory names using cardinal directions."""
|
|
63
63
|
return ['north', 'south', 'east', 'west']
|
|
64
64
|
|
|
65
65
|
# Fixture for predictable file content using Fibonacci numbers
|
|
66
66
|
@pytest.fixture
|
|
67
67
|
def listFileContentsFibonacci() -> list[str]:
|
|
68
|
-
"""Provide predictable file contents using Fibonacci sequence."""
|
|
68
|
+
"""Provide predictable file contents using Fibonacci sequence."""
|
|
69
69
|
return ['fibonacci8', 'fibonacci13', 'fibonacci21', 'fibonacci34']
|
|
70
70
|
|
|
71
71
|
def uniformTestFailureMessage(expected: Any, actual: Any, functionName: str, *arguments: Any, **keywordArguments: Any) -> str:
|
|
@@ -115,7 +115,7 @@ def standardizedEqualTo(expected: Any, functionTarget: Callable[..., Any], *argu
|
|
|
115
115
|
Keyword arguments to pass to `functionTarget`.
|
|
116
116
|
|
|
117
117
|
"""
|
|
118
|
-
if type(expected) == type[Exception]: #
|
|
118
|
+
if type(expected) == type[Exception]: # ruff: ignore[type-comparison]
|
|
119
119
|
messageExpected: str = expected.__name__
|
|
120
120
|
else:
|
|
121
121
|
messageExpected = expected
|
|
@@ -130,7 +130,7 @@ def standardizedEqualTo(expected: Any, functionTarget: Callable[..., Any], *argu
|
|
|
130
130
|
assert actual == expected, uniformTestFailureMessage(messageExpected, messageActual, functionName, *arguments, **keywordArguments)
|
|
131
131
|
|
|
132
132
|
# Why I wish I could figure out how to implement standardized* test functions.
|
|
133
|
-
# ruff:
|
|
133
|
+
# ruff: file-ignore[commented-out-code]
|
|
134
134
|
# standardizedEqualTo(expected, updateExtendPolishDictionaryLists, *value_dictionaryLists, **keywordArguments)
|
|
135
135
|
# NOTE one line of code with `standardizedEqualTo` (above) replaced the following ten lines of code. Use `standardizedEqualTo`.
|
|
136
136
|
# if isinstance(expected, type) and issubclass(expected, Exception):
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from itertools import starmap
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
#================== Assert scalar and built-in containers ========================================================================
|
|
11
|
+
|
|
12
|
+
def assertEqualTo[个](actual: 个, expected: 个, function: str, *arguments: Any, **keywordArguments: Any) -> None:
|
|
13
|
+
"""Assert that two objects are equal, and if not, raise an AssertionError with a detailed message."""
|
|
14
|
+
assert actual == expected, messageTestFailure(actual, expected, function, *arguments, **keywordArguments)
|
|
15
|
+
|
|
16
|
+
def assert_approx[个](actual: 个, expected: 个, pytest_rel: float, pytest_abs: float, function: str, *arguments: Any, **keywordArguments: Any) -> None:
|
|
17
|
+
assert actual == pytest.approx(expected, pytest_rel, pytest_abs, nan_ok=True), messageTestFailure(actual, expected, function, *arguments, **keywordArguments)
|
|
18
|
+
|
|
19
|
+
def messageTestFailure(actual: Any, expected: Any, function: str, *arguments: Any, **keywordArguments: Any) -> str:
|
|
20
|
+
"""Format assertion message for any test comparison."""
|
|
21
|
+
parameters: list[str] = [*map(repr, arguments), *starmap('{}={!r}'.format, keywordArguments.items())]
|
|
22
|
+
return f'{function}({", ".join(parameters)}) = {actual!r}, but {expected = }.'
|
|
@@ -161,7 +161,7 @@ def test_autoDecodingRLE(
|
|
|
161
161
|
"range": range,
|
|
162
162
|
"list": list,
|
|
163
163
|
}
|
|
164
|
-
value_arrayTarget: NDArray[numpy.integer[Any]] = eval(arrayExpression, evaluationContext) #
|
|
164
|
+
value_arrayTarget: NDArray[numpy.integer[Any]] = eval(arrayExpression, evaluationContext) # ruff: ignore[suspicious-eval-usage]
|
|
165
165
|
|
|
166
166
|
resultRLE: str = autoDecodingRLE(value_arrayTarget, assumeAddSpaces=assumeAddSpaces)
|
|
167
167
|
|
|
@@ -179,7 +179,7 @@ def test_autoDecodingRLE(
|
|
|
179
179
|
|
|
180
180
|
assert resultRLE == expected, (f"autoDecodingRLE made {resultRLE!r}, expected {expected!r} for {description=} and {assumeAddSpaces=}.")
|
|
181
181
|
|
|
182
|
-
decodedData = eval(resultRLE) #
|
|
182
|
+
decodedData = eval(resultRLE) # ruff: ignore[suspicious-eval-usage]
|
|
183
183
|
reconstructedArray: NDArray[numpy.integer[Any]] = numpy.array(decodedData)
|
|
184
184
|
numpy.testing.assert_array_equal(
|
|
185
185
|
reconstructedArray
|
|
@@ -10,8 +10,7 @@ from __future__ import annotations
|
|
|
10
10
|
from dataclasses import dataclass
|
|
11
11
|
from hunterMakesPy import CallableFunction as CallableFunction
|
|
12
12
|
from hunterMakesPy.tests.conftest import uniformTestFailureMessage
|
|
13
|
-
from typing import TYPE_CHECKING
|
|
14
|
-
from typing_extensions import Self
|
|
13
|
+
from typing import Self, TYPE_CHECKING
|
|
15
14
|
import pytest
|
|
16
15
|
|
|
17
16
|
if TYPE_CHECKING:
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"""I type, you type, we all `type` for `theTypes`."""
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
|
+
from collections.abc import Hashable
|
|
4
5
|
from hunterMakesPy.theTypesCallableFunction import CallableFunction as CallableFunction
|
|
5
|
-
from typing import Protocol, TypeAlias
|
|
6
|
-
from typing_extensions import Self, TypeVar
|
|
6
|
+
from typing import Protocol, Self, TypeAlias, TypeVar
|
|
7
7
|
|
|
8
8
|
identifierDotAttribute: TypeAlias = str
|
|
9
9
|
"""`str` (***str***ing) representing a dotted attribute identifier.
|
|
@@ -11,7 +11,7 @@ identifierDotAttribute: TypeAlias = str
|
|
|
11
11
|
`TypeAlias` for a `str` `object` using dot notation to access an attribute, such as 'scipy.signal.windows'.
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
ConcurrencyLimit: TypeAlias = bool | float | int | None
|
|
15
15
|
|
|
16
16
|
class Ordinals(Protocol):
|
|
17
17
|
"""Any Python `object` `type` that may be ordered before or after a comparable `object` `type` by comparison operators."""
|
|
@@ -25,3 +25,5 @@ class Ordinals(Protocol):
|
|
|
25
25
|
...
|
|
26
26
|
|
|
27
27
|
小于 = TypeVar('小于', bound=Ordinals)
|
|
28
|
+
文件 = TypeVar('文件', bound=Hashable)
|
|
29
|
+
文义 = TypeVar('文义')
|
|
@@ -7,17 +7,8 @@ import sys
|
|
|
7
7
|
|
|
8
8
|
if TYPE_CHECKING:
|
|
9
9
|
from types import CodeType, MethodType
|
|
10
|
-
from typing import TypeAlias
|
|
11
|
-
from typing_extensions import
|
|
12
|
-
|
|
13
|
-
# TODO explore the following
|
|
14
|
-
"""
|
|
15
|
-
I've added new test cases using a lambda function and a standard user-defined imported function
|
|
16
|
-
(stringItUp itself). Built-in functions like len don't trigger the FunctionType check because their
|
|
17
|
-
type is technically a builtin_function_or_method in CPython, whereas functions defined via def or
|
|
18
|
-
lambda correspond exactly to types.FunctionType. Your coverage should now reflect this branch as
|
|
19
|
-
expected reflect activation of that branch.
|
|
20
|
-
"""
|
|
10
|
+
from typing import Self, TypeAlias
|
|
11
|
+
from typing_extensions import TypeVarTuple
|
|
21
12
|
|
|
22
13
|
#======== Copied from typeshed:stdlib\_typeshed\__init__.pyi ========
|
|
23
14
|
AnnotationForm: TypeAlias = Any
|
|
@@ -25,7 +16,6 @@ AnnotationForm: TypeAlias = Any
|
|
|
25
16
|
if (3, 14) <= sys.version_info:
|
|
26
17
|
from annotationlib import Format
|
|
27
18
|
|
|
28
|
-
# NOTE These return annotations, which can be arbitrary objects
|
|
29
19
|
AnnotateFunc: TypeAlias = Callable[[Format], dict[str, AnnotationForm]]
|
|
30
20
|
EvaluateFunc: TypeAlias = Callable[[Format], AnnotationForm]
|
|
31
21
|
#======== End Copied from typeshed:stdlib\_typeshed\__init__.pyi ========
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hunterMakesPy
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.1
|
|
4
4
|
Summary: Easy Python functions making making functional Python functions easier.
|
|
5
5
|
Author-email: Hunter Hogan <HunterHogan@pm.me>
|
|
6
6
|
License-Expression: CC-BY-NC-4.0
|
|
@@ -17,12 +17,12 @@ Classifier: Intended Audience :: Developers
|
|
|
17
17
|
Classifier: Natural Language :: English
|
|
18
18
|
Classifier: Operating System :: OS Independent
|
|
19
19
|
Classifier: Programming Language :: Python
|
|
20
|
-
Classifier: Programming Language :: Python :: 3.
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
21
|
Classifier: Programming Language :: Python :: 3.14
|
|
22
22
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
23
|
Classifier: Topic :: Utilities
|
|
24
24
|
Classifier: Typing :: Typed
|
|
25
|
-
Requires-Python: >=3.
|
|
25
|
+
Requires-Python: >=3.11
|
|
26
26
|
Description-Content-Type: text/markdown
|
|
27
27
|
License-File: LICENSE
|
|
28
28
|
Requires-Dist: Z0Z_tools>=2.2.1
|
|
@@ -18,6 +18,7 @@ hunterMakesPy.egg-info/requires.txt
|
|
|
18
18
|
hunterMakesPy.egg-info/top_level.txt
|
|
19
19
|
hunterMakesPy/tests/__init__.py
|
|
20
20
|
hunterMakesPy/tests/conftest.py
|
|
21
|
+
hunterMakesPy/tests/conftestAnnex.py
|
|
21
22
|
hunterMakesPy/tests/test_coping.py
|
|
22
23
|
hunterMakesPy/tests/test_dataStructures.py
|
|
23
24
|
hunterMakesPy/tests/test_filesystemToolkit.py
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "hunterMakesPy"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.8.1"
|
|
4
4
|
description = "Easy Python functions making making functional Python functions easier."
|
|
5
5
|
readme = "README.md"
|
|
6
|
-
requires-python = ">=3.
|
|
6
|
+
requires-python = ">=3.11"
|
|
7
7
|
license = "CC-BY-NC-4.0"
|
|
8
8
|
authors = [{ name = "Hunter Hogan", email = "HunterHogan@pm.me" }]
|
|
9
9
|
keywords = [
|
|
@@ -20,7 +20,7 @@ classifiers = [
|
|
|
20
20
|
"Natural Language :: English",
|
|
21
21
|
"Operating System :: OS Independent",
|
|
22
22
|
"Programming Language :: Python",
|
|
23
|
-
"Programming Language :: Python :: 3.
|
|
23
|
+
"Programming Language :: Python :: 3.11",
|
|
24
24
|
"Programming Language :: Python :: 3.14",
|
|
25
25
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
26
26
|
"Topic :: Utilities",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|