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.
- {huntermakespy-0.2.2 → huntermakespy-0.3.0}/PKG-INFO +4 -14
- {huntermakespy-0.2.2 → huntermakespy-0.3.0}/README.md +0 -9
- {huntermakespy-0.2.2 → huntermakespy-0.3.0}/hunterMakesPy/__init__.py +2 -5
- {huntermakespy-0.2.2 → huntermakespy-0.3.0}/hunterMakesPy/coping.py +29 -25
- huntermakespy-0.3.0/hunterMakesPy/dataStructures.py +273 -0
- {huntermakespy-0.2.2 → huntermakespy-0.3.0}/hunterMakesPy/filesystemToolkit.py +55 -1
- {huntermakespy-0.2.2 → huntermakespy-0.3.0}/hunterMakesPy/tests/test_dataStructures.py +115 -120
- {huntermakespy-0.2.2 → huntermakespy-0.3.0}/hunterMakesPy/tests/test_filesystemToolkit.py +187 -1
- {huntermakespy-0.2.2 → huntermakespy-0.3.0}/hunterMakesPy.egg-info/PKG-INFO +4 -14
- {huntermakespy-0.2.2 → huntermakespy-0.3.0}/hunterMakesPy.egg-info/requires.txt +2 -5
- {huntermakespy-0.2.2 → huntermakespy-0.3.0}/pyproject.toml +4 -5
- huntermakespy-0.2.2/hunterMakesPy/dataStructures.py +0 -268
- {huntermakespy-0.2.2 → huntermakespy-0.3.0}/LICENSE +0 -0
- {huntermakespy-0.2.2 → huntermakespy-0.3.0}/hunterMakesPy/_theSSOT.py +0 -0
- {huntermakespy-0.2.2 → huntermakespy-0.3.0}/hunterMakesPy/parseParameters.py +0 -0
- {huntermakespy-0.2.2 → huntermakespy-0.3.0}/hunterMakesPy/py.typed +0 -0
- {huntermakespy-0.2.2 → huntermakespy-0.3.0}/hunterMakesPy/pytestForYourUse.py +0 -0
- {huntermakespy-0.2.2 → huntermakespy-0.3.0}/hunterMakesPy/tests/__init__.py +0 -0
- {huntermakespy-0.2.2 → huntermakespy-0.3.0}/hunterMakesPy/tests/conftest.py +0 -0
- {huntermakespy-0.2.2 → huntermakespy-0.3.0}/hunterMakesPy/tests/test_coping.py +0 -0
- {huntermakespy-0.2.2 → huntermakespy-0.3.0}/hunterMakesPy/tests/test_parseParameters.py +0 -0
- {huntermakespy-0.2.2 → huntermakespy-0.3.0}/hunterMakesPy/theTypes.py +0 -0
- {huntermakespy-0.2.2 → huntermakespy-0.3.0}/hunterMakesPy.egg-info/SOURCES.txt +0 -0
- {huntermakespy-0.2.2 → huntermakespy-0.3.0}/hunterMakesPy.egg-info/dependency_links.txt +0 -0
- {huntermakespy-0.2.2 → huntermakespy-0.3.0}/hunterMakesPy.egg-info/top_level.txt +0 -0
- {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.
|
|
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 ::
|
|
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
|
[](https://HunterThinks.com/support)
|
|
166
165
|
[](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
|
[](https://creativecommons.org/licenses/by-nc/4.0/)
|
|
@@ -119,13 +119,4 @@ def test_myFunction(nameOfTest, callablePytest):
|
|
|
119
119
|
[](https://HunterThinks.com/support)
|
|
120
120
|
[](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
|
[](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
|
|
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
|
-
|
|
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
|
|
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(
|
|
89
|
-
"""
|
|
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
|
-
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
errorMessage : str | None = None
|
|
100
|
-
Custom error message
|
|
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
|
-
|
|
105
|
-
The
|
|
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 `
|
|
110
|
+
If the value returned by `expression` is `None`.
|
|
111
111
|
|
|
112
112
|
Examples
|
|
113
113
|
--------
|
|
114
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
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
|
|
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
|
|
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
|
|
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
|
|