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.
@@ -2,13 +2,17 @@
2
2
  from collections.abc import Callable, Iterable, Iterator
3
3
  from decimal import Decimal
4
4
  from fractions import Fraction
5
- from hunterMakesPy import autoDecodingRLE, stringItUp, updateExtendPolishDictionaryLists
5
+ from hunterMakesPy import stringItUp, updateExtendPolishDictionaryLists
6
+ from hunterMakesPy.tests.conftest import standardizedEqualTo
6
7
  from numpy.typing import NDArray
7
- from tests.conftest import standardizedEqualTo
8
8
  from typing import Any, Literal
9
9
  import datetime
10
10
  import numpy
11
11
  import pytest
12
+ import sys
13
+
14
+ if sys.version_info < (3, 14):
15
+ from hunterMakesPy import autoDecodingRLE
12
16
 
13
17
  class CustomIterable:
14
18
  def __init__(self, items: Iterable[Any]) -> None: self.items = items
@@ -81,7 +85,7 @@ def testStringItUpErrorCases(description: Literal['Memory view'], value_scrapPil
81
85
  def testUpdateExtendPolishDictionaryLists(description: str, value_dictionaryLists: dict[str, Any], keywordArguments: dict[str, Any], expected: dict[str, Any] | type[TypeError]) -> None:
82
86
  standardizedEqualTo(expected, updateExtendPolishDictionaryLists, *value_dictionaryLists, **keywordArguments)
83
87
  # ruff: noqa: ERA001
84
- # NOTE one line of code with `standardizedEqualTo` replaced the following ten lines of code.
88
+ # NOTE one line of code with `standardizedEqualTo` replaced the following ten lines of code. Use `standardizedEqualTo`.
85
89
  # if isinstance(expected, type) and issubclass(expected, Exception):
86
90
  # with pytest.raises(expected):
87
91
  # updateExtendPolishDictionaryLists(*value_dictionaryLists, **keywordArguments)
@@ -94,26 +98,28 @@ def testUpdateExtendPolishDictionaryLists(description: str, value_dictionaryList
94
98
  # assert result == expected
95
99
 
96
100
  # ruff: noqa: RUF005
97
- @pytest.mark.parametrize("description,value_arrayTarget,expected", [
98
- ("One range", numpy.array(list(range(50,60))), "[*range(50,60)]"),
99
- ("Value, range", numpy.array([123]+list(range(71,81))), "[123,*range(71,81)]"),
100
- ("range, value", numpy.array(list(range(91,97))+[101]), "[*range(91,97),101]"),
101
- ("Value, range, value", numpy.array([151]+list(range(163,171))+[181]), "[151,*range(163,171),181]"),
102
- ("Repeat values", numpy.array([191, 191, 191]), "[191]*3"),
103
- ("Value with repeat", numpy.array([211, 223, 223, 223]), "[211]+[223]*3"),
104
- ("Range with repeat", numpy.array(list(range(251,257))+[271, 271, 271]), "[*range(251,257)]+[271]*3"),
105
- ("Value, range, repeat", numpy.array([281]+list(range(291,297))+[307, 307]), "[281,*range(291,297)]+[307]*2"),
106
- ("repeat, value", numpy.array([313, 313, 313, 331, 331, 349]), "[313]*3+[331]*2+[349]"),
107
- ("repeat, range", numpy.array([373, 373, 373]+list(range(383,389))), "[373]*3+[*range(383,389)]"),
108
- ("repeat, range, value", numpy.array(7*[401]+list(range(409,415))+[421]), "[401]*7+[*range(409,415),421]"),
109
- ("Repeated primes", numpy.array([431, 431, 431, 443, 443, 457]), "[431]*3+[443]*2+[457]"),
110
- ("Two Ranges", numpy.array(list(range(461,471))+list(range(479,487))), "[*range(461,471),*range(479,487)]"),
111
- ("2D array primes", numpy.array([[491, 499, 503], [509, 521, 523]]), "[[491,499,503],[509,521,523]]"),
112
- ("3D array primes", numpy.array([[[541, 547], [557, 563]], [[569, 571], [577, 587]]]), "[[[541,547],[557,563]],[[569,571],[577,587]]]"),
113
- ], ids=lambda x: x if isinstance(x, str) else "")
114
- def testAutoDecodingRLE(description: str, value_arrayTarget: NDArray[numpy.integer[Any]], expected: str) -> None:
115
- """Test autoDecodingRLE with various input arrays."""
116
- standardizedEqualTo(expected, autoDecodingRLE, value_arrayTarget)
101
+
102
+ if sys.version_info < (3, 14):
103
+ @pytest.mark.parametrize("description,value_arrayTarget,expected", [
104
+ ("One range", numpy.array(list(range(50,60))), "[*range(50,60)]"),
105
+ ("Value, range", numpy.array([123]+list(range(71,81))), "[123,*range(71,81)]"),
106
+ ("range, value", numpy.array(list(range(91,97))+[101]), "[*range(91,97),101]"),
107
+ ("Value, range, value", numpy.array([151]+list(range(163,171))+[181]), "[151,*range(163,171),181]"),
108
+ ("Repeat values", numpy.array([191, 191, 191]), "[191]*3"),
109
+ ("Value with repeat", numpy.array([211, 223, 223, 223]), "[211]+[223]*3"),
110
+ ("Range with repeat", numpy.array(list(range(251,257))+[271, 271, 271]), "[*range(251,257)]+[271]*3"),
111
+ ("Value, range, repeat", numpy.array([281]+list(range(291,297))+[307, 307]), "[281,*range(291,297)]+[307]*2"),
112
+ ("repeat, value", numpy.array([313, 313, 313, 331, 331, 349]), "[313]*3+[331]*2+[349]"),
113
+ ("repeat, range", numpy.array([373, 373, 373]+list(range(383,389))), "[373]*3+[*range(383,389)]"),
114
+ ("repeat, range, value", numpy.array(7*[401]+list(range(409,415))+[421]), "[401]*7+[*range(409,415),421]"),
115
+ ("Repeated primes", numpy.array([431, 431, 431, 443, 443, 457]), "[431]*3+[443]*2+[457]"),
116
+ ("Two Ranges", numpy.array(list(range(461,471))+list(range(479,487))), "[*range(461,471),*range(479,487)]"),
117
+ ("2D array primes", numpy.array([[491, 499, 503], [509, 521, 523]]), "[[491,499,503],[509,521,523]]"),
118
+ ("3D array primes", numpy.array([[[541, 547], [557, 563]], [[569, 571], [577, 587]]]), "[[[541,547],[557,563]],[[569,571],[577,587]]]"),
119
+ ], ids=lambda x: x if isinstance(x, str) else "")
120
+ def testAutoDecodingRLE(description: str, value_arrayTarget: NDArray[numpy.integer[Any]], expected: str) -> None:
121
+ """Test autoDecodingRLE with various input arrays."""
122
+ standardizedEqualTo(expected, autoDecodingRLE, value_arrayTarget)
117
123
 
118
124
  # Helper functions for generating RLE test data
119
125
  def generateCartesianMapping(dimensions: tuple[int, int], formula: Callable[[int, int], int]) -> NDArray[Any]:
@@ -222,98 +228,98 @@ def generateAlternatingColumns(dimensions: tuple[int, int], blockSize: int = 1)
222
228
 
223
229
  return generateCartesianMapping(dimensions, columnFormula)
224
230
 
225
- # Updated test cases for autoDecodingRLE with more realistic data
226
- @pytest.mark.parametrize("description,value_arrayTarget", [
227
- # Basic test cases with simple patterns
228
- ("Simple range", numpy.array(list(range(50,60)))),
229
-
230
- # Chessboard patterns
231
- ("Small chessboard", generateChessboard((8, 8))),
232
-
233
- # Alternating columns - creates patterns with good RLE opportunities
234
- ("Alternating columns", generateAlternatingColumns((5, 20), 2)),
235
-
236
- # Step pattern - creates horizontal runs
237
- ("Step pattern", generateStepPattern((6, 30), 3)),
238
-
239
- # Repeating zones - creates horizontal bands
240
- ("Repeating zones", generateRepeatingZones((40, 40), 8)),
241
-
242
- # Tile pattern - creates complex repeating regions
243
- ("Tile pattern", generateTilePattern((15, 15), 5)),
244
-
245
- # Signed quadratic function - includes negative values
246
- ("Signed quadratic", generateSignedQuadraticFunction((10, 10))),
247
-
248
- # Prime modulo matrix - periodic patterns
249
- ("Prime modulo", generatePrimeModuloMatrix((12, 12), 7)),
250
-
251
- # Wave pattern - smooth gradients
252
- ("Wave pattern", generateWavePattern((20, 20))),
253
-
254
- # Spiral pattern - complex pattern with good RLE potential
255
- ("Spiral pattern", generateSpiralPattern((15, 15), 2)),
256
- ], ids=lambda x: x if isinstance(x, str) else "")
257
- def testAutoDecodingRLEWithRealisticData(description: str, value_arrayTarget: NDArray[numpy.integer[Any]]) -> None:
258
- """Test autoDecodingRLE with more realistic data patterns."""
259
- # Here we test the function behavior rather than expected string output
260
- resultRLE = autoDecodingRLE(value_arrayTarget)
261
-
262
- # Test that the result is a valid string
263
- assert isinstance(resultRLE, str)
264
-
265
- # Test that the result contains the expected syntax elements
266
- assert "[" in resultRLE, f"Result should contain list syntax: {resultRLE}"
267
- assert "]" in resultRLE, f"Result should contain list syntax: {resultRLE}"
268
-
269
- # Check that the result is more compact than the raw string representation
270
- rawStrLength = len(str(value_arrayTarget.tolist()))
271
- encodedLength = len(resultRLE)
272
- assert encodedLength <= rawStrLength, f"Encoded string ({encodedLength}) should be shorter than raw string ({rawStrLength})"
273
-
274
- @pytest.mark.parametrize("description,addSpaces", [
275
- ("With spaces", True),
276
- ("Without spaces", False),
277
- ], ids=lambda x: x if isinstance(x, str) else "")
278
- def testAutoDecodingRLEWithSpaces(description: str, addSpaces: bool) -> None:
279
- """Test that the addSpaces parameter affects the internal comparison logic.
280
-
281
- Note: addSpaces doesn't directly change the output format, it just changes
282
- the comparison when measuring the length of the string representation.
283
- The feature exists because `ast` inserts spaces in its string representation.
284
- """
285
- # Create a pattern that has repeated sequences to trigger the RLE logic
286
- arrayTarget = generateRepeatingZones((10, 10), 2)
287
-
288
- # Test both configurations
289
- resultWithSpacesFlag = autoDecodingRLE(arrayTarget, assumeAddSpaces=addSpaces)
290
- resultNoSpacesFlag = autoDecodingRLE(arrayTarget, assumeAddSpaces=False)
291
-
292
- # When addSpaces=True, the internal length comparisons change
293
- # but the actual output format doesn't necessarily differ
294
- # Just verify the function runs without errors in both cases
295
- assert isinstance(resultWithSpacesFlag, str)
296
- assert isinstance(resultNoSpacesFlag, str)
297
-
298
- def testAutoDecodingRLELargeCartesianMapping() -> None:
299
- """Test autoDecodingRLE with a large (100x100) cartesian mapping."""
300
- dimensions = (100, 100)
301
-
302
- # Generate a large cartesian mapping with a complex pattern
303
- def complexFormula(x: int, y: int) -> int:
304
- return ((x * 17) % 11 + (y * 13) % 7) % 10
305
-
306
- arrayMapping = generateCartesianMapping(dimensions, complexFormula)
307
-
308
- # Verify the function works with large arrays
309
- resultRLE = autoDecodingRLE(arrayMapping)
310
-
311
- # The result should be a valid string representation
312
- assert isinstance(resultRLE, str)
313
- assert "[" in resultRLE
314
- assert "]" in resultRLE
315
-
316
- # The RLE encoding should be more compact than the raw representation
317
- rawStrLength = len(str(arrayMapping.tolist()))
318
- encodedLength = len(resultRLE)
319
- assert encodedLength <= rawStrLength, f"RLE encoded string ({encodedLength}) should be shorter than raw string ({rawStrLength})"
231
+ if sys.version_info < (3, 14):
232
+ @pytest.mark.parametrize("description,value_arrayTarget", [
233
+ # Basic test cases with simple patterns
234
+ ("Simple range", numpy.array(list(range(50,60)))),
235
+
236
+ # Chessboard patterns
237
+ ("Small chessboard", generateChessboard((8, 8))),
238
+
239
+ # Alternating columns - creates patterns with good RLE opportunities
240
+ ("Alternating columns", generateAlternatingColumns((5, 20), 2)),
241
+
242
+ # Step pattern - creates horizontal runs
243
+ ("Step pattern", generateStepPattern((6, 30), 3)),
244
+
245
+ # Repeating zones - creates horizontal bands
246
+ ("Repeating zones", generateRepeatingZones((40, 40), 8)),
247
+
248
+ # Tile pattern - creates complex repeating regions
249
+ ("Tile pattern", generateTilePattern((15, 15), 5)),
250
+
251
+ # Signed quadratic function - includes negative values
252
+ ("Signed quadratic", generateSignedQuadraticFunction((10, 10))),
253
+
254
+ # Prime modulo matrix - periodic patterns
255
+ ("Prime modulo", generatePrimeModuloMatrix((12, 12), 7)),
256
+
257
+ # Wave pattern - smooth gradients
258
+ ("Wave pattern", generateWavePattern((20, 20))),
259
+
260
+ # Spiral pattern - complex pattern with good RLE potential
261
+ ("Spiral pattern", generateSpiralPattern((15, 15), 2)),
262
+ ], ids=lambda x: x if isinstance(x, str) else "")
263
+ def testAutoDecodingRLEWithRealisticData(description: str, value_arrayTarget: NDArray[numpy.integer[Any]]) -> None:
264
+ """Test autoDecodingRLE with more realistic data patterns."""
265
+ # Here we test the function behavior rather than expected string output
266
+ resultRLE = autoDecodingRLE(value_arrayTarget)
267
+
268
+ # Test that the result is a valid string
269
+ assert isinstance(resultRLE, str)
270
+
271
+ # Test that the result contains the expected syntax elements
272
+ assert "[" in resultRLE, f"Result should contain list syntax: {resultRLE}"
273
+ assert "]" in resultRLE, f"Result should contain list syntax: {resultRLE}"
274
+
275
+ # Check that the result is more compact than the raw string representation
276
+ rawStrLength = len(str(value_arrayTarget.tolist()))
277
+ encodedLength = len(resultRLE)
278
+ assert encodedLength <= rawStrLength, f"Encoded string ({encodedLength}) should be shorter than raw string ({rawStrLength})"
279
+
280
+ @pytest.mark.parametrize("description,addSpaces", [
281
+ ("With spaces", True),
282
+ ("Without spaces", False),
283
+ ], ids=lambda x: x if isinstance(x, str) else "")
284
+ def testAutoDecodingRLEWithSpaces(description: str, addSpaces: bool) -> None:
285
+ """Test that the addSpaces parameter affects the internal comparison logic.
286
+
287
+ Note: addSpaces doesn't directly change the output format, it just changes
288
+ the comparison when measuring the length of the string representation.
289
+ The feature exists because `ast` inserts spaces in its string representation.
290
+ """
291
+ # Create a pattern that has repeated sequences to trigger the RLE logic
292
+ arrayTarget = generateRepeatingZones((10, 10), 2)
293
+
294
+ # Test both configurations
295
+ resultWithSpacesFlag = autoDecodingRLE(arrayTarget, assumeAddSpaces=addSpaces)
296
+ resultNoSpacesFlag = autoDecodingRLE(arrayTarget, assumeAddSpaces=False)
297
+
298
+ # When addSpaces=True, the internal length comparisons change
299
+ # but the actual output format doesn't necessarily differ
300
+ # Just verify the function runs without errors in both cases
301
+ assert isinstance(resultWithSpacesFlag, str)
302
+ assert isinstance(resultNoSpacesFlag, str)
303
+
304
+ def testAutoDecodingRLELargeCartesianMapping() -> None:
305
+ """Test autoDecodingRLE with a large (100x100) cartesian mapping."""
306
+ dimensions = (100, 100)
307
+
308
+ # Generate a large cartesian mapping with a complex pattern
309
+ def complexFormula(x: int, y: int) -> int:
310
+ return ((x * 17) % 11 + (y * 13) % 7) % 10
311
+
312
+ arrayMapping = generateCartesianMapping(dimensions, complexFormula)
313
+
314
+ # Verify the function works with large arrays
315
+ resultRLE = autoDecodingRLE(arrayMapping)
316
+
317
+ # The result should be a valid string representation
318
+ assert isinstance(resultRLE, str)
319
+ assert "[" in resultRLE
320
+ assert "]" in resultRLE
321
+
322
+ # The RLE encoding should be more compact than the raw representation
323
+ rawStrLength = len(str(arrayMapping.tolist()))
324
+ encodedLength = len(resultRLE)
325
+ assert encodedLength <= rawStrLength, f"RLE encoded string ({encodedLength}) should be shorter than raw string ({rawStrLength})"
@@ -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
+