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.
Files changed (28) hide show
  1. {huntermakespy-0.1.2 → huntermakespy-0.2.0}/PKG-INFO +3 -2
  2. {huntermakespy-0.1.2 → huntermakespy-0.2.0}/hunterMakesPy/__init__.py +7 -2
  3. huntermakespy-0.2.0/hunterMakesPy/dataStructures.py +268 -0
  4. {huntermakespy-0.1.2 → huntermakespy-0.2.0}/hunterMakesPy/parseParameters.py +29 -29
  5. huntermakespy-0.2.0/hunterMakesPy/pytestForYourUse.py +10 -0
  6. huntermakespy-0.2.0/hunterMakesPy/tests/__init__.py +5 -0
  7. {huntermakespy-0.1.2 → huntermakespy-0.2.0/hunterMakesPy}/tests/conftest.py +1 -2
  8. {huntermakespy-0.1.2 → huntermakespy-0.2.0/hunterMakesPy}/tests/test_coping.py +1 -1
  9. {huntermakespy-0.1.2 → huntermakespy-0.2.0/hunterMakesPy}/tests/test_dataStructures.py +124 -118
  10. {huntermakespy-0.1.2 → huntermakespy-0.2.0/hunterMakesPy}/tests/test_filesystemToolkit.py +5 -2
  11. huntermakespy-0.1.2/hunterMakesPy/pytestForYourUse.py → huntermakespy-0.2.0/hunterMakesPy/tests/test_parseParameters.py +17 -5
  12. {huntermakespy-0.1.2 → huntermakespy-0.2.0}/hunterMakesPy.egg-info/PKG-INFO +3 -2
  13. {huntermakespy-0.1.2 → huntermakespy-0.2.0}/hunterMakesPy.egg-info/SOURCES.txt +6 -6
  14. {huntermakespy-0.1.2 → huntermakespy-0.2.0}/hunterMakesPy.egg-info/requires.txt +2 -0
  15. {huntermakespy-0.1.2 → huntermakespy-0.2.0}/hunterMakesPy.egg-info/top_level.txt +0 -1
  16. {huntermakespy-0.1.2 → huntermakespy-0.2.0}/pyproject.toml +6 -4
  17. huntermakespy-0.1.2/hunterMakesPy/dataStructures.py +0 -265
  18. huntermakespy-0.1.2/tests/__init__.py +0 -0
  19. huntermakespy-0.1.2/tests/test_parseParameters.py +0 -21
  20. {huntermakespy-0.1.2 → huntermakespy-0.2.0}/LICENSE +0 -0
  21. {huntermakespy-0.1.2 → huntermakespy-0.2.0}/README.md +0 -0
  22. {huntermakespy-0.1.2 → huntermakespy-0.2.0}/hunterMakesPy/_theSSOT.py +0 -0
  23. {huntermakespy-0.1.2 → huntermakespy-0.2.0}/hunterMakesPy/coping.py +0 -0
  24. {huntermakespy-0.1.2 → huntermakespy-0.2.0}/hunterMakesPy/filesystemToolkit.py +0 -0
  25. {huntermakespy-0.1.2 → huntermakespy-0.2.0}/hunterMakesPy/py.typed +0 -0
  26. {huntermakespy-0.1.2 → huntermakespy-0.2.0}/hunterMakesPy/theTypes.py +0 -0
  27. {huntermakespy-0.1.2 → huntermakespy-0.2.0}/hunterMakesPy.egg-info/dependency_links.txt +0 -0
  28. {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.1.2
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 (autoDecodingRLE as autoDecodingRLE, stringItUp as stringItUp,
22
- updateExtendPolishDictionaryLists as updateExtendPolishDictionaryLists)
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
- (parameter2value) The value that caused the error.
15
+ The value that caused the error.
18
16
  parameterValueType : str | None = None
19
- (parameter2value2type) The type name of the parameter value.
17
+ The name of the type of the parameter value.
20
18
  containerType : str | None = None
21
- (container2type) The type name of the container holding the parameter.
19
+ The name of the type of the container holding the parameter.
22
20
  isElement : bool = False
23
- (is2element) Whether the parameter is an element within a container.
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 | `None`] but `parameterName` must have integers [in type(s) `parameterType` | `None`].
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
- (context) The error context containing parameter value, type, and container information.
43
+ The error context containing parameter value, type, and container information.
45
44
  parameterName : str
46
- (parameter2name) The name of the parameter that caused the error.
45
+ The name of the parameter that caused the error.
47
46
  parameterType : type[Any] | None
48
- (parameter2type) The expected type of the parameter, used in error messages.
47
+ The expected type of the parameter, used in error messages.
49
48
 
50
49
  Returns
51
50
  -------
52
51
  errorMessage : str
