hunterMakesPy 0.2.2__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. {huntermakespy-0.2.2 → huntermakespy-0.3.0}/PKG-INFO +4 -14
  2. {huntermakespy-0.2.2 → huntermakespy-0.3.0}/README.md +0 -9
  3. {huntermakespy-0.2.2 → huntermakespy-0.3.0}/hunterMakesPy/__init__.py +2 -5
  4. {huntermakespy-0.2.2 → huntermakespy-0.3.0}/hunterMakesPy/coping.py +29 -25
  5. huntermakespy-0.3.0/hunterMakesPy/dataStructures.py +273 -0
  6. {huntermakespy-0.2.2 → huntermakespy-0.3.0}/hunterMakesPy/filesystemToolkit.py +55 -1
  7. {huntermakespy-0.2.2 → huntermakespy-0.3.0}/hunterMakesPy/tests/test_dataStructures.py +115 -120
  8. {huntermakespy-0.2.2 → huntermakespy-0.3.0}/hunterMakesPy/tests/test_filesystemToolkit.py +187 -1
  9. {huntermakespy-0.2.2 → huntermakespy-0.3.0}/hunterMakesPy.egg-info/PKG-INFO +4 -14
  10. {huntermakespy-0.2.2 → huntermakespy-0.3.0}/hunterMakesPy.egg-info/requires.txt +2 -5
  11. {huntermakespy-0.2.2 → huntermakespy-0.3.0}/pyproject.toml +4 -5
  12. huntermakespy-0.2.2/hunterMakesPy/dataStructures.py +0 -268
  13. {huntermakespy-0.2.2 → huntermakespy-0.3.0}/LICENSE +0 -0
  14. {huntermakespy-0.2.2 → huntermakespy-0.3.0}/hunterMakesPy/_theSSOT.py +0 -0
  15. {huntermakespy-0.2.2 → huntermakespy-0.3.0}/hunterMakesPy/parseParameters.py +0 -0
  16. {huntermakespy-0.2.2 → huntermakespy-0.3.0}/hunterMakesPy/py.typed +0 -0
  17. {huntermakespy-0.2.2 → huntermakespy-0.3.0}/hunterMakesPy/pytestForYourUse.py +0 -0
  18. {huntermakespy-0.2.2 → huntermakespy-0.3.0}/hunterMakesPy/tests/__init__.py +0 -0
  19. {huntermakespy-0.2.2 → huntermakespy-0.3.0}/hunterMakesPy/tests/conftest.py +0 -0
  20. {huntermakespy-0.2.2 → huntermakespy-0.3.0}/hunterMakesPy/tests/test_coping.py +0 -0
  21. {huntermakespy-0.2.2 → huntermakespy-0.3.0}/hunterMakesPy/tests/test_parseParameters.py +0 -0
  22. {huntermakespy-0.2.2 → huntermakespy-0.3.0}/hunterMakesPy/theTypes.py +0 -0
  23. {huntermakespy-0.2.2 → huntermakespy-0.3.0}/hunterMakesPy.egg-info/SOURCES.txt +0 -0
  24. {huntermakespy-0.2.2 → huntermakespy-0.3.0}/hunterMakesPy.egg-info/dependency_links.txt +0 -0
  25. {huntermakespy-0.2.2 → huntermakespy-0.3.0}/hunterMakesPy.egg-info/top_level.txt +0 -0
  26. {huntermakespy-0.2.2 → huntermakespy-0.3.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hunterMakesPy
3
- Version: 0.2.2
3
+ Version: 0.3.0
4
4
  Summary: Easy Python functions making making functional Python functions easier.
5
5
  Author-email: Hunter Hogan <HunterHogan@pm.me>
6
6
  License: CC-BY-NC-4.0
@@ -9,7 +9,7 @@ Project-URL: Homepage, https://github.com/hunterhogan/hunterMakesPy
9
9
  Project-URL: Issues, https://github.com/hunterhogan/hunterMakesPy/issues
10
10
  Project-URL: Repository, https://github.com/hunterhogan/hunterMakesPy
11
11
  Keywords: attribute loading,concurrency limit,configuration,defensive programming,dictionary merging,directory creation,dynamic import,error propagation,file system utilities,input validation,integer parsing,module loading,nested data structures,package settings,parameter validation,pytest,string extraction,test utilities
12
- Classifier: Development Status :: 4 - Beta
12
+ Classifier: Development Status :: 5 - Production/Stable
13
13
  Classifier: Environment :: Console
14
14
  Classifier: Framework :: Pytest
15
15
  Classifier: Intended Audience :: Developers
@@ -30,13 +30,12 @@ Classifier: Typing :: Typed
30
30
  Requires-Python: >=3.11
31
31
  Description-Content-Type: text/markdown
32
32
  License-File: LICENSE
33
+ Requires-Dist: autoflake
33
34
  Requires-Dist: charset_normalizer
35
+ Requires-Dist: isort
34
36
  Requires-Dist: more_itertools
35
37
  Requires-Dist: numpy
36
- Requires-Dist: python_minifier; python_version < "3.14"
37
38
  Provides-Extra: development
38
- Requires-Dist: mypy; extra == "development"
39
- Requires-Dist: pyupgrade; extra == "development"
40
39
  Requires-Dist: setuptools-scm; extra == "development"
41
40
  Provides-Extra: testing
42
41
  Requires-Dist: pytest; extra == "testing"
@@ -165,13 +164,4 @@ def test_myFunction(nameOfTest, callablePytest):
165
164
  [![Static Badge](https://img.shields.io/badge/2011_August-Homeless_since-blue?style=flat)](https://HunterThinks.com/support)
166
165
  [![YouTube Channel Subscribers](https://img.shields.io/youtube/channel/subscribers/UC3Gx7kz61009NbhpRtPP7tw)](https://www.youtube.com/@HunterHogan)
167
166
 
168
- ## How to code
169
-
170
- Coding One Step at a Time:
171
-
172
- 0. WRITE CODE.
173
- 1. Don't write stupid code that's hard to revise.
174
- 2. Write good code.
175
- 3. When revising, write better code.
176
-
177
167
  [![CC-BY-NC-4.0](https://github.com/hunterhogan/hunterMakesPy/blob/main/CC-BY-NC-4.0.png)](https://creativecommons.org/licenses/by-nc/4.0/)
@@ -119,13 +119,4 @@ def test_myFunction(nameOfTest, callablePytest):
119
119
  [![Static Badge](https://img.shields.io/badge/2011_August-Homeless_since-blue?style=flat)](https://HunterThinks.com/support)
120
120
  [![YouTube Channel Subscribers](https://img.shields.io/youtube/channel/subscribers/UC3Gx7kz61009NbhpRtPP7tw)](https://www.youtube.com/@HunterHogan)
121
121
 
122
- ## How to code
123
-
124
- Coding One Step at a Time:
125
-
126
- 0. WRITE CODE.
127
- 1. Don't write stupid code that's hard to revise.
128
- 2. Write good code.
129
- 3. When revising, write better code.
130
-
131
122
  [![CC-BY-NC-4.0](https://github.com/hunterhogan/hunterMakesPy/blob/main/CC-BY-NC-4.0.png)](https://creativecommons.org/licenses/by-nc/4.0/)
@@ -17,13 +17,10 @@ from hunterMakesPy.parseParameters import (defineConcurrencyLimit as defineConcu
17
17
 
18
18
  from hunterMakesPy.filesystemToolkit import (importLogicalPath2Identifier as importLogicalPath2Identifier,
19
19
  importPathFilename2Identifier as importPathFilename2Identifier, makeDirsSafely as makeDirsSafely,
20
- writeStringToHere as writeStringToHere)
20
+ writePython as writePython, writeStringToHere as writeStringToHere)
21
21
 
22
22
  from hunterMakesPy.dataStructures import stringItUp as stringItUp, updateExtendPolishDictionaryLists as updateExtendPolishDictionaryLists
23
23
 
24
- import sys
25
-
26
- if sys.version_info < (3, 14):
27
- from hunterMakesPy.dataStructures import autoDecodingRLE as autoDecodingRLE
24
+ from hunterMakesPy.dataStructures import autoDecodingRLE as autoDecodingRLE
28
25
 
29
26
  from hunterMakesPy._theSSOT import settingsPackage
@@ -36,14 +36,14 @@ class PackageSettings:
36
36
  package identifiers and installation paths if they are not passed to the `class` constructor. Python `dataclasses` are easy to
37
37
  subtype and extend.
38
38
 
39
- Parameters
39
+ Attributes
40
40
  ----------
41
41
  identifierPackageFALLBACK : str = ''
42
42
  Fallback package identifier used only during initialization when automatic discovery fails.
43
- pathPackage : Path = Path()
44
- Absolute path to the installed package directory. Automatically resolved from `identifierPackage` if not provided.
45
43
  identifierPackage : str = ''
46
- Canonical name of the package. Automatically extracted from `pyproject.toml`.
44
+ Canonical name of the package. Automatically extracted from "pyproject.toml".
45
+ pathPackage : Path = getPathPackageINSTALLING(identifierPackage)
46
+ Absolute path to the installed package directory. Automatically resolved from `identifierPackage` if not provided.
47
47
  fileExtension : str = '.py'
48
48
  Default file extension.
49
49
 
@@ -85,34 +85,39 @@ class PackageSettings:
85
85
  if self.pathPackage == Path() and self.identifierPackage:
86
86
  self.pathPackage = getPathPackageINSTALLING(self.identifierPackage)
87
87
 
88
- def raiseIfNone(returnTarget: TypeSansNone | None, errorMessage: str | None = None) -> TypeSansNone:
89
- """Raise a `ValueError` if the target value is `None`, otherwise return the value: tell the type checker that the return value is not `None`.
90
-
91
- (AI generated docstring)
88
+ def raiseIfNone(expression: TypeSansNone | None, errorMessage: str | None = None) -> TypeSansNone:
89
+ """Convert the `expression` return annotation from '`cerPytainty | None`' to '`cerPytainty`' because `expression` cannot be `None`; `raise` an `Exception` if you're wrong.
92
90
 
93
- This is a defensive programming function that converts unexpected `None` values into explicit errors with context. It is useful for asserting that functions that might return `None` have actually returned a meaningful value.
91
+ The Python interpreter evaluates `expression` to a value: think of a function call or an attribute access. You can use
92
+ `raiseIfNone` for fail early defensive programming. I use it, however, to cure type-checker-nihilism: that's when "or `None`"
93
+ return types cause your type checker to repeatedly say, "You can't do that because the value might be `None`."
94
94
 
95
95
  Parameters
96
96
  ----------
97
- returnTarget : TypeSansNone | None
98
- The value to check for `None`. If not `None`, this value is returned unchanged.
99
- errorMessage : str | None = None
100
- Custom error message to include in the `ValueError`. If `None`, a default message with debugging hints is used.
97
+ expression : TypeSansNone | None
98
+ Python code with a return type that is a `union` of `None` and `TypeSansNone`, which is a stand-in for one or more other types.
99
+ errorMessage : str | None = 'A function unexpectedly returned `None`. Hint: look at the traceback immediately before `raiseIfNone`.'
100
+ Custom error message for the `ValueError` `Exception` if `expression` is `None`.
101
101
 
102
102
  Returns
103
103
  -------
104
- returnTarget : TypeSansNone
105
- The original `returnTarget` value, guaranteed to not be `None`.
104
+ contentment : TypeSansNone
105
+ The value returned by `expression`, but guaranteed to not be `None`.
106
106
 
107
107
  Raises
108
108
  ------
109
109
  ValueError
110
- If `returnTarget` is `None`.
110
+ If the value returned by `expression` is `None`.
111
111
 
112
112
  Examples
113
113
  --------
114
- Ensure a function result is not `None`:
114
+ Basic usage with attribute access:
115
+ ```python
116
+ annotation = raiseIfNone(ast_arg.annotation)
117
+ # Raises ValueError if ast_arg.annotation is None
118
+ ```
115
119
 
120
+ Function return value validation:
116
121
  ```python
117
122
  def findFirstMatch(listItems: list[str], pattern: str) -> str | None:
118
123
  for item in listItems:
@@ -122,28 +127,27 @@ def raiseIfNone(returnTarget: TypeSansNone | None, errorMessage: str | None = No
122
127
 
123
128
  listFiles = ['document.txt', 'image.png', 'data.csv']
124
129
  filename = raiseIfNone(findFirstMatch(listFiles, '.txt'))
125
- # Returns 'document.txt'
130
+ # Returns 'document.txt' when match exists
126
131
  ```
127
132
 
128
- Handle dictionary lookups with custom error messages:
129
-
133
+ Dictionary value retrieval with custom message:
130
134
  ```python
131
135
  configurationMapping = {'host': 'localhost', 'port': 8080}
132
136
  host = raiseIfNone(configurationMapping.get('host'),
133
137
  "Configuration must include 'host' setting")
134
- # Returns 'localhost'
138
+ # Returns 'localhost' when key exists
135
139
 
136
140
  # This would raise ValueError with custom message:
137
141
  # database = raiseIfNone(configurationMapping.get('database'),
138
- # "Configuration must include 'database' setting")
142
+ # "Configuration must include 'database' setting")
139
143
  ```
140
144
 
141
145
  Thanks
142
146
  ------
143
- sobolevn, https://github.com/sobolevn, for the seed of the function. https://github.com/python/typing/discussions/1997#discussioncomment-13108399
147
+ sobolevn, https://github.com/sobolevn, for the seed of this function. https://github.com/python/typing/discussions/1997#discussioncomment-13108399
144
148
 
145
149
  """
146
- if returnTarget is None:
150
+ if expression is None:
147
151
  message = errorMessage or 'A function unexpectedly returned `None`. Hint: look at the traceback immediately before `raiseIfNone`.'
148
152
  raise ValueError(message)
149
- return returnTarget
153
+ return expression
@@ -0,0 +1,273 @@
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
+
10
+ def removeExtraWhitespace(text: str) -> str:
11
+ """Remove extra whitespace from string representation of Python data structures."""
12
+ # Remove spaces after commas
13
+ text = regex.sub(r',\s+', ',', text)
14
+ # Remove spaces after opening brackets/parens
15
+ text = regex.sub(r'([\[\(])\s+', r'\1', text)
16
+ # Remove spaces before closing brackets/parens
17
+ return regex.sub(r'\s+([\]\)])', r'\1', text)
18
+
19
+ def autoDecodingRLE(arrayTarget: NDArray[integer[Any]], *, assumeAddSpaces: bool = False) -> str:
20
+ """Transform a NumPy array into a compact, self-decoding run-length encoded string representation.
21
+
22
+ This function converts a NumPy array into a string that, when evaluated as Python code,
23
+ recreates the original array structure. The function employs two compression strategies:
24
+ 1. Python's `range` syntax for consecutive integer sequences
25
+ 2. Multiplication syntax for repeated elements
26
+
27
+ The resulting string representation is designed to be both human-readable and space-efficient,
28
+ especially for large cartesian mappings with repetitive patterns. When this string is used
29
+ as a data source, Python will automatically decode it into Python `list`, which if used as an
30
+ argument to `numpy.array()`, will recreate the original array structure.
31
+
32
+ Parameters
33
+ ----------
34
+ arrayTarget : NDArray[integer[Any]]
35
+ (array2target) The NumPy array to be encoded.
36
+ assumeAddSpaces : bool = False
37
+ (assume2add2spaces) Affects internal length comparison during compression decisions.
38
+ This parameter doesn't directly change output format but influences whether
39
+ `range` or multiplication syntax is preferred in certain cases. The parameter
40
+ exists because the Abstract Syntax Tree (AST) inserts spaces in its string
41
+ representation.
42
+
43
+ Returns
44
+ -------
45
+ rleString : str
46
+ (rle2string) A string representation of the array using run-length encoding that,
47
+ when evaluated as Python code, reproduces the original array structure.
48
+
49
+ Notes
50
+ -----
51
+ The "autoDecoding" feature means that the string representation evaluates directly
52
+ to the desired data structure without explicit decompression steps.
53
+
54
+ """
55
+ def sliceNDArrayToNestedLists(arraySlice: NDArray[integer[Any]]) -> Any:
56
+ def getLengthOption(optionAsStr: str) -> int:
57
+ """`assumeAddSpaces` characters: `,` 1; `]*` 2."""
58
+ return assumeAddSpaces * (optionAsStr.count(',') + optionAsStr.count(']*') * 2) + len(optionAsStr)
59
+
60
+ if arraySlice.ndim > 1:
61
+ axisOfOperation = 0
62
+ return [sliceNDArrayToNestedLists(arraySlice[index]) for index in range(arraySlice.shape[axisOfOperation])]
63
+ if arraySlice.ndim == 1:
64
+ arraySliceAsList: list[int | range] = []
65
+ cache_consecutiveGroup_addMe: dict[Iterator[Any], list[int] | list[range]] = {}
66
+ for consecutiveGroup in more_itertools.consecutive_groups(arraySlice.tolist()):
67
+ if consecutiveGroup in cache_consecutiveGroup_addMe:
68
+ addMe = cache_consecutiveGroup_addMe[consecutiveGroup]
69
+ else:
70
+ ImaSerious: list[int] = list(consecutiveGroup)
71
+ ImaRange = [range(ImaSerious[0], ImaSerious[-1] + 1)]
72
+ ImaRangeAsStr = removeExtraWhitespace(str(ImaRange)).replace('range(0,', 'range(').replace('range', '*range')
73
+
74
+ option1 = ImaRange
75
+ option1AsStr = ImaRangeAsStr
76
+ option2 = ImaSerious
77
+ option2AsStr = None
78
+
79
+ # alpha, potential function
80
+ option1AsStr = option1AsStr or removeExtraWhitespace(str(option1))
81
+ lengthOption1 = getLengthOption(option1AsStr)
82
+
83
+ option2AsStr = option2AsStr or removeExtraWhitespace(str(option2))
84
+ lengthOption2 = getLengthOption(option2AsStr)
85
+
86
+ if lengthOption1 < lengthOption2:
87
+ addMe = option1
88
+ else:
89
+ addMe = option2
90
+
91
+ cache_consecutiveGroup_addMe[consecutiveGroup] = addMe
92
+
93
+ arraySliceAsList += addMe
94
+
95
+ listRangeAndTuple: list[int | range | tuple[int | range, int]] = []
96
+ cache_malkovichGrouped_addMe: dict[tuple[int | range, int], list[tuple[int | range, int]] | list[int | range]] = {}
97
+ for malkovichGrouped in more_itertools.run_length.encode(arraySliceAsList):
98
+ if malkovichGrouped in cache_malkovichGrouped_addMe:
99
+ addMe = cache_malkovichGrouped_addMe[malkovichGrouped]
100
+ else:
101
+ lengthMalkovich = malkovichGrouped[-1]
102
+ malkovichAsList = list(more_itertools.run_length.decode([malkovichGrouped]))
103
+ malkovichMalkovich = f"[{malkovichGrouped[0]}]*{lengthMalkovich}"
104
+
105
+ option1 = [malkovichGrouped]
106
+ option1AsStr = malkovichMalkovich
107
+ option2 = malkovichAsList
108
+ option2AsStr = None
109
+
110
+ # beta, potential function
111
+ option1AsStr = option1AsStr or removeExtraWhitespace(str(option1))
112
+ lengthOption1 = getLengthOption(option1AsStr)
113
+
114
+ option2AsStr = option2AsStr or removeExtraWhitespace(str(option2))
115
+ lengthOption2 = getLengthOption(option2AsStr)
116
+
117
+ if lengthOption1 < lengthOption2:
118
+ addMe = option1
119
+ else:
120
+ addMe = option2
121
+
122
+ cache_malkovichGrouped_addMe[malkovichGrouped] = addMe
123
+
124
+ listRangeAndTuple += addMe
125
+
126
+ return listRangeAndTuple
127
+ return arraySlice
128
+
129
+ arrayAsNestedLists = sliceNDArrayToNestedLists(arrayTarget)
130
+
131
+ arrayAsStr = removeExtraWhitespace(str(arrayAsNestedLists))
132
+
133
+ patternRegex = regex.compile(
134
+ "(?<!rang)(?:"
135
+ # Pattern 1: Comma ahead, bracket behind # noqa: ERA001
136
+ "(?P<joinAhead>,)\\((?P<malkovich>\\d+),(?P<multiply>\\d+)\\)(?P<bracketBehind>])|"
137
+ # Pattern 2: Bracket or start ahead, comma behind # noqa: ERA001
138
+ "(?P<bracketOrStartAhead>\\[|^.)\\((?P<malkovichMalkovich>\\d+),(?P<multiplyIDK>\\d+)\\)(?P<joinBehind>,)|"
139
+ # Pattern 3: Bracket ahead, bracket behind # noqa: ERA001
140
+ "(?P<bracketAhead>\\[)\\((?P<malkovichMalkovichMalkovich>\\d+),(?P<multiply_whatever>\\d+)\\)(?P<bracketBehindBracketBehind>])|"
141
+ # Pattern 4: Comma ahead, comma behind # noqa: ERA001
142
+ "(?P<joinAheadJoinAhead>,)\\((?P<malkovichMalkovichMalkovichMalkovich>\\d+),(?P<multiplyOrSomething>\\d+)\\)(?P<joinBehindJoinBehind>,)"
143
+ ")"
144
+ )
145
+
146
+ def replacementByContext(match: regex.Match[str]) -> str:
147
+ """Generate replacement string based on context patterns."""
148
+ elephino = match.groupdict()
149
+ joinAhead = elephino.get('joinAhead') or elephino.get('joinAheadJoinAhead')
150
+ malkovich = elephino.get('malkovich') or elephino.get('malkovichMalkovich') or elephino.get('malkovichMalkovichMalkovich') or elephino.get('malkovichMalkovichMalkovichMalkovich')
151
+ multiply = elephino.get('multiply') or elephino.get('multiplyIDK') or elephino.get('multiply_whatever') or elephino.get('multiplyOrSomething')
152
+ joinBehind = elephino.get('joinBehind') or elephino.get('joinBehindJoinBehind')
153
+
154
+ replaceAhead = "]+[" if joinAhead == "," else "["
155
+
156
+ replaceBehind = "+[" if joinBehind == "," else ""
157
+
158
+ return f"{replaceAhead}{malkovich}]*{multiply}{replaceBehind}"
159
+
160
+ arrayAsStr = patternRegex.sub(replacementByContext, arrayAsStr)
161
+ arrayAsStr = patternRegex.sub(replacementByContext, arrayAsStr)
162
+
163
+ # Replace `range(0,stop)` syntax with `range(stop)` syntax. # noqa: ERA001
164
+ # Add unpack operator `*` for automatic decoding when evaluated.
165
+ return arrayAsStr.replace('range(0,', 'range(').replace('range', '*range')
166
+
167
+ def stringItUp(*scrapPile: Any) -> list[str]:
168
+ """Convert, if possible, every element in the input data structure to a string.
169
+
170
+ Order is not preserved or readily predictable.
171
+
172
+ Parameters
173
+ ----------
174
+ *scrapPile : Any
175
+ (scrap2pile) One or more data structures to unpack and convert to strings.
176
+
177
+ Returns
178
+ -------
179
+ listStrungUp : list[str]
180
+ (list2strung2up) A `list` of string versions of all convertible elements.
181
+
182
+ """
183
+ scrap = None
184
+ listStrungUp: list[str] = []
185
+
186
+ def drill(KitKat: Any) -> None:
187
+ match KitKat:
188
+ case str():
189
+ listStrungUp.append(KitKat)
190
+ case bool() | bytearray() | bytes() | complex() | float() | int() | memoryview() | None:
191
+ listStrungUp.append(str(KitKat)) # pyright: ignore [reportUnknownArgumentType]
192
+ case dict():
193
+ for broken, piece in KitKat.items(): # pyright: ignore [reportUnknownVariableType]
194
+ drill(broken)
195
+ drill(piece)
196
+ case list() | tuple() | set() | frozenset() | range():
197
+ for kit in KitKat: # pyright: ignore [reportUnknownVariableType]
198
+ drill(kit)
199
+ case _:
200
+ if hasattr(KitKat, '__iter__'): # Unpack other iterables
201
+ for kat in KitKat:
202
+ drill(kat)
203
+ else:
204
+ try:
205
+ sharingIsCaring = KitKat.__str__()
206
+ listStrungUp.append(sharingIsCaring)
207
+ except AttributeError:
208
+ pass
209
+ 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."
210
+ pass
211
+ except:
212
+ print(f"\nWoah! I received '{repr(KitKat)}'.\nTheir report card says, 'Plays well with others: Needs improvement.'\n") # noqa: T201
213
+ raise
214
+ try:
215
+ for scrap in scrapPile:
216
+ drill(scrap)
217
+ except RecursionError:
218
+ listStrungUp.append(repr(scrap))
219
+ return listStrungUp
220
+
221
+ def updateExtendPolishDictionaryLists(*dictionaryLists: Mapping[str, list[Any] | set[Any] | tuple[Any, ...]], destroyDuplicates: bool = False, reorderLists: bool = False, killErroneousDataTypes: bool = False) -> dict[str, list[Any]]:
222
+ """Merge multiple dictionaries containing `list` into a single dictionary.
223
+
224
+ With options to handle duplicates, `list` ordering, and erroneous data types.
225
+
226
+ Parameters
227
+ ----------
228
+ *dictionaryLists : Mapping[str, list[Any] | set[Any] | tuple[Any, ...]]
229
+ (dictionary2lists) Variable number of dictionaries to be merged. If only one dictionary is passed, it will be processed based on the provided options.
230
+ destroyDuplicates : bool = False
231
+ (destroy2duplicates) If `True`, removes duplicate elements from the `list`. Defaults to `False`.
232
+ reorderLists : bool = False
233
+ (reorder2lists) If `True`, sorts the `list`. Defaults to `False`.
234
+ killErroneousDataTypes : bool = False
235
+ (kill2erroneous2data2types) If `True`, skips dictionary keys or dictionary values that cause a `TypeError` during merging. Defaults to `False`.
236
+
237
+ Returns
238
+ -------
239
+ ePluribusUnum : dict[str, list[Any]]
240
+ (e2pluribus2unum) A single dictionary with merged `list` based on the provided options. If only one dictionary is passed,
241
+ it will be cleaned up based on the options.
242
+
243
+ Notes
244
+ -----
245
+ The returned value, `ePluribusUnum`, is a so-called primitive dictionary (`dict`). Furthermore, every dictionary key is a
246
+ so-called primitive string (cf. `str()`) and every dictionary value is a so-called primitive `list` (`list`). If
247
+ `dictionaryLists` has other data types, the data types will not be preserved. That could have unexpected consequences.
248
+ Conversion from the original data type to a `list`, for example, may not preserve the order even if you want the order to be
249
+ preserved.
250
+
251
+ """
252
+ ePluribusUnum: dict[str, list[Any]] = {}
253
+
254
+ for dictionaryListTarget in dictionaryLists:
255
+ for keyName, keyValue in dictionaryListTarget.items():
256
+ try:
257
+ ImaStr = str(keyName)
258
+ ImaList = list(keyValue)
259
+ ePluribusUnum.setdefault(ImaStr, []).extend(ImaList)
260
+ except TypeError:
261
+ if killErroneousDataTypes:
262
+ continue
263
+ else:
264
+ raise
265
+
266
+ if destroyDuplicates:
267
+ for ImaStr, ImaList in ePluribusUnum.items():
268
+ ePluribusUnum[ImaStr] = list(dict.fromkeys(ImaList))
269
+ if reorderLists:
270
+ for ImaStr, ImaList in ePluribusUnum.items():
271
+ ePluribusUnum[ImaStr] = sorted(ImaList)
272
+
273
+ return ePluribusUnum
@@ -3,8 +3,9 @@
3
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
+ from autoflake import fix_code as autoflake_fix_code
7
7
  from hunterMakesPy import identifierDotAttribute
8
+ from isort import code as isort_code
8
9
  from os import PathLike
9
10
  from pathlib import Path, PurePath
10
11
  from typing import Any, TYPE_CHECKING, TypeVar
@@ -97,6 +98,59 @@ def makeDirsSafely(pathFilename: Any) -> None:
97
98
  with contextlib.suppress(OSError):
98
99
  Path(pathFilename).parent.mkdir(parents=True, exist_ok=True)
99
100
 
101
+ settings_autoflakeDEFAULT: dict[str, list[str] | bool] = {
102
+ 'additional_imports': [],
103
+ 'expand_star_imports': True,
104
+ 'remove_all_unused_imports': True,
105
+ 'remove_duplicate_keys': False,
106
+ 'remove_unused_variables': False,
107
+ }
108
+
109
+ settings_isortDEFAULT: dict[str, int | str | list[str]] = {
110
+ "combine_as_imports": True,
111
+ "force_alphabetical_sort_within_sections": True,
112
+ "from_first": True,
113
+ "honor_noqa": True,
114
+ "indent": "\t",
115
+ "line_length": 120,
116
+ "lines_after_imports": 1,
117
+ "lines_between_types": 0,
118
+ "multi_line_output": 4,
119
+ "no_sections": True,
120
+ "skip": ["__init__.py"], # TODO think
121
+ "use_parentheses": True,
122
+ }
123
+
124
+ def writePython(pythonSource: str, pathFilename: PathLike[Any] | PurePath | io.TextIOBase, settings: dict[str, dict[str, Any]] | None = None) -> None:
125
+ """Format and write Python source code to a file or text stream.
126
+
127
+ (AI generated docstring)
128
+
129
+ This function processes Python source code through autoflake and isort formatters before writing to the specified destination.
130
+ The formatters remove unused imports, sort imports, and apply consistent code style according to the provided or default
131
+ settings.
132
+
133
+ Parameters
134
+ ----------
135
+ pythonSource : str
136
+ The Python source code to format and write.
137
+ pathFilename : PathLike[Any] | PurePath | io.TextIOBase
138
+ The target destination: either a file path or an open text stream.
139
+ settings : dict[str, dict[str, Any]] | None = None
140
+ Configuration for the formatters. Keys are `'autoflake'` and `'isort'`, each mapping to a dictionary of formatter-specific
141
+ settings. If `None`, default settings are used for both formatters.
142
+
143
+ """
144
+ if settings is None:
145
+ settings = {}
146
+
147
+ settings_autoflake: dict[str, Any] = settings.get('autoflake', settings_autoflakeDEFAULT)
148
+ pythonSource = autoflake_fix_code(pythonSource, **settings_autoflake)
149
+
150
+ settings_isort: dict[str, Any] = settings.get('isort', settings_isortDEFAULT)
151
+ pythonSource = isort_code(pythonSource, **settings_isort)
152
+ writeStringToHere(pythonSource + '\n', pathFilename)
153
+
100
154
  def writeStringToHere(this: str, pathFilename: PathLike[Any] | PurePath | io.TextIOBase) -> None:
101
155
  """Write a string to a file or text stream.
102
156