hunterMakesPy 0.1.2__tar.gz → 0.2.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. {huntermakespy-0.1.2 → huntermakespy-0.2.1}/PKG-INFO +24 -27
  2. {huntermakespy-0.1.2 → huntermakespy-0.2.1}/README.md +21 -25
  3. {huntermakespy-0.1.2 → huntermakespy-0.2.1}/hunterMakesPy/__init__.py +7 -2
  4. huntermakespy-0.2.1/hunterMakesPy/dataStructures.py +268 -0
  5. {huntermakespy-0.1.2 → huntermakespy-0.2.1}/hunterMakesPy/filesystemToolkit.py +17 -9
  6. {huntermakespy-0.1.2 → huntermakespy-0.2.1}/hunterMakesPy/parseParameters.py +29 -29
  7. huntermakespy-0.2.1/hunterMakesPy/pytestForYourUse.py +10 -0
  8. huntermakespy-0.2.1/hunterMakesPy/tests/__init__.py +5 -0
  9. {huntermakespy-0.1.2 → huntermakespy-0.2.1/hunterMakesPy}/tests/conftest.py +32 -2
  10. {huntermakespy-0.1.2 → huntermakespy-0.2.1/hunterMakesPy}/tests/test_coping.py +1 -1
  11. {huntermakespy-0.1.2 → huntermakespy-0.2.1/hunterMakesPy}/tests/test_dataStructures.py +124 -118
  12. huntermakespy-0.2.1/hunterMakesPy/tests/test_filesystemToolkit.py +263 -0
  13. huntermakespy-0.1.2/hunterMakesPy/pytestForYourUse.py → huntermakespy-0.2.1/hunterMakesPy/tests/test_parseParameters.py +17 -5
  14. {huntermakespy-0.1.2 → huntermakespy-0.2.1}/hunterMakesPy.egg-info/PKG-INFO +24 -27
  15. {huntermakespy-0.1.2 → huntermakespy-0.2.1}/hunterMakesPy.egg-info/SOURCES.txt +6 -6
  16. {huntermakespy-0.1.2 → huntermakespy-0.2.1}/hunterMakesPy.egg-info/requires.txt +2 -0
  17. {huntermakespy-0.1.2 → huntermakespy-0.2.1}/hunterMakesPy.egg-info/top_level.txt +0 -1
  18. {huntermakespy-0.1.2 → huntermakespy-0.2.1}/pyproject.toml +6 -4
  19. huntermakespy-0.1.2/hunterMakesPy/dataStructures.py +0 -265
  20. huntermakespy-0.1.2/tests/__init__.py +0 -0
  21. huntermakespy-0.1.2/tests/test_filesystemToolkit.py +0 -43
  22. huntermakespy-0.1.2/tests/test_parseParameters.py +0 -21
  23. {huntermakespy-0.1.2 → huntermakespy-0.2.1}/LICENSE +0 -0
  24. {huntermakespy-0.1.2 → huntermakespy-0.2.1}/hunterMakesPy/_theSSOT.py +0 -0
  25. {huntermakespy-0.1.2 → huntermakespy-0.2.1}/hunterMakesPy/coping.py +0 -0
  26. {huntermakespy-0.1.2 → huntermakespy-0.2.1}/hunterMakesPy/py.typed +0 -0
  27. {huntermakespy-0.1.2 → huntermakespy-0.2.1}/hunterMakesPy/theTypes.py +0 -0
  28. {huntermakespy-0.1.2 → huntermakespy-0.2.1}/hunterMakesPy.egg-info/dependency_links.txt +0 -0
  29. {huntermakespy-0.1.2 → huntermakespy-0.2.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hunterMakesPy
3
- Version: 0.1.2
3
+ Version: 0.2.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' is required but not found"
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
- from hunterMakesPy import defineConcurrencyLimit, intInnit, oopsieKwargsie
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
- from hunterMakesPy import (
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
- from hunterMakesPy import stringItUp, updateExtendPolishDictionaryLists, autoDecodingRLE
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": ["web1", "web2"], "databases": ["db1"]}
135
- dictionaryBeta = {"servers": ["web3"], "databases": ["db2", "db3"]}
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.pytestForYourUse import (
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("nameOfTest,callablePytest",
161
- PytestFor_intInnit(callableToTest=myFunction))
156
+ @pytest.mark.parametrize(
157
+ "nameOfTest,callablePytest"
158
+ , PytestFor_intInnit(callableToTest=myFunction))
162
159
  def test_myFunction(nameOfTest, callablePytest):
163
160
  callablePytest()
164
161
  ```
@@ -28,7 +28,7 @@ def findConfiguration(configName: str) -> dict[str, str] | None:
28
28
 
29
29
  config = raiseIfNone(
30
30
  findConfiguration("database"),
31
- "Configuration 'database' is required but not found"
31
+ "I could not find Configuration 'database', but I need it to continue."
32
32
  )
33
33
  ```
34
34
 
@@ -37,19 +37,19 @@ config = raiseIfNone(
37
37
  Parameter validation, integer parsing, and concurrency handling.
38
38
 
39
39
  ```python
40
- from hunterMakesPy import defineConcurrencyLimit, intInnit, oopsieKwargsie
40
+ import hunterMakesPy as humpy
41
41
 
42
42
  # Smart concurrency limit calculation
43
- cpuLimit = defineConcurrencyLimit(limit=0.75) # Use 75% of available CPUs
44
- cpuLimit = defineConcurrencyLimit(limit=True) # Use exactly 1 CPU
45
- cpuLimit = defineConcurrencyLimit(limit=4) # Use exactly 4 CPUs
43
+ cpuLimit = humpy.defineConcurrencyLimit(limit=0.75) # Use 75% of available CPUs
44
+ cpuLimit = humpy.defineConcurrencyLimit(limit=True) # Use exactly 1 CPU
45
+ cpuLimit = humpy.defineConcurrencyLimit(limit=4) # Use exactly 4 CPUs
46
46
 
47
47
  # Robust integer validation
48
- validatedIntegers = intInnit([1, "2", 3.0, "4"], "port_numbers")
48
+ validatedIntegers = humpy.intInnit([1, "2", 3.0, "4"], "port_numbers")
49
49
 
50
50
  # String-to-boolean conversion for configuration
51
51
  userInput = "True"
52
- booleanValue = oopsieKwargsie(userInput) # Returns True
52
+ booleanValue = humpy.oopsieKwargsie(userInput) # Returns True
53
53
  ```
54
54
 
55
55
  ## File System Utilities
@@ -57,20 +57,15 @@ booleanValue = oopsieKwargsie(userInput) # Returns True
57
57
  Safe file operations and dynamic module importing.
58
58
 
59
59
  ```python
60
- from hunterMakesPy import (
61
- importLogicalPath2Identifier,
62
- importPathFilename2Identifier,
63
- makeDirsSafely,
64
- writeStringToHere
65
- )
60
+ import hunterMakesPy as humpy
66
61
 
67
62
  # Dynamic imports
68
- gcdFunction = importLogicalPath2Identifier("math", "gcd")
69
- customFunction = importPathFilename2Identifier("path/to/module.py", "functionName")
63
+ gcdFunction = humpy.importLogicalPath2Identifier("math", "gcd")
64
+ customFunction = humpy.importPathFilename2Identifier("path/to/module.py", "functionName")
70
65
 
71
66
  # Safe file operations
72
67
  pathFilename = Path("deep/nested/directory/file.txt")
73
- writeStringToHere("content", pathFilename) # Creates directories automatically
68
+ humpy.writeStringToHere("content", pathFilename) # Creates directories automatically
74
69
  ```
75
70
 
76
71
  ## Data Structure Manipulation
@@ -78,21 +73,21 @@ writeStringToHere("content", pathFilename) # Creates directories automatically
78
73
  Utilities for string extraction, data flattening, and array compression.
79
74
 
80
75
  ```python
81
- from hunterMakesPy import stringItUp, updateExtendPolishDictionaryLists, autoDecodingRLE
76
+ import hunterMakesPy as humpy
82
77
  import numpy
83
78
 
84
79
  # Extract all strings from nested data structures
85
80
  nestedData = {"config": [1, "host", {"port": 8080}], "users": ["alice", "bob"]}
86
- allStrings = stringItUp(nestedData) # ['config', 'host', 'port', 'users', 'alice', 'bob']
81
+ allStrings = humpy.stringItUp(nestedData) # ['config', 'host', 'port', 'users', 'alice', 'bob']
87
82
 
88
83
  # Merge dictionaries containing lists
89
- dictionaryAlpha = {"servers": ["web1", "web2"], "databases": ["db1"]}
90
- dictionaryBeta = {"servers": ["web3"], "databases": ["db2", "db3"]}
91
- merged = updateExtendPolishDictionaryLists(dictionaryAlpha, dictionaryBeta, destroyDuplicates=True)
84
+ dictionaryAlpha = {"servers": ["chicago", "tokyo"], "databases": ["elm"]}
85
+ dictionaryBeta = {"servers": ["mumbai"], "databases": ["oak", "cedar"]}
86
+ merged = humpy.updateExtendPolishDictionaryLists(dictionaryAlpha, dictionaryBeta, destroyDuplicates=True)
92
87
 
93
88
  # Compress NumPy arrays with run-length encoding
94
89
  arrayData = numpy.array([1, 2, 3, 4, 5, 5, 5, 6, 7, 8, 9])
95
- compressed = autoDecodingRLE(arrayData) # "[1,*range(2,6)]+[5]*2+[*range(6,10)]"
90
+ compressed = humpy.autoDecodingRLE(arrayData) # "[1,*range(2,6)]+[5]*2+[*range(6,10)]"
96
91
  ```
97
92
 
98
93
  ## Testing
@@ -100,7 +95,7 @@ compressed = autoDecodingRLE(arrayData) # "[1,*range(2,6)]+[5]*2+[*range(6,10)]
100
95
  The package includes comprehensive test suites that you can import and run:
101
96
 
102
97
  ```python
103
- from hunterMakesPy.pytestForYourUse import (
98
+ from hunterMakesPy.tests.test_parseParameters import (
104
99
  PytestFor_defineConcurrencyLimit,
105
100
  PytestFor_intInnit,
106
101
  PytestFor_oopsieKwargsie
@@ -112,8 +107,9 @@ for nameOfTest, callablePytest in listOfTests:
112
107
  callablePytest()
113
108
 
114
109
  # Or test your own compatible functions
115
- @pytest.mark.parametrize("nameOfTest,callablePytest",
116
- PytestFor_intInnit(callableToTest=myFunction))
110
+ @pytest.mark.parametrize(
111
+ "nameOfTest,callablePytest"
112
+ , PytestFor_intInnit(callableToTest=myFunction))
117
113
  def test_myFunction(nameOfTest, callablePytest):
118
114
  callablePytest()
119
115
  ```
@@ -7,6 +7,7 @@ This package provides:
7
7
  - Utilities for string extraction from nested data structures and merging dictionaries of lists.
8
8
 
9
9
  """
10
+ # pyright: reportUnusedImport=false
10
11
  from hunterMakesPy.theTypes import identifierDotAttribute as identifierDotAttribute
11
12
 
12
13
  from hunterMakesPy.coping import PackageSettings as PackageSettings, raiseIfNone as raiseIfNone
@@ -18,7 +19,11 @@ from hunterMakesPy.filesystemToolkit import (importLogicalPath2Identifier as imp
18
19
  importPathFilename2Identifier as importPathFilename2Identifier, makeDirsSafely as makeDirsSafely,
19
20
  writeStringToHere as writeStringToHere)
20
21
 
21
- from hunterMakesPy.dataStructures import (autoDecodingRLE as autoDecodingRLE, stringItUp as stringItUp,
22
- updateExtendPolishDictionaryLists as updateExtendPolishDictionaryLists)
22
+ from hunterMakesPy.dataStructures import stringItUp as stringItUp, updateExtendPolishDictionaryLists as updateExtendPolishDictionaryLists
23
+
24
+ import sys
25
+
26
+ if sys.version_info < (3, 14):
27
+ from hunterMakesPy.dataStructures import autoDecodingRLE as autoDecodingRLE
23
28
 
24
29
  from hunterMakesPy._theSSOT import settingsPackage
@@ -0,0 +1,268 @@
1
+ """Provides utilities for string extraction from nested data structures and merges multiple dictionaries containing lists into one dictionary."""
2
+
3
+ from collections.abc import Iterator, Mapping
4
+ from numpy import integer
5
+ from numpy.typing import NDArray
6
+ from typing import Any
7
+ import more_itertools
8
+ import re as regex
9
+ import sys
10
+
11
+ if sys.version_info < (3, 14):
12
+ import python_minifier
13
+
14
+ def autoDecodingRLE(arrayTarget: NDArray[integer[Any]], *, assumeAddSpaces: bool = False) -> str: # noqa: C901, PLR0915
15
+ """Transform a NumPy array into a compact, self-decoding run-length encoded string representation.
16
+
17
+ This function converts a NumPy array into a string that, when evaluated as Python code,
18
+ recreates the original array structure. The function employs two compression strategies:
19
+ 1. Python's `range` syntax for consecutive integer sequences
20
+ 2. Multiplication syntax for repeated elements
21
+
22
+ The resulting string representation is designed to be both human-readable and space-efficient,
23
+ especially for large cartesian mappings with repetitive patterns. When this string is used
24
+ as a data source, Python will automatically decode it into Python `list`, which if used as an
25
+ argument to `numpy.array()`, will recreate the original array structure.
26
+
27
+ Parameters
28
+ ----------
29
+ arrayTarget : NDArray[integer[Any]]
30
+ (array2target) The NumPy array to be encoded.
31
+ assumeAddSpaces : bool = False
32
+ (assume2add2spaces) Affects internal length comparison during compression decisions.
33
+ This parameter doesn't directly change output format but influences whether
34
+ `range` or multiplication syntax is preferred in certain cases. The parameter
35
+ exists because the Abstract Syntax Tree (AST) inserts spaces in its string
36
+ representation.
37
+
38
+ Returns
39
+ -------
40
+ rleString : str
41
+ (rle2string) A string representation of the array using run-length encoding that,
42
+ when evaluated as Python code, reproduces the original array structure.
43
+
44
+ Notes
45
+ -----
46
+ The "autoDecoding" feature means that the string representation evaluates directly
47
+ to the desired data structure without explicit decompression steps.
48
+
49
+ """
50
+ def sliceNDArrayToNestedLists(arraySlice: NDArray[integer[Any]]) -> Any:
51
+ def getLengthOption(optionAsStr: str) -> int:
52
+ """`assumeAddSpaces` characters: `,` 1; `]*` 2."""
53
+ return assumeAddSpaces * (optionAsStr.count(',') + optionAsStr.count(']*') * 2) + len(optionAsStr)
54
+
55
+ if arraySlice.ndim > 1:
56
+ axisOfOperation = 0
57
+ return [sliceNDArrayToNestedLists(arraySlice[index]) for index in range(arraySlice.shape[axisOfOperation])]
58
+ if arraySlice.ndim == 1:
59
+ arraySliceAsList: list[int | range] = []
60
+ cache_consecutiveGroup_addMe: dict[Iterator[Any], list[int] | list[range]] = {}
61
+ for consecutiveGroup in more_itertools.consecutive_groups(arraySlice.tolist()):
62
+ if consecutiveGroup in cache_consecutiveGroup_addMe:
63
+ addMe = cache_consecutiveGroup_addMe[consecutiveGroup]
64
+ else:
65
+ ImaSerious: list[int] = list(consecutiveGroup)
66
+ ImaRange = [range(ImaSerious[0], ImaSerious[-1] + 1)]
67
+ ImaRangeAsStr = python_minifier.minify(str(ImaRange)).replace('range(0,', 'range(').replace('range', '*range')
68
+
69
+ option1 = ImaRange
70
+ option1AsStr = ImaRangeAsStr
71
+ option2 = ImaSerious
72
+ option2AsStr = None
73
+
74
+ # alpha, potential function
75
+ option1AsStr = option1AsStr or python_minifier.minify(str(option1))
76
+ lengthOption1 = getLengthOption(option1AsStr)
77
+
78
+ option2AsStr = option2AsStr or python_minifier.minify(str(option2))
79
+ lengthOption2 = getLengthOption(option2AsStr)
80
+
81
+ if lengthOption1 < lengthOption2:
82
+ addMe = option1
83
+ else:
84
+ addMe = option2
85
+
86
+ cache_consecutiveGroup_addMe[consecutiveGroup] = addMe
87
+
88
+ arraySliceAsList += addMe
89
+
90
+ listRangeAndTuple: list[int | range | tuple[int | range, int]] = []
91
+ cache_malkovichGrouped_addMe: dict[tuple[int | range, int], list[tuple[int | range, int]] | list[int | range]] = {}
92
+ for malkovichGrouped in more_itertools.run_length.encode(arraySliceAsList):
93
+ if malkovichGrouped in cache_malkovichGrouped_addMe:
94
+ addMe = cache_malkovichGrouped_addMe[malkovichGrouped]
95
+ else:
96
+ lengthMalkovich = malkovichGrouped[-1]
97
+ malkovichAsList = list(more_itertools.run_length.decode([malkovichGrouped]))
98
+ malkovichMalkovich = f"[{malkovichGrouped[0]}]*{lengthMalkovich}"
99
+
100
+ option1 = [malkovichGrouped]
101
+ option1AsStr = malkovichMalkovich
102
+ option2 = malkovichAsList
103
+ option2AsStr = None
104
+
105
+ # beta, potential function
106
+ option1AsStr = option1AsStr or python_minifier.minify(str(option1))
107
+ lengthOption1 = getLengthOption(option1AsStr)
108
+
109
+ option2AsStr = option2AsStr or python_minifier.minify(str(option2))
110
+ lengthOption2 = getLengthOption(option2AsStr)
111
+
112
+ if lengthOption1 < lengthOption2:
113
+ addMe = option1
114
+ else:
115
+ addMe = option2
116
+
117
+ cache_malkovichGrouped_addMe[malkovichGrouped] = addMe
118
+
119
+ listRangeAndTuple += addMe
120
+
121
+ return listRangeAndTuple
122
+ return arraySlice
123
+
124
+ arrayAsNestedLists = sliceNDArrayToNestedLists(arrayTarget)
125
+
126
+ arrayAsStr = python_minifier.minify(str(arrayAsNestedLists))
127
+
128
+ patternRegex = regex.compile(
129
+ "(?<!rang)(?:"
130
+ # Pattern 1: Comma ahead, bracket behind # noqa: ERA001
131
+ "(?P<joinAhead>,)\\((?P<malkovich>\\d+),(?P<multiply>\\d+)\\)(?P<bracketBehind>])|"
132
+ # Pattern 2: Bracket or start ahead, comma behind # noqa: ERA001
133
+ "(?P<bracketOrStartAhead>\\[|^.)\\((?P<malkovichMalkovich>\\d+),(?P<multiplyIDK>\\d+)\\)(?P<joinBehind>,)|"
134
+ # Pattern 3: Bracket ahead, bracket behind # noqa: ERA001
135
+ "(?P<bracketAhead>\\[)\\((?P<malkovichMalkovichMalkovich>\\d+),(?P<multiply_whatever>\\d+)\\)(?P<bracketBehindBracketBehind>])|"
136
+ # Pattern 4: Comma ahead, comma behind # noqa: ERA001
137
+ "(?P<joinAheadJoinAhead>,)\\((?P<malkovichMalkovichMalkovichMalkovich>\\d+),(?P<multiplyOrSomething>\\d+)\\)(?P<joinBehindJoinBehind>,)"
138
+ ")"
139
+ )
140
+
141
+ def replacementByContext(match: regex.Match[str]) -> str:
142
+ """Generate replacement string based on context patterns."""
143
+ elephino = match.groupdict()
144
+ joinAhead = elephino.get('joinAhead') or elephino.get('joinAheadJoinAhead')
145
+ malkovich = elephino.get('malkovich') or elephino.get('malkovichMalkovich') or elephino.get('malkovichMalkovichMalkovich') or elephino.get('malkovichMalkovichMalkovichMalkovich')
146
+ multiply = elephino.get('multiply') or elephino.get('multiplyIDK') or elephino.get('multiply_whatever') or elephino.get('multiplyOrSomething')
147
+ joinBehind = elephino.get('joinBehind') or elephino.get('joinBehindJoinBehind')
148
+
149
+ replaceAhead = "]+[" if joinAhead == "," else "["
150
+
151
+ replaceBehind = "+[" if joinBehind == "," else ""
152
+
153
+ return f"{replaceAhead}{malkovich}]*{multiply}{replaceBehind}"
154
+
155
+ arrayAsStr = patternRegex.sub(replacementByContext, arrayAsStr)
156
+ arrayAsStr = patternRegex.sub(replacementByContext, arrayAsStr)
157
+
158
+ # Replace `range(0,stop)` syntax with `range(stop)` syntax. # noqa: ERA001
159
+ # Add unpack operator `*` for automatic decoding when evaluated.
160
+ return arrayAsStr.replace('range(0,', 'range(').replace('range', '*range')
161
+
162
+ def stringItUp(*scrapPile: Any) -> list[str]: # noqa: C901
163
+ """Convert, if possible, every element in the input data structure to a string.
164
+
165
+ Order is not preserved or readily predictable.
166
+
167
+ Parameters
168
+ ----------
169
+ *scrapPile : Any
170
+ (scrap2pile) One or more data structures to unpack and convert to strings.
171
+
172
+ Returns
173
+ -------
174
+ listStrungUp : list[str]
175
+ (list2strung2up) A `list` of string versions of all convertible elements.
176
+
177
+ """
178
+ scrap = None
179
+ listStrungUp: list[str] = []
180
+
181
+ def drill(KitKat: Any) -> None: # noqa: C901, PLR0912
182
+ match KitKat:
183
+ case str():
184
+ listStrungUp.append(KitKat)
185
+ case bool() | bytearray() | bytes() | complex() | float() | int() | memoryview() | None:
186
+ listStrungUp.append(str(KitKat)) # pyright: ignore [reportUnknownArgumentType]
187
+ case dict():
188
+ for broken, piece in KitKat.items(): # pyright: ignore [reportUnknownVariableType]
189
+ drill(broken)
190
+ drill(piece)
191
+ case list() | tuple() | set() | frozenset() | range():
192
+ for kit in KitKat: # pyright: ignore [reportUnknownVariableType]
193
+ drill(kit)
194
+ case _:
195
+ if hasattr(KitKat, '__iter__'): # Unpack other iterables
196
+ for kat in KitKat:
197
+ drill(kat)
198
+ else:
199
+ try:
200
+ sharingIsCaring = KitKat.__str__()
201
+ listStrungUp.append(sharingIsCaring)
202
+ except AttributeError:
203
+ pass
204
+ except TypeError: # "The error traceback provided indicates that there is an issue when calling the __str__ method on an object that does not have this method properly defined, leading to a TypeError."
205
+ pass
206
+ except:
207
+ print(f"\nWoah! I received '{repr(KitKat)}'.\nTheir report card says, 'Plays well with others: Needs improvement.'\n") # noqa: RUF010, T201
208
+ raise
209
+ try:
210
+ for scrap in scrapPile:
211
+ drill(scrap)
212
+ except RecursionError:
213
+ listStrungUp.append(repr(scrap))
214
+ return listStrungUp
215
+
216
+ def updateExtendPolishDictionaryLists(*dictionaryLists: Mapping[str, list[Any] | set[Any] | tuple[Any, ...]], destroyDuplicates: bool = False, reorderLists: bool = False, killErroneousDataTypes: bool = False) -> dict[str, list[Any]]:
217
+ """Merge multiple dictionaries containing `list` into a single dictionary.
218
+
219
+ With options to handle duplicates, `list` ordering, and erroneous data types.
220
+
221
+ Parameters
222
+ ----------
223
+ *dictionaryLists : Mapping[str, list[Any] | set[Any] | tuple[Any, ...]]
224
+ (dictionary2lists) Variable number of dictionaries to be merged. If only one dictionary is passed, it will be processed based on the provided options.
225
+ destroyDuplicates : bool = False
226
+ (destroy2duplicates) If `True`, removes duplicate elements from the `list`. Defaults to `False`.
227
+ reorderLists : bool = False
228
+ (reorder2lists) If `True`, sorts the `list`. Defaults to `False`.
229
+ killErroneousDataTypes : bool = False
230
+ (kill2erroneous2data2types) If `True`, skips dictionary keys or dictionary values that cause a `TypeError` during merging. Defaults to `False`.
231
+
232
+ Returns
233
+ -------
234
+ ePluribusUnum : dict[str, list[Any]]
235
+ (e2pluribus2unum) A single dictionary with merged `list` based on the provided options. If only one dictionary is passed,
236
+ it will be cleaned up based on the options.
237
+
238
+ Notes
239
+ -----
240
+ The returned value, `ePluribusUnum`, is a so-called primitive dictionary (`dict`). Furthermore, every dictionary key is a
241
+ so-called primitive string (cf. `str()`) and every dictionary value is a so-called primitive `list` (`list`). If
242
+ `dictionaryLists` has other data types, the data types will not be preserved. That could have unexpected consequences.
243
+ Conversion from the original data type to a `list`, for example, may not preserve the order even if you want the order to be
244
+ preserved.
245
+
246
+ """
247
+ ePluribusUnum: dict[str, list[Any]] = {}
248
+
249
+ for dictionaryListTarget in dictionaryLists:
250
+ for keyName, keyValue in dictionaryListTarget.items():
251
+ try:
252
+ ImaStr = str(keyName)
253
+ ImaList = list(keyValue)
254
+ ePluribusUnum.setdefault(ImaStr, []).extend(ImaList)
255
+ except TypeError:
256
+ if killErroneousDataTypes:
257
+ continue
258
+ else:
259
+ raise
260
+
261
+ if destroyDuplicates:
262
+ for ImaStr, ImaList in ePluribusUnum.items():
263
+ ePluribusUnum[ImaStr] = list(dict.fromkeys(ImaList))
264
+ if reorderLists:
265
+ for ImaStr, ImaList in ePluribusUnum.items():
266
+ ePluribusUnum[ImaStr] = sorted(ImaList)
267
+
268
+ return ePluribusUnum
@@ -1,6 +1,6 @@
1
1
  """File system and module import utilities.
2
2
 
3
- This module provides basic file I/O utilities such as importing callables from modules, and safely creating directories.
3
+ This module provides basic file I/O utilities such as importing callables from modules, safely creating directories, and writing to files or streams (pipes).
4
4
 
5
5
  """
6
6
 
@@ -97,17 +97,25 @@ def makeDirsSafely(pathFilename: Any) -> None:
97
97
  with contextlib.suppress(OSError):
98
98
  Path(pathFilename).parent.mkdir(parents=True, exist_ok=True)
99
99
 
100
- def writeStringToHere(this: str, pathFilename: PathLike[Any] | PurePath) -> None:
101
- """Write a string to a file, creating parent directories as needed.
100
+ def writeStringToHere(this: str, pathFilename: PathLike[Any] | PurePath | io.TextIOBase) -> None:
101
+ """Write a string to a file or text stream.
102
+
103
+ This function writes a string to either a file path or an open text stream. For file paths, it creates parent directories as
104
+ needed and writes with UTF-8 encoding. For text streams, it writes directly to the stream and flushes the buffer.
102
105
 
103
106
  Parameters
104
107
  ----------
105
108
  this : str
106
- The string content to write to the file.
107
- pathFilename : PathLike[Any] | PurePath
108
- The path and filename where the string will be written.
109
+ The string content to write.
110
+ pathFilename : PathLike[Any] | PurePath | io.TextIOBase
111
+ The target destination: either a file path or an open text stream.
109
112
 
110
113
  """
111
- pathFilename = Path(pathFilename)
112
- makeDirsSafely(pathFilename)
113
- pathFilename.write_text(str(this), encoding='utf-8')
114
+ if isinstance(pathFilename, io.TextIOBase):
115
+ pathFilename.write(str(this))
116
+ pathFilename.flush()
117
+ else:
118
+ pathFilename = Path(pathFilename)
119
+ makeDirsSafely(pathFilename)
120
+ pathFilename.write_text(str(this), encoding='utf-8')
121
+