hunterMakesPy 0.1.1__py3-none-any.whl → 0.2.0__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/__init__.py +8 -5
- hunterMakesPy/_theSSOT.py +3 -39
- hunterMakesPy/coping.py +82 -6
- hunterMakesPy/dataStructures.py +129 -126
- hunterMakesPy/parseParameters.py +29 -29
- hunterMakesPy/pytestForYourUse.py +5 -322
- hunterMakesPy/tests/__init__.py +5 -0
- {tests → hunterMakesPy/tests}/conftest.py +1 -2
- hunterMakesPy/tests/test_coping.py +216 -0
- {tests → hunterMakesPy/tests}/test_dataStructures.py +124 -118
- {tests → hunterMakesPy/tests}/test_filesystemToolkit.py +5 -2
- hunterMakesPy/tests/test_parseParameters.py +339 -0
- {huntermakespy-0.1.1.dist-info → huntermakespy-0.2.0.dist-info}/METADATA +12 -12
- huntermakespy-0.2.0.dist-info/RECORD +20 -0
- {huntermakespy-0.1.1.dist-info → huntermakespy-0.2.0.dist-info}/top_level.txt +0 -1
- huntermakespy-0.1.1.dist-info/RECORD +0 -20
- tests/__init__.py +0 -0
- tests/test_coping.py +0 -56
- tests/test_parseParameters.py +0 -21
- {huntermakespy-0.1.1.dist-info → huntermakespy-0.2.0.dist-info}/WHEEL +0 -0
- {huntermakespy-0.1.1.dist-info → huntermakespy-0.2.0.dist-info}/licenses/LICENSE +0 -0
hunterMakesPy/__init__.py
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
"""A modular toolkit for defensive programming, parameter validation, file system utilities, and data structure manipulation.
|
|
2
2
|
|
|
3
|
-
(AI generated docstring)
|
|
4
|
-
|
|
5
3
|
This package provides:
|
|
6
4
|
- Defensive programming helpers for handling `None` values and error propagation.
|
|
7
5
|
- Parameter and input validation, integer parsing, and concurrency limit utilities.
|
|
@@ -9,9 +7,10 @@ This package provides:
|
|
|
9
7
|
- Utilities for string extraction from nested data structures and merging dictionaries of lists.
|
|
10
8
|
|
|
11
9
|
"""
|
|
10
|
+
# pyright: reportUnusedImport=false
|
|
12
11
|
from hunterMakesPy.theTypes import identifierDotAttribute as identifierDotAttribute
|
|
13
12
|
|
|
14
|
-
from hunterMakesPy.coping import raiseIfNone as raiseIfNone
|
|
13
|
+
from hunterMakesPy.coping import PackageSettings as PackageSettings, raiseIfNone as raiseIfNone
|
|
15
14
|
|
|
16
15
|
from hunterMakesPy.parseParameters import (defineConcurrencyLimit as defineConcurrencyLimit, intInnit as intInnit,
|
|
17
16
|
oopsieKwargsie as oopsieKwargsie)
|
|
@@ -20,7 +19,11 @@ from hunterMakesPy.filesystemToolkit import (importLogicalPath2Identifier as imp
|
|
|
20
19
|
importPathFilename2Identifier as importPathFilename2Identifier, makeDirsSafely as makeDirsSafely,
|
|
21
20
|
writeStringToHere as writeStringToHere)
|
|
22
21
|
|
|
23
|
-
from hunterMakesPy.dataStructures import
|
|
24
|
-
|
|
22
|
+
from hunterMakesPy.dataStructures import stringItUp as stringItUp, updateExtendPolishDictionaryLists as updateExtendPolishDictionaryLists
|
|
23
|
+
|
|
24
|
+
import sys
|
|
25
|
+
|
|
26
|
+
if sys.version_info < (3, 14):
|
|
27
|
+
from hunterMakesPy.dataStructures import autoDecodingRLE as autoDecodingRLE
|
|
25
28
|
|
|
26
29
|
from hunterMakesPy._theSSOT import settingsPackage
|
hunterMakesPy/_theSSOT.py
CHANGED
|
@@ -1,40 +1,4 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Settings for this package."""
|
|
2
|
+
from hunterMakesPy import PackageSettings
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
Tertiary: hardcoded values until I implement a dynamic solution.
|
|
5
|
-
"""
|
|
6
|
-
from importlib.util import find_spec
|
|
7
|
-
from pathlib import Path
|
|
8
|
-
from tomli import loads as tomli_loads
|
|
9
|
-
from typing import TYPE_CHECKING
|
|
10
|
-
import dataclasses
|
|
11
|
-
|
|
12
|
-
if TYPE_CHECKING:
|
|
13
|
-
from importlib.machinery import ModuleSpec
|
|
14
|
-
|
|
15
|
-
try:
|
|
16
|
-
identifierPackagePACKAGING: str = tomli_loads(Path("pyproject.toml").read_text(encoding="utf-8"))["project"]["name"]
|
|
17
|
-
except Exception: # noqa: BLE001
|
|
18
|
-
identifierPackagePACKAGING = "hunterMakesPy"
|
|
19
|
-
|
|
20
|
-
def getPathPackageINSTALLING() -> Path:
|
|
21
|
-
"""Return the root directory of the installed package."""
|
|
22
|
-
try:
|
|
23
|
-
moduleSpecification: ModuleSpec | None = find_spec(identifierPackagePACKAGING)
|
|
24
|
-
if moduleSpecification and moduleSpecification.origin:
|
|
25
|
-
pathFilename = Path(moduleSpecification.origin)
|
|
26
|
-
return pathFilename.parent if pathFilename.is_file() else pathFilename
|
|
27
|
-
except ModuleNotFoundError:
|
|
28
|
-
pass
|
|
29
|
-
return Path.cwd()
|
|
30
|
-
|
|
31
|
-
@dataclasses.dataclass
|
|
32
|
-
class PackageSettings:
|
|
33
|
-
fileExtension: str = dataclasses.field(default='.py', metadata={'evaluateWhen': 'installing'})
|
|
34
|
-
"""Default file extension for generated code files."""
|
|
35
|
-
identifierPackage: str = dataclasses.field(default = identifierPackagePACKAGING, metadata={'evaluateWhen': 'packaging'})
|
|
36
|
-
"""Name of this package, used for import paths and configuration."""
|
|
37
|
-
pathPackage: Path = dataclasses.field(default_factory=getPathPackageINSTALLING, metadata={'evaluateWhen': 'installing'})
|
|
38
|
-
"""Absolute path to the installed package directory."""
|
|
39
|
-
|
|
40
|
-
settingsPackage = PackageSettings()
|
|
4
|
+
settingsPackage = PackageSettings(identifierPackageFALLBACK="hunterMakesPy")
|
hunterMakesPy/coping.py
CHANGED
|
@@ -1,13 +1,89 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Package configuration and defensive programming utilities for Python projects."""
|
|
2
|
+
from importlib.util import find_spec
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from tomllib import loads as tomllib_loads
|
|
5
|
+
from typing import TYPE_CHECKING, TypeVar
|
|
6
|
+
import dataclasses
|
|
2
7
|
|
|
3
|
-
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from importlib.machinery import ModuleSpec
|
|
4
10
|
|
|
5
|
-
|
|
11
|
+
TypeSansNone = TypeVar('TypeSansNone')
|
|
6
12
|
|
|
7
|
-
|
|
8
|
-
from
|
|
13
|
+
def getIdentifierPackagePACKAGING(identifierPackageFALLBACK: str) -> str:
|
|
14
|
+
"""Get package name from pyproject.toml or fallback to provided value."""
|
|
15
|
+
try:
|
|
16
|
+
return tomllib_loads(Path('pyproject.toml').read_text(encoding='utf-8'))['project']['name']
|
|
17
|
+
except Exception: # noqa: BLE001
|
|
18
|
+
return identifierPackageFALLBACK
|
|
19
|
+
|
|
20
|
+
def getPathPackageINSTALLING(identifierPackage: str) -> Path:
|
|
21
|
+
"""Return the root directory of the installed package."""
|
|
22
|
+
try:
|
|
23
|
+
moduleSpecification: ModuleSpec | None = find_spec(identifierPackage)
|
|
24
|
+
if moduleSpecification and moduleSpecification.origin:
|
|
25
|
+
pathFilename = Path(moduleSpecification.origin)
|
|
26
|
+
return pathFilename.parent if pathFilename.is_file() else pathFilename
|
|
27
|
+
except ModuleNotFoundError:
|
|
28
|
+
pass
|
|
29
|
+
return Path.cwd()
|
|
30
|
+
|
|
31
|
+
@dataclasses.dataclass
|
|
32
|
+
class PackageSettings:
|
|
33
|
+
"""Configuration container for Python package metadata and runtime settings.
|
|
34
|
+
|
|
35
|
+
This `class` provides a simple way to store and access basic information about a Python package, It will automatically resolve
|
|
36
|
+
package identifiers and installation paths if they are not passed to the `class` constructor. Python `dataclasses` are easy to
|
|
37
|
+
subtype and extend.
|
|
9
38
|
|
|
10
|
-
|
|
39
|
+
Parameters
|
|
40
|
+
----------
|
|
41
|
+
identifierPackageFALLBACK : str = ''
|
|
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
|
+
identifierPackage : str = ''
|
|
46
|
+
Canonical name of the package. Automatically extracted from `pyproject.toml`.
|
|
47
|
+
fileExtension : str = '.py'
|
|
48
|
+
Default file extension.
|
|
49
|
+
|
|
50
|
+
Examples
|
|
51
|
+
--------
|
|
52
|
+
Automatic package discovery from development environment:
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
settings = PackageSettings(identifierPackageFALLBACK='cobraPy')
|
|
56
|
+
# Automatically discovers package name from pyproject.toml
|
|
57
|
+
# Resolves installation path from package identifier
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Explicit configuration for specific deployment:
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
settings = PackageSettings(
|
|
64
|
+
identifierPackage='cobraPy',
|
|
65
|
+
pathPackage=Path('/opt/tenEx/packages/cobraPy'),
|
|
66
|
+
fileExtension='.pyx'
|
|
67
|
+
)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
identifierPackageFALLBACK: dataclasses.InitVar[str] = ''
|
|
73
|
+
"""Fallback package identifier used during initialization only."""
|
|
74
|
+
pathPackage: Path = dataclasses.field(default_factory=Path, metadata={'evaluateWhen': 'installing'})
|
|
75
|
+
"""Absolute path to the installed package."""
|
|
76
|
+
identifierPackage: str = dataclasses.field(default='', metadata={'evaluateWhen': 'packaging'})
|
|
77
|
+
"""Name of this package."""
|
|
78
|
+
fileExtension: str = dataclasses.field(default='.py', metadata={'evaluateWhen': 'installing'})
|
|
79
|
+
"""Default file extension for files."""
|
|
80
|
+
|
|
81
|
+
def __post_init__(self, identifierPackageFALLBACK: str) -> None:
|
|
82
|
+
"""Initialize computed fields after dataclass initialization."""
|
|
83
|
+
if not self.identifierPackage and identifierPackageFALLBACK:
|
|
84
|
+
self.identifierPackage = getIdentifierPackagePACKAGING(identifierPackageFALLBACK)
|
|
85
|
+
if self.pathPackage == Path() and self.identifierPackage:
|
|
86
|
+
self.pathPackage = getPathPackageINSTALLING(self.identifierPackage)
|
|
11
87
|
|
|
12
88
|
def raiseIfNone(returnTarget: TypeSansNone | None, errorMessage: str | None = None) -> TypeSansNone:
|
|
13
89
|
"""Raise a `ValueError` if the target value is `None`, otherwise return the value: tell the type checker that the return value is not `None`.
|
hunterMakesPy/dataStructures.py
CHANGED
|
@@ -5,156 +5,159 @@ from numpy import integer
|
|
|
5
5
|
from numpy.typing import NDArray
|
|
6
6
|
from typing import Any
|
|
7
7
|
import more_itertools
|
|
8
|
-
import python_minifier
|
|
9
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')
|
|
10
68
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
recreates the original array structure. The function employs two compression strategies:
|
|
16
|
-
1. Python's `range` syntax for consecutive integer sequences
|
|
17
|
-
2. Multiplication syntax for repeated elements
|
|
18
|
-
|
|
19
|
-
The resulting string representation is designed to be both human-readable and space-efficient,
|
|
20
|
-
especially for large cartesian mappings with repetitive patterns. When this string is used
|
|
21
|
-
as a data source, Python will automatically decode it into Python `list`, which if used as an
|
|
22
|
-
argument to `numpy.array()`, will recreate the original array structure.
|
|
23
|
-
|
|
24
|
-
Parameters
|
|
25
|
-
----------
|
|
26
|
-
arrayTarget : NDArray[integer[Any]]
|
|
27
|
-
(array2target) The NumPy array to be encoded.
|
|
28
|
-
assumeAddSpaces : bool = False
|
|
29
|
-
(assume2add2spaces) Affects internal length comparison during compression decisions.
|
|
30
|
-
This parameter doesn't directly change output format but influences whether
|
|
31
|
-
`range` or multiplication syntax is preferred in certain cases. The parameter
|
|
32
|
-
exists because the Abstract Syntax Tree (AST) inserts spaces in its string
|
|
33
|
-
representation.
|
|
34
|
-
|
|
35
|
-
Returns
|
|
36
|
-
-------
|
|
37
|
-
rleString : str
|
|
38
|
-
(rle2string) A string representation of the array using run-length encoding that,
|
|
39
|
-
when evaluated as Python code, reproduces the original array structure.
|
|
69
|
+
option1 = ImaRange
|
|
70
|
+
option1AsStr = ImaRangeAsStr
|
|
71
|
+
option2 = ImaSerious
|
|
72
|
+
option2AsStr = None
|
|
40
73
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
to the desired data structure without explicit decompression steps.
|
|
74
|
+
# alpha, potential function
|
|
75
|
+
option1AsStr = option1AsStr or python_minifier.minify(str(option1))
|
|
76
|
+
lengthOption1 = getLengthOption(option1AsStr)
|
|
45
77
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def getLengthOption(optionAsStr: str) -> int:
|
|
49
|
-
# `assumeAddSpaces` characters: `,` 1; `]*` 2 # noqa: ERA001
|
|
50
|
-
return assumeAddSpaces * (optionAsStr.count(',') + optionAsStr.count(']*') * 2) + len(optionAsStr)
|
|
51
|
-
|
|
52
|
-
if arraySlice.ndim > 1:
|
|
53
|
-
axisOfOperation = 0
|
|
54
|
-
return [sliceNDArrayToNestedLists(arraySlice[index]) for index in range(arraySlice.shape[axisOfOperation])]
|
|
55
|
-
if arraySlice.ndim == 1:
|
|
56
|
-
arraySliceAsList: list[int | range] = []
|
|
57
|
-
cache_consecutiveGroup_addMe: dict[Iterator[Any], list[int] | list[range]] = {}
|
|
58
|
-
for consecutiveGroup in more_itertools.consecutive_groups(arraySlice.tolist()):
|
|
59
|
-
if consecutiveGroup in cache_consecutiveGroup_addMe:
|
|
60
|
-
addMe = cache_consecutiveGroup_addMe[consecutiveGroup]
|
|
61
|
-
else:
|
|
62
|
-
ImaSerious: list[int] = list(consecutiveGroup)
|
|
63
|
-
ImaRange = [range(ImaSerious[0], ImaSerious[-1] + 1)]
|
|
64
|
-
ImaRangeAsStr = python_minifier.minify(str(ImaRange)).replace('range(0,', 'range(').replace('range', '*range')
|
|
78
|
+
option2AsStr = option2AsStr or python_minifier.minify(str(option2))
|
|
79
|
+
lengthOption2 = getLengthOption(option2AsStr)
|
|
65
80
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
81
|
+
if lengthOption1 < lengthOption2:
|
|
82
|
+
addMe = option1
|
|
83
|
+
else:
|
|
84
|
+
addMe = option2
|
|
70
85
|
|
|
71
|
-
|
|
72
|
-
option1AsStr = option1AsStr or python_minifier.minify(str(option1))
|
|
73
|
-
lengthOption1 = getLengthOption(option1AsStr)
|
|
86
|
+
cache_consecutiveGroup_addMe[consecutiveGroup] = addMe
|
|
74
87
|
|
|
75
|
-
|
|
76
|
-
lengthOption2 = getLengthOption(option2AsStr)
|
|
88
|
+
arraySliceAsList += addMe
|
|
77
89
|
|
|
78
|
-
|
|
79
|
-
|
|
90
|
+
listRangeAndTuple: list[int | range | tuple[int | range, int]] = []
|
|
91
|
+
cache_malkovichGrouped_addMe: dict[tuple[int | range, int], list[tuple[int | range, int]] | list[int | range]] = {}
|
|
92
|
+
for malkovichGrouped in more_itertools.run_length.encode(arraySliceAsList):
|
|
93
|
+
if malkovichGrouped in cache_malkovichGrouped_addMe:
|
|
94
|
+
addMe = cache_malkovichGrouped_addMe[malkovichGrouped]
|
|
80
95
|
else:
|
|
81
|
-
|
|
96
|
+
lengthMalkovich = malkovichGrouped[-1]
|
|
97
|
+
malkovichAsList = list(more_itertools.run_length.decode([malkovichGrouped]))
|
|
98
|
+
malkovichMalkovich = f"[{malkovichGrouped[0]}]*{lengthMalkovich}"
|
|
82
99
|
|
|
83
|
-
|
|
100
|
+
option1 = [malkovichGrouped]
|
|
101
|
+
option1AsStr = malkovichMalkovich
|
|
102
|
+
option2 = malkovichAsList
|
|
103
|
+
option2AsStr = None
|
|
84
104
|
|
|
85
|
-
|
|
105
|
+
# beta, potential function
|
|
106
|
+
option1AsStr = option1AsStr or python_minifier.minify(str(option1))
|
|
107
|
+
lengthOption1 = getLengthOption(option1AsStr)
|
|
86
108
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
for malkovichGrouped in more_itertools.run_length.encode(arraySliceAsList):
|
|
90
|
-
if malkovichGrouped in cache_malkovichGrouped_addMe:
|
|
91
|
-
addMe = cache_malkovichGrouped_addMe[malkovichGrouped]
|
|
92
|
-
else:
|
|
93
|
-
lengthMalkovich = malkovichGrouped[-1]
|
|
94
|
-
malkovichAsList = list(more_itertools.run_length.decode([malkovichGrouped]))
|
|
95
|
-
malkovichMalkovich = f"[{malkovichGrouped[0]}]*{lengthMalkovich}"
|
|
109
|
+
option2AsStr = option2AsStr or python_minifier.minify(str(option2))
|
|
110
|
+
lengthOption2 = getLengthOption(option2AsStr)
|
|
96
111
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
# beta, potential function
|
|
103
|
-
option1AsStr = option1AsStr or python_minifier.minify(str(option1))
|
|
104
|
-
lengthOption1 = getLengthOption(option1AsStr)
|
|
105
|
-
|
|
106
|
-
option2AsStr = option2AsStr or python_minifier.minify(str(option2))
|
|
107
|
-
lengthOption2 = getLengthOption(option2AsStr)
|
|
108
|
-
|
|
109
|
-
if lengthOption1 < lengthOption2:
|
|
110
|
-
addMe = option1
|
|
111
|
-
else:
|
|
112
|
-
addMe = option2
|
|
112
|
+
if lengthOption1 < lengthOption2:
|
|
113
|
+
addMe = option1
|
|
114
|
+
else:
|
|
115
|
+
addMe = option2
|
|
113
116
|
|
|
114
|
-
|
|
117
|
+
cache_malkovichGrouped_addMe[malkovichGrouped] = addMe
|
|
115
118
|
|
|
116
|
-
|
|
119
|
+
listRangeAndTuple += addMe
|
|
117
120
|
|
|
118
|
-
|
|
119
|
-
|
|
121
|
+
return listRangeAndTuple
|
|
122
|
+
return arraySlice
|
|
120
123
|
|
|
121
|
-
|
|
124
|
+
arrayAsNestedLists = sliceNDArrayToNestedLists(arrayTarget)
|
|
122
125
|
|
|
123
|
-
|
|
126
|
+
arrayAsStr = python_minifier.minify(str(arrayAsNestedLists))
|
|
124
127
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
128
|
+
patternRegex = regex.compile(
|
|
129
|
+
"(?<!rang)(?:"
|
|
130
|
+
# Pattern 1: Comma ahead, bracket behind # noqa: ERA001
|
|
131
|
+
"(?P<joinAhead>,)\\((?P<malkovich>\\d+),(?P<multiply>\\d+)\\)(?P<bracketBehind>])|"
|
|
132
|
+
# Pattern 2: Bracket or start ahead, comma behind # noqa: ERA001
|
|
133
|
+
"(?P<bracketOrStartAhead>\\[|^.)\\((?P<malkovichMalkovich>\\d+),(?P<multiplyIDK>\\d+)\\)(?P<joinBehind>,)|"
|
|
134
|
+
# Pattern 3: Bracket ahead, bracket behind # noqa: ERA001
|
|
135
|
+
"(?P<bracketAhead>\\[)\\((?P<malkovichMalkovichMalkovich>\\d+),(?P<multiply_whatever>\\d+)\\)(?P<bracketBehindBracketBehind>])|"
|
|
136
|
+
# Pattern 4: Comma ahead, comma behind # noqa: ERA001
|
|
137
|
+
"(?P<joinAheadJoinAhead>,)\\((?P<malkovichMalkovichMalkovichMalkovich>\\d+),(?P<multiplyOrSomething>\\d+)\\)(?P<joinBehindJoinBehind>,)"
|
|
138
|
+
")"
|
|
139
|
+
)
|
|
137
140
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
141
|
+
def replacementByContext(match: regex.Match[str]) -> str:
|
|
142
|
+
"""Generate replacement string based on context patterns."""
|
|
143
|
+
elephino = match.groupdict()
|
|
144
|
+
joinAhead = elephino.get('joinAhead') or elephino.get('joinAheadJoinAhead')
|
|
145
|
+
malkovich = elephino.get('malkovich') or elephino.get('malkovichMalkovich') or elephino.get('malkovichMalkovichMalkovich') or elephino.get('malkovichMalkovichMalkovichMalkovich')
|
|
146
|
+
multiply = elephino.get('multiply') or elephino.get('multiplyIDK') or elephino.get('multiply_whatever') or elephino.get('multiplyOrSomething')
|
|
147
|
+
joinBehind = elephino.get('joinBehind') or elephino.get('joinBehindJoinBehind')
|
|
145
148
|
|
|
146
|
-
|
|
149
|
+
replaceAhead = "]+[" if joinAhead == "," else "["
|
|
147
150
|
|
|
148
|
-
|
|
151
|
+
replaceBehind = "+[" if joinBehind == "," else ""
|
|
149
152
|
|
|
150
|
-
|
|
153
|
+
return f"{replaceAhead}{malkovich}]*{multiply}{replaceBehind}"
|
|
151
154
|
|
|
152
|
-
|
|
153
|
-
|
|
155
|
+
arrayAsStr = patternRegex.sub(replacementByContext, arrayAsStr)
|
|
156
|
+
arrayAsStr = patternRegex.sub(replacementByContext, arrayAsStr)
|
|
154
157
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
+
# Replace `range(0,stop)` syntax with `range(stop)` syntax. # noqa: ERA001
|
|
159
|
+
# Add unpack operator `*` for automatic decoding when evaluated.
|
|
160
|
+
return arrayAsStr.replace('range(0,', 'range(').replace('range', '*range')
|
|
158
161
|
|
|
159
162
|
def stringItUp(*scrapPile: Any) -> list[str]: # noqa: C901
|
|
160
163
|
"""Convert, if possible, every element in the input data structure to a string.
|
|
@@ -249,7 +252,7 @@ def updateExtendPolishDictionaryLists(*dictionaryLists: Mapping[str, list[Any] |
|
|
|
249
252
|
ImaStr = str(keyName)
|
|
250
253
|
ImaList = list(keyValue)
|
|
251
254
|
ePluribusUnum.setdefault(ImaStr, []).extend(ImaList)
|
|
252
|
-
except TypeError:
|
|
255
|
+
except TypeError:
|
|
253
256
|
if killErroneousDataTypes:
|
|
254
257
|
continue
|
|
255
258
|
else:
|
hunterMakesPy/parseParameters.py
CHANGED
|
@@ -9,18 +9,16 @@ import multiprocessing
|
|
|
9
9
|
class ErrorMessageContext:
|
|
10
10
|
"""Context information for constructing error messages.
|
|
11
11
|
|
|
12
|
-
(AI generated docstring)
|
|
13
|
-
|
|
14
12
|
Parameters
|
|
15
13
|
----------
|
|
16
14
|
parameterValue : Any = None
|
|
17
|
-
|
|
15
|
+
The value that caused the error.
|
|
18
16
|
parameterValueType : str | None = None
|
|
19
|
-
|
|
17
|
+
The name of the type of the parameter value.
|
|
20
18
|
containerType : str | None = None
|
|
21
|
-
|
|
19
|
+
The name of the type of the container holding the parameter.
|
|
22
20
|
isElement : bool = False
|
|
23
|
-
|
|
21
|
+
Whether the parameter is an element within a container.
|
|
24
22
|
|
|
25
23
|
"""
|
|
26
24
|
|
|
@@ -32,7 +30,8 @@ class ErrorMessageContext:
|
|
|
32
30
|
def _constructErrorMessage(context: ErrorMessageContext, parameterName: str, parameterType: type[Any] | None) -> str:
|
|
33
31
|
"""Construct error message from available context using template.
|
|
34
32
|
|
|
35
|
-
I received ["value" | a value | `None`] [of type `type` | `None`] [as an element in | `None`] [a `containerType` type |
|
|
33
|
+
I received ["value" | a value | `None`] [of type `type` | `None`] [as an element in | `None`] [a `containerType` type |
|
|
34
|
+
`None`] but `parameterName` must have integers [in type(s) `parameterType` | `None`].
|
|
36
35
|
|
|
37
36
|
Hypothetically, this is a prototype that can be generalized to other functions. In this package and a few of my other
|
|
38
37
|
packages, I have developed standardized error messages, but those are quite different from this. I will certainly continue to
|
|
@@ -41,16 +40,16 @@ def _constructErrorMessage(context: ErrorMessageContext, parameterName: str, par
|
|
|
41
40
|
Parameters
|
|
42
41
|
----------
|
|
43
42
|
context : ErrorMessageContext
|
|
44
|
-
|
|
43
|
+
The error context containing parameter value, type, and container information.
|
|
45
44
|
parameterName : str
|
|
46
|
-
|
|
45
|
+
The name of the parameter that caused the error.
|
|
47
46
|
parameterType : type[Any] | None
|
|
48
|
-
|
|
47
|
+
The expected type of the parameter, used in error messages.
|
|
49
48
|
|
|
50
49
|
Returns
|
|
51
50
|
-------
|
|
52
51
|
errorMessage : str
|
|
53
|
-
|
|
52
|
+
The constructed error message string.
|
|
54
53
|
|
|
55
54
|
"""
|
|
56
55
|
messageParts = ["I received "]
|
|
@@ -79,7 +78,8 @@ def _constructErrorMessage(context: ErrorMessageContext, parameterName: str, par
|
|
|
79
78
|
def defineConcurrencyLimit(*, limit: bool | float | int | None, cpuTotal: int = multiprocessing.cpu_count()) -> int: # noqa: C901, PYI041
|
|
80
79
|
"""Determine the concurrency limit based on the provided parameter.
|
|
81
80
|
|
|
82
|
-
|
|
81
|
+
Tests for this function can be run with:
|
|
82
|
+
`from hunterMakesPy.tests.test_parseParameters import PytestFor_defineConcurrencyLimit`
|
|
83
83
|
|
|
84
84
|
Parameters
|
|
85
85
|
----------
|
|
@@ -92,12 +92,12 @@ def defineConcurrencyLimit(*, limit: bool | float | int | None, cpuTotal: int =
|
|
|
92
92
|
Returns
|
|
93
93
|
-------
|
|
94
94
|
concurrencyLimit : int
|
|
95
|
-
|
|
95
|
+
The calculated concurrency limit, ensuring it is at least 1.
|
|
96
96
|
|
|
97
97
|
Notes
|
|
98
98
|
-----
|
|
99
|
-
If you want to be extra nice to your users, consider using `hunterMakesPy.oopsieKwargsie()` to handle
|
|
100
|
-
|
|
99
|
+
If you want to be extra nice to your users, consider using `hunterMakesPy.oopsieKwargsie()` to handle malformed inputs. For
|
|
100
|
+
example:
|
|
101
101
|
|
|
102
102
|
```
|
|
103
103
|
if not (CPUlimit is None or isinstance(CPUlimit, (bool, int, float))):
|
|
@@ -163,23 +163,25 @@ def defineConcurrencyLimit(*, limit: bool | float | int | None, cpuTotal: int =
|
|
|
163
163
|
def intInnit(listInt_Allegedly: Iterable[Any], parameterName: str | None = None, parameterType: type[Any] | None = None) -> list[int]: # noqa: C901, PLR0912, PLR0915
|
|
164
164
|
"""Validate and convert input values to a `list` of integers.
|
|
165
165
|
|
|
166
|
-
Accepts various numeric types and attempts to convert them into integers while providing descriptive error messages.
|
|
166
|
+
Accepts various numeric types and attempts to convert them into integers while providing descriptive error messages. This
|
|
167
|
+
package includes Pytest tests that can be imported and run: `from hunterMakesPy.tests.test_parseParameters import
|
|
168
|
+
PytestFor_intInnit`.
|
|
167
169
|
|
|
168
170
|
Parameters
|
|
169
171
|
----------
|
|
170
172
|
listInt_Allegedly : Iterable[Any]
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
Rejects boolean values and non-integer numeric values.
|
|
173
|
+
The input sequence that should contain integer-compatible values. Accepts integers, strings, floats, complex numbers, and
|
|
174
|
+
binary data. Rejects boolean values and non-integer numeric values.
|
|
174
175
|
parameterName : str | None = None
|
|
175
|
-
|
|
176
|
+
Name of the parameter from your function for which this function is validating the input validated. If there is an error
|
|
177
|
+
message, it provides context to your user. Defaults to 'the parameter'.
|
|
176
178
|
parameterType : type[Any] | None = None
|
|
177
|
-
|
|
179
|
+
Expected type(s) of the parameter, used in error messages.
|
|
178
180
|
|
|
179
181
|
Returns
|
|
180
182
|
-------
|
|
181
183
|
listValidated : list[int]
|
|
182
|
-
|
|
184
|
+
A `list` containing validated integers.
|
|
183
185
|
|
|
184
186
|
Raises
|
|
185
187
|
------
|
|
@@ -192,9 +194,6 @@ def intInnit(listInt_Allegedly: Iterable[Any], parameterName: str | None = None,
|
|
|
192
194
|
|
|
193
195
|
Notes
|
|
194
196
|
-----
|
|
195
|
-
This package includes Pytest tests that can be imported and run:
|
|
196
|
-
`from hunterMakesPy.pytest_parseParameters import makeTestSuiteIntInnit`
|
|
197
|
-
|
|
198
197
|
The function performs strict validation and follows fail-early principles to catch potential issues before they become catastrophic.
|
|
199
198
|
|
|
200
199
|
"""
|
|
@@ -284,19 +283,20 @@ def intInnit(listInt_Allegedly: Iterable[Any], parameterName: str | None = None,
|
|
|
284
283
|
def oopsieKwargsie(huh: Any) -> bool | None | str:
|
|
285
284
|
"""Interpret a `str` as `True`, `False`, or `None` to avoid an `Exception`.
|
|
286
285
|
|
|
287
|
-
If a calling function passes a `str` to a parameter that shouldn't receive a `str`, `oopsieKwargsie()` might help you avoid an
|
|
288
|
-
`
|
|
286
|
+
If a calling function passes a `str` to a parameter that shouldn't receive a `str`, `oopsieKwargsie()` might help you avoid an
|
|
287
|
+
`Exception`. It tries to interpret the string as `True`, `False`, or `None`.
|
|
288
|
+
|
|
289
|
+
Tests for this function can be run with: `from hunterMakesPy.tests.test_parseParameters import PytestFor_oopsieKwargsie`.
|
|
289
290
|
|
|
290
291
|
Parameters
|
|
291
292
|
----------
|
|
292
293
|
huh : Any
|
|
293
294
|
(huh) The input string to be parsed.
|
|
294
295
|
|
|
295
|
-
|
|
296
296
|
Returns
|
|
297
297
|
-------
|
|
298
298
|
interpretedValue : bool | None | str
|
|
299
|
-
|
|
299
|
+
The reserved keyword `True`, `False`, or `None` or the original string, `huh`.
|
|
300
300
|
|
|
301
301
|
"""
|
|
302
302
|
if not isinstance(huh, str):
|