hunterMakesPy 0.2.0__tar.gz → 0.2.2__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.0 → huntermakespy-0.2.2}/PKG-INFO +26 -30
- {huntermakespy-0.2.0 → huntermakespy-0.2.2}/README.md +22 -26
- {huntermakespy-0.2.0 → huntermakespy-0.2.2}/hunterMakesPy/dataStructures.py +4 -4
- {huntermakespy-0.2.0 → huntermakespy-0.2.2}/hunterMakesPy/filesystemToolkit.py +17 -9
- {huntermakespy-0.2.0 → huntermakespy-0.2.2}/hunterMakesPy/parseParameters.py +28 -24
- {huntermakespy-0.2.0 → huntermakespy-0.2.2}/hunterMakesPy/tests/conftest.py +31 -0
- huntermakespy-0.2.2/hunterMakesPy/tests/test_filesystemToolkit.py +263 -0
- {huntermakespy-0.2.0 → huntermakespy-0.2.2}/hunterMakesPy.egg-info/PKG-INFO +26 -30
- {huntermakespy-0.2.0 → huntermakespy-0.2.2}/pyproject.toml +2 -2
- huntermakespy-0.2.0/hunterMakesPy/tests/test_filesystemToolkit.py +0 -46
- {huntermakespy-0.2.0 → huntermakespy-0.2.2}/LICENSE +0 -0
- {huntermakespy-0.2.0 → huntermakespy-0.2.2}/hunterMakesPy/__init__.py +0 -0
- {huntermakespy-0.2.0 → huntermakespy-0.2.2}/hunterMakesPy/_theSSOT.py +0 -0
- {huntermakespy-0.2.0 → huntermakespy-0.2.2}/hunterMakesPy/coping.py +0 -0
- {huntermakespy-0.2.0 → huntermakespy-0.2.2}/hunterMakesPy/py.typed +0 -0
- {huntermakespy-0.2.0 → huntermakespy-0.2.2}/hunterMakesPy/pytestForYourUse.py +0 -0
- {huntermakespy-0.2.0 → huntermakespy-0.2.2}/hunterMakesPy/tests/__init__.py +0 -0
- {huntermakespy-0.2.0 → huntermakespy-0.2.2}/hunterMakesPy/tests/test_coping.py +0 -0
- {huntermakespy-0.2.0 → huntermakespy-0.2.2}/hunterMakesPy/tests/test_dataStructures.py +0 -0
- {huntermakespy-0.2.0 → huntermakespy-0.2.2}/hunterMakesPy/tests/test_parseParameters.py +0 -0
- {huntermakespy-0.2.0 → huntermakespy-0.2.2}/hunterMakesPy/theTypes.py +0 -0
- {huntermakespy-0.2.0 → huntermakespy-0.2.2}/hunterMakesPy.egg-info/SOURCES.txt +0 -0
- {huntermakespy-0.2.0 → huntermakespy-0.2.2}/hunterMakesPy.egg-info/dependency_links.txt +0 -0
- {huntermakespy-0.2.0 → huntermakespy-0.2.2}/hunterMakesPy.egg-info/requires.txt +0 -0
- {huntermakespy-0.2.0 → huntermakespy-0.2.2}/hunterMakesPy.egg-info/top_level.txt +0 -0
- {huntermakespy-0.2.0 → huntermakespy-0.2.2}/setup.cfg +0 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hunterMakesPy
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
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
|
|
7
7
|
Project-URL: Donate, https://www.patreon.com/integrated
|
|
8
|
-
Project-URL: Homepage, https://github.com/hunterhogan/
|
|
9
|
-
Project-URL: Issues, https://github.com/hunterhogan/
|
|
10
|
-
Project-URL: Repository, https://github.com/hunterhogan/
|
|
8
|
+
Project-URL: Homepage, https://github.com/hunterhogan/hunterMakesPy
|
|
9
|
+
Project-URL: Issues, https://github.com/hunterhogan/hunterMakesPy/issues
|
|
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
12
|
Classifier: Development Status :: 4 - Beta
|
|
13
13
|
Classifier: Environment :: Console
|
|
@@ -74,7 +74,7 @@ def findConfiguration(configName: str) -> dict[str, str] | None:
|
|
|
74
74
|
|
|
75
75
|
config = raiseIfNone(
|
|
76
76
|
findConfiguration("database"),
|
|
77
|
-
"Configuration 'database'
|
|
77
|
+
"I could not find Configuration 'database', but I need it to continue."
|
|
78
78
|
)
|
|
79
79
|
```
|
|
80
80
|
|
|
@@ -83,19 +83,19 @@ config = raiseIfNone(
|
|
|
83
83
|
Parameter validation, integer parsing, and concurrency handling.
|
|
84
84
|
|
|
85
85
|
```python
|
|
86
|
-
|
|
86
|
+
import hunterMakesPy as humpy
|
|
87
87
|
|
|
88
88
|
# Smart concurrency limit calculation
|
|
89
|
-
cpuLimit = defineConcurrencyLimit(limit=0.75) # Use 75% of available CPUs
|
|
90
|
-
cpuLimit = defineConcurrencyLimit(limit=True) # Use exactly 1 CPU
|
|
91
|
-
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
|
|
92
92
|
|
|
93
93
|
# Robust integer validation
|
|
94
|
-
validatedIntegers = intInnit([1, "2", 3.0, "4"], "port_numbers")
|
|
94
|
+
validatedIntegers = humpy.intInnit([1, "2", 3.0, "4"], "port_numbers")
|
|
95
95
|
|
|
96
96
|
# String-to-boolean conversion for configuration
|
|
97
97
|
userInput = "True"
|
|
98
|
-
booleanValue = oopsieKwargsie(userInput) # Returns True
|
|
98
|
+
booleanValue = humpy.oopsieKwargsie(userInput) # Returns True
|
|
99
99
|
```
|
|
100
100
|
|
|
101
101
|
## File System Utilities
|
|
@@ -103,20 +103,15 @@ booleanValue = oopsieKwargsie(userInput) # Returns True
|
|
|
103
103
|
Safe file operations and dynamic module importing.
|
|
104
104
|
|
|
105
105
|
```python
|
|
106
|
-
|
|
107
|
-
importLogicalPath2Identifier,
|
|
108
|
-
importPathFilename2Identifier,
|
|
109
|
-
makeDirsSafely,
|
|
110
|
-
writeStringToHere
|
|
111
|
-
)
|
|
106
|
+
import hunterMakesPy as humpy
|
|
112
107
|
|
|
113
108
|
# Dynamic imports
|
|
114
|
-
gcdFunction = importLogicalPath2Identifier("math", "gcd")
|
|
115
|
-
customFunction = importPathFilename2Identifier("path/to/module.py", "functionName")
|
|
109
|
+
gcdFunction = humpy.importLogicalPath2Identifier("math", "gcd")
|
|
110
|
+
customFunction = humpy.importPathFilename2Identifier("path/to/module.py", "functionName")
|
|
116
111
|
|
|
117
112
|
# Safe file operations
|
|
118
113
|
pathFilename = Path("deep/nested/directory/file.txt")
|
|
119
|
-
writeStringToHere("content", pathFilename) # Creates directories automatically
|
|
114
|
+
humpy.writeStringToHere("content", pathFilename) # Creates directories automatically
|
|
120
115
|
```
|
|
121
116
|
|
|
122
117
|
## Data Structure Manipulation
|
|
@@ -124,21 +119,21 @@ writeStringToHere("content", pathFilename) # Creates directories automatically
|
|
|
124
119
|
Utilities for string extraction, data flattening, and array compression.
|
|
125
120
|
|
|
126
121
|
```python
|
|
127
|
-
|
|
122
|
+
import hunterMakesPy as humpy
|
|
128
123
|
import numpy
|
|
129
124
|
|
|
130
125
|
# Extract all strings from nested data structures
|
|
131
126
|
nestedData = {"config": [1, "host", {"port": 8080}], "users": ["alice", "bob"]}
|
|
132
|
-
allStrings = stringItUp(nestedData) # ['config', 'host', 'port', 'users', 'alice', 'bob']
|
|
127
|
+
allStrings = humpy.stringItUp(nestedData) # ['config', 'host', 'port', 'users', 'alice', 'bob']
|
|
133
128
|
|
|
134
129
|
# Merge dictionaries containing lists
|
|
135
|
-
dictionaryAlpha = {"servers": ["
|
|
136
|
-
dictionaryBeta = {"servers": ["
|
|
137
|
-
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)
|
|
138
133
|
|
|
139
134
|
# Compress NumPy arrays with run-length encoding
|
|
140
135
|
arrayData = numpy.array([1, 2, 3, 4, 5, 5, 5, 6, 7, 8, 9])
|
|
141
|
-
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)]"
|
|
142
137
|
```
|
|
143
138
|
|
|
144
139
|
## Testing
|
|
@@ -146,7 +141,7 @@ compressed = autoDecodingRLE(arrayData) # "[1,*range(2,6)]+[5]*2+[*range(6,10)]
|
|
|
146
141
|
The package includes comprehensive test suites that you can import and run:
|
|
147
142
|
|
|
148
143
|
```python
|
|
149
|
-
from hunterMakesPy.
|
|
144
|
+
from hunterMakesPy.tests.test_parseParameters import (
|
|
150
145
|
PytestFor_defineConcurrencyLimit,
|
|
151
146
|
PytestFor_intInnit,
|
|
152
147
|
PytestFor_oopsieKwargsie
|
|
@@ -158,8 +153,9 @@ for nameOfTest, callablePytest in listOfTests:
|
|
|
158
153
|
callablePytest()
|
|
159
154
|
|
|
160
155
|
# Or test your own compatible functions
|
|
161
|
-
@pytest.mark.parametrize(
|
|
162
|
-
|
|
156
|
+
@pytest.mark.parametrize(
|
|
157
|
+
"nameOfTest,callablePytest"
|
|
158
|
+
, PytestFor_intInnit(callableToTest=myFunction))
|
|
163
159
|
def test_myFunction(nameOfTest, callablePytest):
|
|
164
160
|
callablePytest()
|
|
165
161
|
```
|
|
@@ -178,4 +174,4 @@ Coding One Step at a Time:
|
|
|
178
174
|
2. Write good code.
|
|
179
175
|
3. When revising, write better code.
|
|
180
176
|
|
|
181
|
-
[](https://creativecommons.org/licenses/by-nc/4.0/)
|
|
@@ -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'
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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": ["
|
|
90
|
-
dictionaryBeta = {"servers": ["
|
|
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.
|
|
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(
|
|
116
|
-
|
|
110
|
+
@pytest.mark.parametrize(
|
|
111
|
+
"nameOfTest,callablePytest"
|
|
112
|
+
, PytestFor_intInnit(callableToTest=myFunction))
|
|
117
113
|
def test_myFunction(nameOfTest, callablePytest):
|
|
118
114
|
callablePytest()
|
|
119
115
|
```
|
|
@@ -132,4 +128,4 @@ Coding One Step at a Time:
|
|
|
132
128
|
2. Write good code.
|
|
133
129
|
3. When revising, write better code.
|
|
134
130
|
|
|
135
|
-
[](https://creativecommons.org/licenses/by-nc/4.0/)
|
|
@@ -11,7 +11,7 @@ import sys
|
|
|
11
11
|
if sys.version_info < (3, 14):
|
|
12
12
|
import python_minifier
|
|
13
13
|
|
|
14
|
-
def autoDecodingRLE(arrayTarget: NDArray[integer[Any]], *, assumeAddSpaces: bool = False) -> str:
|
|
14
|
+
def autoDecodingRLE(arrayTarget: NDArray[integer[Any]], *, assumeAddSpaces: bool = False) -> str:
|
|
15
15
|
"""Transform a NumPy array into a compact, self-decoding run-length encoded string representation.
|
|
16
16
|
|
|
17
17
|
This function converts a NumPy array into a string that, when evaluated as Python code,
|
|
@@ -159,7 +159,7 @@ if sys.version_info < (3, 14):
|
|
|
159
159
|
# Add unpack operator `*` for automatic decoding when evaluated.
|
|
160
160
|
return arrayAsStr.replace('range(0,', 'range(').replace('range', '*range')
|
|
161
161
|
|
|
162
|
-
def stringItUp(*scrapPile: Any) -> list[str]:
|
|
162
|
+
def stringItUp(*scrapPile: Any) -> list[str]:
|
|
163
163
|
"""Convert, if possible, every element in the input data structure to a string.
|
|
164
164
|
|
|
165
165
|
Order is not preserved or readily predictable.
|
|
@@ -178,7 +178,7 @@ def stringItUp(*scrapPile: Any) -> list[str]: # noqa: C901
|
|
|
178
178
|
scrap = None
|
|
179
179
|
listStrungUp: list[str] = []
|
|
180
180
|
|
|
181
|
-
def drill(KitKat: Any) -> None:
|
|
181
|
+
def drill(KitKat: Any) -> None:
|
|
182
182
|
match KitKat:
|
|
183
183
|
case str():
|
|
184
184
|
listStrungUp.append(KitKat)
|
|
@@ -204,7 +204,7 @@ def stringItUp(*scrapPile: Any) -> list[str]: # noqa: C901
|
|
|
204
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
205
|
pass
|
|
206
206
|
except:
|
|
207
|
-
print(f"\nWoah! I received '{repr(KitKat)}'.\nTheir report card says, 'Plays well with others: Needs improvement.'\n") # noqa:
|
|
207
|
+
print(f"\nWoah! I received '{repr(KitKat)}'.\nTheir report card says, 'Plays well with others: Needs improvement.'\n") # noqa: T201
|
|
208
208
|
raise
|
|
209
209
|
try:
|
|
210
210
|
for scrap in scrapPile:
|
|
@@ -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,
|
|
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
|
|
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
|
|
107
|
-
pathFilename : PathLike[Any] | PurePath
|
|
108
|
-
The
|
|
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
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
+
|
|
@@ -75,7 +75,7 @@ def _constructErrorMessage(context: ErrorMessageContext, parameterName: str, par
|
|
|
75
75
|
|
|
76
76
|
return "".join(messageParts)
|
|
77
77
|
|
|
78
|
-
def defineConcurrencyLimit(*, limit: bool | float | int | None, cpuTotal: int = multiprocessing.cpu_count()) -> int:
|
|
78
|
+
def defineConcurrencyLimit(*, limit: bool | float | int | None, cpuTotal: int = multiprocessing.cpu_count()) -> int:
|
|
79
79
|
"""Determine the concurrency limit based on the provided parameter.
|
|
80
80
|
|
|
81
81
|
Tests for this function can be run with:
|
|
@@ -84,8 +84,7 @@ def defineConcurrencyLimit(*, limit: bool | float | int | None, cpuTotal: int =
|
|
|
84
84
|
Parameters
|
|
85
85
|
----------
|
|
86
86
|
limit : bool | float | int | None
|
|
87
|
-
Whether and how to limit CPU usage.
|
|
88
|
-
negative values have different behaviors, see code for details.
|
|
87
|
+
Whether and how to limit CPU usage. See notes and examples for details how to describe the options to your users.
|
|
89
88
|
cpuTotal : int = multiprocessing.cpu_count()
|
|
90
89
|
The total number of CPUs available in the system. Default is `multiprocessing.cpu_count()`.
|
|
91
90
|
|
|
@@ -96,35 +95,40 @@ def defineConcurrencyLimit(*, limit: bool | float | int | None, cpuTotal: int =
|
|
|
96
95
|
|
|
97
96
|
Notes
|
|
98
97
|
-----
|
|
99
|
-
|
|
100
|
-
example:
|
|
98
|
+
Consider using `hunterMakesPy.oopsieKwargsie()` to handle malformed inputs. For example:
|
|
101
99
|
|
|
102
100
|
```
|
|
103
101
|
if not (CPUlimit is None or isinstance(CPUlimit, (bool, int, float))):
|
|
104
102
|
CPUlimit = oopsieKwargsie(CPUlimit)
|
|
105
103
|
```
|
|
106
104
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
105
|
+
Example parameters
|
|
106
|
+
------------------
|
|
107
|
+
```python
|
|
108
|
+
CPUlimit: bool | float | int | None
|
|
109
|
+
CPUlimit: bool | float | int | None = None
|
|
110
|
+
```
|
|
112
111
|
|
|
113
|
-
Example docstring
|
|
114
|
-
|
|
112
|
+
Example docstring
|
|
113
|
+
-----------------
|
|
114
|
+
```python
|
|
115
115
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
116
|
+
Arguments
|
|
117
|
+
---------
|
|
118
|
+
CPUlimit: bool | float | int | None
|
|
119
|
+
Whether and how to limit the the number of available processors used by the function. See notes for details.
|
|
119
120
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
121
|
+
Notes
|
|
122
|
+
-----
|
|
123
|
+
Limits on CPU usage, `CPUlimit`:
|
|
124
|
+
- `False`, `None`, or `0`: No limits on processor usage; uses all available processors. All other values will potentially limit processor usage.
|
|
125
|
+
- `True`: Yes, limit the processor usage; limits to 1 processor.
|
|
126
|
+
- `int >= 1`: The maximum number of available processors to use.
|
|
127
|
+
- `0 < float < 1`: The maximum number of processors to use expressed as a fraction of available processors.
|
|
128
|
+
- `-1 < float < 0`: The number of processors to *not* use expressed as a fraction of available processors.
|
|
129
|
+
- `int <= -1`: The number of available processors to *not* use.
|
|
130
|
+
- If the value of `CPUlimit` is a `float` greater than 1 or less than -1, the function truncates the value to an `int` with the same sign as the `float`.
|
|
131
|
+
```
|
|
128
132
|
|
|
129
133
|
"""
|
|
130
134
|
concurrencyLimit = cpuTotal
|
|
@@ -160,7 +164,7 @@ def defineConcurrencyLimit(*, limit: bool | float | int | None, cpuTotal: int =
|
|
|
160
164
|
return max(int(concurrencyLimit), 1)
|
|
161
165
|
|
|
162
166
|
# ruff: noqa: TRY301
|
|
163
|
-
def intInnit(listInt_Allegedly: Iterable[Any], parameterName: str | None = None, parameterType: type[Any] | None = None) -> list[int]:
|
|
167
|
+
def intInnit(listInt_Allegedly: Iterable[Any], parameterName: str | None = None, parameterType: type[Any] | None = None) -> list[int]:
|
|
164
168
|
"""Validate and convert input values to a `list` of integers.
|
|
165
169
|
|
|
166
170
|
Accepts various numeric types and attempts to convert them into integers while providing descriptive error messages. This
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# pyright: standard
|
|
2
2
|
from collections.abc import Callable
|
|
3
3
|
from typing import Any
|
|
4
|
+
import io
|
|
4
5
|
import pathlib
|
|
5
6
|
import pytest
|
|
6
7
|
|
|
@@ -12,6 +13,36 @@ pathDataSamples = pathlib.Path("hunterMakesPy/tests/dataSamples")
|
|
|
12
13
|
def pathTmpTesting(tmp_path: pathlib.Path) -> pathlib.Path:
|
|
13
14
|
return tmp_path
|
|
14
15
|
|
|
16
|
+
# Fixture for predictable Python source code samples
|
|
17
|
+
@pytest.fixture
|
|
18
|
+
def dictionaryPythonSourceSamples() -> dict[str, str]:
|
|
19
|
+
"""Provide predictable Python source code samples for testing."""
|
|
20
|
+
return {
|
|
21
|
+
'functionFibonacci': "def fibonacciNumber():\n return 13\n",
|
|
22
|
+
'functionPrime': "def primeNumber():\n return 17\n",
|
|
23
|
+
'variablePrime': "prime = 19\n",
|
|
24
|
+
'variableFibonacci': "fibonacci = 21\n",
|
|
25
|
+
'classCardinal': "class CardinalDirection:\n north = 'N'\n south = 'S'\n",
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
# Fixture for IO stream objects
|
|
29
|
+
@pytest.fixture
|
|
30
|
+
def streamMemoryString() -> io.StringIO:
|
|
31
|
+
"""Provide a StringIO object for testing stream operations."""
|
|
32
|
+
return io.StringIO()
|
|
33
|
+
|
|
34
|
+
# Fixture for predictable directory names using cardinal directions
|
|
35
|
+
@pytest.fixture
|
|
36
|
+
def listDirectoryNamesCardinal() -> list[str]:
|
|
37
|
+
"""Provide predictable directory names using cardinal directions."""
|
|
38
|
+
return ['north', 'south', 'east', 'west']
|
|
39
|
+
|
|
40
|
+
# Fixture for predictable file content using Fibonacci numbers
|
|
41
|
+
@pytest.fixture
|
|
42
|
+
def listFileContentsFibonacci() -> list[str]:
|
|
43
|
+
"""Provide predictable file contents using Fibonacci sequence."""
|
|
44
|
+
return ['fibonacci8', 'fibonacci13', 'fibonacci21', 'fibonacci34']
|
|
45
|
+
|
|
15
46
|
def uniformTestFailureMessage(expected: Any, actual: Any, functionName: str, *arguments: Any, **keywordArguments: Any) -> str:
|
|
16
47
|
"""Format assertion message for any test comparison."""
|
|
17
48
|
listArgumentComponents: list[str] = [str(parameter) for parameter in arguments]
|
|
@@ -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
|
+
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hunterMakesPy
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
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
|
|
7
7
|
Project-URL: Donate, https://www.patreon.com/integrated
|
|
8
|
-
Project-URL: Homepage, https://github.com/hunterhogan/
|
|
9
|
-
Project-URL: Issues, https://github.com/hunterhogan/
|
|
10
|
-
Project-URL: Repository, https://github.com/hunterhogan/
|
|
8
|
+
Project-URL: Homepage, https://github.com/hunterhogan/hunterMakesPy
|
|
9
|
+
Project-URL: Issues, https://github.com/hunterhogan/hunterMakesPy/issues
|
|
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
12
|
Classifier: Development Status :: 4 - Beta
|
|
13
13
|
Classifier: Environment :: Console
|
|
@@ -74,7 +74,7 @@ def findConfiguration(configName: str) -> dict[str, str] | None:
|
|
|
74
74
|
|
|
75
75
|
config = raiseIfNone(
|
|
76
76
|
findConfiguration("database"),
|
|
77
|
-
"Configuration 'database'
|
|
77
|
+
"I could not find Configuration 'database', but I need it to continue."
|
|
78
78
|
)
|
|
79
79
|
```
|
|
80
80
|
|
|
@@ -83,19 +83,19 @@ config = raiseIfNone(
|
|
|
83
83
|
Parameter validation, integer parsing, and concurrency handling.
|
|
84
84
|
|
|
85
85
|
```python
|
|
86
|
-
|
|
86
|
+
import hunterMakesPy as humpy
|
|
87
87
|
|
|
88
88
|
# Smart concurrency limit calculation
|
|
89
|
-
cpuLimit = defineConcurrencyLimit(limit=0.75) # Use 75% of available CPUs
|
|
90
|
-
cpuLimit = defineConcurrencyLimit(limit=True) # Use exactly 1 CPU
|
|
91
|
-
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
|
|
92
92
|
|
|
93
93
|
# Robust integer validation
|
|
94
|
-
validatedIntegers = intInnit([1, "2", 3.0, "4"], "port_numbers")
|
|
94
|
+
validatedIntegers = humpy.intInnit([1, "2", 3.0, "4"], "port_numbers")
|
|
95
95
|
|
|
96
96
|
# String-to-boolean conversion for configuration
|
|
97
97
|
userInput = "True"
|
|
98
|
-
booleanValue = oopsieKwargsie(userInput) # Returns True
|
|
98
|
+
booleanValue = humpy.oopsieKwargsie(userInput) # Returns True
|
|
99
99
|
```
|
|
100
100
|
|
|
101
101
|
## File System Utilities
|
|
@@ -103,20 +103,15 @@ booleanValue = oopsieKwargsie(userInput) # Returns True
|
|
|
103
103
|
Safe file operations and dynamic module importing.
|
|
104
104
|
|
|
105
105
|
```python
|
|
106
|
-
|
|
107
|
-
importLogicalPath2Identifier,
|
|
108
|
-
importPathFilename2Identifier,
|
|
109
|
-
makeDirsSafely,
|
|
110
|
-
writeStringToHere
|
|
111
|
-
)
|
|
106
|
+
import hunterMakesPy as humpy
|
|
112
107
|
|
|
113
108
|
# Dynamic imports
|
|
114
|
-
gcdFunction = importLogicalPath2Identifier("math", "gcd")
|
|
115
|
-
customFunction = importPathFilename2Identifier("path/to/module.py", "functionName")
|
|
109
|
+
gcdFunction = humpy.importLogicalPath2Identifier("math", "gcd")
|
|
110
|
+
customFunction = humpy.importPathFilename2Identifier("path/to/module.py", "functionName")
|
|
116
111
|
|
|
117
112
|
# Safe file operations
|
|
118
113
|
pathFilename = Path("deep/nested/directory/file.txt")
|
|
119
|
-
writeStringToHere("content", pathFilename) # Creates directories automatically
|
|
114
|
+
humpy.writeStringToHere("content", pathFilename) # Creates directories automatically
|
|
120
115
|
```
|
|
121
116
|
|
|
122
117
|
## Data Structure Manipulation
|
|
@@ -124,21 +119,21 @@ writeStringToHere("content", pathFilename) # Creates directories automatically
|
|
|
124
119
|
Utilities for string extraction, data flattening, and array compression.
|
|
125
120
|
|
|
126
121
|
```python
|
|
127
|
-
|
|
122
|
+
import hunterMakesPy as humpy
|
|
128
123
|
import numpy
|
|
129
124
|
|
|
130
125
|
# Extract all strings from nested data structures
|
|
131
126
|
nestedData = {"config": [1, "host", {"port": 8080}], "users": ["alice", "bob"]}
|
|
132
|
-
allStrings = stringItUp(nestedData) # ['config', 'host', 'port', 'users', 'alice', 'bob']
|
|
127
|
+
allStrings = humpy.stringItUp(nestedData) # ['config', 'host', 'port', 'users', 'alice', 'bob']
|
|
133
128
|
|
|
134
129
|
# Merge dictionaries containing lists
|
|
135
|
-
dictionaryAlpha = {"servers": ["
|
|
136
|
-
dictionaryBeta = {"servers": ["
|
|
137
|
-
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)
|
|
138
133
|
|
|
139
134
|
# Compress NumPy arrays with run-length encoding
|
|
140
135
|
arrayData = numpy.array([1, 2, 3, 4, 5, 5, 5, 6, 7, 8, 9])
|
|
141
|
-
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)]"
|
|
142
137
|
```
|
|
143
138
|
|
|
144
139
|
## Testing
|
|
@@ -146,7 +141,7 @@ compressed = autoDecodingRLE(arrayData) # "[1,*range(2,6)]+[5]*2+[*range(6,10)]
|
|
|
146
141
|
The package includes comprehensive test suites that you can import and run:
|
|
147
142
|
|
|
148
143
|
```python
|
|
149
|
-
from hunterMakesPy.
|
|
144
|
+
from hunterMakesPy.tests.test_parseParameters import (
|
|
150
145
|
PytestFor_defineConcurrencyLimit,
|
|
151
146
|
PytestFor_intInnit,
|
|
152
147
|
PytestFor_oopsieKwargsie
|
|
@@ -158,8 +153,9 @@ for nameOfTest, callablePytest in listOfTests:
|
|
|
158
153
|
callablePytest()
|
|
159
154
|
|
|
160
155
|
# Or test your own compatible functions
|
|
161
|
-
@pytest.mark.parametrize(
|
|
162
|
-
|
|
156
|
+
@pytest.mark.parametrize(
|
|
157
|
+
"nameOfTest,callablePytest"
|
|
158
|
+
, PytestFor_intInnit(callableToTest=myFunction))
|
|
163
159
|
def test_myFunction(nameOfTest, callablePytest):
|
|
164
160
|
callablePytest()
|
|
165
161
|
```
|
|
@@ -178,4 +174,4 @@ Coding One Step at a Time:
|
|
|
178
174
|
2. Write good code.
|
|
179
175
|
3. When revising, write better code.
|
|
180
176
|
|
|
181
|
-
[](https://creativecommons.org/licenses/by-nc/4.0/)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "hunterMakesPy"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.2"
|
|
4
4
|
description = "Easy Python functions making making functional Python functions easier."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.11"
|
|
@@ -46,7 +46,7 @@ classifiers = [
|
|
|
46
46
|
"Topic :: Utilities",
|
|
47
47
|
"Typing :: Typed",
|
|
48
48
|
]
|
|
49
|
-
urls = { Donate = "https://www.patreon.com/integrated", Homepage = "https://github.com/hunterhogan/", Issues = "https://github.com/hunterhogan/", Repository = "https://github.com/hunterhogan/" }
|
|
49
|
+
urls = { Donate = "https://www.patreon.com/integrated", Homepage = "https://github.com/hunterhogan/hunterMakesPy", Issues = "https://github.com/hunterhogan/hunterMakesPy/issues", Repository = "https://github.com/hunterhogan/hunterMakesPy" }
|
|
50
50
|
dependencies = [
|
|
51
51
|
"charset_normalizer",
|
|
52
52
|
"more_itertools",
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
# pyright: standard
|
|
2
|
-
from hunterMakesPy import importLogicalPath2Identifier, importPathFilename2Identifier, makeDirsSafely, writeStringToHere
|
|
3
|
-
from hunterMakesPy.tests.conftest import uniformTestFailureMessage
|
|
4
|
-
import io
|
|
5
|
-
import math
|
|
6
|
-
import os
|
|
7
|
-
import pathlib
|
|
8
|
-
import pytest
|
|
9
|
-
|
|
10
|
-
def testMakeDirsSafelyCreatesParentDirectories(pathTmpTesting: pathlib.Path) -> None:
|
|
11
|
-
nestedDirectory = pathTmpTesting / "sub1" / "sub2"
|
|
12
|
-
filePath = nestedDirectory / "dummy.txt"
|
|
13
|
-
makeDirsSafely(filePath)
|
|
14
|
-
assert nestedDirectory.exists() and nestedDirectory.is_dir(), uniformTestFailureMessage(True, nestedDirectory.exists() and nestedDirectory.is_dir(), "testMakeDirsSafelyCreatesParentDirectories", filePath)
|
|
15
|
-
|
|
16
|
-
def testMakeDirsSafelyWithIOBaseDoesNotRaise() -> None:
|
|
17
|
-
memoryStream = io.StringIO()
|
|
18
|
-
makeDirsSafely(memoryStream)
|
|
19
|
-
|
|
20
|
-
def testWriteStringToHereCreatesFileAndWritesContent(pathTmpTesting: pathlib.Path) -> None:
|
|
21
|
-
nestedDirectory = pathTmpTesting / "a" / "b"
|
|
22
|
-
filePath = nestedDirectory / "test.txt"
|
|
23
|
-
writeStringToHere("hello world", filePath)
|
|
24
|
-
assert filePath.exists(), uniformTestFailureMessage(True, filePath.exists(), "testWriteStringToHereCreatesFileAndWritesContent", filePath)
|
|
25
|
-
assert filePath.read_text(encoding="utf-8") == "hello world", uniformTestFailureMessage("hello world", filePath.read_text(encoding="utf-8"), "testWriteStringToHereCreatesFileAndWritesContent", filePath)
|
|
26
|
-
|
|
27
|
-
@pytest.mark.parametrize(
|
|
28
|
-
"moduleName, identifier, expectedType",
|
|
29
|
-
[("math", "gcd", type(math.gcd)),("os.path", "join", type(os.path.join))])
|
|
30
|
-
def testImportLogicalPath2Identifier(moduleName: str, identifier: str, expectedType: type) -> None:
|
|
31
|
-
imported = importLogicalPath2Identifier(moduleName, identifier)
|
|
32
|
-
assert isinstance(imported, expectedType), uniformTestFailureMessage(expectedType, type(imported), "testImportLogicalPath2Identifier", (moduleName, identifier))
|
|
33
|
-
|
|
34
|
-
@pytest.mark.parametrize(
|
|
35
|
-
"source, identifier, expected"
|
|
36
|
-
, [("def fibonacciNumber():\n return 13\n", "fibonacciNumber", 13)
|
|
37
|
-
, ("prime = 17\n", "prime", 17)])
|
|
38
|
-
def testImportPathFilename2Identifier(tmp_path: pathlib.Path, source: str, identifier: str, expected: object) -> None:
|
|
39
|
-
filePath = tmp_path / "moduleTest.py"
|
|
40
|
-
filePath.write_text(source)
|
|
41
|
-
imported = importPathFilename2Identifier(filePath, identifier)
|
|
42
|
-
if callable(imported):
|
|
43
|
-
actual = imported()
|
|
44
|
-
else:
|
|
45
|
-
actual = imported
|
|
46
|
-
assert actual == expected, uniformTestFailureMessage(expected, actual, "testImportPathFilename2Identifier", (filePath, identifier))
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|