53
- (error2message) The constructed error message string.
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
- This package has Pytest tests you can import and run on this function. `from hunterMakesPy.pytest_parseParameters import makeTestSuiteConcurrencyLimit`
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
- (concurrency2limit) The calculated concurrency limit, ensuring it is at least 1.
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
- malformed inputs. For example:
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
- (list2int2allegedly) The input sequence that should contain integer-compatible values.
172
- Accepts integers, strings, floats, complex numbers, and binary data.
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
- (parameter2name) Name of the parameter from your function for which this function is validating the input validated. If there is an error message, it provides context to your user. Defaults to 'the parameter'.
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
- (parameter2type) Expected type(s) of the parameter, used in error messages.
179
+ Expected type(s) of the parameter, used in error messages.
178
180
 
179
181
  Returns
180
182
  -------
181
183
  listValidated : list[int]
182
- (list2validated) A `list` containing validated integers.
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 `Exception`. It tries to interpret the string as `True`, `False`, or `None`. This package has Pytest tests you can import and run on this function.
288
- `from hunterMakesPy.pytest_parseParameters import makeTestSuiteOopsieKwargsie`
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
- (interpreted2value) The reserved keyword `True`, `False`, or `None` or the original string, `huh`.
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)
@@ -0,0 +1,5 @@
1
+ """Test modules for hunterMakesPy package.
2
+
3
+ This module contains comprehensive test suites for all major functions and features in the hunterMakesPy package. Tests can be run
4
+ independently or imported into your project.
5
+ """
@@ -5,8 +5,7 @@ import pathlib
5
5
  import pytest
6
6
 
7
7
  # SSOT for test data paths and filenames
