hunterMakesPy 0.2.1__py3-none-any.whl → 0.2.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hunterMakesPy/coping.py +29 -25
- hunterMakesPy/dataStructures.py +141 -131
- hunterMakesPy/parseParameters.py +28 -24
- hunterMakesPy/tests/test_dataStructures.py +115 -120
- {huntermakespy-0.2.1.dist-info → huntermakespy-0.2.4.dist-info}/METADATA +5 -6
- {huntermakespy-0.2.1.dist-info → huntermakespy-0.2.4.dist-info}/RECORD +9 -9
- {huntermakespy-0.2.1.dist-info → huntermakespy-0.2.4.dist-info}/WHEEL +0 -0
- {huntermakespy-0.2.1.dist-info → huntermakespy-0.2.4.dist-info}/licenses/LICENSE +0 -0
- {huntermakespy-0.2.1.dist-info → huntermakespy-0.2.4.dist-info}/top_level.txt +0 -0
hunterMakesPy/coping.py
CHANGED
|
@@ -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
|
hunterMakesPy/dataStructures.py
CHANGED
|
@@ -6,160 +6,170 @@ from numpy.typing import NDArray
|
|
|
6
6
|
from typing import Any
|
|
7
7
|
import more_itertools
|
|
8
8
|
import re as regex
|
|
9
|
-
import sys
|
|
10
|
-
|
|
11
|
-
if sys.version_info < (3, 14):
|
|
12
|
-
import python_minifier
|
|
13
|
-
|
|
14
|
-
def autoDecodingRLE(arrayTarget: NDArray[integer[Any]], *, assumeAddSpaces: bool = False) -> str: # noqa: C901, PLR0915
|
|
15
|
-
"""Transform a NumPy array into a compact, self-decoding run-length encoded string representation.
|
|
16
|
-
|
|
17
|
-
This function converts a NumPy array into a string that, when evaluated as Python code,
|
|
18
|
-
recreates the original array structure. The function employs two compression strategies:
|
|
19
|
-
1. Python's `range` syntax for consecutive integer sequences
|
|
20
|
-
2. Multiplication syntax for repeated elements
|
|
21
|
-
|
|
22
|
-
The resulting string representation is designed to be both human-readable and space-efficient,
|
|
23
|
-
especially for large cartesian mappings with repetitive patterns. When this string is used
|
|
24
|
-
as a data source, Python will automatically decode it into Python `list`, which if used as an
|
|
25
|
-
argument to `numpy.array()`, will recreate the original array structure.
|
|
26
|
-
|
|
27
|
-
Parameters
|
|
28
|
-
----------
|
|
29
|
-
arrayTarget : NDArray[integer[Any]]
|
|
30
|
-
(array2target) The NumPy array to be encoded.
|
|
31
|
-
assumeAddSpaces : bool = False
|
|
32
|
-
(assume2add2spaces) Affects internal length comparison during compression decisions.
|
|
33
|
-
This parameter doesn't directly change output format but influences whether
|
|
34
|
-
`range` or multiplication syntax is preferred in certain cases. The parameter
|
|
35
|
-
exists because the Abstract Syntax Tree (AST) inserts spaces in its string
|
|
36
|
-
representation.
|
|
37
|
-
|
|
38
|
-
Returns
|
|
39
|
-
-------
|
|
40
|
-
rleString : str
|
|
41
|
-
(rle2string) A string representation of the array using run-length encoding that,
|
|
42
|
-
when evaluated as Python code, reproduces the original array structure.
|
|
43
|
-
|
|
44
|
-
Notes
|
|
45
|
-
-----
|
|
46
|
-
The "autoDecoding" feature means that the string representation evaluates directly
|
|
47
|
-
to the desired data structure without explicit decompression steps.
|
|
48
|
-
|
|
49
|
-
"""
|
|
50
|
-
def sliceNDArrayToNestedLists(arraySlice: NDArray[integer[Any]]) -> Any:
|
|
51
|
-
def getLengthOption(optionAsStr: str) -> int:
|
|
52
|
-
"""`assumeAddSpaces` characters: `,` 1; `]*` 2."""
|
|
53
|
-
return assumeAddSpaces * (optionAsStr.count(',') + optionAsStr.count(']*') * 2) + len(optionAsStr)
|
|
54
|
-
|
|
55
|
-
if arraySlice.ndim > 1:
|
|
56
|
-
axisOfOperation = 0
|
|
57
|
-
return [sliceNDArrayToNestedLists(arraySlice[index]) for index in range(arraySlice.shape[axisOfOperation])]
|
|
58
|
-
if arraySlice.ndim == 1:
|
|
59
|
-
arraySliceAsList: list[int | range] = []
|
|
60
|
-
cache_consecutiveGroup_addMe: dict[Iterator[Any], list[int] | list[range]] = {}
|
|
61
|
-
for consecutiveGroup in more_itertools.consecutive_groups(arraySlice.tolist()):
|
|
62
|
-
if consecutiveGroup in cache_consecutiveGroup_addMe:
|
|
63
|
-
addMe = cache_consecutiveGroup_addMe[consecutiveGroup]
|
|
64
|
-
else:
|
|
65
|
-
ImaSerious: list[int] = list(consecutiveGroup)
|
|
66
|
-
ImaRange = [range(ImaSerious[0], ImaSerious[-1] + 1)]
|
|
67
|
-
ImaRangeAsStr = python_minifier.minify(str(ImaRange)).replace('range(0,', 'range(').replace('range', '*range')
|
|
68
9
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
10
|
+
def removeExtraWhitespace(text: str) -> str:
|
|
11
|
+
"""Remove extra whitespace from string representation of Python data structures.
|
|
12
|
+
|
|
13
|
+
This function replaces python_minifier.minify() for the specific use case of
|
|
14
|
+
minimizing string representations of lists, tuples, ranges, etc. It removes
|
|
15
|
+
spaces after commas, around brackets and parentheses.
|
|
16
|
+
"""
|
|
17
|
+
# Remove spaces after commas
|
|
18
|
+
text = regex.sub(r',\s+', ',', text)
|
|
19
|
+
# Remove spaces after opening brackets/parens
|
|
20
|
+
text = regex.sub(r'([\[\(])\s+', r'\1', text)
|
|
21
|
+
# Remove spaces before closing brackets/parens
|
|
22
|
+
return regex.sub(r'\s+([\]\)])', r'\1', text)
|
|
23
|
+
|
|
24
|
+
def autoDecodingRLE(arrayTarget: NDArray[integer[Any]], *, assumeAddSpaces: bool = False) -> str:
|
|
25
|
+
"""Transform a NumPy array into a compact, self-decoding run-length encoded string representation.
|
|
26
|
+
|
|
27
|
+
This function converts a NumPy array into a string that, when evaluated as Python code,
|
|
28
|
+
recreates the original array structure. The function employs two compression strategies:
|
|
29
|
+
1. Python's `range` syntax for consecutive integer sequences
|
|
30
|
+
2. Multiplication syntax for repeated elements
|
|
31
|
+
|
|
32
|
+
The resulting string representation is designed to be both human-readable and space-efficient,
|
|
33
|
+
especially for large cartesian mappings with repetitive patterns. When this string is used
|
|
34
|
+
as a data source, Python will automatically decode it into Python `list`, which if used as an
|
|
35
|
+
argument to `numpy.array()`, will recreate the original array structure.
|
|
36
|
+
|
|
37
|
+
Parameters
|
|
38
|
+
----------
|
|
39
|
+
arrayTarget : NDArray[integer[Any]]
|
|
40
|
+
(array2target) The NumPy array to be encoded.
|
|
41
|
+
assumeAddSpaces : bool = False
|
|
42
|
+
(assume2add2spaces) Affects internal length comparison during compression decisions.
|
|
43
|
+
This parameter doesn't directly change output format but influences whether
|
|
44
|
+
`range` or multiplication syntax is preferred in certain cases. The parameter
|
|
45
|
+
exists because the Abstract Syntax Tree (AST) inserts spaces in its string
|
|
46
|
+
representation.
|
|
47
|
+
|
|
48
|
+
Returns
|
|
49
|
+
-------
|
|
50
|
+
rleString : str
|
|
51
|
+
(rle2string) A string representation of the array using run-length encoding that,
|
|
52
|
+
when evaluated as Python code, reproduces the original array structure.
|
|
73
53
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
54
|
+
Notes
|
|
55
|
+
-----
|
|
56
|
+
The "autoDecoding" feature means that the string representation evaluates directly
|
|
57
|
+
to the desired data structure without explicit decompression steps.
|
|
77
58
|
|
|
78
|
-
|
|
79
|
-
|
|
59
|
+
"""
|
|
60
|
+
def sliceNDArrayToNestedLists(arraySlice: NDArray[integer[Any]]) -> Any:
|
|
61
|
+
def getLengthOption(optionAsStr: str) -> int:
|
|
62
|
+
"""`assumeAddSpaces` characters: `,` 1; `]*` 2."""
|
|
63
|
+
return assumeAddSpaces * (optionAsStr.count(',') + optionAsStr.count(']*') * 2) + len(optionAsStr)
|
|
64
|
+
|
|
65
|
+
if arraySlice.ndim > 1:
|
|
66
|
+
axisOfOperation = 0
|
|
67
|
+
return [sliceNDArrayToNestedLists(arraySlice[index]) for index in range(arraySlice.shape[axisOfOperation])]
|
|
68
|
+
if arraySlice.ndim == 1:
|
|
69
|
+
arraySliceAsList: list[int | range] = []
|
|
70
|
+
cache_consecutiveGroup_addMe: dict[Iterator[Any], list[int] | list[range]] = {}
|
|
71
|
+
for consecutiveGroup in more_itertools.consecutive_groups(arraySlice.tolist()):
|
|
72
|
+
if consecutiveGroup in cache_consecutiveGroup_addMe:
|
|
73
|
+
addMe = cache_consecutiveGroup_addMe[consecutiveGroup]
|
|
74
|
+
else:
|
|
75
|
+
ImaSerious: list[int] = list(consecutiveGroup)
|
|
76
|
+
ImaRange = [range(ImaSerious[0], ImaSerious[-1] + 1)]
|
|
77
|
+
ImaRangeAsStr = removeExtraWhitespace(str(ImaRange)).replace('range(0,', 'range(').replace('range', '*range')
|
|
80
78
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
79
|
+
option1 = ImaRange
|
|
80
|
+
option1AsStr = ImaRangeAsStr
|
|
81
|
+
option2 = ImaSerious
|
|
82
|
+
option2AsStr = None
|
|
85
83
|
|
|
86
|
-
|
|
84
|
+
# alpha, potential function
|
|
85
|
+
option1AsStr = option1AsStr or removeExtraWhitespace(str(option1))
|
|
86
|
+
lengthOption1 = getLengthOption(option1AsStr)
|
|
87
87
|
|
|
88
|
-
|
|
88
|
+
option2AsStr = option2AsStr or removeExtraWhitespace(str(option2))
|
|
89
|
+
lengthOption2 = getLengthOption(option2AsStr)
|
|
89
90
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
for malkovichGrouped in more_itertools.run_length.encode(arraySliceAsList):
|
|
93
|
-
if malkovichGrouped in cache_malkovichGrouped_addMe:
|
|
94
|
-
addMe = cache_malkovichGrouped_addMe[malkovichGrouped]
|
|
91
|
+
if lengthOption1 < lengthOption2:
|
|
92
|
+
addMe = option1
|
|
95
93
|
else:
|
|
96
|
-
|
|
97
|
-
malkovichAsList = list(more_itertools.run_length.decode([malkovichGrouped]))
|
|
98
|
-
malkovichMalkovich = f"[{malkovichGrouped[0]}]*{lengthMalkovich}"
|
|
94
|
+
addMe = option2
|
|
99
95
|
|
|
100
|
-
|
|
101
|
-
option1AsStr = malkovichMalkovich
|
|
102
|
-
option2 = malkovichAsList
|
|
103
|
-
option2AsStr = None
|
|
96
|
+
cache_consecutiveGroup_addMe[consecutiveGroup] = addMe
|
|
104
97
|
|
|
105
|
-
|
|
106
|
-
option1AsStr = option1AsStr or python_minifier.minify(str(option1))
|
|
107
|
-
lengthOption1 = getLengthOption(option1AsStr)
|
|
98
|
+
arraySliceAsList += addMe
|
|
108
99
|
|
|
109
|
-
|
|
110
|
-
|
|
100
|
+
listRangeAndTuple: list[int | range | tuple[int | range, int]] = []
|
|
101
|
+
cache_malkovichGrouped_addMe: dict[tuple[int | range, int], list[tuple[int | range, int]] | list[int | range]] = {}
|
|
102
|
+
for malkovichGrouped in more_itertools.run_length.encode(arraySliceAsList):
|
|
103
|
+
if malkovichGrouped in cache_malkovichGrouped_addMe:
|
|
104
|
+
addMe = cache_malkovichGrouped_addMe[malkovichGrouped]
|
|
105
|
+
else:
|
|
106
|
+
lengthMalkovich = malkovichGrouped[-1]
|
|
107
|
+
malkovichAsList = list(more_itertools.run_length.decode([malkovichGrouped]))
|
|
108
|
+
malkovichMalkovich = f"[{malkovichGrouped[0]}]*{lengthMalkovich}"
|
|
111
109
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
110
|
+
option1 = [malkovichGrouped]
|
|
111
|
+
option1AsStr = malkovichMalkovich
|
|
112
|
+
option2 = malkovichAsList
|
|
113
|
+
option2AsStr = None
|
|
114
|
+
|
|
115
|
+
# beta, potential function
|
|
116
|
+
option1AsStr = option1AsStr or removeExtraWhitespace(str(option1))
|
|
117
|
+
lengthOption1 = getLengthOption(option1AsStr)
|
|
118
|
+
|
|
119
|
+
option2AsStr = option2AsStr or removeExtraWhitespace(str(option2))
|
|
120
|
+
lengthOption2 = getLengthOption(option2AsStr)
|
|
121
|
+
|
|
122
|
+
if lengthOption1 < lengthOption2:
|
|
123
|
+
addMe = option1
|
|
124
|
+
else:
|
|
125
|
+
addMe = option2
|
|
116
126
|
|
|
117
|
-
|
|
127
|
+
cache_malkovichGrouped_addMe[malkovichGrouped] = addMe
|
|
118
128
|
|
|
119
|
-
|
|
129
|
+
listRangeAndTuple += addMe
|
|
120
130
|
|
|
121
|
-
|
|
122
|
-
|
|
131
|
+
return listRangeAndTuple
|
|
132
|
+
return arraySlice
|
|
123
133
|
|
|
124
|
-
|
|
134
|
+
arrayAsNestedLists = sliceNDArrayToNestedLists(arrayTarget)
|
|
125
135
|
|
|
126
|
-
|
|
136
|
+
arrayAsStr = removeExtraWhitespace(str(arrayAsNestedLists))
|
|
127
137
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
138
|
+
patternRegex = regex.compile(
|
|
139
|
+
"(?<!rang)(?:"
|
|
140
|
+
# Pattern 1: Comma ahead, bracket behind # noqa: ERA001
|
|
141
|
+
"(?P<joinAhead>,)\\((?P<malkovich>\\d+),(?P<multiply>\\d+)\\)(?P<bracketBehind>])|"
|
|
142
|
+
# Pattern 2: Bracket or start ahead, comma behind # noqa: ERA001
|
|
143
|
+
"(?P<bracketOrStartAhead>\\[|^.)\\((?P<malkovichMalkovich>\\d+),(?P<multiplyIDK>\\d+)\\)(?P<joinBehind>,)|"
|
|
144
|
+
# Pattern 3: Bracket ahead, bracket behind # noqa: ERA001
|
|
145
|
+
"(?P<bracketAhead>\\[)\\((?P<malkovichMalkovichMalkovich>\\d+),(?P<multiply_whatever>\\d+)\\)(?P<bracketBehindBracketBehind>])|"
|
|
146
|
+
# Pattern 4: Comma ahead, comma behind # noqa: ERA001
|
|
147
|
+
"(?P<joinAheadJoinAhead>,)\\((?P<malkovichMalkovichMalkovichMalkovich>\\d+),(?P<multiplyOrSomething>\\d+)\\)(?P<joinBehindJoinBehind>,)"
|
|
148
|
+
")"
|
|
149
|
+
)
|
|
140
150
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
151
|
+
def replacementByContext(match: regex.Match[str]) -> str:
|
|
152
|
+
"""Generate replacement string based on context patterns."""
|
|
153
|
+
elephino = match.groupdict()
|
|
154
|
+
joinAhead = elephino.get('joinAhead') or elephino.get('joinAheadJoinAhead')
|
|
155
|
+
malkovich = elephino.get('malkovich') or elephino.get('malkovichMalkovich') or elephino.get('malkovichMalkovichMalkovich') or elephino.get('malkovichMalkovichMalkovichMalkovich')
|
|
156
|
+
multiply = elephino.get('multiply') or elephino.get('multiplyIDK') or elephino.get('multiply_whatever') or elephino.get('multiplyOrSomething')
|
|
157
|
+
joinBehind = elephino.get('joinBehind') or elephino.get('joinBehindJoinBehind')
|
|
148
158
|
|
|
149
|
-
|
|
159
|
+
replaceAhead = "]+[" if joinAhead == "," else "["
|
|
150
160
|
|
|
151
|
-
|
|
161
|
+
replaceBehind = "+[" if joinBehind == "," else ""
|
|
152
162
|
|
|
153
|
-
|
|
163
|
+
return f"{replaceAhead}{malkovich}]*{multiply}{replaceBehind}"
|
|
154
164
|
|
|
155
|
-
|
|
156
|
-
|
|
165
|
+
arrayAsStr = patternRegex.sub(replacementByContext, arrayAsStr)
|
|
166
|
+
arrayAsStr = patternRegex.sub(replacementByContext, arrayAsStr)
|
|
157
167
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
168
|
+
# Replace `range(0,stop)` syntax with `range(stop)` syntax. # noqa: ERA001
|
|
169
|
+
# Add unpack operator `*` for automatic decoding when evaluated.
|
|
170
|
+
return arrayAsStr.replace('range(0,', 'range(').replace('range', '*range')
|
|
161
171
|
|
|
162
|
-
def stringItUp(*scrapPile: Any) -> list[str]:
|
|
172
|
+
def stringItUp(*scrapPile: Any) -> list[str]:
|
|
163
173
|
"""Convert, if possible, every element in the input data structure to a string.
|
|
164
174
|
|
|
165
175
|
Order is not preserved or readily predictable.
|
|
@@ -178,7 +188,7 @@ def stringItUp(*scrapPile: Any) -> list[str]: # noqa: C901
|
|
|
178
188
|
scrap = None
|
|
179
189
|
listStrungUp: list[str] = []
|
|
180
190
|
|
|
181
|
-
def drill(KitKat: Any) -> None:
|
|
191
|
+
def drill(KitKat: Any) -> None:
|
|
182
192
|
match KitKat:
|
|
183
193
|
case str():
|
|
184
194
|
listStrungUp.append(KitKat)
|
|
@@ -204,7 +214,7 @@ def stringItUp(*scrapPile: Any) -> list[str]: # noqa: C901
|
|
|
204
214
|
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
215
|
pass
|
|
206
216
|
except:
|
|
207
|
-
print(f"\nWoah! I received '{repr(KitKat)}'.\nTheir report card says, 'Plays well with others: Needs improvement.'\n") # noqa:
|
|
217
|
+
print(f"\nWoah! I received '{repr(KitKat)}'.\nTheir report card says, 'Plays well with others: Needs improvement.'\n") # noqa: T201
|
|
208
218
|
raise
|
|
209
219
|
try:
|
|
210
220
|
for scrap in scrapPile:
|
hunterMakesPy/parseParameters.py
CHANGED
|
@@ -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
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
from collections.abc import Callable, Iterable, Iterator
|
|
3
3
|
from decimal import Decimal
|
|
4
4
|
from fractions import Fraction
|
|
5
|
-
from hunterMakesPy import stringItUp, updateExtendPolishDictionaryLists
|
|
5
|
+
from hunterMakesPy import autoDecodingRLE, stringItUp, updateExtendPolishDictionaryLists
|
|
6
6
|
from hunterMakesPy.tests.conftest import standardizedEqualTo
|
|
7
7
|
from numpy.typing import NDArray
|
|
8
8
|
from typing import Any, Literal
|
|
@@ -11,9 +11,6 @@ import numpy
|
|
|
11
11
|
import pytest
|
|
12
12
|
import sys
|
|
13
13
|
|
|
14
|
-
if sys.version_info < (3, 14):
|
|
15
|
-
from hunterMakesPy import autoDecodingRLE
|
|
16
|
-
|
|
17
14
|
class CustomIterable:
|
|
18
15
|
def __init__(self, items: Iterable[Any]) -> None: self.items = items
|
|
19
16
|
def __iter__(self) -> Iterator[Any]: return iter(self.items)
|
|
@@ -99,27 +96,26 @@ def testUpdateExtendPolishDictionaryLists(description: str, value_dictionaryList
|
|
|
99
96
|
|
|
100
97
|
# ruff: noqa: RUF005
|
|
101
98
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
standardizedEqualTo(expected, autoDecodingRLE, value_arrayTarget)
|
|
99
|
+
@pytest.mark.parametrize("description,value_arrayTarget,expected", [
|
|
100
|
+
("One range", numpy.array(list(range(50,60))), "[*range(50,60)]"),
|
|
101
|
+
("Value, range", numpy.array([123]+list(range(71,81))), "[123,*range(71,81)]"),
|
|
102
|
+
("range, value", numpy.array(list(range(91,97))+[101]), "[*range(91,97),101]"),
|
|
103
|
+
("Value, range, value", numpy.array([151]+list(range(163,171))+[181]), "[151,*range(163,171),181]"),
|
|
104
|
+
("Repeat values", numpy.array([191, 191, 191]), "[191]*3"),
|
|
105
|
+
("Value with repeat", numpy.array([211, 223, 223, 223]), "[211]+[223]*3"),
|
|
106
|
+
("Range with repeat", numpy.array(list(range(251,257))+[271, 271, 271]), "[*range(251,257)]+[271]*3"),
|
|
107
|
+
("Value, range, repeat", numpy.array([281]+list(range(291,297))+[307, 307]), "[281,*range(291,297)]+[307]*2"),
|
|
108
|
+
("repeat, value", numpy.array([313, 313, 313, 331, 331, 349]), "[313]*3+[331]*2+[349]"),
|
|
109
|
+
("repeat, range", numpy.array([373, 373, 373]+list(range(383,389))), "[373]*3+[*range(383,389)]"),
|
|
110
|
+
("repeat, range, value", numpy.array(7*[401]+list(range(409,415))+[421]), "[401]*7+[*range(409,415),421]"),
|
|
111
|
+
("Repeated primes", numpy.array([431, 431, 431, 443, 443, 457]), "[431]*3+[443]*2+[457]"),
|
|
112
|
+
("Two Ranges", numpy.array(list(range(461,471))+list(range(479,487))), "[*range(461,471),*range(479,487)]"),
|
|
113
|
+
("2D array primes", numpy.array([[491, 499, 503], [509, 521, 523]]), "[[491,499,503],[509,521,523]]"),
|
|
114
|
+
("3D array primes", numpy.array([[[541, 547], [557, 563]], [[569, 571], [577, 587]]]), "[[[541,547],[557,563]],[[569,571],[577,587]]]"),
|
|
115
|
+
], ids=lambda x: x if isinstance(x, str) else "")
|
|
116
|
+
def testAutoDecodingRLE(description: str, value_arrayTarget: NDArray[numpy.integer[Any]], expected: str) -> None:
|
|
117
|
+
"""Test autoDecodingRLE with various input arrays."""
|
|
118
|
+
standardizedEqualTo(expected, autoDecodingRLE, value_arrayTarget)
|
|
123
119
|
|
|
124
120
|
# Helper functions for generating RLE test data
|
|
125
121
|
def generateCartesianMapping(dimensions: tuple[int, int], formula: Callable[[int, int], int]) -> NDArray[Any]:
|
|
@@ -228,98 +224,97 @@ def generateAlternatingColumns(dimensions: tuple[int, int], blockSize: int = 1)
|
|
|
228
224
|
|
|
229
225
|
return generateCartesianMapping(dimensions, columnFormula)
|
|
230
226
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
assert encodedLength <= rawStrLength, f"RLE encoded string ({encodedLength}) should be shorter than raw string ({rawStrLength})"
|
|
227
|
+
@pytest.mark.parametrize("description,value_arrayTarget", [
|
|
228
|
+
# Basic test cases with simple patterns
|
|
229
|
+
("Simple range", numpy.array(list(range(50,60)))),
|
|
230
|
+
|
|
231
|
+
# Chessboard patterns
|
|
232
|
+
("Small chessboard", generateChessboard((8, 8))),
|
|
233
|
+
|
|
234
|
+
# Alternating columns - creates patterns with good RLE opportunities
|
|
235
|
+
("Alternating columns", generateAlternatingColumns((5, 20), 2)),
|
|
236
|
+
|
|
237
|
+
# Step pattern - creates horizontal runs
|
|
238
|
+
("Step pattern", generateStepPattern((6, 30), 3)),
|
|
239
|
+
|
|
240
|
+
# Repeating zones - creates horizontal bands
|
|
241
|
+
("Repeating zones", generateRepeatingZones((40, 40), 8)),
|
|
242
|
+
|
|
243
|
+
# Tile pattern - creates complex repeating regions
|
|
244
|
+
("Tile pattern", generateTilePattern((15, 15), 5)),
|
|
245
|
+
|
|
246
|
+
# Signed quadratic function - includes negative values
|
|
247
|
+
("Signed quadratic", generateSignedQuadraticFunction((10, 10))),
|
|
248
|
+
|
|
249
|
+
# Prime modulo matrix - periodic patterns
|
|
250
|
+
("Prime modulo", generatePrimeModuloMatrix((12, 12), 7)),
|
|
251
|
+
|
|
252
|
+
# Wave pattern - smooth gradients
|
|
253
|
+
("Wave pattern", generateWavePattern((20, 20))),
|
|
254
|
+
|
|
255
|
+
# Spiral pattern - complex pattern with good RLE potential
|
|
256
|
+
("Spiral pattern", generateSpiralPattern((15, 15), 2)),
|
|
257
|
+
], ids=lambda x: x if isinstance(x, str) else "")
|
|
258
|
+
def testAutoDecodingRLEWithRealisticData(description: str, value_arrayTarget: NDArray[numpy.integer[Any]]) -> None:
|
|
259
|
+
"""Test autoDecodingRLE with more realistic data patterns."""
|
|
260
|
+
# Here we test the function behavior rather than expected string output
|
|
261
|
+
resultRLE = autoDecodingRLE(value_arrayTarget)
|
|
262
|
+
|
|
263
|
+
# Test that the result is a valid string
|
|
264
|
+
assert isinstance(resultRLE, str)
|
|
265
|
+
|
|
266
|
+
# Test that the result contains the expected syntax elements
|
|
267
|
+
assert "[" in resultRLE, f"Result should contain list syntax: {resultRLE}"
|
|
268
|
+
assert "]" in resultRLE, f"Result should contain list syntax: {resultRLE}"
|
|
269
|
+
|
|
270
|
+
# Check that the result is more compact than the raw string representation
|
|
271
|
+
rawStrLength = len(str(value_arrayTarget.tolist()))
|
|
272
|
+
encodedLength = len(resultRLE)
|
|
273
|
+
assert encodedLength <= rawStrLength, f"Encoded string ({encodedLength}) should be shorter than raw string ({rawStrLength})"
|
|
274
|
+
|
|
275
|
+
@pytest.mark.parametrize("description,addSpaces", [
|
|
276
|
+
("With spaces", True),
|
|
277
|
+
("Without spaces", False),
|
|
278
|
+
], ids=lambda x: x if isinstance(x, str) else "")
|
|
279
|
+
def testAutoDecodingRLEWithSpaces(description: str, addSpaces: bool) -> None:
|
|
280
|
+
"""Test that the addSpaces parameter affects the internal comparison logic.
|
|
281
|
+
|
|
282
|
+
Note: addSpaces doesn't directly change the output format, it just changes
|
|
283
|
+
the comparison when measuring the length of the string representation.
|
|
284
|
+
The feature exists because `ast` inserts spaces in its string representation.
|
|
285
|
+
"""
|
|
286
|
+
# Create a pattern that has repeated sequences to trigger the RLE logic
|
|
287
|
+
arrayTarget = generateRepeatingZones((10, 10), 2)
|
|
288
|
+
|
|
289
|
+
# Test both configurations
|
|
290
|
+
resultWithSpacesFlag = autoDecodingRLE(arrayTarget, assumeAddSpaces=addSpaces)
|
|
291
|
+
resultNoSpacesFlag = autoDecodingRLE(arrayTarget, assumeAddSpaces=False)
|
|
292
|
+
|
|
293
|
+
# When addSpaces=True, the internal length comparisons change
|
|
294
|
+
# but the actual output format doesn't necessarily differ
|
|
295
|
+
# Just verify the function runs without errors in both cases
|
|
296
|
+
assert isinstance(resultWithSpacesFlag, str)
|
|
297
|
+
assert isinstance(resultNoSpacesFlag, str)
|
|
298
|
+
|
|
299
|
+
def testAutoDecodingRLELargeCartesianMapping() -> None:
|
|
300
|
+
"""Test autoDecodingRLE with a large (100x100) cartesian mapping."""
|
|
301
|
+
dimensions = (100, 100)
|
|
302
|
+
|
|
303
|
+
# Generate a large cartesian mapping with a complex pattern
|
|
304
|
+
def complexFormula(x: int, y: int) -> int:
|
|
305
|
+
return ((x * 17) % 11 + (y * 13) % 7) % 10
|
|
306
|
+
|
|
307
|
+
arrayMapping = generateCartesianMapping(dimensions, complexFormula)
|
|
308
|
+
|
|
309
|
+
# Verify the function works with large arrays
|
|
310
|
+
resultRLE = autoDecodingRLE(arrayMapping)
|
|
311
|
+
|
|
312
|
+
# The result should be a valid string representation
|
|
313
|
+
assert isinstance(resultRLE, str)
|
|
314
|
+
assert "[" in resultRLE
|
|
315
|
+
assert "]" in resultRLE
|
|
316
|
+
|
|
317
|
+
# The RLE encoding should be more compact than the raw representation
|
|
318
|
+
rawStrLength = len(str(arrayMapping.tolist()))
|
|
319
|
+
encodedLength = len(resultRLE)
|
|
320
|
+
assert encodedLength <= rawStrLength, f"RLE encoded string ({encodedLength}) should be shorter than raw string ({rawStrLength})"
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hunterMakesPy
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
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
|
|
@@ -33,7 +33,6 @@ License-File: LICENSE
|
|
|
33
33
|
Requires-Dist: charset_normalizer
|
|
34
34
|
Requires-Dist: more_itertools
|
|
35
35
|
Requires-Dist: numpy
|
|
36
|
-
Requires-Dist: python_minifier; python_version < "3.14"
|
|
37
36
|
Provides-Extra: development
|
|
38
37
|
Requires-Dist: mypy; extra == "development"
|
|
39
38
|
Requires-Dist: pyupgrade; extra == "development"
|
|
@@ -174,4 +173,4 @@ Coding One Step at a Time:
|
|
|
174
173
|
2. Write good code.
|
|
175
174
|
3. When revising, write better code.
|
|
176
175
|
|
|
177
|
-
[](https://creativecommons.org/licenses/by-nc/4.0/)
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
hunterMakesPy/__init__.py,sha256=bVF1F2Mdo5AOiioEfxKvNrnsa3vCFI16eMK7Oy5O5TU,1450
|
|
2
2
|
hunterMakesPy/_theSSOT.py,sha256=lkLOG3oTIWNKD_ULX55chlUGNqCHgqVIrBvolvK1vbQ,153
|
|
3
|
-
hunterMakesPy/coping.py,sha256=
|
|
4
|
-
hunterMakesPy/dataStructures.py,sha256=
|
|
3
|
+
hunterMakesPy/coping.py,sha256=7NBwaGutEr6Q-2mIz65M69NkrbpG24u1I5HXx6VaAWI,6057
|
|
4
|
+
hunterMakesPy/dataStructures.py,sha256=7CxCBpQmHJzGxTq_AfkAeh2QdJDkzBr5lfSpPaA1GkE,11722
|
|
5
5
|
hunterMakesPy/filesystemToolkit.py,sha256=jd7H5UtrIrPiCYWcvNBNa6DAy-2Ewcf21-jbGXg_IVI,4702
|
|
6
|
-
hunterMakesPy/parseParameters.py,sha256=
|
|
6
|
+
hunterMakesPy/parseParameters.py,sha256=1mwGNVIZ1x23k_di3lhOhQb8QMXMUCHgt7xw3NRzDXs,11814
|
|
7
7
|
hunterMakesPy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
8
|
hunterMakesPy/pytestForYourUse.py,sha256=GiN1C1gTTM0ZunRPEMnrKlLQLMdH0wF_ZGr_RPgRjjA,500
|
|
9
9
|
hunterMakesPy/theTypes.py,sha256=C2d0uLn1VIx6_2CK41it3IP7iplSQqe51tzWc-RT320,306
|
|
10
10
|
hunterMakesPy/tests/__init__.py,sha256=C_FzfKDi_VrGVxlenWHyOYtKShAKlt3KW14jeRx1mQI,224
|
|
11
11
|
hunterMakesPy/tests/conftest.py,sha256=NZQPRiwvGhP16hJ6WGGm9eKLxfQArYV8E9X12YzSpP0,2827
|
|
12
12
|
hunterMakesPy/tests/test_coping.py,sha256=mH89TUAL6fJanBLlhdVlCNNQqm5OpdcQMP_p5W2JJwo,9860
|
|
13
|
-
hunterMakesPy/tests/test_dataStructures.py,sha256=
|
|
13
|
+
hunterMakesPy/tests/test_dataStructures.py,sha256=OouddHjN-Km26U92jYwnjYeP6_Y2DrJLgq3qD_8GvGw,16393
|
|
14
14
|
hunterMakesPy/tests/test_filesystemToolkit.py,sha256=q2voXjCbQPIT8l8VF9iuWX1Bs2ZieABItWoVkITj_fo,8841
|
|
15
15
|
hunterMakesPy/tests/test_parseParameters.py,sha256=80npsoWcCackjxvoW2dMXMpHeale7fuRXyXp78MibLs,14037
|
|
16
|
-
huntermakespy-0.2.
|
|
17
|
-
huntermakespy-0.2.
|
|
18
|
-
huntermakespy-0.2.
|
|
19
|
-
huntermakespy-0.2.
|
|
20
|
-
huntermakespy-0.2.
|
|
16
|
+
huntermakespy-0.2.4.dist-info/licenses/LICENSE,sha256=NxH5Y8BdC-gNU-WSMwim3uMbID2iNDXJz7fHtuTdXhk,19346
|
|
17
|
+
huntermakespy-0.2.4.dist-info/METADATA,sha256=UeGvfHoO_DMo_K5yy8daea6K7Ska8X6hHbC7MuCh7p8,6491
|
|
18
|
+
huntermakespy-0.2.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
19
|
+
huntermakespy-0.2.4.dist-info/top_level.txt,sha256=Uh4bj8EDTdsRpqY1VlK_his_B4HDfZ6Tqrwhoj75P_w,14
|
|
20
|
+
huntermakespy-0.2.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|