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