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
|
@@ -2,13 +2,17 @@
|
|
|
2
2
|
from collections.abc import Callable, Iterable, Iterator
|
|
3
3
|
from decimal import Decimal
|
|
4
4
|
from fractions import Fraction
|
|
5
|
-
from hunterMakesPy import
|
|
5
|
+
from hunterMakesPy import stringItUp, updateExtendPolishDictionaryLists
|
|
6
|
+
from hunterMakesPy.tests.conftest import standardizedEqualTo
|
|
6
7
|
from numpy.typing import NDArray
|
|
7
|
-
from tests.conftest import standardizedEqualTo
|
|
8
8
|
from typing import Any, Literal
|
|
9
9
|
import datetime
|
|
10
10
|
import numpy
|
|
11
11
|
import pytest
|
|
12
|
+
import sys
|
|
13
|
+
|
|
14
|
+
if sys.version_info < (3, 14):
|
|
15
|
+
from hunterMakesPy import autoDecodingRLE
|
|
12
16
|
|
|
13
17
|
class CustomIterable:
|
|
14
18
|
def __init__(self, items: Iterable[Any]) -> None: self.items = items
|
|
@@ -81,7 +85,7 @@ def testStringItUpErrorCases(description: Literal['Memory view'], value_scrapPil
|
|
|
81
85
|
def testUpdateExtendPolishDictionaryLists(description: str, value_dictionaryLists: dict[str, Any], keywordArguments: dict[str, Any], expected: dict[str, Any] | type[TypeError]) -> None:
|
|
82
86
|
standardizedEqualTo(expected, updateExtendPolishDictionaryLists, *value_dictionaryLists, **keywordArguments)
|
|
83
87
|
# ruff: noqa: ERA001
|
|
84
|
-
# NOTE one line of code with `standardizedEqualTo` replaced the following ten lines of code.
|
|
88
|
+
# NOTE one line of code with `standardizedEqualTo` replaced the following ten lines of code. Use `standardizedEqualTo`.
|
|
85
89
|
# if isinstance(expected, type) and issubclass(expected, Exception):
|
|
86
90
|
# with pytest.raises(expected):
|
|
87
91
|
# updateExtendPolishDictionaryLists(*value_dictionaryLists, **keywordArguments)
|
|
@@ -94,26 +98,28 @@ def testUpdateExtendPolishDictionaryLists(description: str, value_dictionaryList
|
|
|
94
98
|
# assert result == expected
|
|
95
99
|
|
|
96
100
|
# ruff: noqa: RUF005
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
("
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
101
|
+
|
|
102
|
+
if sys.version_info < (3, 14):
|
|
103
|
+
@pytest.mark.parametrize("description,value_arrayTarget,expected", [
|
|
104
|
+
("One range", numpy.array(list(range(50,60))), "[*range(50,60)]"),
|
|
105
|
+
("Value, range", numpy.array([123]+list(range(71,81))), "[123,*range(71,81)]"),
|
|
106
|
+
("range, value", numpy.array(list(range(91,97))+[101]), "[*range(91,97),101]"),
|
|
107
|
+
("Value, range, value", numpy.array([151]+list(range(163,171))+[181]), "[151,*range(163,171),181]"),
|
|
108
|
+
("Repeat values", numpy.array([191, 191, 191]), "[191]*3"),
|
|
109
|
+
("Value with repeat", numpy.array([211, 223, 223, 223]), "[211]+[223]*3"),
|
|
110
|
+
("Range with repeat", numpy.array(list(range(251,257))+[271, 271, 271]), "[*range(251,257)]+[271]*3"),
|
|
111
|
+
("Value, range, repeat", numpy.array([281]+list(range(291,297))+[307, 307]), "[281,*range(291,297)]+[307]*2"),
|
|
112
|
+
("repeat, value", numpy.array([313, 313, 313, 331, 331, 349]), "[313]*3+[331]*2+[349]"),
|
|
113
|
+
("repeat, range", numpy.array([373, 373, 373]+list(range(383,389))), "[373]*3+[*range(383,389)]"),
|
|
114
|
+
("repeat, range, value", numpy.array(7*[401]+list(range(409,415))+[421]), "[401]*7+[*range(409,415),421]"),
|
|
115
|
+
("Repeated primes", numpy.array([431, 431, 431, 443, 443, 457]), "[431]*3+[443]*2+[457]"),
|
|
116
|
+
("Two Ranges", numpy.array(list(range(461,471))+list(range(479,487))), "[*range(461,471),*range(479,487)]"),
|
|
117
|
+
("2D array primes", numpy.array([[491, 499, 503], [509, 521, 523]]), "[[491,499,503],[509,521,523]]"),
|
|
118
|
+
("3D array primes", numpy.array([[[541, 547], [557, 563]], [[569, 571], [577, 587]]]), "[[[541,547],[557,563]],[[569,571],[577,587]]]"),
|
|
119
|
+
], ids=lambda x: x if isinstance(x, str) else "")
|
|
120
|
+
def testAutoDecodingRLE(description: str, value_arrayTarget: NDArray[numpy.integer[Any]], expected: str) -> None:
|
|
121
|
+
"""Test autoDecodingRLE with various input arrays."""
|
|
122
|
+
standardizedEqualTo(expected, autoDecodingRLE, value_arrayTarget)
|
|
117
123
|
|
|
118
124
|
# Helper functions for generating RLE test data
|
|
119
125
|
def generateCartesianMapping(dimensions: tuple[int, int], formula: Callable[[int, int], int]) -> NDArray[Any]:
|
|
@@ -222,98 +228,98 @@ def generateAlternatingColumns(dimensions: tuple[int, int], blockSize: int = 1)
|
|
|
222
228
|
|
|
223
229
|
return generateCartesianMapping(dimensions, columnFormula)
|
|
224
230
|
|
|
225
|
-
|
|
226
|
-
@pytest.mark.parametrize("description,value_arrayTarget", [
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
], ids=lambda x: x if isinstance(x, str) else "")
|
|
257
|
-
def testAutoDecodingRLEWithRealisticData(description: str, value_arrayTarget: NDArray[numpy.integer[Any]]) -> None:
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
@pytest.mark.parametrize("description,addSpaces", [
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
], ids=lambda x: x if isinstance(x, str) else "")
|
|
278
|
-
def testAutoDecodingRLEWithSpaces(description: str, addSpaces: bool) -> None:
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
def testAutoDecodingRLELargeCartesianMapping() -> None:
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
231
|
+
if sys.version_info < (3, 14):
|
|
232
|
+
@pytest.mark.parametrize("description,value_arrayTarget", [
|
|
233
|
+
# Basic test cases with simple patterns
|
|
234
|
+
("Simple range", numpy.array(list(range(50,60)))),
|
|
235
|
+
|
|
236
|
+
# Chessboard patterns
|
|
237
|
+
("Small chessboard", generateChessboard((8, 8))),
|
|
238
|
+
|
|
239
|
+
# Alternating columns - creates patterns with good RLE opportunities
|
|
240
|
+
("Alternating columns", generateAlternatingColumns((5, 20), 2)),
|
|
241
|
+
|
|
242
|
+
# Step pattern - creates horizontal runs
|
|
243
|
+
("Step pattern", generateStepPattern((6, 30), 3)),
|
|
244
|
+
|
|
245
|
+
# Repeating zones - creates horizontal bands
|
|
246
|
+
("Repeating zones", generateRepeatingZones((40, 40), 8)),
|
|
247
|
+
|
|
248
|
+
# Tile pattern - creates complex repeating regions
|
|
249
|
+
("Tile pattern", generateTilePattern((15, 15), 5)),
|
|
250
|
+
|
|
251
|
+
# Signed quadratic function - includes negative values
|
|
252
|
+
("Signed quadratic", generateSignedQuadraticFunction((10, 10))),
|
|
253
|
+
|
|
254
|
+
# Prime modulo matrix - periodic patterns
|
|
255
|
+
("Prime modulo", generatePrimeModuloMatrix((12, 12), 7)),
|
|
256
|
+
|
|
257
|
+
# Wave pattern - smooth gradients
|
|
258
|
+
("Wave pattern", generateWavePattern((20, 20))),
|
|
259
|
+
|
|
260
|
+
# Spiral pattern - complex pattern with good RLE potential
|
|
261
|
+
("Spiral pattern", generateSpiralPattern((15, 15), 2)),
|
|
262
|
+
], ids=lambda x: x if isinstance(x, str) else "")
|
|
263
|
+
def testAutoDecodingRLEWithRealisticData(description: str, value_arrayTarget: NDArray[numpy.integer[Any]]) -> None:
|
|
264
|
+
"""Test autoDecodingRLE with more realistic data patterns."""
|
|
265
|
+
# Here we test the function behavior rather than expected string output
|
|
266
|
+
resultRLE = autoDecodingRLE(value_arrayTarget)
|
|
267
|
+
|
|
268
|
+
# Test that the result is a valid string
|
|
269
|
+
assert isinstance(resultRLE, str)
|
|
270
|
+
|
|
271
|
+
# Test that the result contains the expected syntax elements
|
|
272
|
+
assert "[" in resultRLE, f"Result should contain list syntax: {resultRLE}"
|
|
273
|
+
assert "]" in resultRLE, f"Result should contain list syntax: {resultRLE}"
|
|
274
|
+
|
|
275
|
+
# Check that the result is more compact than the raw string representation
|
|
276
|
+
rawStrLength = len(str(value_arrayTarget.tolist()))
|
|
277
|
+
encodedLength = len(resultRLE)
|
|
278
|
+
assert encodedLength <= rawStrLength, f"Encoded string ({encodedLength}) should be shorter than raw string ({rawStrLength})"
|
|
279
|
+
|
|
280
|
+
@pytest.mark.parametrize("description,addSpaces", [
|
|
281
|
+
("With spaces", True),
|
|
282
|
+
("Without spaces", False),
|
|
283
|
+
], ids=lambda x: x if isinstance(x, str) else "")
|
|
284
|
+
def testAutoDecodingRLEWithSpaces(description: str, addSpaces: bool) -> None:
|
|
285
|
+
"""Test that the addSpaces parameter affects the internal comparison logic.
|
|
286
|
+
|
|
287
|
+
Note: addSpaces doesn't directly change the output format, it just changes
|
|
288
|
+
the comparison when measuring the length of the string representation.
|
|
289
|
+
The feature exists because `ast` inserts spaces in its string representation.
|
|
290
|
+
"""
|
|
291
|
+
# Create a pattern that has repeated sequences to trigger the RLE logic
|
|
292
|
+
arrayTarget = generateRepeatingZones((10, 10), 2)
|
|
293
|
+
|
|
294
|
+
# Test both configurations
|
|
295
|
+
resultWithSpacesFlag = autoDecodingRLE(arrayTarget, assumeAddSpaces=addSpaces)
|
|
296
|
+
resultNoSpacesFlag = autoDecodingRLE(arrayTarget, assumeAddSpaces=False)
|
|
297
|
+
|
|
298
|
+
# When addSpaces=True, the internal length comparisons change
|
|
299
|
+
# but the actual output format doesn't necessarily differ
|
|
300
|
+
# Just verify the function runs without errors in both cases
|
|
301
|
+
assert isinstance(resultWithSpacesFlag, str)
|
|
302
|
+
assert isinstance(resultNoSpacesFlag, str)
|
|
303
|
+
|
|
304
|
+
def testAutoDecodingRLELargeCartesianMapping() -> None:
|
|
305
|
+
"""Test autoDecodingRLE with a large (100x100) cartesian mapping."""
|
|
306
|
+
dimensions = (100, 100)
|
|
307
|
+
|
|
308
|
+
# Generate a large cartesian mapping with a complex pattern
|
|
309
|
+
def complexFormula(x: int, y: int) -> int:
|
|
310
|
+
return ((x * 17) % 11 + (y * 13) % 7) % 10
|
|
311
|
+
|
|
312
|
+
arrayMapping = generateCartesianMapping(dimensions, complexFormula)
|
|
313
|
+
|
|
314
|
+
# Verify the function works with large arrays
|
|
315
|
+
resultRLE = autoDecodingRLE(arrayMapping)
|
|
316
|
+
|
|
317
|
+
# The result should be a valid string representation
|
|
318
|
+
assert isinstance(resultRLE, str)
|
|
319
|
+
assert "[" in resultRLE
|
|
320
|
+
assert "]" in resultRLE
|
|
321
|
+
|
|
322
|
+
# The RLE encoding should be more compact than the raw representation
|
|
323
|
+
rawStrLength = len(str(arrayMapping.tolist()))
|
|
324
|
+
encodedLength = len(resultRLE)
|
|
325
|
+
assert encodedLength <= rawStrLength, f"RLE encoded string ({encodedLength}) should be shorter than raw string ({rawStrLength})"
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
# pyright: standard
|
|
2
|
+
from hunterMakesPy import importLogicalPath2Identifier, importPathFilename2Identifier, makeDirsSafely, writeStringToHere
|
|
3
|
+
from hunterMakesPy.tests.conftest import standardizedEqualTo
|
|
4
|
+
import io
|
|
5
|
+
import math
|
|
6
|
+
import os
|
|
7
|
+
import pathlib
|
|
8
|
+
import pytest
|
|
9
|
+
import sys
|
|
10
|
+
|
|
11
|
+
@pytest.mark.parametrize(
|
|
12
|
+
"logicalPathModuleTarget, identifierTarget, packageIdentifierIfRelativeTarget, expectedType",
|
|
13
|
+
[
|
|
14
|
+
('math', 'gcd', None, type(math.gcd)),
|
|
15
|
+
('os.path', 'join', None, type(os.path.join)),
|
|
16
|
+
('pathlib', 'Path', None, type(pathlib.Path)),
|
|
17
|
+
('sys', 'version', None, type(sys.version)),
|
|
18
|
+
]
|
|
19
|
+
)
|
|
20
|
+
def testImportLogicalPath2IdentifierWithAbsolutePaths(
|
|
21
|
+
logicalPathModuleTarget: str,
|
|
22
|
+
identifierTarget: str,
|
|
23
|
+
packageIdentifierIfRelativeTarget: str | None,
|
|
24
|
+
expectedType: type
|
|
25
|
+
) -> None:
|
|
26
|
+
"""Test importing identifiers from modules using absolute logical paths."""
|
|
27
|
+
identifierImported = importLogicalPath2Identifier(logicalPathModuleTarget, identifierTarget, packageIdentifierIfRelativeTarget)
|
|
28
|
+
|
|
29
|
+
assert isinstance(identifierImported, expectedType), (
|
|
30
|
+
f"\nTesting: `importLogicalPath2Identifier({logicalPathModuleTarget}, {identifierTarget}, {packageIdentifierIfRelativeTarget})`\n"
|
|
31
|
+
f"Expected type: {expectedType}\n"
|
|
32
|
+
f"Got type: {type(identifierImported)}"
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@pytest.mark.parametrize(
|
|
37
|
+
"pythonSourceTarget, identifierTarget, moduleIdentifierTarget, expectedValueWhenCalled",
|
|
38
|
+
[
|
|
39
|
+
("def fibonacciNumber():\n return 13\n", "fibonacciNumber", None, 13),
|
|
40
|
+
("def primeNumber():\n return 17\n", "primeNumber", "moduleNorth", 17),
|
|
41
|
+
("def cardinalDirection():\n return 'N'\n", "cardinalDirection", "moduleSouth", 'N'),
|
|
42
|
+
("def fibonacciSequence():\n return 21\n", "fibonacciSequence", "moduleEast", 21),
|
|
43
|
+
]
|
|
44
|
+
)
|
|
45
|
+
def testImportPathFilename2IdentifierWithCallables(
|
|
46
|
+
pathTmpTesting: pathlib.Path,
|
|
47
|
+
pythonSourceTarget: str,
|
|
48
|
+
identifierTarget: str,
|
|
49
|
+
moduleIdentifierTarget: str | None,
|
|
50
|
+
expectedValueWhenCalled: object
|
|
51
|
+
) -> None:
|
|
52
|
+
"""Test importing callable identifiers from Python files."""
|
|
53
|
+
pathFilenameModule = pathTmpTesting / f"moduleTest{hash(pythonSourceTarget) % 89}.py" # Use prime number 89
|
|
54
|
+
pathFilenameModule.write_text(pythonSourceTarget)
|
|
55
|
+
|
|
56
|
+
standardizedEqualTo(
|
|
57
|
+
expectedValueWhenCalled,
|
|
58
|
+
lambda: importPathFilename2Identifier(pathFilenameModule, identifierTarget, moduleIdentifierTarget)(),
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@pytest.mark.parametrize(
|
|
63
|
+
"pythonSourceTarget, identifierTarget, moduleIdentifierTarget, expectedValue",
|
|
64
|
+
[
|
|
65
|
+
("prime = 23\n", "prime", None, 23),
|
|
66
|
+
("fibonacci = 34\n", "fibonacci", "moduleWest", 34),
|
|
67
|
+
("cardinalDirection = 'S'\n", "cardinalDirection", "moduleNorthEast", 'S'),
|
|
68
|
+
("sequenceValue = 55\n", "sequenceValue", "moduleSouthWest", 55),
|
|
69
|
+
]
|
|
70
|
+
)
|
|
71
|
+
def testImportPathFilename2IdentifierWithVariables(
|
|
72
|
+
pathTmpTesting: pathlib.Path,
|
|
73
|
+
pythonSourceTarget: str,
|
|
74
|
+
identifierTarget: str,
|
|
75
|
+
moduleIdentifierTarget: str | None,
|
|
76
|
+
expectedValue: object
|
|
77
|
+
) -> None:
|
|
78
|
+
"""Test importing variable identifiers from Python files."""
|
|
79
|
+
pathFilenameModule = pathTmpTesting / f"moduleTest{hash(pythonSourceTarget) % 97}.py" # Use prime number 97
|
|
80
|
+
pathFilenameModule.write_text(pythonSourceTarget)
|
|
81
|
+
|
|
82
|
+
standardizedEqualTo(
|
|
83
|
+
expectedValue,
|
|
84
|
+
importPathFilename2Identifier,
|
|
85
|
+
pathFilenameModule,
|
|
86
|
+
identifierTarget,
|
|
87
|
+
moduleIdentifierTarget
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@pytest.mark.parametrize(
|
|
92
|
+
"listDirectoryComponents, filenameTarget",
|
|
93
|
+
[
|
|
94
|
+
(['north', 'south'], 'fibonacci13.txt'),
|
|
95
|
+
(['east', 'west', 'northeast'], 'prime17.txt'),
|
|
96
|
+
(['southwest', 'northwest'], 'fibonacci21.txt'),
|
|
97
|
+
(['cardinal', 'directions', 'multiple'], 'prime23.txt'),
|
|
98
|
+
]
|
|
99
|
+
)
|
|
100
|
+
def testMakeDirsSafelyCreatesNestedDirectories(
|
|
101
|
+
pathTmpTesting: pathlib.Path,
|
|
102
|
+
listDirectoryComponents: list[str],
|
|
103
|
+
filenameTarget: str
|
|
104
|
+
) -> None:
|
|
105
|
+
"""Test that makeDirsSafely creates nested parent directories."""
|
|
106
|
+
pathDirectoryNested = pathTmpTesting
|
|
107
|
+
for directoryComponent in listDirectoryComponents:
|
|
108
|
+
pathDirectoryNested = pathDirectoryNested / directoryComponent
|
|
109
|
+
|
|
110
|
+
pathFilenameTarget = pathDirectoryNested / filenameTarget
|
|
111
|
+
makeDirsSafely(pathFilenameTarget)
|
|
112
|
+
|
|
113
|
+
assert pathDirectoryNested.exists() and pathDirectoryNested.is_dir(), (
|
|
114
|
+
f"\nTesting: `makeDirsSafely({pathFilenameTarget})`\n"
|
|
115
|
+
f"Expected: Directory {pathDirectoryNested} to exist and be a directory\n"
|
|
116
|
+
f"Got: exists={pathDirectoryNested.exists()}, is_dir={pathDirectoryNested.is_dir() if pathDirectoryNested.exists() else False}"
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@pytest.mark.parametrize(
|
|
121
|
+
"streamTypeTarget",
|
|
122
|
+
[
|
|
123
|
+
io.StringIO(),
|
|
124
|
+
io.StringIO("initialContent"),
|
|
125
|
+
]
|
|
126
|
+
)
|
|
127
|
+
def testMakeDirsSafelyWithIOStreamDoesNotRaise(streamTypeTarget: io.IOBase) -> None:
|
|
128
|
+
"""Test that makeDirsSafely handles IO streams without raising exceptions."""
|
|
129
|
+
# This test verifies that no exception is raised
|
|
130
|
+
makeDirsSafely(streamTypeTarget)
|
|
131
|
+
|
|
132
|
+
# If we reach this point, no exception was raised
|
|
133
|
+
assert True
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@pytest.mark.parametrize(
|
|
137
|
+
"listDirectoryComponents, filenameTarget, contentTarget",
|
|
138
|
+
[
|
|
139
|
+
(['north', 'fibonacci'], 'test13.txt', 'fibonacci content 13'),
|
|
140
|
+
(['south', 'prime'], 'test17.txt', 'prime content 17'),
|
|
141
|
+
(['east', 'cardinal'], 'test21.txt', 'cardinal direction east'),
|
|
142
|
+
(['west', 'sequence'], 'test23.txt', 'sequence value 23'),
|
|
143
|
+
]
|
|
144
|
+
)
|
|
145
|
+
def testWriteStringToHereCreatesFileAndDirectories(
|
|
146
|
+
pathTmpTesting: pathlib.Path,
|
|
147
|
+
listDirectoryComponents: list[str],
|
|
148
|
+
filenameTarget: str,
|
|
149
|
+
contentTarget: str
|
|
150
|
+
) -> None:
|
|
151
|
+
"""Test that writeStringToHere creates directories and writes content to files."""
|
|
152
|
+
pathDirectoryNested = pathTmpTesting
|
|
153
|
+
for directoryComponent in listDirectoryComponents:
|
|
154
|
+
pathDirectoryNested = pathDirectoryNested / directoryComponent
|
|
155
|
+
|
|
156
|
+
pathFilenameTarget = pathDirectoryNested / filenameTarget
|
|
157
|
+
writeStringToHere(contentTarget, pathFilenameTarget)
|
|
158
|
+
|
|
159
|
+
assert pathFilenameTarget.exists(), (
|
|
160
|
+
f"\nTesting: `writeStringToHere({contentTarget}, {pathFilenameTarget})`\n"
|
|
161
|
+
f"Expected: File {pathFilenameTarget} to exist\n"
|
|
162
|
+
f"Got: exists={pathFilenameTarget.exists()}"
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
contentActual = pathFilenameTarget.read_text(encoding="utf-8")
|
|
166
|
+
assert contentActual == contentTarget, (
|
|
167
|
+
f"\nTesting: `writeStringToHere({contentTarget}, {pathFilenameTarget})`\n"
|
|
168
|
+
f"Expected content: {contentTarget}\n"
|
|
169
|
+
f"Got content: {contentActual}"
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
@pytest.mark.parametrize(
|
|
174
|
+
"contentTarget",
|
|
175
|
+
[
|
|
176
|
+
'fibonacci content 34',
|
|
177
|
+
'prime content 29',
|
|
178
|
+
'cardinal direction NE',
|
|
179
|
+
'sequence value 55',
|
|
180
|
+
]
|
|
181
|
+
)
|
|
182
|
+
def testWriteStringToHereWithIOStream(contentTarget: str) -> None:
|
|
183
|
+
"""Test that writeStringToHere writes content to IO streams."""
|
|
184
|
+
streamMemory = io.StringIO()
|
|
185
|
+
writeStringToHere(contentTarget, streamMemory)
|
|
186
|
+
|
|
187
|
+
contentActual = streamMemory.getvalue()
|
|
188
|
+
assert contentActual == contentTarget, (
|
|
189
|
+
f"\nTesting: `writeStringToHere({contentTarget}, StringIO)`\n"
|
|
190
|
+
f"Expected content: {contentTarget}\n"
|
|
191
|
+
f"Got content: {contentActual}"
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
@pytest.mark.parametrize(
|
|
196
|
+
"logicalPathModuleTarget, identifierTarget, expectedExceptionType",
|
|
197
|
+
[
|
|
198
|
+
('nonexistent.module', 'anyIdentifier', ModuleNotFoundError),
|
|
199
|
+
('math', 'nonexistentFunction', AttributeError),
|
|
200
|
+
('os.path', 'nonexistentAttribute', AttributeError),
|
|
201
|
+
]
|
|
202
|
+
)
|
|
203
|
+
def testImportLogicalPath2IdentifierWithInvalidInputs(
|
|
204
|
+
logicalPathModuleTarget: str,
|
|
205
|
+
identifierTarget: str,
|
|
206
|
+
expectedExceptionType: type[Exception]
|
|
207
|
+
) -> None:
|
|
208
|
+
"""Test that importLogicalPath2Identifier raises appropriate exceptions for invalid inputs."""
|
|
209
|
+
standardizedEqualTo(
|
|
210
|
+
expectedExceptionType,
|
|
211
|
+
importLogicalPath2Identifier,
|
|
212
|
+
logicalPathModuleTarget,
|
|
213
|
+
identifierTarget
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
@pytest.mark.parametrize(
|
|
218
|
+
"pathFilenameTarget, identifierTarget, expectedExceptionType",
|
|
219
|
+
[
|
|
220
|
+
('nonexistent.py', 'anyIdentifier', FileNotFoundError),
|
|
221
|
+
]
|
|
222
|
+
)
|
|
223
|
+
def testImportPathFilename2IdentifierWithInvalidInputs(
|
|
224
|
+
pathTmpTesting: pathlib.Path,
|
|
225
|
+
pathFilenameTarget: str,
|
|
226
|
+
identifierTarget: str,
|
|
227
|
+
expectedExceptionType: type[Exception]
|
|
228
|
+
) -> None:
|
|
229
|
+
"""Test that importPathFilename2Identifier raises appropriate exceptions for invalid inputs."""
|
|
230
|
+
pathFilenameNonexistent = pathTmpTesting / pathFilenameTarget
|
|
231
|
+
|
|
232
|
+
standardizedEqualTo(
|
|
233
|
+
expectedExceptionType,
|
|
234
|
+
importPathFilename2Identifier,
|
|
235
|
+
pathFilenameNonexistent,
|
|
236
|
+
identifierTarget
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
@pytest.mark.parametrize(
|
|
241
|
+
"pythonSourceTarget, identifierTarget, expectedExceptionType",
|
|
242
|
+
[
|
|
243
|
+
("def validFunction():\n return 89\n", "nonexistentIdentifier", AttributeError),
|
|
244
|
+
("validVariable = 97\n", "nonexistentVariable", AttributeError),
|
|
245
|
+
]
|
|
246
|
+
)
|
|
247
|
+
def testImportPathFilename2IdentifierWithValidFileInvalidIdentifier(
|
|
248
|
+
pathTmpTesting: pathlib.Path,
|
|
249
|
+
pythonSourceTarget: str,
|
|
250
|
+
identifierTarget: str,
|
|
251
|
+
expectedExceptionType: type[Exception]
|
|
252
|
+
) -> None:
|
|
253
|
+
"""Test that importPathFilename2Identifier raises AttributeError for nonexistent identifiers in valid files."""
|
|
254
|
+
pathFilenameModule = pathTmpTesting / f"moduleTest{hash(pythonSourceTarget) % 101}.py" # Use prime number 101
|
|
255
|
+
pathFilenameModule.write_text(pythonSourceTarget)
|
|
256
|
+
|
|
257
|
+
standardizedEqualTo(
|
|
258
|
+
expectedExceptionType,
|
|
259
|
+
importPathFilename2Identifier,
|
|
260
|
+
pathFilenameModule,
|
|
261
|
+
identifierTarget
|
|
262
|
+
)
|
|
263
|
+
|