hunterMakesPy 0.1.2__tar.gz → 0.2.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.
- {huntermakespy-0.1.2 → huntermakespy-0.2.0}/PKG-INFO +3 -2
- {huntermakespy-0.1.2 → huntermakespy-0.2.0}/hunterMakesPy/__init__.py +7 -2
- huntermakespy-0.2.0/hunterMakesPy/dataStructures.py +268 -0
- {huntermakespy-0.1.2 → huntermakespy-0.2.0}/hunterMakesPy/parseParameters.py +29 -29
- huntermakespy-0.2.0/hunterMakesPy/pytestForYourUse.py +10 -0
- huntermakespy-0.2.0/hunterMakesPy/tests/__init__.py +5 -0
- {huntermakespy-0.1.2 → huntermakespy-0.2.0/hunterMakesPy}/tests/conftest.py +1 -2
- {huntermakespy-0.1.2 → huntermakespy-0.2.0/hunterMakesPy}/tests/test_coping.py +1 -1
- {huntermakespy-0.1.2 → huntermakespy-0.2.0/hunterMakesPy}/tests/test_dataStructures.py +124 -118
- {huntermakespy-0.1.2 → huntermakespy-0.2.0/hunterMakesPy}/tests/test_filesystemToolkit.py +5 -2
- huntermakespy-0.1.2/hunterMakesPy/pytestForYourUse.py → huntermakespy-0.2.0/hunterMakesPy/tests/test_parseParameters.py +17 -5
- {huntermakespy-0.1.2 → huntermakespy-0.2.0}/hunterMakesPy.egg-info/PKG-INFO +3 -2
- {huntermakespy-0.1.2 → huntermakespy-0.2.0}/hunterMakesPy.egg-info/SOURCES.txt +6 -6
- {huntermakespy-0.1.2 → huntermakespy-0.2.0}/hunterMakesPy.egg-info/requires.txt +2 -0
- {huntermakespy-0.1.2 → huntermakespy-0.2.0}/hunterMakesPy.egg-info/top_level.txt +0 -1
- {huntermakespy-0.1.2 → huntermakespy-0.2.0}/pyproject.toml +6 -4
- huntermakespy-0.1.2/hunterMakesPy/dataStructures.py +0 -265
- huntermakespy-0.1.2/tests/__init__.py +0 -0
- huntermakespy-0.1.2/tests/test_parseParameters.py +0 -21
- {huntermakespy-0.1.2 → huntermakespy-0.2.0}/LICENSE +0 -0
- {huntermakespy-0.1.2 → huntermakespy-0.2.0}/README.md +0 -0
- {huntermakespy-0.1.2 → huntermakespy-0.2.0}/hunterMakesPy/_theSSOT.py +0 -0
- {huntermakespy-0.1.2 → huntermakespy-0.2.0}/hunterMakesPy/coping.py +0 -0
- {huntermakespy-0.1.2 → huntermakespy-0.2.0}/hunterMakesPy/filesystemToolkit.py +0 -0
- {huntermakespy-0.1.2 → huntermakespy-0.2.0}/hunterMakesPy/py.typed +0 -0
- {huntermakespy-0.1.2 → huntermakespy-0.2.0}/hunterMakesPy/theTypes.py +0 -0
- {huntermakespy-0.1.2 → huntermakespy-0.2.0}/hunterMakesPy.egg-info/dependency_links.txt +0 -0
- {huntermakespy-0.1.2 → huntermakespy-0.2.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hunterMakesPy
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Easy Python functions making making functional Python functions easier.
|
|
5
5
|
Author-email: Hunter Hogan <HunterHogan@pm.me>
|
|
6
6
|
License: CC-BY-NC-4.0
|
|
@@ -22,6 +22,7 @@ Classifier: Programming Language :: Python :: 3
|
|
|
22
22
|
Classifier: Programming Language :: Python :: 3.11
|
|
23
23
|
Classifier: Programming Language :: Python :: 3.12
|
|
24
24
|
Classifier: Programming Language :: Python :: 3.13
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
25
26
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
26
27
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
27
28
|
Classifier: Topic :: Utilities
|
|
@@ -32,7 +33,7 @@ License-File: LICENSE
|
|
|
32
33
|
Requires-Dist: charset_normalizer
|
|
33
34
|
Requires-Dist: more_itertools
|
|
34
35
|
Requires-Dist: numpy
|
|
35
|
-
Requires-Dist: python_minifier
|
|
36
|
+
Requires-Dist: python_minifier; python_version < "3.14"
|
|
36
37
|
Provides-Extra: development
|
|
37
38
|
Requires-Dist: mypy; extra == "development"
|
|
38
39
|
Requires-Dist: pyupgrade; extra == "development"
|
|
@@ -7,6 +7,7 @@ This package provides:
|
|
|
7
7
|
- Utilities for string extraction from nested data structures and merging dictionaries of lists.
|
|
8
8
|
|
|
9
9
|
"""
|
|
10
|
+
# pyright: reportUnusedImport=false
|
|
10
11
|
from hunterMakesPy.theTypes import identifierDotAttribute as identifierDotAttribute
|
|
11
12
|
|
|
12
13
|
from hunterMakesPy.coping import PackageSettings as PackageSettings, raiseIfNone as raiseIfNone
|
|
@@ -18,7 +19,11 @@ from hunterMakesPy.filesystemToolkit import (importLogicalPath2Identifier as imp
|
|
|
18
19
|
importPathFilename2Identifier as importPathFilename2Identifier, makeDirsSafely as makeDirsSafely,
|
|
19
20
|
writeStringToHere as writeStringToHere)
|
|
20
21
|
|
|
21
|
-
from hunterMakesPy.dataStructures import
|
|
22
|
-
|
|
22
|
+
from hunterMakesPy.dataStructures import stringItUp as stringItUp, updateExtendPolishDictionaryLists as updateExtendPolishDictionaryLists
|
|
23
|
+
|
|
24
|
+
import sys
|
|
25
|
+
|
|
26
|
+
if sys.version_info < (3, 14):
|
|
27
|
+
from hunterMakesPy.dataStructures import autoDecodingRLE as autoDecodingRLE
|
|
23
28
|
|
|
24
29
|
from hunterMakesPy._theSSOT import settingsPackage
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
"""Provides utilities for string extraction from nested data structures and merges multiple dictionaries containing lists into one dictionary."""
|
|
2
|
+
|
|
3
|
+
from collections.abc import Iterator, Mapping
|
|
4
|
+
from numpy import integer
|
|
5
|
+
from numpy.typing import NDArray
|
|
6
|
+
from typing import Any
|
|
7
|
+
import more_itertools
|
|
8
|
+
import re as regex
|
|
9
|
+
import sys
|
|
10
|
+
|
|
11
|
+
if sys.version_info < (3, 14):
|
|
12
|
+
import python_minifier
|
|
13
|
+
|
|
14
|
+
def autoDecodingRLE(arrayTarget: NDArray[integer[Any]], *, assumeAddSpaces: bool = False) -> str: # noqa: C901, PLR0915
|
|
15
|
+
"""Transform a NumPy array into a compact, self-decoding run-length encoded string representation.
|
|
16
|
+
|
|
17
|
+
This function converts a NumPy array into a string that, when evaluated as Python code,
|
|
18
|
+
recreates the original array structure. The function employs two compression strategies:
|
|
19
|
+
1. Python's `range` syntax for consecutive integer sequences
|
|
20
|
+
2. Multiplication syntax for repeated elements
|
|
21
|
+
|
|
22
|
+
The resulting string representation is designed to be both human-readable and space-efficient,
|
|
23
|
+
especially for large cartesian mappings with repetitive patterns. When this string is used
|
|
24
|
+
as a data source, Python will automatically decode it into Python `list`, which if used as an
|
|
25
|
+
argument to `numpy.array()`, will recreate the original array structure.
|
|
26
|
+
|
|
27
|
+
Parameters
|
|
28
|
+
----------
|
|
29
|
+
arrayTarget : NDArray[integer[Any]]
|
|
30
|
+
(array2target) The NumPy array to be encoded.
|
|
31
|
+
assumeAddSpaces : bool = False
|
|
32
|
+
(assume2add2spaces) Affects internal length comparison during compression decisions.
|
|
33
|
+
This parameter doesn't directly change output format but influences whether
|
|
34
|
+
`range` or multiplication syntax is preferred in certain cases. The parameter
|
|
35
|
+
exists because the Abstract Syntax Tree (AST) inserts spaces in its string
|
|
36
|
+
representation.
|
|
37
|
+
|
|
38
|
+
Returns
|
|
39
|
+
-------
|
|
40
|
+
rleString : str
|
|
41
|
+
(rle2string) A string representation of the array using run-length encoding that,
|
|
42
|
+
when evaluated as Python code, reproduces the original array structure.
|
|
43
|
+
|
|
44
|
+
Notes
|
|
45
|
+
-----
|
|
46
|
+
The "autoDecoding" feature means that the string representation evaluates directly
|
|
47
|
+
to the desired data structure without explicit decompression steps.
|
|
48
|
+
|
|
49
|
+
"""
|
|
50
|
+
def sliceNDArrayToNestedLists(arraySlice: NDArray[integer[Any]]) -> Any:
|
|
51
|
+
def getLengthOption(optionAsStr: str) -> int:
|
|
52
|
+
"""`assumeAddSpaces` characters: `,` 1; `]*` 2."""
|
|
53
|
+
return assumeAddSpaces * (optionAsStr.count(',') + optionAsStr.count(']*') * 2) + len(optionAsStr)
|
|
54
|
+
|
|
55
|
+
if arraySlice.ndim > 1:
|
|
56
|
+
axisOfOperation = 0
|
|
57
|
+
return [sliceNDArrayToNestedLists(arraySlice[index]) for index in range(arraySlice.shape[axisOfOperation])]
|
|
58
|
+
if arraySlice.ndim == 1:
|
|
59
|
+
arraySliceAsList: list[int | range] = []
|
|
60
|
+
cache_consecutiveGroup_addMe: dict[Iterator[Any], list[int] | list[range]] = {}
|
|
61
|
+
for consecutiveGroup in more_itertools.consecutive_groups(arraySlice.tolist()):
|
|
62
|
+
if consecutiveGroup in cache_consecutiveGroup_addMe:
|
|
63
|
+
addMe = cache_consecutiveGroup_addMe[consecutiveGroup]
|
|
64
|
+
else:
|
|
65
|
+
ImaSerious: list[int] = list(consecutiveGroup)
|
|
66
|
+
ImaRange = [range(ImaSerious[0], ImaSerious[-1] + 1)]
|
|
67
|
+
ImaRangeAsStr = python_minifier.minify(str(ImaRange)).replace('range(0,', 'range(').replace('range', '*range')
|
|
68
|
+
|
|
69
|
+
option1 = ImaRange
|
|
70
|
+
option1AsStr = ImaRangeAsStr
|
|
71
|
+
option2 = ImaSerious
|
|
72
|
+
option2AsStr = None
|
|
73
|
+
|
|
74
|
+
# alpha, potential function
|
|
75
|
+
option1AsStr = option1AsStr or python_minifier.minify(str(option1))
|
|
76
|
+
lengthOption1 = getLengthOption(option1AsStr)
|
|
77
|
+
|
|
78
|
+
option2AsStr = option2AsStr or python_minifier.minify(str(option2))
|
|
79
|
+
lengthOption2 = getLengthOption(option2AsStr)
|
|
80
|
+
|
|
81
|
+
if lengthOption1 < lengthOption2:
|
|
82
|
+
addMe = option1
|
|
83
|
+
else:
|
|
84
|
+
addMe = option2
|
|
85
|
+
|
|
86
|
+
cache_consecutiveGroup_addMe[consecutiveGroup] = addMe
|
|
87
|
+
|
|
88
|
+
arraySliceAsList += addMe
|
|
89
|
+
|
|
90
|
+
listRangeAndTuple: list[int | range | tuple[int | range, int]] = []
|
|
91
|
+
cache_malkovichGrouped_addMe: dict[tuple[int | range, int], list[tuple[int | range, int]] | list[int | range]] = {}
|
|
92
|
+
for malkovichGrouped in more_itertools.run_length.encode(arraySliceAsList):
|
|
93
|
+
if malkovichGrouped in cache_malkovichGrouped_addMe:
|
|
94
|
+
addMe = cache_malkovichGrouped_addMe[malkovichGrouped]
|
|
95
|
+
else:
|
|
96
|
+
lengthMalkovich = malkovichGrouped[-1]
|
|
97
|
+
malkovichAsList = list(more_itertools.run_length.decode([malkovichGrouped]))
|
|
98
|
+
malkovichMalkovich = f"[{malkovichGrouped[0]}]*{lengthMalkovich}"
|
|
99
|
+
|
|
100
|
+
option1 = [malkovichGrouped]
|
|
101
|
+
option1AsStr = malkovichMalkovich
|
|
102
|
+
option2 = malkovichAsList
|
|
103
|
+
option2AsStr = None
|
|
104
|
+
|
|
105
|
+
# beta, potential function
|
|
106
|
+
option1AsStr = option1AsStr or python_minifier.minify(str(option1))
|
|
107
|
+
lengthOption1 = getLengthOption(option1AsStr)
|
|
108
|
+
|
|
109
|
+
option2AsStr = option2AsStr or python_minifier.minify(str(option2))
|
|
110
|
+
lengthOption2 = getLengthOption(option2AsStr)
|
|
111
|
+
|
|
112
|
+
if lengthOption1 < lengthOption2:
|
|
113
|
+
addMe = option1
|
|
114
|
+
else:
|
|
115
|
+
addMe = option2
|
|
116
|
+
|
|
117
|
+
cache_malkovichGrouped_addMe[malkovichGrouped] = addMe
|
|
118
|
+
|
|
119
|
+
listRangeAndTuple += addMe
|
|
120
|
+
|
|
121
|
+
return listRangeAndTuple
|
|
122
|
+
return arraySlice
|
|
123
|
+
|
|
124
|
+
arrayAsNestedLists = sliceNDArrayToNestedLists(arrayTarget)
|
|
125
|
+
|
|
126
|
+
arrayAsStr = python_minifier.minify(str(arrayAsNestedLists))
|
|
127
|
+
|
|
128
|
+
patternRegex = regex.compile(
|
|
129
|
+
"(?<!rang)(?:"
|
|
130
|
+
# Pattern 1: Comma ahead, bracket behind # noqa: ERA001
|
|
131
|
+
"(?P<joinAhead>,)\\((?P<malkovich>\\d+),(?P<multiply>\\d+)\\)(?P<bracketBehind>])|"
|
|
132
|
+
# Pattern 2: Bracket or start ahead, comma behind # noqa: ERA001
|
|
133
|
+
"(?P<bracketOrStartAhead>\\[|^.)\\((?P<malkovichMalkovich>\\d+),(?P<multiplyIDK>\\d+)\\)(?P<joinBehind>,)|"
|
|
134
|
+
# Pattern 3: Bracket ahead, bracket behind # noqa: ERA001
|
|
135
|
+
"(?P<bracketAhead>\\[)\\((?P<malkovichMalkovichMalkovich>\\d+),(?P<multiply_whatever>\\d+)\\)(?P<bracketBehindBracketBehind>])|"
|
|
136
|
+
# Pattern 4: Comma ahead, comma behind # noqa: ERA001
|
|
137
|
+
"(?P<joinAheadJoinAhead>,)\\((?P<malkovichMalkovichMalkovichMalkovich>\\d+),(?P<multiplyOrSomething>\\d+)\\)(?P<joinBehindJoinBehind>,)"
|
|
138
|
+
")"
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
def replacementByContext(match: regex.Match[str]) -> str:
|
|
142
|
+
"""Generate replacement string based on context patterns."""
|
|
143
|
+
elephino = match.groupdict()
|
|
144
|
+
joinAhead = elephino.get('joinAhead') or elephino.get('joinAheadJoinAhead')
|
|
145
|
+
malkovich = elephino.get('malkovich') or elephino.get('malkovichMalkovich') or elephino.get('malkovichMalkovichMalkovich') or elephino.get('malkovichMalkovichMalkovichMalkovich')
|
|
146
|
+
multiply = elephino.get('multiply') or elephino.get('multiplyIDK') or elephino.get('multiply_whatever') or elephino.get('multiplyOrSomething')
|
|
147
|
+
joinBehind = elephino.get('joinBehind') or elephino.get('joinBehindJoinBehind')
|
|
148
|
+
|
|
149
|
+
replaceAhead = "]+[" if joinAhead == "," else "["
|
|
150
|
+
|
|
151
|
+
replaceBehind = "+[" if joinBehind == "," else ""
|
|
152
|
+
|
|
153
|
+
return f"{replaceAhead}{malkovich}]*{multiply}{replaceBehind}"
|
|
154
|
+
|
|
155
|
+
arrayAsStr = patternRegex.sub(replacementByContext, arrayAsStr)
|
|
156
|
+
arrayAsStr = patternRegex.sub(replacementByContext, arrayAsStr)
|
|
157
|
+
|
|
158
|
+
# Replace `range(0,stop)` syntax with `range(stop)` syntax. # noqa: ERA001
|
|
159
|
+
# Add unpack operator `*` for automatic decoding when evaluated.
|
|
160
|
+
return arrayAsStr.replace('range(0,', 'range(').replace('range', '*range')
|
|
161
|
+
|
|
162
|
+
def stringItUp(*scrapPile: Any) -> list[str]: # noqa: C901
|
|
163
|
+
"""Convert, if possible, every element in the input data structure to a string.
|
|
164
|
+
|
|
165
|
+
Order is not preserved or readily predictable.
|
|
166
|
+
|
|
167
|
+
Parameters
|
|
168
|
+
----------
|
|
169
|
+
*scrapPile : Any
|
|
170
|
+
(scrap2pile) One or more data structures to unpack and convert to strings.
|
|
171
|
+
|
|
172
|
+
Returns
|
|
173
|
+
-------
|
|
174
|
+
listStrungUp : list[str]
|
|
175
|
+
(list2strung2up) A `list` of string versions of all convertible elements.
|
|
176
|
+
|
|
177
|
+
"""
|
|
178
|
+
scrap = None
|
|
179
|
+
listStrungUp: list[str] = []
|
|
180
|
+
|
|
181
|
+
def drill(KitKat: Any) -> None: # noqa: C901, PLR0912
|
|
182
|
+
match KitKat:
|
|
183
|
+
case str():
|
|
184
|
+
listStrungUp.append(KitKat)
|
|
185
|
+
case bool() | bytearray() | bytes() | complex() | float() | int() | memoryview() | None:
|
|
186
|
+
listStrungUp.append(str(KitKat)) # pyright: ignore [reportUnknownArgumentType]
|
|
187
|
+
case dict():
|
|
188
|
+
for broken, piece in KitKat.items(): # pyright: ignore [reportUnknownVariableType]
|
|
189
|
+
drill(broken)
|
|
190
|
+
drill(piece)
|
|
191
|
+
case list() | tuple() | set() | frozenset() | range():
|
|
192
|
+
for kit in KitKat: # pyright: ignore [reportUnknownVariableType]
|
|
193
|
+
drill(kit)
|
|
194
|
+
case _:
|
|
195
|
+
if hasattr(KitKat, '__iter__'): # Unpack other iterables
|
|
196
|
+
for kat in KitKat:
|
|
197
|
+
drill(kat)
|
|
198
|
+
else:
|
|
199
|
+
try:
|
|
200
|
+
sharingIsCaring = KitKat.__str__()
|
|
201
|
+
listStrungUp.append(sharingIsCaring)
|
|
202
|
+
except AttributeError:
|
|
203
|
+
pass
|
|
204
|
+
except TypeError: # "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."
|
|
205
|
+
pass
|
|
206
|
+
except:
|
|
207
|
+
print(f"\nWoah! I received '{repr(KitKat)}'.\nTheir report card says, 'Plays well with others: Needs improvement.'\n") # noqa: RUF010, T201
|
|
208
|
+
raise
|
|
209
|
+
try:
|
|
210
|
+
for scrap in scrapPile:
|
|
211
|
+
drill(scrap)
|
|
212
|
+
except RecursionError:
|
|
213
|
+
listStrungUp.append(repr(scrap))
|
|
214
|
+
return listStrungUp
|
|
215
|
+
|
|
216
|
+
def updateExtendPolishDictionaryLists(*dictionaryLists: Mapping[str, list[Any] | set[Any] | tuple[Any, ...]], destroyDuplicates: bool = False, reorderLists: bool = False, killErroneousDataTypes: bool = False) -> dict[str, list[Any]]:
|
|
217
|
+
"""Merge multiple dictionaries containing `list` into a single dictionary.
|
|
218
|
+
|
|
219
|
+
With options to handle duplicates, `list` ordering, and erroneous data types.
|
|
220
|
+
|
|
221
|
+
Parameters
|
|
222
|
+
----------
|
|
223
|
+
*dictionaryLists : Mapping[str, list[Any] | set[Any] | tuple[Any, ...]]
|
|
224
|
+
(dictionary2lists) Variable number of dictionaries to be merged. If only one dictionary is passed, it will be processed based on the provided options.
|
|
225
|
+
destroyDuplicates : bool = False
|
|
226
|
+
(destroy2duplicates) If `True`, removes duplicate elements from the `list`. Defaults to `False`.
|
|
227
|
+
reorderLists : bool = False
|
|
228
|
+
(reorder2lists) If `True`, sorts the `list`. Defaults to `False`.
|
|
229
|
+
killErroneousDataTypes : bool = False
|
|
230
|
+
(kill2erroneous2data2types) If `True`, skips dictionary keys or dictionary values that cause a `TypeError` during merging. Defaults to `False`.
|
|
231
|
+
|
|
232
|
+
Returns
|
|
233
|
+
-------
|
|
234
|
+
ePluribusUnum : dict[str, list[Any]]
|
|
235
|
+
(e2pluribus2unum) A single dictionary with merged `list` based on the provided options. If only one dictionary is passed,
|
|
236
|
+
it will be cleaned up based on the options.
|
|
237
|
+
|
|
238
|
+
Notes
|
|
239
|
+
-----
|
|
240
|
+
The returned value, `ePluribusUnum`, is a so-called primitive dictionary (`dict`). Furthermore, every dictionary key is a
|
|
241
|
+
so-called primitive string (cf. `str()`) and every dictionary value is a so-called primitive `list` (`list`). If
|
|
242
|
+
`dictionaryLists` has other data types, the data types will not be preserved. That could have unexpected consequences.
|
|
243
|
+
Conversion from the original data type to a `list`, for example, may not preserve the order even if you want the order to be
|
|
244
|
+
preserved.
|
|
245
|
+
|
|
246
|
+
"""
|
|
247
|
+
ePluribusUnum: dict[str, list[Any]] = {}
|
|
248
|
+
|
|
249
|
+
for dictionaryListTarget in dictionaryLists:
|
|
250
|
+
for keyName, keyValue in dictionaryListTarget.items():
|
|
251
|
+
try:
|
|
252
|
+
ImaStr = str(keyName)
|
|
253
|
+
ImaList = list(keyValue)
|
|
254
|
+
ePluribusUnum.setdefault(ImaStr, []).extend(ImaList)
|
|
255
|
+
except TypeError:
|
|
256
|
+
if killErroneousDataTypes:
|
|
257
|
+
continue
|
|
258
|
+
else:
|
|
259
|
+
raise
|
|
260
|
+
|
|
261
|
+
if destroyDuplicates:
|
|
262
|
+
for ImaStr, ImaList in ePluribusUnum.items():
|
|
263
|
+
ePluribusUnum[ImaStr] = list(dict.fromkeys(ImaList))
|
|
264
|
+
if reorderLists:
|
|
265
|
+
for ImaStr, ImaList in ePluribusUnum.items():
|
|
266
|
+
ePluribusUnum[ImaStr] = sorted(ImaList)
|
|
267
|
+
|
|
268
|
+
return ePluribusUnum
|
|
@@ -9,18 +9,16 @@ import multiprocessing
|
|
|
9
9
|
class ErrorMessageContext:
|
|
10
10
|
"""Context information for constructing error messages.
|
|
11
11
|
|
|
12
|
-
(AI generated docstring)
|
|
13
|
-
|
|
14
12
|
Parameters
|
|
15
13
|
----------
|
|
16
14
|
parameterValue : Any = None
|
|
17
|
-
|
|
15
|
+
The value that caused the error.
|
|
18
16
|
parameterValueType : str | None = None
|
|
19
|
-
|
|
17
|
+
The name of the type of the parameter value.
|
|
20
18
|
containerType : str | None = None
|
|
21
|
-
|
|
19
|
+
The name of the type of the container holding the parameter.
|
|
22
20
|
isElement : bool = False
|
|
23
|
-
|
|
21
|
+
Whether the parameter is an element within a container.
|
|
24
22
|
|
|
25
23
|
"""
|
|
26
24
|
|
|
@@ -32,7 +30,8 @@ class ErrorMessageContext:
|
|
|
32
30
|
def _constructErrorMessage(context: ErrorMessageContext, parameterName: str, parameterType: type[Any] | None) -> str:
|
|
33
31
|
"""Construct error message from available context using template.
|
|
34
32
|
|
|
35
|
-
I received ["value" | a value | `None`] [of type `type` | `None`] [as an element in | `None`] [a `containerType` type |
|
|
33
|
+
I received ["value" | a value | `None`] [of type `type` | `None`] [as an element in | `None`] [a `containerType` type |
|
|
34
|
+
`None`] but `parameterName` must have integers [in type(s) `parameterType` | `None`].
|
|
36
35
|
|
|
37
36
|
Hypothetically, this is a prototype that can be generalized to other functions. In this package and a few of my other
|
|
38
37
|
packages, I have developed standardized error messages, but those are quite different from this. I will certainly continue to
|
|
@@ -41,16 +40,16 @@ def _constructErrorMessage(context: ErrorMessageContext, parameterName: str, par
|
|
|
41
40
|
Parameters
|
|
42
41
|
----------
|
|
43
42
|
context : ErrorMessageContext
|
|
44
|
-
|
|
43
|
+
The error context containing parameter value, type, and container information.
|
|
45
44
|
parameterName : str
|
|
46
|
-
|
|
45
|
+
The name of the parameter that caused the error.
|
|
47
46
|
parameterType : type[Any] | None
|
|
48
|
-
|
|
47
|
+
The expected type of the parameter, used in error messages.
|
|
49
48
|
|
|
50
49
|
Returns
|
|
51
50
|
-------
|
|
52
51
|
errorMessage : str
|
|
53
|
-
|
|
52
|
+
The constructed error message string.
|
|
54
53
|
|
|
55
54
|
"""
|
|
56
55
|
messageParts = ["I received "]
|
|
@@ -79,7 +78,8 @@ def _constructErrorMessage(context: ErrorMessageContext, parameterName: str, par
|
|
|
79
78
|
def defineConcurrencyLimit(*, limit: bool | float | int | None, cpuTotal: int = multiprocessing.cpu_count()) -> int: # noqa: C901, PYI041
|
|
80
79
|
"""Determine the concurrency limit based on the provided parameter.
|
|
81
80
|
|
|
82
|
-
|
|
81
|
+
Tests for this function can be run with:
|
|
82
|
+
`from hunterMakesPy.tests.test_parseParameters import PytestFor_defineConcurrencyLimit`
|
|
83
83
|
|
|
84
84
|
Parameters
|
|
85
85
|
----------
|
|
@@ -92,12 +92,12 @@ def defineConcurrencyLimit(*, limit: bool | float | int | None, cpuTotal: int =
|
|
|
92
92
|
Returns
|
|
93
93
|
-------
|
|
94
94
|
concurrencyLimit : int
|
|
95
|
-
|
|
95
|
+
The calculated concurrency limit, ensuring it is at least 1.
|
|
96
96
|
|
|
97
97
|
Notes
|
|
98
98
|
-----
|
|
99
|
-
If you want to be extra nice to your users, consider using `hunterMakesPy.oopsieKwargsie()` to handle
|
|
100
|
-
|
|
99
|
+
If you want to be extra nice to your users, consider using `hunterMakesPy.oopsieKwargsie()` to handle malformed inputs. For
|
|
100
|
+
example:
|
|
101
101
|
|
|
102
102
|
```
|
|
103
103
|
if not (CPUlimit is None or isinstance(CPUlimit, (bool, int, float))):
|
|
@@ -163,23 +163,25 @@ def defineConcurrencyLimit(*, limit: bool | float | int | None, cpuTotal: int =
|
|
|
163
163
|
def intInnit(listInt_Allegedly: Iterable[Any], parameterName: str | None = None, parameterType: type[Any] | None = None) -> list[int]: # noqa: C901, PLR0912, PLR0915
|
|
164
164
|
"""Validate and convert input values to a `list` of integers.
|
|
165
165
|
|
|
166
|
-
Accepts various numeric types and attempts to convert them into integers while providing descriptive error messages.
|
|
166
|
+
Accepts various numeric types and attempts to convert them into integers while providing descriptive error messages. This
|
|
167
|
+
package includes Pytest tests that can be imported and run: `from hunterMakesPy.tests.test_parseParameters import
|
|
168
|
+
PytestFor_intInnit`.
|
|
167
169
|
|
|
168
170
|
Parameters
|
|
169
171
|
----------
|
|
170
172
|
listInt_Allegedly : Iterable[Any]
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
Rejects boolean values and non-integer numeric values.
|
|
173
|
+
The input sequence that should contain integer-compatible values. Accepts integers, strings, floats, complex numbers, and
|
|
174
|
+
binary data. Rejects boolean values and non-integer numeric values.
|
|
174
175
|
parameterName : str | None = None
|
|
175
|
-
|
|
176
|
+
Name of the parameter from your function for which this function is validating the input validated. If there is an error
|
|
177
|
+
message, it provides context to your user. Defaults to 'the parameter'.
|
|
176
178
|
parameterType : type[Any] | None = None
|
|
177
|
-
|
|
179
|
+
Expected type(s) of the parameter, used in error messages.
|
|
178
180
|
|
|
179
181
|
Returns
|
|
180
182
|
-------
|
|
181
183
|
listValidated : list[int]
|
|
182
|
-
|
|
184
|
+
A `list` containing validated integers.
|
|
183
185
|
|
|
184
186
|
Raises
|
|
185
187
|
------
|
|
@@ -192,9 +194,6 @@ def intInnit(listInt_Allegedly: Iterable[Any], parameterName: str | None = None,
|
|
|
192
194
|
|
|
193
195
|
Notes
|
|
194
196
|
-----
|
|
195
|
-
This package includes Pytest tests that can be imported and run:
|
|
196
|
-
`from hunterMakesPy.pytest_parseParameters import makeTestSuiteIntInnit`
|
|
197
|
-
|
|
198
197
|
The function performs strict validation and follows fail-early principles to catch potential issues before they become catastrophic.
|
|
199
198
|
|
|
200
199
|
"""
|
|
@@ -284,19 +283,20 @@ def intInnit(listInt_Allegedly: Iterable[Any], parameterName: str | None = None,
|
|
|
284
283
|
def oopsieKwargsie(huh: Any) -> bool | None | str:
|
|
285
284
|
"""Interpret a `str` as `True`, `False`, or `None` to avoid an `Exception`.
|
|
286
285
|
|
|
287
|
-
If a calling function passes a `str` to a parameter that shouldn't receive a `str`, `oopsieKwargsie()` might help you avoid an
|
|
288
|
-
`
|
|
286
|
+
If a calling function passes a `str` to a parameter that shouldn't receive a `str`, `oopsieKwargsie()` might help you avoid an
|
|
287
|
+
`Exception`. It tries to interpret the string as `True`, `False`, or `None`.
|
|
288
|
+
|
|
289
|
+
Tests for this function can be run with: `from hunterMakesPy.tests.test_parseParameters import PytestFor_oopsieKwargsie`.
|
|
289
290
|
|
|
290
291
|
Parameters
|
|
291
292
|
----------
|
|
292
293
|
huh : Any
|
|
293
294
|
(huh) The input string to be parsed.
|
|
294
295
|
|
|
295
|
-
|
|
296
296
|
Returns
|
|
297
297
|
-------
|
|
298
298
|
interpretedValue : bool | None | str
|
|
299
|
-
|
|
299
|
+
The reserved keyword `True`, `False`, or `None` or the original string, `huh`.
|
|
300
300
|
|
|
301
301
|
"""
|
|
302
302
|
if not isinstance(huh, str):
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""Pytest tests you can use in your package to test some hunterMakesPy functions.
|
|
2
|
+
|
|
3
|
+
Each function in this module returns a list of test functions that can be used with `pytest.parametrize`.
|
|
4
|
+
|
|
5
|
+
Note: These test functions are now in `hunterMakesPy.tests` with all other tests.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from hunterMakesPy.tests.test_parseParameters import (
|
|
9
|
+
PytestFor_defineConcurrencyLimit as PytestFor_defineConcurrencyLimit, PytestFor_intInnit as PytestFor_intInnit,
|
|
10
|
+
PytestFor_oopsieKwargsie as PytestFor_oopsieKwargsie)
|
|
@@ -5,8 +5,7 @@ import pathlib
|
|
|
5
5
|
import pytest
|
|
6
6
|
|
|
7
7
|
# SSOT for test data paths and filenames
|
|
8
|
-
|
|
9
|
-
pathDataSamples = pathlib.Path("tests/dataSamples")
|
|
8
|
+
pathDataSamples = pathlib.Path("hunterMakesPy/tests/dataSamples")
|
|
10
9
|
|
|
11
10
|
# Fixture to provide a temporary directory for filesystem tests
|
|
12
11
|
@pytest.fixture
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from hunterMakesPy import PackageSettings, raiseIfNone
|
|
2
2
|
from hunterMakesPy.coping import getIdentifierPackagePACKAGING, getPathPackageINSTALLING
|
|
3
|
+
from hunterMakesPy.tests.conftest import uniformTestFailureMessage
|
|
3
4
|
from pathlib import Path
|
|
4
|
-
from tests.conftest import uniformTestFailureMessage
|
|
5
5
|
import pytest
|
|
6
6
|
|
|
7
7
|
@pytest.mark.parametrize(
|
|
@@ -2,13 +2,17 @@
|
|
|
2
2
|
from collections.abc import Callable, Iterable, Iterator
|
|
3
3
|
from decimal import Decimal
|
|
4
4
|
from fractions import Fraction
|
|
5
|
-
from hunterMakesPy import
|
|
5
|
+
from hunterMakesPy import stringItUp, updateExtendPolishDictionaryLists
|
|
6
|
+
from hunterMakesPy.tests.conftest import standardizedEqualTo
|
|
6
7
|
from numpy.typing import NDArray
|
|
7
|
-
from tests.conftest import standardizedEqualTo
|
|
8
8
|
from typing import Any, Literal
|
|
9
9
|
import datetime
|
|
10
10
|
import numpy
|
|
11
11
|
import pytest
|
|
12
|
+
import sys
|
|
13
|
+
|
|
14
|
+
if sys.version_info < (3, 14):
|
|
15
|
+
from hunterMakesPy import autoDecodingRLE
|
|
12
16
|
|
|
13
17
|
class CustomIterable:
|
|
14
18
|
def __init__(self, items: Iterable[Any]) -> None: self.items = items
|
|
@@ -81,7 +85,7 @@ def testStringItUpErrorCases(description: Literal['Memory view'], value_scrapPil
|
|
|
81
85
|
def testUpdateExtendPolishDictionaryLists(description: str, value_dictionaryLists: dict[str, Any], keywordArguments: dict[str, Any], expected: dict[str, Any] | type[TypeError]) -> None:
|
|
82
86
|
standardizedEqualTo(expected, updateExtendPolishDictionaryLists, *value_dictionaryLists, **keywordArguments)
|
|
83
87
|
# ruff: noqa: ERA001
|
|
84
|
-
# NOTE one line of code with `standardizedEqualTo` replaced the following ten lines of code.
|
|
88
|
+
# NOTE one line of code with `standardizedEqualTo` replaced the following ten lines of code. Use `standardizedEqualTo`.
|
|
85
89
|
# if isinstance(expected, type) and issubclass(expected, Exception):
|
|
86
90
|
# with pytest.raises(expected):
|
|
87
91
|
# updateExtendPolishDictionaryLists(*value_dictionaryLists, **keywordArguments)
|
|
@@ -94,26 +98,28 @@ def testUpdateExtendPolishDictionaryLists(description: str, value_dictionaryList
|
|
|
94
98
|
# assert result == expected
|
|
95
99
|
|
|
96
100
|
# ruff: noqa: RUF005
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
("
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
101
|
+
|
|
102
|
+
if sys.version_info < (3, 14):
|
|
103
|
+
@pytest.mark.parametrize("description,value_arrayTarget,expected", [
|
|
104
|
+
("One range", numpy.array(list(range(50,60))), "[*range(50,60)]"),
|
|
105
|
+
("Value, range", numpy.array([123]+list(range(71,81))), "[123,*range(71,81)]"),
|
|
106
|
+
("range, value", numpy.array(list(range(91,97))+[101]), "[*range(91,97),101]"),
|
|
107
|
+
("Value, range, value", numpy.array([151]+list(range(163,171))+[181]), "[151,*range(163,171),181]"),
|
|
108
|
+
("Repeat values", numpy.array([191, 191, 191]), "[191]*3"),
|
|
109
|
+
("Value with repeat", numpy.array([211, 223, 223, 223]), "[211]+[223]*3"),
|
|
110
|
+
("Range with repeat", numpy.array(list(range(251,257))+[271, 271, 271]), "[*range(251,257)]+[271]*3"),
|
|
111
|
+
("Value, range, repeat", numpy.array([281]+list(range(291,297))+[307, 307]), "[281,*range(291,297)]+[307]*2"),
|
|
112
|
+
("repeat, value", numpy.array([313, 313, 313, 331, 331, 349]), "[313]*3+[331]*2+[349]"),
|
|
113
|
+
("repeat, range", numpy.array([373, 373, 373]+list(range(383,389))), "[373]*3+[*range(383,389)]"),
|
|
114
|
+
("repeat, range, value", numpy.array(7*[401]+list(range(409,415))+[421]), "[401]*7+[*range(409,415),421]"),
|
|
115
|
+
("Repeated primes", numpy.array([431, 431, 431, 443, 443, 457]), "[431]*3+[443]*2+[457]"),
|
|
116
|
+
("Two Ranges", numpy.array(list(range(461,471))+list(range(479,487))), "[*range(461,471),*range(479,487)]"),
|
|
117
|
+
("2D array primes", numpy.array([[491, 499, 503], [509, 521, 523]]), "[[491,499,503],[509,521,523]]"),
|
|
118
|
+
("3D array primes", numpy.array([[[541, 547], [557, 563]], [[569, 571], [577, 587]]]), "[[[541,547],[557,563]],[[569,571],[577,587]]]"),
|
|
119
|
+
], ids=lambda x: x if isinstance(x, str) else "")
|
|
120
|
+
def testAutoDecodingRLE(description: str, value_arrayTarget: NDArray[numpy.integer[Any]], expected: str) -> None:
|
|
121
|
+
"""Test autoDecodingRLE with various input arrays."""
|
|
122
|
+
standardizedEqualTo(expected, autoDecodingRLE, value_arrayTarget)
|
|
117
123
|
|
|
118
124
|
# Helper functions for generating RLE test data
|
|
119
125
|
def generateCartesianMapping(dimensions: tuple[int, int], formula: Callable[[int, int], int]) -> NDArray[Any]:
|
|
@@ -222,98 +228,98 @@ def generateAlternatingColumns(dimensions: tuple[int, int], blockSize: int = 1)
|
|
|
222
228
|
|
|
223
229
|
return generateCartesianMapping(dimensions, columnFormula)
|
|
224
230
|
|
|
225
|
-
|
|
226
|
-
@pytest.mark.parametrize("description,value_arrayTarget", [
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
], ids=lambda x: x if isinstance(x, str) else "")
|
|
257
|
-
def testAutoDecodingRLEWithRealisticData(description: str, value_arrayTarget: NDArray[numpy.integer[Any]]) -> None:
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
@pytest.mark.parametrize("description,addSpaces", [
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
], ids=lambda x: x if isinstance(x, str) else "")
|
|
278
|
-
def testAutoDecodingRLEWithSpaces(description: str, addSpaces: bool) -> None:
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
def testAutoDecodingRLELargeCartesianMapping() -> None:
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
231
|
+
if sys.version_info < (3, 14):
|
|
232
|
+
@pytest.mark.parametrize("description,value_arrayTarget", [
|
|
233
|
+
# Basic test cases with simple patterns
|
|
234
|
+
("Simple range", numpy.array(list(range(50,60)))),
|
|
235
|
+
|
|
236
|
+
# Chessboard patterns
|
|
237
|
+
("Small chessboard", generateChessboard((8, 8))),
|
|
238
|
+
|
|
239
|
+
# Alternating columns - creates patterns with good RLE opportunities
|
|
240
|
+
("Alternating columns", generateAlternatingColumns((5, 20), 2)),
|
|
241
|
+
|
|
242
|
+
# Step pattern - creates horizontal runs
|
|
243
|
+
("Step pattern", generateStepPattern((6, 30), 3)),
|
|
244
|
+
|
|
245
|
+
# Repeating zones - creates horizontal bands
|
|
246
|
+
("Repeating zones", generateRepeatingZones((40, 40), 8)),
|
|
247
|
+
|
|
248
|
+
# Tile pattern - creates complex repeating regions
|
|
249
|
+
("Tile pattern", generateTilePattern((15, 15), 5)),
|
|
250
|
+
|
|
251
|
+
# Signed quadratic function - includes negative values
|
|
252
|
+
("Signed quadratic", generateSignedQuadraticFunction((10, 10))),
|
|
253
|
+
|
|
254
|
+
# Prime modulo matrix - periodic patterns
|
|
255
|
+
("Prime modulo", generatePrimeModuloMatrix((12, 12), 7)),
|
|
256
|
+
|
|
257
|
+
# Wave pattern - smooth gradients
|
|
258
|
+
("Wave pattern", generateWavePattern((20, 20))),
|
|
259
|
+
|
|
260
|
+
# Spiral pattern - complex pattern with good RLE potential
|
|
261
|
+
("Spiral pattern", generateSpiralPattern((15, 15), 2)),
|
|
262
|
+
], ids=lambda x: x if isinstance(x, str) else "")
|
|
263
|
+
def testAutoDecodingRLEWithRealisticData(description: str, value_arrayTarget: NDArray[numpy.integer[Any]]) -> None:
|
|
264
|
+
"""Test autoDecodingRLE with more realistic data patterns."""
|
|
265
|
+
# Here we test the function behavior rather than expected string output
|
|
266
|
+
resultRLE = autoDecodingRLE(value_arrayTarget)
|
|
267
|
+
|
|
268
|
+
# Test that the result is a valid string
|
|
269
|
+
assert isinstance(resultRLE, str)
|
|
270
|
+
|
|
271
|
+
# Test that the result contains the expected syntax elements
|
|
272
|
+
assert "[" in resultRLE, f"Result should contain list syntax: {resultRLE}"
|
|
273
|
+
assert "]" in resultRLE, f"Result should contain list syntax: {resultRLE}"
|
|
274
|
+
|
|
275
|
+
# Check that the result is more compact than the raw string representation
|
|
276
|
+
rawStrLength = len(str(value_arrayTarget.tolist()))
|
|
277
|
+
encodedLength = len(resultRLE)
|
|
278
|
+
assert encodedLength <= rawStrLength, f"Encoded string ({encodedLength}) should be shorter than raw string ({rawStrLength})"
|
|
279
|
+
|
|
280
|
+
@pytest.mark.parametrize("description,addSpaces", [
|
|
281
|
+
("With spaces", True),
|
|
282
|
+
("Without spaces", False),
|
|
283
|
+
], ids=lambda x: x if isinstance(x, str) else "")
|
|
284
|
+
def testAutoDecodingRLEWithSpaces(description: str, addSpaces: bool) -> None:
|
|
285
|
+
"""Test that the addSpaces parameter affects the internal comparison logic.
|
|
286
|
+
|
|
287
|
+
Note: addSpaces doesn't directly change the output format, it just changes
|
|
288
|
+
the comparison when measuring the length of the string representation.
|
|
289
|
+
The feature exists because `ast` inserts spaces in its string representation.
|
|
290
|
+
"""
|
|
291
|
+
# Create a pattern that has repeated sequences to trigger the RLE logic
|
|
292
|
+
arrayTarget = generateRepeatingZones((10, 10), 2)
|
|
293
|
+
|
|
294
|
+
# Test both configurations
|
|
295
|
+
resultWithSpacesFlag = autoDecodingRLE(arrayTarget, assumeAddSpaces=addSpaces)
|
|
296
|
+
resultNoSpacesFlag = autoDecodingRLE(arrayTarget, assumeAddSpaces=False)
|
|
297
|
+
|
|
298
|
+
# When addSpaces=True, the internal length comparisons change
|
|
299
|
+
# but the actual output format doesn't necessarily differ
|
|
300
|
+
# Just verify the function runs without errors in both cases
|
|
301
|
+
assert isinstance(resultWithSpacesFlag, str)
|
|
302
|
+
assert isinstance(resultNoSpacesFlag, str)
|
|
303
|
+
|
|
304
|
+
def testAutoDecodingRLELargeCartesianMapping() -> None:
|
|
305
|
+
"""Test autoDecodingRLE with a large (100x100) cartesian mapping."""
|
|
306
|
+
dimensions = (100, 100)
|
|
307
|
+
|
|
308
|
+
# Generate a large cartesian mapping with a complex pattern
|
|
309
|
+
def complexFormula(x: int, y: int) -> int:
|
|
310
|
+
return ((x * 17) % 11 + (y * 13) % 7) % 10
|
|
311
|
+
|
|
312
|
+
arrayMapping = generateCartesianMapping(dimensions, complexFormula)
|
|
313
|
+
|
|
314
|
+
# Verify the function works with large arrays
|
|
315
|
+
resultRLE = autoDecodingRLE(arrayMapping)
|
|
316
|
+
|
|
317
|
+
# The result should be a valid string representation
|
|
318
|
+
assert isinstance(resultRLE, str)
|
|
319
|
+
assert "[" in resultRLE
|
|
320
|
+
assert "]" in resultRLE
|
|
321
|
+
|
|
322
|
+
# The RLE encoding should be more compact than the raw representation
|
|
323
|
+
rawStrLength = len(str(arrayMapping.tolist()))
|
|
324
|
+
encodedLength = len(resultRLE)
|
|
325
|
+
assert encodedLength <= rawStrLength, f"RLE encoded string ({encodedLength}) should be shorter than raw string ({rawStrLength})"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# pyright: standard
|
|
2
2
|
from hunterMakesPy import importLogicalPath2Identifier, importPathFilename2Identifier, makeDirsSafely, writeStringToHere
|
|
3
|
-
from tests.conftest import uniformTestFailureMessage
|
|
3
|
+
from hunterMakesPy.tests.conftest import uniformTestFailureMessage
|
|
4
4
|
import io
|
|
5
5
|
import math
|
|
6
6
|
import os
|
|
@@ -31,7 +31,10 @@ def testImportLogicalPath2Identifier(moduleName: str, identifier: str, expectedT
|
|
|
31
31
|
imported = importLogicalPath2Identifier(moduleName, identifier)
|
|
32
32
|
assert isinstance(imported, expectedType), uniformTestFailureMessage(expectedType, type(imported), "testImportLogicalPath2Identifier", (moduleName, identifier))
|
|
33
33
|
|
|
34
|
-
@pytest.mark.parametrize(
|
|
34
|
+
@pytest.mark.parametrize(
|
|
35
|
+
"source, identifier, expected"
|
|
36
|
+
, [("def fibonacciNumber():\n return 13\n", "fibonacciNumber", 13)
|
|
37
|
+
, ("prime = 17\n", "prime", 17)])
|
|
35
38
|
def testImportPathFilename2Identifier(tmp_path: pathlib.Path, source: str, identifier: str, expected: object) -> None:
|
|
36
39
|
filePath = tmp_path / "moduleTest.py"
|
|
37
40
|
filePath.write_text(source)
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
Each function in this module returns a list of test functions that can be used with `pytest.parametrize`.
|
|
4
|
-
"""
|
|
1
|
+
# pyright: standard
|
|
5
2
|
from collections.abc import Callable, Iterable, Iterator
|
|
6
3
|
from hunterMakesPy import defineConcurrencyLimit, intInnit, oopsieKwargsie
|
|
7
|
-
from typing import Any, NoReturn
|
|
4
|
+
from typing import Any, NoReturn, ParamSpec, TypeVar
|
|
8
5
|
from unittest.mock import Mock, patch
|
|
9
6
|
import pytest
|
|
10
7
|
|
|
8
|
+
parameters = ParamSpec('parameters')
|
|
9
|
+
returnType = TypeVar('returnType')
|
|
10
|
+
|
|
11
11
|
def PytestFor_defineConcurrencyLimit(callableToTest: Callable[..., int] = defineConcurrencyLimit, cpuCount: int = 8) -> list[tuple[str, Callable[[], None]]]: # noqa: C901
|
|
12
12
|
"""Return a list of test functions to validate concurrency limit behavior.
|
|
13
13
|
|
|
@@ -325,3 +325,15 @@ def PytestFor_oopsieKwargsie(callableToTest: Callable[[str], bool | None | str]
|
|
|
325
325
|
('testReturnsOriginalString', testReturnsOriginalString),
|
|
326
326
|
('testHandlesNonStringObjects', testHandlesNonStringObjects)
|
|
327
327
|
]
|
|
328
|
+
|
|
329
|
+
@pytest.mark.parametrize("nameOfTest,aPytest", PytestFor_defineConcurrencyLimit())
|
|
330
|
+
def testConcurrencyLimit(nameOfTest: str, aPytest: Callable[parameters, returnType], *arguments: parameters.args, **keywordArguments: parameters.kwargs) -> None:
|
|
331
|
+
aPytest(*arguments, **keywordArguments)
|
|
332
|
+
|
|
333
|
+
@pytest.mark.parametrize("nameOfTest,aPytest", PytestFor_intInnit())
|
|
334
|
+
def testIntInnit(nameOfTest: str, aPytest: Callable[parameters, returnType], *arguments: parameters.args, **keywordArguments: parameters.kwargs) -> None:
|
|
335
|
+
aPytest(*arguments, **keywordArguments)
|
|
336
|
+
|
|
337
|
+
@pytest.mark.parametrize("nameOfTest,aPytest", PytestFor_oopsieKwargsie())
|
|
338
|
+
def testOopsieKwargsie(nameOfTest: str, aPytest: Callable[parameters, returnType], *arguments: parameters.args, **keywordArguments: parameters.kwargs) -> None:
|
|
339
|
+
aPytest(*arguments, **keywordArguments)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hunterMakesPy
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Easy Python functions making making functional Python functions easier.
|
|
5
5
|
Author-email: Hunter Hogan <HunterHogan@pm.me>
|
|
6
6
|
License: CC-BY-NC-4.0
|
|
@@ -22,6 +22,7 @@ Classifier: Programming Language :: Python :: 3
|
|
|
22
22
|
Classifier: Programming Language :: Python :: 3.11
|
|
23
23
|
Classifier: Programming Language :: Python :: 3.12
|
|
24
24
|
Classifier: Programming Language :: Python :: 3.13
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
25
26
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
26
27
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
27
28
|
Classifier: Topic :: Utilities
|
|
@@ -32,7 +33,7 @@ License-File: LICENSE
|
|
|
32
33
|
Requires-Dist: charset_normalizer
|
|
33
34
|
Requires-Dist: more_itertools
|
|
34
35
|
Requires-Dist: numpy
|
|
35
|
-
Requires-Dist: python_minifier
|
|
36
|
+
Requires-Dist: python_minifier; python_version < "3.14"
|
|
36
37
|
Provides-Extra: development
|
|
37
38
|
Requires-Dist: mypy; extra == "development"
|
|
38
39
|
Requires-Dist: pyupgrade; extra == "development"
|
|
@@ -15,9 +15,9 @@ hunterMakesPy.egg-info/SOURCES.txt
|
|
|
15
15
|
hunterMakesPy.egg-info/dependency_links.txt
|
|
16
16
|
hunterMakesPy.egg-info/requires.txt
|
|
17
17
|
hunterMakesPy.egg-info/top_level.txt
|
|
18
|
-
tests/__init__.py
|
|
19
|
-
tests/conftest.py
|
|
20
|
-
tests/test_coping.py
|
|
21
|
-
tests/test_dataStructures.py
|
|
22
|
-
tests/test_filesystemToolkit.py
|
|
23
|
-
tests/test_parseParameters.py
|
|
18
|
+
hunterMakesPy/tests/__init__.py
|
|
19
|
+
hunterMakesPy/tests/conftest.py
|
|
20
|
+
hunterMakesPy/tests/test_coping.py
|
|
21
|
+
hunterMakesPy/tests/test_dataStructures.py
|
|
22
|
+
hunterMakesPy/tests/test_filesystemToolkit.py
|
|
23
|
+
hunterMakesPy/tests/test_parseParameters.py
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "hunterMakesPy"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.2.0"
|
|
4
4
|
description = "Easy Python functions making making functional Python functions easier."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.11"
|
|
@@ -40,6 +40,7 @@ classifiers = [
|
|
|
40
40
|
"Programming Language :: Python :: 3.11",
|
|
41
41
|
"Programming Language :: Python :: 3.12",
|
|
42
42
|
"Programming Language :: Python :: 3.13",
|
|
43
|
+
"Programming Language :: Python :: 3.14",
|
|
43
44
|
"Programming Language :: Python :: Implementation :: CPython",
|
|
44
45
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
45
46
|
"Topic :: Utilities",
|
|
@@ -50,7 +51,7 @@ dependencies = [
|
|
|
50
51
|
"charset_normalizer",
|
|
51
52
|
"more_itertools",
|
|
52
53
|
"numpy",
|
|
53
|
-
"python_minifier",
|
|
54
|
+
"python_minifier; python_version < '3.14'",
|
|
54
55
|
]
|
|
55
56
|
optional-dependencies = { development = [
|
|
56
57
|
"mypy",
|
|
@@ -75,15 +76,16 @@ run = { branch = true, concurrency = [
|
|
|
75
76
|
"multiprocessing",
|
|
76
77
|
], data_file = "tests/coverage/.coverage", omit = [
|
|
77
78
|
"tests/*",
|
|
79
|
+
"hunterMakesPy/tests/*",
|
|
80
|
+
"hunterMakesPy/pytestForYourUse.py",
|
|
78
81
|
], parallel = true, source = [
|
|
79
82
|
".",
|
|
80
83
|
] }
|
|
81
|
-
xml = { output = "tests/coverage/coverage.xml" }
|
|
82
84
|
|
|
83
85
|
[tool.pytest.ini_options]
|
|
84
86
|
addopts = ["--color=auto"]
|
|
85
87
|
log_auto_indent = true
|
|
86
|
-
testpaths = ["tests"]
|
|
88
|
+
testpaths = ["hunterMakesPy/tests"]
|
|
87
89
|
|
|
88
90
|
[tool.setuptools]
|
|
89
91
|
package-data = { "*" = ["py.typed"] }
|
|
@@ -1,265 +0,0 @@
|
|
|
1
|
-
"""Provides utilities for string extraction from nested data structures and merges multiple dictionaries containing lists into one dictionary."""
|
|
2
|
-
|
|
3
|
-
from collections.abc import Iterator, Mapping
|
|
4
|
-
from numpy import integer
|
|
5
|
-
from numpy.typing import NDArray
|
|
6
|
-
from typing import Any
|
|
7
|
-
import more_itertools
|
|
8
|
-
import python_minifier
|
|
9
|
-
import re as regex
|
|
10
|
-
|
|
11
|
-
def autoDecodingRLE(arrayTarget: NDArray[integer[Any]], *, assumeAddSpaces: bool = False) -> str: # noqa: C901, PLR0915
|
|
12
|
-
"""Transform a NumPy array into a compact, self-decoding run-length encoded string representation.
|
|
13
|
-
|
|
14
|
-
This function converts a NumPy array into a string that, when evaluated as Python code,
|
|
15
|
-
recreates the original array structure. The function employs two compression strategies:
|
|
16
|
-
1. Python's `range` syntax for consecutive integer sequences
|
|
17
|
-
2. Multiplication syntax for repeated elements
|
|
18
|
-
|
|
19
|
-
The resulting string representation is designed to be both human-readable and space-efficient,
|
|
20
|
-
especially for large cartesian mappings with repetitive patterns. When this string is used
|
|
21
|
-
as a data source, Python will automatically decode it into Python `list`, which if used as an
|
|
22
|
-
argument to `numpy.array()`, will recreate the original array structure.
|
|
23
|
-
|
|
24
|
-
Parameters
|
|
25
|
-
----------
|
|
26
|
-
arrayTarget : NDArray[integer[Any]]
|
|
27
|
-
(array2target) The NumPy array to be encoded.
|
|
28
|
-
assumeAddSpaces : bool = False
|
|
29
|
-
(assume2add2spaces) Affects internal length comparison during compression decisions.
|
|
30
|
-
This parameter doesn't directly change output format but influences whether
|
|
31
|
-
`range` or multiplication syntax is preferred in certain cases. The parameter
|
|
32
|
-
exists because the Abstract Syntax Tree (AST) inserts spaces in its string
|
|
33
|
-
representation.
|
|
34
|
-
|
|
35
|
-
Returns
|
|
36
|
-
-------
|
|
37
|
-
rleString : str
|
|
38
|
-
(rle2string) A string representation of the array using run-length encoding that,
|
|
39
|
-
when evaluated as Python code, reproduces the original array structure.
|
|
40
|
-
|
|
41
|
-
Notes
|
|
42
|
-
-----
|
|
43
|
-
The "autoDecoding" feature means that the string representation evaluates directly
|
|
44
|
-
to the desired data structure without explicit decompression steps.
|
|
45
|
-
|
|
46
|
-
"""
|
|
47
|
-
def sliceNDArrayToNestedLists(arraySlice: NDArray[integer[Any]]) -> Any:
|
|
48
|
-
def getLengthOption(optionAsStr: str) -> int:
|
|
49
|
-
"""`assumeAddSpaces` characters: `,` 1; `]*` 2."""
|
|
50
|
-
return assumeAddSpaces * (optionAsStr.count(',') + optionAsStr.count(']*') * 2) + len(optionAsStr)
|
|
51
|
-
|
|
52
|
-
if arraySlice.ndim > 1:
|
|
53
|
-
axisOfOperation = 0
|
|
54
|
-
return [sliceNDArrayToNestedLists(arraySlice[index]) for index in range(arraySlice.shape[axisOfOperation])]
|
|
55
|
-
if arraySlice.ndim == 1:
|
|
56
|
-
arraySliceAsList: list[int | range] = []
|
|
57
|
-
cache_consecutiveGroup_addMe: dict[Iterator[Any], list[int] | list[range]] = {}
|
|
58
|
-
for consecutiveGroup in more_itertools.consecutive_groups(arraySlice.tolist()):
|
|
59
|
-
if consecutiveGroup in cache_consecutiveGroup_addMe:
|
|
60
|
-
addMe = cache_consecutiveGroup_addMe[consecutiveGroup]
|
|
61
|
-
else:
|
|
62
|
-
ImaSerious: list[int] = list(consecutiveGroup)
|
|
63
|
-
ImaRange = [range(ImaSerious[0], ImaSerious[-1] + 1)]
|
|
64
|
-
ImaRangeAsStr = python_minifier.minify(str(ImaRange)).replace('range(0,', 'range(').replace('range', '*range')
|
|
65
|
-
|
|
66
|
-
option1 = ImaRange
|
|
67
|
-
option1AsStr = ImaRangeAsStr
|
|
68
|
-
option2 = ImaSerious
|
|
69
|
-
option2AsStr = None
|
|
70
|
-
|
|
71
|
-
# alpha, potential function
|
|
72
|
-
option1AsStr = option1AsStr or python_minifier.minify(str(option1))
|
|
73
|
-
lengthOption1 = getLengthOption(option1AsStr)
|
|
74
|
-
|
|
75
|
-
option2AsStr = option2AsStr or python_minifier.minify(str(option2))
|
|
76
|
-
lengthOption2 = getLengthOption(option2AsStr)
|
|
77
|
-
|
|
78
|
-
if lengthOption1 < lengthOption2:
|
|
79
|
-
addMe = option1
|
|
80
|
-
else:
|
|
81
|
-
addMe = option2
|
|
82
|
-
|
|
83
|
-
cache_consecutiveGroup_addMe[consecutiveGroup] = addMe
|
|
84
|
-
|
|
85
|
-
arraySliceAsList += addMe
|
|
86
|
-
|
|
87
|
-
listRangeAndTuple: list[int | range | tuple[int | range, int]] = []
|
|
88
|
-
cache_malkovichGrouped_addMe: dict[tuple[int | range, int], list[tuple[int | range, int]] | list[int | range]] = {}
|
|
89
|
-
for malkovichGrouped in more_itertools.run_length.encode(arraySliceAsList):
|
|
90
|
-
if malkovichGrouped in cache_malkovichGrouped_addMe:
|
|
91
|
-
addMe = cache_malkovichGrouped_addMe[malkovichGrouped]
|
|
92
|
-
else:
|
|
93
|
-
lengthMalkovich = malkovichGrouped[-1]
|
|
94
|
-
malkovichAsList = list(more_itertools.run_length.decode([malkovichGrouped]))
|
|
95
|
-
malkovichMalkovich = f"[{malkovichGrouped[0]}]*{lengthMalkovich}"
|
|
96
|
-
|
|
97
|
-
option1 = [malkovichGrouped]
|
|
98
|
-
option1AsStr = malkovichMalkovich
|
|
99
|
-
option2 = malkovichAsList
|
|
100
|
-
option2AsStr = None
|
|
101
|
-
|
|
102
|
-
# beta, potential function
|
|
103
|
-
option1AsStr = option1AsStr or python_minifier.minify(str(option1))
|
|
104
|
-
lengthOption1 = getLengthOption(option1AsStr)
|
|
105
|
-
|
|
106
|
-
option2AsStr = option2AsStr or python_minifier.minify(str(option2))
|
|
107
|
-
lengthOption2 = getLengthOption(option2AsStr)
|
|
108
|
-
|
|
109
|
-
if lengthOption1 < lengthOption2:
|
|
110
|
-
addMe = option1
|
|
111
|
-
else:
|
|
112
|
-
addMe = option2
|
|
113
|
-
|
|
114
|
-
cache_malkovichGrouped_addMe[malkovichGrouped] = addMe
|
|
115
|
-
|
|
116
|
-
listRangeAndTuple += addMe
|
|
117
|
-
|
|
118
|
-
return listRangeAndTuple
|
|
119
|
-
return arraySlice
|
|
120
|
-
|
|
121
|
-
arrayAsNestedLists = sliceNDArrayToNestedLists(arrayTarget)
|
|
122
|
-
|
|
123
|
-
arrayAsStr = python_minifier.minify(str(arrayAsNestedLists))
|
|
124
|
-
|
|
125
|
-
patternRegex = regex.compile(
|
|
126
|
-
"(?<!rang)(?:"
|
|
127
|
-
# Pattern 1: Comma ahead, bracket behind # noqa: ERA001
|
|
128
|
-
"(?P<joinAhead>,)\\((?P<malkovich>\\d+),(?P<multiply>\\d+)\\)(?P<bracketBehind>])|"
|
|
129
|
-
# Pattern 2: Bracket or start ahead, comma behind # noqa: ERA001
|
|
130
|
-
"(?P<bracketOrStartAhead>\\[|^.)\\((?P<malkovichMalkovich>\\d+),(?P<multiplyIDK>\\d+)\\)(?P<joinBehind>,)|"
|
|
131
|
-
# Pattern 3: Bracket ahead, bracket behind # noqa: ERA001
|
|
132
|
-
"(?P<bracketAhead>\\[)\\((?P<malkovichMalkovichMalkovich>\\d+),(?P<multiply_whatever>\\d+)\\)(?P<bracketBehindBracketBehind>])|"
|
|
133
|
-
# Pattern 4: Comma ahead, comma behind # noqa: ERA001
|
|
134
|
-
"(?P<joinAheadJoinAhead>,)\\((?P<malkovichMalkovichMalkovichMalkovich>\\d+),(?P<multiplyOrSomething>\\d+)\\)(?P<joinBehindJoinBehind>,)"
|
|
135
|
-
")"
|
|
136
|
-
)
|
|
137
|
-
|
|
138
|
-
def replacementByContext(match: regex.Match[str]) -> str:
|
|
139
|
-
"""Generate replacement string based on context patterns."""
|
|
140
|
-
elephino = match.groupdict()
|
|
141
|
-
joinAhead = elephino.get('joinAhead') or elephino.get('joinAheadJoinAhead')
|
|
142
|
-
malkovich = elephino.get('malkovich') or elephino.get('malkovichMalkovich') or elephino.get('malkovichMalkovichMalkovich') or elephino.get('malkovichMalkovichMalkovichMalkovich')
|
|
143
|
-
multiply = elephino.get('multiply') or elephino.get('multiplyIDK') or elephino.get('multiply_whatever') or elephino.get('multiplyOrSomething')
|
|
144
|
-
joinBehind = elephino.get('joinBehind') or elephino.get('joinBehindJoinBehind')
|
|
145
|
-
|
|
146
|
-
replaceAhead = "]+[" if joinAhead == "," else "["
|
|
147
|
-
|
|
148
|
-
replaceBehind = "+[" if joinBehind == "," else ""
|
|
149
|
-
|
|
150
|
-
return f"{replaceAhead}{malkovich}]*{multiply}{replaceBehind}"
|
|
151
|
-
|
|
152
|
-
arrayAsStr = patternRegex.sub(replacementByContext, arrayAsStr)
|
|
153
|
-
arrayAsStr = patternRegex.sub(replacementByContext, arrayAsStr)
|
|
154
|
-
|
|
155
|
-
# Replace `range(0,stop)` syntax with `range(stop)` syntax. # noqa: ERA001
|
|
156
|
-
# Add unpack operator `*` for automatic decoding when evaluated.
|
|
157
|
-
return arrayAsStr.replace('range(0,', 'range(').replace('range', '*range')
|
|
158
|
-
|
|
159
|
-
def stringItUp(*scrapPile: Any) -> list[str]: # noqa: C901
|
|
160
|
-
"""Convert, if possible, every element in the input data structure to a string.
|
|
161
|
-
|
|
162
|
-
Order is not preserved or readily predictable.
|
|
163
|
-
|
|
164
|
-
Parameters
|
|
165
|
-
----------
|
|
166
|
-
*scrapPile : Any
|
|
167
|
-
(scrap2pile) One or more data structures to unpack and convert to strings.
|
|
168
|
-
|
|
169
|
-
Returns
|
|
170
|
-
-------
|
|
171
|
-
listStrungUp : list[str]
|
|
172
|
-
(list2strung2up) A `list` of string versions of all convertible elements.
|
|
173
|
-
|
|
174
|
-
"""
|
|
175
|
-
scrap = None
|
|
176
|
-
listStrungUp: list[str] = []
|
|
177
|
-
|
|
178
|
-
def drill(KitKat: Any) -> None: # noqa: C901, PLR0912
|
|
179
|
-
match KitKat:
|
|
180
|
-
case str():
|
|
181
|
-
listStrungUp.append(KitKat)
|
|
182
|
-
case bool() | bytearray() | bytes() | complex() | float() | int() | memoryview() | None:
|
|
183
|
-
listStrungUp.append(str(KitKat)) # pyright: ignore [reportUnknownArgumentType]
|
|
184
|
-
case dict():
|
|
185
|
-
for broken, piece in KitKat.items(): # pyright: ignore [reportUnknownVariableType]
|
|
186
|
-
drill(broken)
|
|
187
|
-
drill(piece)
|
|
188
|
-
case list() | tuple() | set() | frozenset() | range():
|
|
189
|
-
for kit in KitKat: # pyright: ignore [reportUnknownVariableType]
|
|
190
|
-
drill(kit)
|
|
191
|
-
case _:
|
|
192
|
-
if hasattr(KitKat, '__iter__'): # Unpack other iterables
|
|
193
|
-
for kat in KitKat:
|
|
194
|
-
drill(kat)
|
|
195
|
-
else:
|
|
196
|
-
try:
|
|
197
|
-
sharingIsCaring = KitKat.__str__()
|
|
198
|
-
listStrungUp.append(sharingIsCaring)
|
|
199
|
-
except AttributeError:
|
|
200
|
-
pass
|
|
201
|
-
except TypeError: # "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."
|
|
202
|
-
pass
|
|
203
|
-
except:
|
|
204
|
-
print(f"\nWoah! I received '{repr(KitKat)}'.\nTheir report card says, 'Plays well with others: Needs improvement.'\n") # noqa: RUF010, T201
|
|
205
|
-
raise
|
|
206
|
-
try:
|
|
207
|
-
for scrap in scrapPile:
|
|
208
|
-
drill(scrap)
|
|
209
|
-
except RecursionError:
|
|
210
|
-
listStrungUp.append(repr(scrap))
|
|
211
|
-
return listStrungUp
|
|
212
|
-
|
|
213
|
-
def updateExtendPolishDictionaryLists(*dictionaryLists: Mapping[str, list[Any] | set[Any] | tuple[Any, ...]], destroyDuplicates: bool = False, reorderLists: bool = False, killErroneousDataTypes: bool = False) -> dict[str, list[Any]]:
|
|
214
|
-
"""Merge multiple dictionaries containing `list` into a single dictionary.
|
|
215
|
-
|
|
216
|
-
With options to handle duplicates, `list` ordering, and erroneous data types.
|
|
217
|
-
|
|
218
|
-
Parameters
|
|
219
|
-
----------
|
|
220
|
-
*dictionaryLists : Mapping[str, list[Any] | set[Any] | tuple[Any, ...]]
|
|
221
|
-
(dictionary2lists) Variable number of dictionaries to be merged. If only one dictionary is passed, it will be processed based on the provided options.
|
|
222
|
-
destroyDuplicates : bool = False
|
|
223
|
-
(destroy2duplicates) If `True`, removes duplicate elements from the `list`. Defaults to `False`.
|
|
224
|
-
reorderLists : bool = False
|
|
225
|
-
(reorder2lists) If `True`, sorts the `list`. Defaults to `False`.
|
|
226
|
-
killErroneousDataTypes : bool = False
|
|
227
|
-
(kill2erroneous2data2types) If `True`, skips dictionary keys or dictionary values that cause a `TypeError` during merging. Defaults to `False`.
|
|
228
|
-
|
|
229
|
-
Returns
|
|
230
|
-
-------
|
|
231
|
-
ePluribusUnum : dict[str, list[Any]]
|
|
232
|
-
(e2pluribus2unum) A single dictionary with merged `list` based on the provided options. If only one dictionary is passed,
|
|
233
|
-
it will be cleaned up based on the options.
|
|
234
|
-
|
|
235
|
-
Notes
|
|
236
|
-
-----
|
|
237
|
-
The returned value, `ePluribusUnum`, is a so-called primitive dictionary (`dict`). Furthermore, every dictionary key is a
|
|
238
|
-
so-called primitive string (cf. `str()`) and every dictionary value is a so-called primitive `list` (`list`). If
|
|
239
|
-
`dictionaryLists` has other data types, the data types will not be preserved. That could have unexpected consequences.
|
|
240
|
-
Conversion from the original data type to a `list`, for example, may not preserve the order even if you want the order to be
|
|
241
|
-
preserved.
|
|
242
|
-
|
|
243
|
-
"""
|
|
244
|
-
ePluribusUnum: dict[str, list[Any]] = {}
|
|
245
|
-
|
|
246
|
-
for dictionaryListTarget in dictionaryLists:
|
|
247
|
-
for keyName, keyValue in dictionaryListTarget.items():
|
|
248
|
-
try:
|
|
249
|
-
ImaStr = str(keyName)
|
|
250
|
-
ImaList = list(keyValue)
|
|
251
|
-
ePluribusUnum.setdefault(ImaStr, []).extend(ImaList)
|
|
252
|
-
except TypeError:
|
|
253
|
-
if killErroneousDataTypes:
|
|
254
|
-
continue
|
|
255
|
-
else:
|
|
256
|
-
raise
|
|
257
|
-
|
|
258
|
-
if destroyDuplicates:
|
|
259
|
-
for ImaStr, ImaList in ePluribusUnum.items():
|
|
260
|
-
ePluribusUnum[ImaStr] = list(dict.fromkeys(ImaList))
|
|
261
|
-
if reorderLists:
|
|
262
|
-
for ImaStr, ImaList in ePluribusUnum.items():
|
|
263
|
-
ePluribusUnum[ImaStr] = sorted(ImaList)
|
|
264
|
-
|
|
265
|
-
return ePluribusUnum
|
|
File without changes
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
# pyright: standard
|
|
2
|
-
from collections.abc import Callable
|
|
3
|
-
from hunterMakesPy.pytestForYourUse import (
|
|
4
|
-
PytestFor_defineConcurrencyLimit, PytestFor_intInnit, PytestFor_oopsieKwargsie)
|
|
5
|
-
from typing import ParamSpec, TypeVar
|
|
6
|
-
import pytest
|
|
7
|
-
|
|
8
|
-
parameters = ParamSpec('parameters')
|
|
9
|
-
returnType = TypeVar('returnType')
|
|
10
|
-
|
|
11
|
-
@pytest.mark.parametrize("nameOfTest,aPytest", PytestFor_defineConcurrencyLimit())
|
|
12
|
-
def testConcurrencyLimit(nameOfTest: str, aPytest: Callable[parameters, returnType], *arguments: parameters.args, **keywordArguments: parameters.kwargs) -> None:
|
|
13
|
-
aPytest(*arguments, **keywordArguments)
|
|
14
|
-
|
|
15
|
-
@pytest.mark.parametrize("nameOfTest,aPytest", PytestFor_intInnit())
|
|
16
|
-
def testIntInnit(nameOfTest: str, aPytest: Callable[parameters, returnType], *arguments: parameters.args, **keywordArguments: parameters.kwargs) -> None:
|
|
17
|
-
aPytest(*arguments, **keywordArguments)
|
|
18
|
-
|
|
19
|
-
@pytest.mark.parametrize("nameOfTest,aPytest", PytestFor_oopsieKwargsie())
|
|
20
|
-
def testOopsieKwargsie(nameOfTest: str, aPytest: Callable[parameters, returnType], *arguments: parameters.args, **keywordArguments: parameters.kwargs) -> None:
|
|
21
|
-
aPytest(*arguments, **keywordArguments)
|
|
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
|