hunterMakesPy 0.1.2__py3-none-any.whl → 0.2.1__py3-none-any.whl
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/__init__.py +7 -2
- hunterMakesPy/dataStructures.py +128 -125
- hunterMakesPy/filesystemToolkit.py +17 -9
- hunterMakesPy/parseParameters.py +29 -29
- hunterMakesPy/pytestForYourUse.py +5 -322
- hunterMakesPy/tests/__init__.py +5 -0
- {tests → hunterMakesPy/tests}/conftest.py +32 -2
- {tests → hunterMakesPy/tests}/test_coping.py +1 -1
- {tests → hunterMakesPy/tests}/test_dataStructures.py +124 -118
- hunterMakesPy/tests/test_filesystemToolkit.py +263 -0
- hunterMakesPy/tests/test_parseParameters.py +339 -0
- {huntermakespy-0.1.2.dist-info → huntermakespy-0.2.1.dist-info}/METADATA +24 -27
- huntermakespy-0.2.1.dist-info/RECORD +20 -0
- {huntermakespy-0.1.2.dist-info → huntermakespy-0.2.1.dist-info}/top_level.txt +0 -1
- huntermakespy-0.1.2.dist-info/RECORD +0 -20
- tests/__init__.py +0 -0
- tests/test_filesystemToolkit.py +0 -43
- tests/test_parseParameters.py +0 -21
- {huntermakespy-0.1.2.dist-info → huntermakespy-0.2.1.dist-info}/WHEEL +0 -0
- {huntermakespy-0.1.2.dist-info → huntermakespy-0.2.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
# pyright: standard
|
|
2
|
+
from collections.abc import Callable, Iterable, Iterator
|
|
3
|
+
from hunterMakesPy import defineConcurrencyLimit, intInnit, oopsieKwargsie
|
|
4
|
+
from typing import Any, NoReturn, ParamSpec, TypeVar
|
|
5
|
+
from unittest.mock import Mock, patch
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
parameters = ParamSpec('parameters')
|
|
9
|
+
returnType = TypeVar('returnType')
|
|
10
|
+
|
|
11
|
+
def PytestFor_defineConcurrencyLimit(callableToTest: Callable[..., int] = defineConcurrencyLimit, cpuCount: int = 8) -> list[tuple[str, Callable[[], None]]]: # noqa: C901
|
|
12
|
+
"""Return a list of test functions to validate concurrency limit behavior.
|
|
13
|
+
|
|
14
|
+
This function provides a comprehensive test suite for validating concurrency limit parsing
|
|
15
|
+
and computation, checking both valid and invalid input scenarios.
|
|
16
|
+
|
|
17
|
+
Parameters
|
|
18
|
+
----------
|
|
19
|
+
callableToTest : Callable[[bool | float | int | None, int], int] = defineConcurrencyLimit
|
|
20
|
+
The function to test, which should accept various input types and return an integer
|
|
21
|
+
representing the concurrency limit.
|
|
22
|
+
cpuCount : int = 8
|
|
23
|
+
The number of CPUs to simulate in the test environment.
|
|
24
|
+
|
|
25
|
+
Returns
|
|
26
|
+
-------
|
|
27
|
+
listOfTestFunctions : list[tuple[str, Callable[[], None]]]
|
|
28
|
+
A list of tuples, each containing a string describing the test case and a callable
|
|
29
|
+
test function that implements the test case.
|
|
30
|
+
|
|
31
|
+
Examples
|
|
32
|
+
--------
|
|
33
|
+
Run each test on `hunterMakesPy.defineConcurrencyLimit`:
|
|
34
|
+
```python
|
|
35
|
+
from hunterMakesPy.pytestForYourUse import PytestFor_defineConcurrencyLimit
|
|
36
|
+
|
|
37
|
+
listOfTests = PytestFor_defineConcurrencyLimit()
|
|
38
|
+
for nameOfTest, callablePytest in listOfTests:
|
|
39
|
+
callablePytest()
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Or, run each test on your function that has a compatible signature:
|
|
43
|
+
```python
|
|
44
|
+
from hunterMakesPy.pytestForYourUse import PytestFor_defineConcurrencyLimit
|
|
45
|
+
from packageLocal import functionLocal
|
|
46
|
+
|
|
47
|
+
@pytest.mark.parametrize("nameOfTest,callablePytest", PytestFor_defineConcurrencyLimit(callableToTest=functionLocal))
|
|
48
|
+
def test_functionLocal(nameOfTest, callablePytest):
|
|
49
|
+
callablePytest()
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
"""
|
|
53
|
+
@patch('multiprocessing.cpu_count', return_value=cpuCount)
|
|
54
|
+
def testDefaults(_mockCpu: Mock) -> None:
|
|
55
|
+
listOfParameters: list[bool | int | None] = [None, False, 0]
|
|
56
|
+
for limitParameter in listOfParameters:
|
|
57
|
+
assert callableToTest(limit=limitParameter, cpuTotal=cpuCount) == cpuCount
|
|
58
|
+
|
|
59
|
+
@patch('multiprocessing.cpu_count', return_value=cpuCount)
|
|
60
|
+
def testDirectIntegers(_mockCpu: Mock) -> None:
|
|
61
|
+
for limitParameter in [1, 4, 16]:
|
|
62
|
+
assert callableToTest(limit=limitParameter, cpuTotal=cpuCount) == limitParameter
|
|
63
|
+
|
|
64
|
+
@patch('multiprocessing.cpu_count', return_value=cpuCount)
|
|
65
|
+
def testFractionalFloats(_mockCpu: Mock) -> None:
|
|
66
|
+
testCases: dict[float, int] = {
|
|
67
|
+
0.5: cpuCount // 2,
|
|
68
|
+
0.25: cpuCount // 4,
|
|
69
|
+
0.75: int(cpuCount * 0.75)
|
|
70
|
+
}
|
|
71
|
+
for limit, expected in testCases.items():
|
|
72
|
+
assert callableToTest(limit=limit, cpuTotal=cpuCount) == expected
|
|
73
|
+
|
|
74
|
+
@patch('multiprocessing.cpu_count', return_value=cpuCount)
|
|
75
|
+
def testMinimumOne(_mockCpu: Mock) -> None:
|
|
76
|
+
listOfParameters: list[float | int] = [-10, -0.99, 0.1]
|
|
77
|
+
for limitParameter in listOfParameters:
|
|
78
|
+
assert callableToTest(limit=limitParameter, cpuTotal=cpuCount) >= 1
|
|
79
|
+
|
|
80
|
+
@patch('multiprocessing.cpu_count', return_value=cpuCount)
|
|
81
|
+
def testBooleanTrue(_mockCpu: Mock) -> None:
|
|
82
|
+
assert callableToTest(limit=True, cpuTotal=cpuCount) == 1
|
|
83
|
+
assert callableToTest(limit='True', cpuTotal=cpuCount) == 1 # pyright: ignore[reportArgumentType]
|
|
84
|
+
assert callableToTest(limit='TRUE', cpuTotal=cpuCount) == 1 # pyright: ignore[reportArgumentType]
|
|
85
|
+
assert callableToTest(limit=' true ', cpuTotal=cpuCount) == 1 # pyright: ignore[reportArgumentType]
|
|
86
|
+
|
|
87
|
+
@patch('multiprocessing.cpu_count', return_value=cpuCount)
|
|
88
|
+
def testInvalidStrings(_mockCpu: Mock) -> None:
|
|
89
|
+
for stringInput in ["invalid", "True but not quite", "None of the above"]:
|
|
90
|
+
with pytest.raises(ValueError, match="must be a number, `True`, `False`, or `None`"):
|
|
91
|
+
callableToTest(limit=stringInput, cpuTotal=cpuCount) # pyright: ignore[reportArgumentType]
|
|
92
|
+
|
|
93
|
+
@patch('multiprocessing.cpu_count', return_value=cpuCount)
|
|
94
|
+
def testStringNumbers(_mockCpu: Mock) -> None:
|
|
95
|
+
testCases: list[tuple[str, int]] = [
|
|
96
|
+
("1.51", 2),
|
|
97
|
+
("-2.51", 5),
|
|
98
|
+
("4", 4),
|
|
99
|
+
("0.5", 4),
|
|
100
|
+
("-0.25", 6),
|
|
101
|
+
]
|
|
102
|
+
for stringNumber, expectedLimit in testCases:
|
|
103
|
+
assert callableToTest(limit=stringNumber, cpuTotal=cpuCount) == expectedLimit # pyright: ignore[reportArgumentType]
|
|
104
|
+
|
|
105
|
+
return [
|
|
106
|
+
('testDefaults', testDefaults),
|
|
107
|
+
('testDirectIntegers', testDirectIntegers),
|
|
108
|
+
('testFractionalFloats', testFractionalFloats),
|
|
109
|
+
('testMinimumOne', testMinimumOne),
|
|
110
|
+
('testBooleanTrue', testBooleanTrue),
|
|
111
|
+
('testInvalidStrings', testInvalidStrings),
|
|
112
|
+
('testStringNumbers', testStringNumbers)
|
|
113
|
+
]
|
|
114
|
+
|
|
115
|
+
def PytestFor_intInnit(callableToTest: Callable[[Iterable[int], str | None, type[Any] | None], list[int]] = intInnit) -> list[tuple[str, Callable[[], None]]]: # noqa: C901
|
|
116
|
+
"""Return a list of test functions to validate integer initialization behavior.
|
|
117
|
+
|
|
118
|
+
This function provides a comprehensive test suite for validating integer parsing
|
|
119
|
+
and initialization, checking both valid and invalid input scenarios.
|
|
120
|
+
|
|
121
|
+
Parameters
|
|
122
|
+
----------
|
|
123
|
+
callableToTest : Callable[[Iterable[int], str | None, type[Any] | None], list[int]] = intInnit
|
|
124
|
+
The function to test. Should accept a sequence of integer-compatible values,
|
|
125
|
+
an optional parameter name string, and an optional parameter type.
|
|
126
|
+
Returns a list of validated integers.
|
|
127
|
+
|
|
128
|
+
Returns
|
|
129
|
+
-------
|
|
130
|
+
listOfTestFunctions : list[tuple[str, Callable[[], None]]]
|
|
131
|
+
A list of tuples containing a string describing the test case and a callable
|
|
132
|
+
test function implementing the test case.
|
|
133
|
+
|
|
134
|
+
Examples
|
|
135
|
+
--------
|
|
136
|
+
Run tests on `hunterMakesPy.intInnit`:
|
|
137
|
+
```python
|
|
138
|
+
from hunterMakesPy.pytestForYourUse import PytestFor_intInnit
|
|
139
|
+
|
|
140
|
+
listOfTests = PytestFor_intInnit()
|
|
141
|
+
for nameOfTest, callablePytest in listOfTests:
|
|
142
|
+
callablePytest()
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Run tests on your compatible function:
|
|
146
|
+
```python
|
|
147
|
+
from hunterMakesPy.pytestForYourUse import PytestFor_intInnit
|
|
148
|
+
from packageLocal import functionLocal
|
|
149
|
+
|
|
150
|
+
@pytest.mark.parametrize("nameOfTest,callablePytest",
|
|
151
|
+
PytestFor_intInnit(callableToTest=functionLocal))
|
|
152
|
+
def test_functionLocal(nameOfTest, callablePytest):
|
|
153
|
+
callablePytest()
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
"""
|
|
157
|
+
def testHandlesValidIntegers() -> None:
|
|
158
|
+
assert callableToTest([2, 3, 5, 8], 'test', None) == [2, 3, 5, 8]
|
|
159
|
+
assert callableToTest([13.0, 21.0, 34.0], 'test', None) == [13, 21, 34] # pyright: ignore[reportArgumentType]
|
|
160
|
+
assert callableToTest(['55', '89', '144'], 'test', None) == [55, 89, 144] # pyright: ignore[reportArgumentType]
|
|
161
|
+
assert callableToTest([' 233 ', '377', '-610'], 'test', None) == [233, 377, -610] # pyright: ignore[reportArgumentType]
|
|
162
|
+
|
|
163
|
+
def testRejectsNonWholeNumbers() -> None:
|
|
164
|
+
listInvalidNumbers: list[float] = [13.7, 21.5, 34.8, -55.9]
|
|
165
|
+
for invalidNumber in listInvalidNumbers:
|
|
166
|
+
with pytest.raises(ValueError):
|
|
167
|
+
callableToTest([invalidNumber], 'test', None) # pyright: ignore[reportArgumentType]
|
|
168
|
+
|
|
169
|
+
def testRejectsBooleans() -> None:
|
|
170
|
+
with pytest.raises(TypeError):
|
|
171
|
+
callableToTest([True, False], 'test', None)
|
|
172
|
+
|
|
173
|
+
def testRejectsInvalidStrings() -> None:
|
|
174
|
+
for invalidString in ['NW', '', ' ', 'SE.SW']:
|
|
175
|
+
with pytest.raises(ValueError):
|
|
176
|
+
callableToTest([invalidString], 'test', None) # pyright: ignore[reportArgumentType]
|
|
177
|
+
|
|
178
|
+
def testRejectsEmptyList() -> None:
|
|
179
|
+
with pytest.raises(ValueError):
|
|
180
|
+
callableToTest([], 'test', None)
|
|
181
|
+
|
|
182
|
+
def testHandlesMixedValidTypes() -> None:
|
|
183
|
+
assert callableToTest([13, '21', 34.0], 'test', None) == [13, 21, 34] # pyright: ignore[reportArgumentType]
|
|
184
|
+
|
|
185
|
+
def testHandlesBytes() -> None:
|
|
186
|
+
validCases: list[tuple[list[bytes], str, list[int]]] = [
|
|
187
|
+
([b'123'], '123', [123]),
|
|
188
|
+
]
|
|
189
|
+
for inputData, testName, expected in validCases:
|
|
190
|
+
assert callableToTest(inputData, testName, None) == expected # pyright: ignore[reportArgumentType]
|
|
191
|
+
|
|
192
|
+
extendedCases: list[tuple[list[bytes], str, list[int]]] = [
|
|
193
|
+
([b'123456789'], '123456789', [123456789]),
|
|
194
|
+
]
|
|
195
|
+
for inputData, testName, expected in extendedCases:
|
|
196
|
+
assert callableToTest(inputData, testName, None) == expected # pyright: ignore[reportArgumentType]
|
|
197
|
+
|
|
198
|
+
invalidCases: list[list[bytes]] = [[b'\x00']]
|
|
199
|
+
for inputData in invalidCases:
|
|
200
|
+
with pytest.raises(ValueError):
|
|
201
|
+
callableToTest(inputData, 'test', None) # pyright: ignore[reportArgumentType]
|
|
202
|
+
|
|
203
|
+
def testHandlesMemoryview() -> None:
|
|
204
|
+
validCases: list[tuple[list[memoryview], str, list[int]]] = [
|
|
205
|
+
([memoryview(b'123')], '123', [123]),
|
|
206
|
+
]
|
|
207
|
+
for inputData, testName, expected in validCases:
|
|
208
|
+
assert callableToTest(inputData, testName, None) == expected # pyright: ignore[reportArgumentType]
|
|
209
|
+
|
|
210
|
+
largeMemoryviewCase: list[memoryview] = [memoryview(b'9999999999')]
|
|
211
|
+
assert callableToTest(largeMemoryviewCase, 'test', None) == [9999999999] # pyright: ignore[reportArgumentType]
|
|
212
|
+
|
|
213
|
+
invalidMemoryviewCases: list[list[memoryview]] = [[memoryview(b'\x00')]]
|
|
214
|
+
for inputData in invalidMemoryviewCases:
|
|
215
|
+
with pytest.raises(ValueError):
|
|
216
|
+
callableToTest(inputData, 'test', None) # pyright: ignore[reportArgumentType]
|
|
217
|
+
|
|
218
|
+
def testRejectsMutableSequence() -> None:
|
|
219
|
+
class MutableList(list[int]):
|
|
220
|
+
def __iter__(self) -> Iterator[int]:
|
|
221
|
+
self.append(89)
|
|
222
|
+
return super().__iter__()
|
|
223
|
+
with pytest.raises(RuntimeError, match=".*modified during iteration.*"):
|
|
224
|
+
callableToTest(MutableList([13, 21, 34]), 'test', None)
|
|
225
|
+
|
|
226
|
+
def testHandlesComplexIntegers() -> None:
|
|
227
|
+
testCases: list[tuple[list[complex], list[int]]] = [
|
|
228
|
+
([13+0j], [13]),
|
|
229
|
+
([21+0j, 34+0j], [21, 34])
|
|
230
|
+
]
|
|
231
|
+
for inputData, expected in testCases:
|
|
232
|
+
assert callableToTest(inputData, 'test', None) == expected # pyright: ignore[reportArgumentType]
|
|
233
|
+
|
|
234
|
+
def testRejectsInvalidComplex() -> None:
|
|
235
|
+
for invalidComplex in [13+1j, 21+0.5j, 34.5+0j]:
|
|
236
|
+
with pytest.raises(ValueError):
|
|
237
|
+
callableToTest([invalidComplex], 'test', None) # pyright: ignore[reportArgumentType]
|
|
238
|
+
|
|
239
|
+
return [
|
|
240
|
+
('testHandlesValidIntegers', testHandlesValidIntegers),
|
|
241
|
+
('testRejectsNonWholeNumbers', testRejectsNonWholeNumbers),
|
|
242
|
+
('testRejectsBooleans', testRejectsBooleans),
|
|
243
|
+
('testRejectsInvalidStrings', testRejectsInvalidStrings),
|
|
244
|
+
('testRejectsEmptyList', testRejectsEmptyList),
|
|
245
|
+
('testHandlesMixedValidTypes', testHandlesMixedValidTypes),
|
|
246
|
+
('testHandlesBytes', testHandlesBytes),
|
|
247
|
+
('testHandlesMemoryview', testHandlesMemoryview),
|
|
248
|
+
('testRejectsMutableSequence', testRejectsMutableSequence),
|
|
249
|
+
('testHandlesComplexIntegers', testHandlesComplexIntegers),
|
|
250
|
+
('testRejectsInvalidComplex', testRejectsInvalidComplex)
|
|
251
|
+
]
|
|
252
|
+
|
|
253
|
+
def PytestFor_oopsieKwargsie(callableToTest: Callable[[str], bool | None | str] = oopsieKwargsie) -> list[tuple[str, Callable[[], None]]]: # noqa: C901
|
|
254
|
+
"""Return a list of test functions to validate string-to-boolean/None conversion behavior.
|
|
255
|
+
|
|
256
|
+
This function provides a comprehensive test suite for validating string parsing and conversion
|
|
257
|
+
to boolean or None values, with fallback to the original string when appropriate.
|
|
258
|
+
|
|
259
|
+
Parameters
|
|
260
|
+
----------
|
|
261
|
+
callableToTest : Callable[[str], bool | None | str] = oopsieKwargsie
|
|
262
|
+
The function to test, which should accept a string and return either a boolean, None,
|
|
263
|
+
or the original input.
|
|
264
|
+
|
|
265
|
+
Returns
|
|
266
|
+
-------
|
|
267
|
+
listOfTestFunctions : list[tuple[str, Callable[[], None]]]
|
|
268
|
+
A list of tuples, each containing a string describing the test case and a callable
|
|
269
|
+
test function that implements the test case.
|
|
270
|
+
|
|
271
|
+
Examples
|
|
272
|
+
--------
|
|
273
|
+
Run each test on `hunterMakesPy.oopsieKwargsie`:
|
|
274
|
+
```python
|
|
275
|
+
from hunterMakesPy.pytestForYourUse import PytestFor_oopsieKwargsie
|
|
276
|
+
|
|
277
|
+
listOfTests = PytestFor_oopsieKwargsie()
|
|
278
|
+
for nameOfTest, callablePytest in listOfTests:
|
|
279
|
+
callablePytest()
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
Or, run each test on your function that has a compatible signature:
|
|
283
|
+
```python
|
|
284
|
+
from hunterMakesPy.pytestForYourUse import PytestFor_oopsieKwargsie
|
|
285
|
+
from packageLocal import functionLocal
|
|
286
|
+
|
|
287
|
+
@pytest.mark.parametrize("nameOfTest,callablePytest", PytestFor_oopsieKwargsie(callableToTest=functionLocal))
|
|
288
|
+
def test_functionLocal(nameOfTest, callablePytest):
|
|
289
|
+
callablePytest()
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
"""
|
|
293
|
+
def testHandlesTrueVariants() -> None:
|
|
294
|
+
for variantTrue in ['True', 'TRUE', ' true ', 'TrUe']:
|
|
295
|
+
assert callableToTest(variantTrue) is True
|
|
296
|
+
|
|
297
|
+
def testHandlesFalseVariants() -> None:
|
|
298
|
+
for variantFalse in ['False', 'FALSE', ' false ', 'FaLsE']:
|
|
299
|
+
assert callableToTest(variantFalse) is False
|
|
300
|
+
|
|
301
|
+
def testHandlesNoneVariants() -> None:
|
|
302
|
+
for variantNone in ['None', 'NONE', ' none ', 'NoNe']:
|
|
303
|
+
assert callableToTest(variantNone) is None
|
|
304
|
+
|
|
305
|
+
def testReturnsOriginalString() -> None:
|
|
306
|
+
for stringInput in ['hello', '123', 'True story', 'False alarm']:
|
|
307
|
+
assert callableToTest(stringInput) == stringInput
|
|
308
|
+
|
|
309
|
+
def testHandlesNonStringObjects() -> None:
|
|
310
|
+
class NeverGonnaStringIt:
|
|
311
|
+
def __str__(self) -> NoReturn:
|
|
312
|
+
message = "Cannot be stringified"
|
|
313
|
+
raise TypeError(message)
|
|
314
|
+
|
|
315
|
+
assert callableToTest(123) == "123" # pyright: ignore[reportArgumentType]
|
|
316
|
+
|
|
317
|
+
neverGonnaStringIt = NeverGonnaStringIt()
|
|
318
|
+
result = callableToTest(neverGonnaStringIt) # pyright: ignore[reportArgumentType]
|
|
319
|
+
assert result is neverGonnaStringIt
|
|
320
|
+
|
|
321
|
+
return [
|
|
322
|
+
('testHandlesTrueVariants', testHandlesTrueVariants),
|
|
323
|
+
('testHandlesFalseVariants', testHandlesFalseVariants),
|
|
324
|
+
('testHandlesNoneVariants', testHandlesNoneVariants),
|
|
325
|
+
('testReturnsOriginalString', testReturnsOriginalString),
|
|
326
|
+
('testHandlesNonStringObjects', testHandlesNonStringObjects)
|
|
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
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: Easy Python functions making making functional Python functions easier.
|
|
5
5
|
Author-email: Hunter Hogan <HunterHogan@pm.me>
|
|
6
6
|
License: 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"
|
|
@@ -73,7 +74,7 @@ def findConfiguration(configName: str) -> dict[str, str] | None:
|
|
|
73
74
|
|
|
74
75
|
config = raiseIfNone(
|
|
75
76
|
findConfiguration("database"),
|
|
76
|
-
"Configuration 'database'
|
|
77
|
+
"I could not find Configuration 'database', but I need it to continue."
|
|
77
78
|
)
|
|
78
79
|
```
|
|
79
80
|
|
|
@@ -82,19 +83,19 @@ config = raiseIfNone(
|
|
|
82
83
|
Parameter validation, integer parsing, and concurrency handling.
|
|
83
84
|
|
|
84
85
|
```python
|
|
85
|
-
|
|
86
|
+
import hunterMakesPy as humpy
|
|
86
87
|
|
|
87
88
|
# Smart concurrency limit calculation
|
|
88
|
-
cpuLimit = defineConcurrencyLimit(limit=0.75) # Use 75% of available CPUs
|
|
89
|
-
cpuLimit = defineConcurrencyLimit(limit=True) # Use exactly 1 CPU
|
|
90
|
-
cpuLimit = defineConcurrencyLimit(limit=4) # Use exactly 4 CPUs
|
|
89
|
+
cpuLimit = humpy.defineConcurrencyLimit(limit=0.75) # Use 75% of available CPUs
|
|
90
|
+
cpuLimit = humpy.defineConcurrencyLimit(limit=True) # Use exactly 1 CPU
|
|
91
|
+
cpuLimit = humpy.defineConcurrencyLimit(limit=4) # Use exactly 4 CPUs
|
|
91
92
|
|
|
92
93
|
# Robust integer validation
|
|
93
|
-
validatedIntegers = intInnit([1, "2", 3.0, "4"], "port_numbers")
|
|
94
|
+
validatedIntegers = humpy.intInnit([1, "2", 3.0, "4"], "port_numbers")
|
|
94
95
|
|
|
95
96
|
# String-to-boolean conversion for configuration
|
|
96
97
|
userInput = "True"
|
|
97
|
-
booleanValue = oopsieKwargsie(userInput) # Returns True
|
|
98
|
+
booleanValue = humpy.oopsieKwargsie(userInput) # Returns True
|
|
98
99
|
```
|
|
99
100
|
|
|
100
101
|
## File System Utilities
|
|
@@ -102,20 +103,15 @@ booleanValue = oopsieKwargsie(userInput) # Returns True
|
|
|
102
103
|
Safe file operations and dynamic module importing.
|
|
103
104
|
|
|
104
105
|
```python
|
|
105
|
-
|
|
106
|
-
importLogicalPath2Identifier,
|
|
107
|
-
importPathFilename2Identifier,
|
|
108
|
-
makeDirsSafely,
|
|
109
|
-
writeStringToHere
|
|
110
|
-
)
|
|
106
|
+
import hunterMakesPy as humpy
|
|
111
107
|
|
|
112
108
|
# Dynamic imports
|
|
113
|
-
gcdFunction = importLogicalPath2Identifier("math", "gcd")
|
|
114
|
-
customFunction = importPathFilename2Identifier("path/to/module.py", "functionName")
|
|
109
|
+
gcdFunction = humpy.importLogicalPath2Identifier("math", "gcd")
|
|
110
|
+
customFunction = humpy.importPathFilename2Identifier("path/to/module.py", "functionName")
|
|
115
111
|
|
|
116
112
|
# Safe file operations
|
|
117
113
|
pathFilename = Path("deep/nested/directory/file.txt")
|
|
118
|
-
writeStringToHere("content", pathFilename) # Creates directories automatically
|
|
114
|
+
humpy.writeStringToHere("content", pathFilename) # Creates directories automatically
|
|
119
115
|
```
|
|
120
116
|
|
|
121
117
|
## Data Structure Manipulation
|
|
@@ -123,21 +119,21 @@ writeStringToHere("content", pathFilename) # Creates directories automatically
|
|
|
123
119
|
Utilities for string extraction, data flattening, and array compression.
|
|
124
120
|
|
|
125
121
|
```python
|
|
126
|
-
|
|
122
|
+
import hunterMakesPy as humpy
|
|
127
123
|
import numpy
|
|
128
124
|
|
|
129
125
|
# Extract all strings from nested data structures
|
|
130
126
|
nestedData = {"config": [1, "host", {"port": 8080}], "users": ["alice", "bob"]}
|
|
131
|
-
allStrings = stringItUp(nestedData) # ['config', 'host', 'port', 'users', 'alice', 'bob']
|
|
127
|
+
allStrings = humpy.stringItUp(nestedData) # ['config', 'host', 'port', 'users', 'alice', 'bob']
|
|
132
128
|
|
|
133
129
|
# Merge dictionaries containing lists
|
|
134
|
-
dictionaryAlpha = {"servers": ["
|
|
135
|
-
dictionaryBeta = {"servers": ["
|
|
136
|
-
merged = updateExtendPolishDictionaryLists(dictionaryAlpha, dictionaryBeta, destroyDuplicates=True)
|
|
130
|
+
dictionaryAlpha = {"servers": ["chicago", "tokyo"], "databases": ["elm"]}
|
|
131
|
+
dictionaryBeta = {"servers": ["mumbai"], "databases": ["oak", "cedar"]}
|
|
132
|
+
merged = humpy.updateExtendPolishDictionaryLists(dictionaryAlpha, dictionaryBeta, destroyDuplicates=True)
|
|
137
133
|
|
|
138
134
|
# Compress NumPy arrays with run-length encoding
|
|
139
135
|
arrayData = numpy.array([1, 2, 3, 4, 5, 5, 5, 6, 7, 8, 9])
|
|
140
|
-
compressed = autoDecodingRLE(arrayData) # "[1,*range(2,6)]+[5]*2+[*range(6,10)]"
|
|
136
|
+
compressed = humpy.autoDecodingRLE(arrayData) # "[1,*range(2,6)]+[5]*2+[*range(6,10)]"
|
|
141
137
|
```
|
|
142
138
|
|
|
143
139
|
## Testing
|
|
@@ -145,7 +141,7 @@ compressed = autoDecodingRLE(arrayData) # "[1,*range(2,6)]+[5]*2+[*range(6,10)]
|
|
|
145
141
|
The package includes comprehensive test suites that you can import and run:
|
|
146
142
|
|
|
147
143
|
```python
|
|
148
|
-
from hunterMakesPy.
|
|
144
|
+
from hunterMakesPy.tests.test_parseParameters import (
|
|
149
145
|
PytestFor_defineConcurrencyLimit,
|
|
150
146
|
PytestFor_intInnit,
|
|
151
147
|
PytestFor_oopsieKwargsie
|
|
@@ -157,8 +153,9 @@ for nameOfTest, callablePytest in listOfTests:
|
|
|
157
153
|
callablePytest()
|
|
158
154
|
|
|
159
155
|
# Or test your own compatible functions
|
|
160
|
-
@pytest.mark.parametrize(
|
|
161
|
-
|
|
156
|
+
@pytest.mark.parametrize(
|
|
157
|
+
"nameOfTest,callablePytest"
|
|
158
|
+
, PytestFor_intInnit(callableToTest=myFunction))
|
|
162
159
|
def test_myFunction(nameOfTest, callablePytest):
|
|
163
160
|
callablePytest()
|
|
164
161
|
```
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
hunterMakesPy/__init__.py,sha256=bVF1F2Mdo5AOiioEfxKvNrnsa3vCFI16eMK7Oy5O5TU,1450
|
|
2
|
+
hunterMakesPy/_theSSOT.py,sha256=lkLOG3oTIWNKD_ULX55chlUGNqCHgqVIrBvolvK1vbQ,153
|
|
3
|
+
hunterMakesPy/coping.py,sha256=covqNFAwkF9gjafrlAvMdtCO8haFsESQBhO-7s68qSg,5581
|
|
4
|
+
hunterMakesPy/dataStructures.py,sha256=CFyGjmAOoN2MoEPwWwWdwKNXmOXX8kCS3ttMOa2Rsx0,11379
|
|
5
|
+
hunterMakesPy/filesystemToolkit.py,sha256=jd7H5UtrIrPiCYWcvNBNa6DAy-2Ewcf21-jbGXg_IVI,4702
|
|
6
|
+
hunterMakesPy/parseParameters.py,sha256=DXpyATx7trGBg_8jlO5dXGfLcG8vg_lU3R1sq1rRFOQ,11644
|
|
7
|
+
hunterMakesPy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
hunterMakesPy/pytestForYourUse.py,sha256=GiN1C1gTTM0ZunRPEMnrKlLQLMdH0wF_ZGr_RPgRjjA,500
|
|
9
|
+
hunterMakesPy/theTypes.py,sha256=C2d0uLn1VIx6_2CK41it3IP7iplSQqe51tzWc-RT320,306
|
|
10
|
+
hunterMakesPy/tests/__init__.py,sha256=C_FzfKDi_VrGVxlenWHyOYtKShAKlt3KW14jeRx1mQI,224
|
|
11
|
+
hunterMakesPy/tests/conftest.py,sha256=NZQPRiwvGhP16hJ6WGGm9eKLxfQArYV8E9X12YzSpP0,2827
|
|
12
|
+
hunterMakesPy/tests/test_coping.py,sha256=mH89TUAL6fJanBLlhdVlCNNQqm5OpdcQMP_p5W2JJwo,9860
|
|
13
|
+
hunterMakesPy/tests/test_dataStructures.py,sha256=O4aqzSKg7KfWWVhIewOH0Y8Zj28PbFCb4XX3xhwuFQA,16605
|
|
14
|
+
hunterMakesPy/tests/test_filesystemToolkit.py,sha256=q2voXjCbQPIT8l8VF9iuWX1Bs2ZieABItWoVkITj_fo,8841
|
|
15
|
+
hunterMakesPy/tests/test_parseParameters.py,sha256=80npsoWcCackjxvoW2dMXMpHeale7fuRXyXp78MibLs,14037
|
|
16
|
+
huntermakespy-0.2.1.dist-info/licenses/LICENSE,sha256=NxH5Y8BdC-gNU-WSMwim3uMbID2iNDXJz7fHtuTdXhk,19346
|
|
17
|
+
huntermakespy-0.2.1.dist-info/METADATA,sha256=U7mdPv2TiV-JgdBigReM9zxA1N9uZ0Nb93fmASS1-bk,6501
|
|
18
|
+
huntermakespy-0.2.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
19
|
+
huntermakespy-0.2.1.dist-info/top_level.txt,sha256=Uh4bj8EDTdsRpqY1VlK_his_B4HDfZ6Tqrwhoj75P_w,14
|
|
20
|
+
huntermakespy-0.2.1.dist-info/RECORD,,
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
hunterMakesPy/__init__.py,sha256=4gIm--UJmlQEhbLCqpiVFM_JVbyfVpvFFAJyMNFHekk,1329
|
|
2
|
-
hunterMakesPy/_theSSOT.py,sha256=lkLOG3oTIWNKD_ULX55chlUGNqCHgqVIrBvolvK1vbQ,153
|
|
3
|
-
hunterMakesPy/coping.py,sha256=covqNFAwkF9gjafrlAvMdtCO8haFsESQBhO-7s68qSg,5581
|
|
4
|
-
hunterMakesPy/dataStructures.py,sha256=znMEnboo2iNXZtQKdmruRrJDEoeTfqoH4-coUDM75g4,11218
|
|
5
|
-
hunterMakesPy/filesystemToolkit.py,sha256=vDYS0Rc1aP7ETLXHKoO3TfOpuwJQFQW2ybwtJNiIIo4,4305
|
|
6
|
-
hunterMakesPy/parseParameters.py,sha256=plrJ4xR1FQnQR9j-oeMAwB2H9r_8QD8OALnw0OH8Kt0,11947
|
|
7
|
-
hunterMakesPy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
-
hunterMakesPy/pytestForYourUse.py,sha256=21BI8TXyinf8rMmS6dTsuRMBOOFTcyoG_gD__Gz_e7Q,13288
|
|
9
|
-
hunterMakesPy/theTypes.py,sha256=C2d0uLn1VIx6_2CK41it3IP7iplSQqe51tzWc-RT320,306
|
|
10
|
-
huntermakespy-0.1.2.dist-info/licenses/LICENSE,sha256=NxH5Y8BdC-gNU-WSMwim3uMbID2iNDXJz7fHtuTdXhk,19346
|
|
11
|
-
tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
-
tests/conftest.py,sha256=MkNc4Ar6yYrnAok4t1NWsgyOYFDiGo1qwXHyp8ChJf4,1672
|
|
13
|
-
tests/test_coping.py,sha256=MQl0fhdWX2YSFzzjWMThSm_ZSnglE3anSJO2b33typU,9846
|
|
14
|
-
tests/test_dataStructures.py,sha256=VhM3VzG1l8l3Iz1q9sQjpNBQTWSrl4zWbnefsGTccos,16406
|
|
15
|
-
tests/test_filesystemToolkit.py,sha256=jk6Ke0fW6dlHIVyvA1lh7o7_I7DVZr6uMO3s_i43isQ,2445
|
|
16
|
-
tests/test_parseParameters.py,sha256=bHD-O-OyWWwPOx3zvS53pSNPDm3LbFS05N5JcfNCLG4,1137
|
|
17
|
-
huntermakespy-0.1.2.dist-info/METADATA,sha256=b1Xr8jhpu8t-H3BTVQUcCww_gG8q5fcb1REz-8AQbY8,6555
|
|
18
|
-
huntermakespy-0.1.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
19
|
-
huntermakespy-0.1.2.dist-info/top_level.txt,sha256=dUy7z3LNO6aqNjPD81tZjE5N9HO70a14Y9lAWjWC2gA,20
|
|
20
|
-
huntermakespy-0.1.2.dist-info/RECORD,,
|
tests/__init__.py
DELETED
|
File without changes
|
tests/test_filesystemToolkit.py
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
# pyright: standard
|
|
2
|
-
from hunterMakesPy import importLogicalPath2Identifier, importPathFilename2Identifier, makeDirsSafely, writeStringToHere
|
|
3
|
-
from tests.conftest import uniformTestFailureMessage
|
|
4
|
-
import io
|
|
5
|
-
import math
|
|
6
|
-
import os
|
|
7
|
-
import pathlib
|
|
8
|
-
import pytest
|
|
9
|
-
|
|
10
|
-
def testMakeDirsSafelyCreatesParentDirectories(pathTmpTesting: pathlib.Path) -> None:
|
|
11
|
-
nestedDirectory = pathTmpTesting / "sub1" / "sub2"
|
|
12
|
-
filePath = nestedDirectory / "dummy.txt"
|
|
13
|
-
makeDirsSafely(filePath)
|
|
14
|
-
assert nestedDirectory.exists() and nestedDirectory.is_dir(), uniformTestFailureMessage(True, nestedDirectory.exists() and nestedDirectory.is_dir(), "testMakeDirsSafelyCreatesParentDirectories", filePath)
|
|
15
|
-
|
|
16
|
-
def testMakeDirsSafelyWithIOBaseDoesNotRaise() -> None:
|
|
17
|
-
memoryStream = io.StringIO()
|
|
18
|
-
makeDirsSafely(memoryStream)
|
|
19
|
-
|
|
20
|
-
def testWriteStringToHereCreatesFileAndWritesContent(pathTmpTesting: pathlib.Path) -> None:
|
|
21
|
-
nestedDirectory = pathTmpTesting / "a" / "b"
|
|
22
|
-
filePath = nestedDirectory / "test.txt"
|
|
23
|
-
writeStringToHere("hello world", filePath)
|
|
24
|
-
assert filePath.exists(), uniformTestFailureMessage(True, filePath.exists(), "testWriteStringToHereCreatesFileAndWritesContent", filePath)
|
|
25
|
-
assert filePath.read_text(encoding="utf-8") == "hello world", uniformTestFailureMessage("hello world", filePath.read_text(encoding="utf-8"), "testWriteStringToHereCreatesFileAndWritesContent", filePath)
|
|
26
|
-
|
|
27
|
-
@pytest.mark.parametrize(
|
|
28
|
-
"moduleName, identifier, expectedType",
|
|
29
|
-
[("math", "gcd", type(math.gcd)),("os.path", "join", type(os.path.join))])
|
|
30
|
-
def testImportLogicalPath2Identifier(moduleName: str, identifier: str, expectedType: type) -> None:
|
|
31
|
-
imported = importLogicalPath2Identifier(moduleName, identifier)
|
|
32
|
-
assert isinstance(imported, expectedType), uniformTestFailureMessage(expectedType, type(imported), "testImportLogicalPath2Identifier", (moduleName, identifier))
|
|
33
|
-
|
|
34
|
-
@pytest.mark.parametrize( "source, identifier, expected", [ ("def fibonacciNumber():\n return 13\n", "fibonacciNumber", 13), ("prime = 17\n", "prime", 17), ] )
|
|
35
|
-
def testImportPathFilename2Identifier(tmp_path: pathlib.Path, source: str, identifier: str, expected: object) -> None:
|
|
36
|
-
filePath = tmp_path / "moduleTest.py"
|
|
37
|
-
filePath.write_text(source)
|
|
38
|
-
imported = importPathFilename2Identifier(filePath, identifier)
|
|
39
|
-
if callable(imported):
|
|
40
|
-
actual = imported()
|
|
41
|
-
else:
|
|
42
|
-
actual = imported
|
|
43
|
-
assert actual == expected, uniformTestFailureMessage(expected, actual, "testImportPathFilename2Identifier", (filePath, identifier))
|
tests/test_parseParameters.py
DELETED
|
@@ -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
|