8
- # SSOT for test data paths and filenames
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 autoDecodingRLE, stringItUp, updateExtendPolishDictionaryLists
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
- @pytest.mark.parametrize("description,value_arrayTarget,expected", [
98
- ("One range", numpy.array(list(range(50,60))), "[*range(50,60)]"),
99
- ("Value, range", numpy.array([123]+list(range(71,81))), "[123,*range(71,81)]"),
100
- ("range, value", numpy.array(list(range(91,97))+[101]), "[*range(91,97),101]"),
101
- ("Value, range, value", numpy.array([151]+list(range(163,171))+[181]), "[151,*range(163,171),181]"),
102
- ("Repeat values", numpy.array([191, 191, 191]), "[191]*3"),
103
- ("Value with repeat", numpy.array([211, 223, 223, 223]), "[211]+[223]*3"),
104
- ("Range with repeat", numpy.array(list(range(251,257))+[271, 271, 271]), "[*range(251,257)]+[271]*3"),
105
- ("Value, range, repeat", numpy.array([281]+list(range(291,297))+[307, 307]), "[281,*range(291,297)]+[307]*2"),
106
- ("repeat, value", numpy.array([313, 313, 313, 331, 331, 349]), "[313]*3+[331]*2+[349]"),
107
- ("repeat, range", numpy.array([373, 373, 373]+list(range(383,389))), "[373]*3+[*range(383,389)]"),
108
- ("repeat, range, value", numpy.array(7*[401]+list(range(409,415))+[421]), "[401]*7+[*range(409,415),421]"),
109
- ("Repeated primes", numpy.array([431, 431, 431, 443, 443, 457]), "[431]*3+[443]*2+[457]"),
110
- ("Two Ranges", numpy.array(list(range(461,471))+list(range(479,487))), "[*range(461,471),*range(479,487)]"),
111
- ("2D array primes", numpy.array([[491, 499, 503], [509, 521, 523]]), "[[491,499,503],[509,521,523]]"),
112
- ("3D array primes", numpy.array([[[541, 547], [557, 563]], [[569, 571], [577, 587]]]), "[[[541,547],[557,563]],[[569,571],[577,587]]]"),
113
- ], ids=lambda x: x if isinstance(x, str) else "")
114
- def testAutoDecodingRLE(description: str, value_arrayTarget: NDArray[numpy.integer[Any]], expected: str) -> None:
115
- """Test autoDecodingRLE with various input arrays."""
116
- standardizedEqualTo(expected, autoDecodingRLE, value_arrayTarget)
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
- # Updated test cases for autoDecodingRLE with more realistic data
226
- @pytest.mark.parametrize("description,value_arrayTarget", [
227
- # Basic test cases with simple patterns
228
- ("Simple range", numpy.array(list(range(50,60)))),
229
-
230
- # Chessboard patterns
231
- ("Small chessboard", generateChessboard((8, 8))),
232
-
233
- # Alternating columns - creates patterns with good RLE opportunities
234
- ("Alternating columns", generateAlternatingColumns((5, 20), 2)),
235
-
236
- # Step pattern - creates horizontal runs
237
- ("Step pattern", generateStepPattern((6, 30), 3)),
238
-
239
- # Repeating zones - creates horizontal bands
240
- ("Repeating zones", generateRepeatingZones((40, 40), 8)),
241
-
242
- # Tile pattern - creates complex repeating regions
243
- ("Tile pattern", generateTilePattern((15, 15), 5)),
244
-
245
- # Signed quadratic function - includes negative values
246
- ("Signed quadratic", generateSignedQuadraticFunction((10, 10))),
247
-
248
- # Prime modulo matrix - periodic patterns
249
- ("Prime modulo", generatePrimeModuloMatrix((12, 12), 7)),
250
-
251
- # Wave pattern - smooth gradients
252
- ("Wave pattern", generateWavePattern((20, 20))),
253
-
254
- # Spiral pattern - complex pattern with good RLE potential
255
- ("Spiral pattern", generateSpiralPattern((15, 15), 2)),
256
- ], ids=lambda x: x if isinstance(x, str) else "")
257
- def testAutoDecodingRLEWithRealisticData(description: str, value_arrayTarget: NDArray[numpy.integer[Any]]) -> None:
258
- """Test autoDecodingRLE with more realistic data patterns."""
259
- # Here we test the function behavior rather than expected string output
260
- resultRLE = autoDecodingRLE(value_arrayTarget)
261
-
262
- # Test that the result is a valid string
263
- assert isinstance(resultRLE, str)
264
-
265
- # Test that the result contains the expected syntax elements
266
- assert "[" in resultRLE, f"Result should contain list syntax: {resultRLE}"
267
- assert "]" in resultRLE, f"Result should contain list syntax: {resultRLE}"
268
-
269
- # Check that the result is more compact than the raw string representation
270
- rawStrLength = len(str(value_arrayTarget.tolist()))
271
- encodedLength = len(resultRLE)
272
- assert encodedLength <= rawStrLength, f"Encoded string ({encodedLength}) should be shorter than raw string ({rawStrLength})"
273
-
274
- @pytest.mark.parametrize("description,addSpaces", [
275
- ("With spaces", True),
276
- ("Without spaces", False),
277
- ], ids=lambda x: x if isinstance(x, str) else "")
278
- def testAutoDecodingRLEWithSpaces(description: str, addSpaces: bool) -> None:
279
- """Test that the addSpaces parameter affects the internal comparison logic.
280
-
281
- Note: addSpaces doesn't directly change the output format, it just changes
282
- the comparison when measuring the length of the string representation.
283
- The feature exists because `ast` inserts spaces in its string representation.
284
- """
285
- # Create a pattern that has repeated sequences to trigger the RLE logic
286
- arrayTarget = generateRepeatingZones((10, 10), 2)
287
-
288
- # Test both configurations
289
- resultWithSpacesFlag = autoDecodingRLE(arrayTarget, assumeAddSpaces=addSpaces)
290
- resultNoSpacesFlag = autoDecodingRLE(arrayTarget, assumeAddSpaces=False)
291
-
292
- # When addSpaces=True, the internal length comparisons change
293
- # but the actual output format doesn't necessarily differ
294
- # Just verify the function runs without errors in both cases
295
- assert isinstance(resultWithSpacesFlag, str)
296
- assert isinstance(resultNoSpacesFlag, str)
297
-
298
- def testAutoDecodingRLELargeCartesianMapping() -> None:
299
- """Test autoDecodingRLE with a large (100x100) cartesian mapping."""
300
- dimensions = (100, 100)
301
-
302
- # Generate a large cartesian mapping with a complex pattern
303
- def complexFormula(x: int, y: int) -> int:
304
- return ((x * 17) % 11 + (y * 13) % 7) % 10
305
-
306
- arrayMapping = generateCartesianMapping(dimensions, complexFormula)
307
-
308
- # Verify the function works with large arrays
309
- resultRLE = autoDecodingRLE(arrayMapping)
310
-
311
- # The result should be a valid string representation
312
- assert isinstance(resultRLE, str)
313
- assert "[" in resultRLE
314
- assert "]" in resultRLE
315
-
316
- # The RLE encoding should be more compact than the raw representation
317
- rawStrLength = len(str(arrayMapping.tolist()))
318
- encodedLength = len(resultRLE)
319
- assert encodedLength <= rawStrLength, f"RLE encoded string ({encodedLength}) should be shorter than raw string ({rawStrLength})"
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( "source, identifier, expected", [ ("def fibonacciNumber():\n return 13\n", "fibonacciNumber", 13), ("prime = 17\n", "prime", 17), ] )
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
- """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
- """
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.1.2
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,8 @@
1
1
  charset_normalizer
2
2
  more_itertools
3
3
  numpy
4
+
5
+ [:python_version < "3.14"]
4
6
  python_minifier
5
7
 
6
8
  [development]
@@ -1,4 +1,3 @@
1
1
  citations
2
2
  dist
3
3
  hunterMakesPy
4
- tests
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "hunterMakesPy"
3
- version = "0.1.2"
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