hunterMakesPy 0.1.1__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. {huntermakespy-0.1.1 → huntermakespy-0.2.0}/PKG-INFO +12 -12
  2. {huntermakespy-0.1.1 → huntermakespy-0.2.0}/hunterMakesPy/__init__.py +8 -5
  3. huntermakespy-0.2.0/hunterMakesPy/_theSSOT.py +4 -0
  4. huntermakespy-0.2.0/hunterMakesPy/coping.py +149 -0
  5. huntermakespy-0.2.0/hunterMakesPy/dataStructures.py +268 -0
  6. {huntermakespy-0.1.1 → huntermakespy-0.2.0}/hunterMakesPy/parseParameters.py +29 -29
  7. huntermakespy-0.2.0/hunterMakesPy/pytestForYourUse.py +10 -0
  8. huntermakespy-0.2.0/hunterMakesPy/tests/__init__.py +5 -0
  9. {huntermakespy-0.1.1 → huntermakespy-0.2.0/hunterMakesPy}/tests/conftest.py +1 -2
  10. huntermakespy-0.2.0/hunterMakesPy/tests/test_coping.py +216 -0
  11. {huntermakespy-0.1.1 → huntermakespy-0.2.0/hunterMakesPy}/tests/test_dataStructures.py +124 -118
  12. {huntermakespy-0.1.1 → huntermakespy-0.2.0/hunterMakesPy}/tests/test_filesystemToolkit.py +5 -2
  13. huntermakespy-0.1.1/hunterMakesPy/pytestForYourUse.py → huntermakespy-0.2.0/hunterMakesPy/tests/test_parseParameters.py +17 -5
  14. {huntermakespy-0.1.1 → huntermakespy-0.2.0}/hunterMakesPy.egg-info/PKG-INFO +12 -12
  15. {huntermakespy-0.1.1 → huntermakespy-0.2.0}/hunterMakesPy.egg-info/SOURCES.txt +6 -6
  16. {huntermakespy-0.1.1 → huntermakespy-0.2.0}/hunterMakesPy.egg-info/requires.txt +7 -4
  17. {huntermakespy-0.1.1 → huntermakespy-0.2.0}/hunterMakesPy.egg-info/top_level.txt +0 -1
  18. {huntermakespy-0.1.1 → huntermakespy-0.2.0}/pyproject.toml +25 -24
  19. huntermakespy-0.1.1/hunterMakesPy/_theSSOT.py +0 -40
  20. huntermakespy-0.1.1/hunterMakesPy/coping.py +0 -73
  21. huntermakespy-0.1.1/hunterMakesPy/dataStructures.py +0 -265
  22. huntermakespy-0.1.1/tests/__init__.py +0 -0
  23. huntermakespy-0.1.1/tests/test_coping.py +0 -56
  24. huntermakespy-0.1.1/tests/test_parseParameters.py +0 -21
  25. {huntermakespy-0.1.1 → huntermakespy-0.2.0}/LICENSE +0 -0
  26. {huntermakespy-0.1.1 → huntermakespy-0.2.0}/README.md +0 -0
  27. {huntermakespy-0.1.1 → huntermakespy-0.2.0}/hunterMakesPy/filesystemToolkit.py +0 -0
  28. {huntermakespy-0.1.1 → huntermakespy-0.2.0}/hunterMakesPy/py.typed +0 -0
  29. {huntermakespy-0.1.1 → huntermakespy-0.2.0}/hunterMakesPy/theTypes.py +0 -0
  30. {huntermakespy-0.1.1 → huntermakespy-0.2.0}/hunterMakesPy.egg-info/dependency_links.txt +0 -0
  31. {huntermakespy-0.1.1 → huntermakespy-0.2.0}/setup.cfg +0 -0
@@ -1,14 +1,14 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hunterMakesPy
3
- Version: 0.1.1
4
- Summary: A modular Python toolkit for defensive programming, parameter validation, file system utilities, and flexible data structure manipulation. Provides helpers for error propagation, input validation, concurrency limits, safe directory creation, dynamic module loading, string extraction from nested structures, and dictionary merging.
3
+ Version: 0.2.0
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
8
  Project-URL: Homepage, https://github.com/hunterhogan/
9
9
  Project-URL: Issues, https://github.com/hunterhogan/
10
10
  Project-URL: Repository, https://github.com/hunterhogan/
11
- Keywords: defensive programming,parameter validation,input validation,error propagation,concurrency limit,integer parsing,file system utilities,directory creation,dynamic import,module loading,attribute loading,string extraction,nested data structures,dictionary merging,package settings,configuration,pytest,test utilities
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
14
14
  Classifier: Framework :: Pytest
@@ -17,31 +17,31 @@ Classifier: Intended Audience :: End Users/Desktop
17
17
  Classifier: Intended Audience :: Other Audience
18
18
  Classifier: Natural Language :: English
19
19
  Classifier: Operating System :: OS Independent
20
- Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python
21
+ Classifier: Programming Language :: Python :: 3
21
22
  Classifier: Programming Language :: Python :: 3.11
22
23
  Classifier: Programming Language :: Python :: 3.12
23
24
  Classifier: Programming Language :: Python :: 3.13
24
- Classifier: Programming Language :: Python :: 3
25
+ Classifier: Programming Language :: Python :: 3.14
25
26
  Classifier: Programming Language :: Python :: Implementation :: CPython
26
- Classifier: Programming Language :: Python
27
27
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
28
28
  Classifier: Topic :: Utilities
29
29
  Classifier: Typing :: Typed
30
- Requires-Python: >=3.10
30
+ Requires-Python: >=3.11
31
31
  Description-Content-Type: text/markdown
32
32
  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
37
- Requires-Dist: tomli
36
+ Requires-Dist: python_minifier; python_version < "3.14"
37
+ Provides-Extra: development
38
+ Requires-Dist: mypy; extra == "development"
39
+ Requires-Dist: pyupgrade; extra == "development"
40
+ Requires-Dist: setuptools-scm; extra == "development"
38
41
  Provides-Extra: testing
39
- Requires-Dist: mypy; extra == "testing"
40
42
  Requires-Dist: pytest; extra == "testing"
41
43
  Requires-Dist: pytest-cov; extra == "testing"
42
44
  Requires-Dist: pytest-xdist; extra == "testing"
43
- Requires-Dist: pyupgrade; extra == "testing"
44
- Requires-Dist: setuptools-scm; extra == "testing"
45
45
  Dynamic: license-file
46
46
 
47
47
  # hunterMakesPy
@@ -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 (autoDecodingRLE as autoDecodingRLE, stringItUp as stringItUp,
24
- updateExtendPolishDictionaryLists as updateExtendPolishDictionaryLists)
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
@@ -0,0 +1,4 @@
1
+ """Settings for this package."""
2
+ from hunterMakesPy import PackageSettings
3
+
4
+ settingsPackage = PackageSettings(identifierPackageFALLBACK="hunterMakesPy")
@@ -0,0 +1,149 @@
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
7
+
8
+ if TYPE_CHECKING:
9
+ from importlib.machinery import ModuleSpec
10
+
11
+ TypeSansNone = TypeVar('TypeSansNone')
12
+
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.
38
+
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)
87
+
88
+ def raiseIfNone(returnTarget: TypeSansNone | None, errorMessage: str | None = None) -> TypeSansNone:
89
+ """Raise a `ValueError` if the target value is `None`, otherwise return the value: tell the type checker that the return value is not `None`.
90
+
91
+ (AI generated docstring)
92
+
93
+ This is a defensive programming function that converts unexpected `None` values into explicit errors with context. It is useful for asserting that functions that might return `None` have actually returned a meaningful value.
94
+
95
+ Parameters
96
+ ----------
97
+ returnTarget : TypeSansNone | None
98
+ The value to check for `None`. If not `None`, this value is returned unchanged.
99
+ errorMessage : str | None = None
100
+ Custom error message to include in the `ValueError`. If `None`, a default message with debugging hints is used.
101
+
102
+ Returns
103
+ -------
104
+ returnTarget : TypeSansNone
105
+ The original `returnTarget` value, guaranteed to not be `None`.
106
+
107
+ Raises
108
+ ------
109
+ ValueError
110
+ If `returnTarget` is `None`.
111
+
112
+ Examples
113
+ --------
114
+ Ensure a function result is not `None`:
115
+
116
+ ```python
117
+ def findFirstMatch(listItems: list[str], pattern: str) -> str | None:
118
+ for item in listItems:
119
+ if pattern in item:
120
+ return item
121
+ return None
122
+
123
+ listFiles = ['document.txt', 'image.png', 'data.csv']
124
+ filename = raiseIfNone(findFirstMatch(listFiles, '.txt'))
125
+ # Returns 'document.txt'
126
+ ```
127
+
128
+ Handle dictionary lookups with custom error messages:
129
+
130
+ ```python
131
+ configurationMapping = {'host': 'localhost', 'port': 8080}
132
+ host = raiseIfNone(configurationMapping.get('host'),
133
+ "Configuration must include 'host' setting")
134
+ # Returns 'localhost'
135
+
136
+ # This would raise ValueError with custom message:
137
+ # database = raiseIfNone(configurationMapping.get('database'),
138
+ # "Configuration must include 'database' setting")
139
+ ```
140
+
141
+ Thanks
142
+ ------
143
+ sobolevn, https://github.com/sobolevn, for the seed of the function. https://github.com/python/typing/discussions/1997#discussioncomment-13108399
144
+
145
+ """
146
+ if returnTarget is None:
147
+ message = errorMessage or 'A function unexpectedly returned `None`. Hint: look at the traceback immediately before `raiseIfNone`.'
148
+ raise ValueError(message)
149
+ return returnTarget
@@ -0,0 +1,268 @@
1
+ """Provides utilities for string extraction from nested data structures and merges multiple dictionaries containing lists into one dictionary."""
2
+
3
+ from collections.abc import Iterator, Mapping
4
+ from numpy import integer
5
+ from numpy.typing import NDArray
6
+ from typing import Any
7
+ import more_itertools
8
+ import re as regex
9
+ 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
+
69
+ option1 = ImaRange
70
+ option1AsStr = ImaRangeAsStr
71
+ option2 = ImaSerious
72
+ option2AsStr = None
73
+
74
+ # alpha, potential function
75
+ option1AsStr = option1AsStr or python_minifier.minify(str(option1))
76
+ lengthOption1 = getLengthOption(option1AsStr)
77
+
78
+ option2AsStr = option2AsStr or python_minifier.minify(str(option2))
79
+ lengthOption2 = getLengthOption(option2AsStr)
80
+
81
+ if lengthOption1 < lengthOption2:
82
+ addMe = option1
83
+ else:
84
+ addMe = option2
85
+
86
+ cache_consecutiveGroup_addMe[consecutiveGroup] = addMe
87
+
88
+ arraySliceAsList += addMe
89
+
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]
95
+ else:
96
+ lengthMalkovich = malkovichGrouped[-1]
97
+ malkovichAsList = list(more_itertools.run_length.decode([malkovichGrouped]))
98
+ malkovichMalkovich = f"[{malkovichGrouped[0]}]*{lengthMalkovich}"
99
+
100
+ option1 = [malkovichGrouped]
101
+ option1AsStr = malkovichMalkovich
102
+ option2 = malkovichAsList
103
+ option2AsStr = None
104
+
105
+ # beta, potential function
106
+ option1AsStr = option1AsStr or python_minifier.minify(str(option1))
107
+ lengthOption1 = getLengthOption(option1AsStr)
108
+
109
+ option2AsStr = option2AsStr or python_minifier.minify(str(option2))
110
+ lengthOption2 = getLengthOption(option2AsStr)
111
+
112
+ if lengthOption1 < lengthOption2:
113
+ addMe = option1
114
+ else:
115
+ addMe = option2
116
+
117
+ cache_malkovichGrouped_addMe[malkovichGrouped] = addMe
118
+
119
+ listRangeAndTuple += addMe
120
+
121
+ return listRangeAndTuple
122
+ return arraySlice
123
+
124
+ arrayAsNestedLists = sliceNDArrayToNestedLists(arrayTarget)
125
+
126
+ arrayAsStr = python_minifier.minify(str(arrayAsNestedLists))
127
+
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
+ )
140
+
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')
148
+
149
+ replaceAhead = "]+[" if joinAhead == "," else "["
150
+
151
+ replaceBehind = "+[" if joinBehind == "," else ""
152
+
153
+ return f"{replaceAhead}{malkovich}]*{multiply}{replaceBehind}"
154
+
155
+ arrayAsStr = patternRegex.sub(replacementByContext, arrayAsStr)
156
+ arrayAsStr = patternRegex.sub(replacementByContext, arrayAsStr)
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')
161
+
162
+ def stringItUp(*scrapPile: Any) -> list[str]: # noqa: C901
163
+ """Convert, if possible, every element in the input data structure to a string.
164
+
165
+ Order is not preserved or readily predictable.
166
+
167
+ Parameters
168
+ ----------
169
+ *scrapPile : Any
170
+ (scrap2pile) One or more data structures to unpack and convert to strings.
171
+
172
+ Returns
173
+ -------
174
+ listStrungUp : list[str]
175
+ (list2strung2up) A `list` of string versions of all convertible elements.
176
+
177
+ """
178
+ scrap = None
179
+ listStrungUp: list[str] = []
180
+
181
+ def drill(KitKat: Any) -> None: # noqa: C901, PLR0912
182
+ match KitKat:
183
+ case str():
184
+ listStrungUp.append(KitKat)
185
+ case bool() | bytearray() | bytes() | complex() | float() | int() | memoryview() | None:
186
+ listStrungUp.append(str(KitKat)) # pyright: ignore [reportUnknownArgumentType]
187
+ case dict():
188
+ for broken, piece in KitKat.items(): # pyright: ignore [reportUnknownVariableType]
189
+ drill(broken)
190
+ drill(piece)
191
+ case list() | tuple() | set() | frozenset() | range():
192
+ for kit in KitKat: # pyright: ignore [reportUnknownVariableType]
193
+ drill(kit)
194
+ case _:
195
+ if hasattr(KitKat, '__iter__'): # Unpack other iterables
196
+ for kat in KitKat:
197
+ drill(kat)
198
+ else:
199
+ try:
200
+ sharingIsCaring = KitKat.__str__()
201
+ listStrungUp.append(sharingIsCaring)
202
+ except AttributeError:
203
+ pass
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
+ pass
206
+ except:
207
+ print(f"\nWoah! I received '{repr(KitKat)}'.\nTheir report card says, 'Plays well with others: Needs improvement.'\n") # noqa: RUF010, T201
208
+ raise
209
+ try:
210
+ for scrap in scrapPile:
211
+ drill(scrap)
212
+ except RecursionError:
213
+ listStrungUp.append(repr(scrap))
214
+ return listStrungUp
215
+
216
+ def updateExtendPolishDictionaryLists(*dictionaryLists: Mapping[str, list[Any] | set[Any] | tuple[Any, ...]], destroyDuplicates: bool = False, reorderLists: bool = False, killErroneousDataTypes: bool = False) -> dict[str, list[Any]]:
217
+ """Merge multiple dictionaries containing `list` into a single dictionary.
218
+
219
+ With options to handle duplicates, `list` ordering, and erroneous data types.
220
+
221
+ Parameters
222
+ ----------
223
+ *dictionaryLists : Mapping[str, list[Any] | set[Any] | tuple[Any, ...]]
224
+ (dictionary2lists) Variable number of dictionaries to be merged. If only one dictionary is passed, it will be processed based on the provided options.
225
+ destroyDuplicates : bool = False
226
+ (destroy2duplicates) If `True`, removes duplicate elements from the `list`. Defaults to `False`.
227
+ reorderLists : bool = False
228
+ (reorder2lists) If `True`, sorts the `list`. Defaults to `False`.
229
+ killErroneousDataTypes : bool = False
230
+ (kill2erroneous2data2types) If `True`, skips dictionary keys or dictionary values that cause a `TypeError` during merging. Defaults to `False`.
231
+
232
+ Returns
233
+ -------
234
+ ePluribusUnum : dict[str, list[Any]]
235
+ (e2pluribus2unum) A single dictionary with merged `list` based on the provided options. If only one dictionary is passed,
236
+ it will be cleaned up based on the options.
237
+
238
+ Notes
239
+ -----
240
+ The returned value, `ePluribusUnum`, is a so-called primitive dictionary (`dict`). Furthermore, every dictionary key is a
241
+ so-called primitive string (cf. `str()`) and every dictionary value is a so-called primitive `list` (`list`). If
242
+ `dictionaryLists` has other data types, the data types will not be preserved. That could have unexpected consequences.
243
+ Conversion from the original data type to a `list`, for example, may not preserve the order even if you want the order to be
244
+ preserved.
245
+
246
+ """
247
+ ePluribusUnum: dict[str, list[Any]] = {}
248
+
249
+ for dictionaryListTarget in dictionaryLists:
250
+ for keyName, keyValue in dictionaryListTarget.items():
251
+ try:
252
+ ImaStr = str(keyName)
253
+ ImaList = list(keyValue)
254
+ ePluribusUnum.setdefault(ImaStr, []).extend(ImaList)
255
+ except TypeError:
256
+ if killErroneousDataTypes:
257
+ continue
258
+ else:
259
+ raise
260
+
261
+ if destroyDuplicates:
262
+ for ImaStr, ImaList in ePluribusUnum.items():
263
+ ePluribusUnum[ImaStr] = list(dict.fromkeys(ImaList))
264
+ if reorderLists:
265
+ for ImaStr, ImaList in ePluribusUnum.items():
266
+ ePluribusUnum[ImaStr] = sorted(ImaList)
267
+
268
+ return ePluribusUnum
@@ -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
- (parameter2value) The value that caused the error.
15
+ The value that caused the error.
18
16
  parameterValueType : str | None = None
19
- (parameter2value2type) The type name of the parameter value.
17
+ The name of the type of the parameter value.
20
18
  containerType : str | None = None
21
- (container2type) The type name of the container holding the parameter.
19
+ The name of the type of the container holding the parameter.
22
20
  isElement : bool = False
23
- (is2element) Whether the parameter is an element within a container.
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 | `None`] but `parameterName` must have integers [in type(s) `parameterType` | `None`].
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
- (context) The error context containing parameter value, type, and container information.
43
+ The error context containing parameter value, type, and container information.
45
44
  parameterName : str
46
- (parameter2name) The name of the parameter that caused the error.
45
+ The name of the parameter that caused the error.
47
46
  parameterType : type[Any] | None
48
- (parameter2type) The expected type of the parameter, used in error messages.
47
+ The expected type of the parameter, used in error messages.
49
48
 
50
49
  Returns
51
50
  -------
52
51
  errorMessage : str
53
- (error2message) The constructed error message string.
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
- This package has Pytest tests you can import and run on this function. `from hunterMakesPy.pytest_parseParameters import makeTestSuiteConcurrencyLimit`
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
- (concurrency2limit) The calculated concurrency limit, ensuring it is at least 1.
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
- malformed inputs. For example:
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
- (list2int2allegedly) The input sequence that should contain integer-compatible values.
172
- Accepts integers, strings, floats, complex numbers, and binary data.
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
- (parameter2name) Name of the parameter from your function for which this function is validating the input validated. If there is an error message, it provides context to your user. Defaults to 'the parameter'.
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
- (parameter2type) Expected type(s) of the parameter, used in error messages.
179
+ Expected type(s) of the parameter, used in error messages.
178
180
 
179
181
  Returns
180
182
  -------
181
183
  listValidated : list[int]
182
- (list2validated) A `list` containing validated integers.
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 `Exception`. It tries to interpret the string as `True`, `False`, or `None`. This package has Pytest tests you can import and run on this function.
288
- `from hunterMakesPy.pytest_parseParameters import makeTestSuiteOopsieKwargsie`
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
- (interpreted2value) The reserved keyword `True`, `False`, or `None` or the original string, `huh`.
299
+ The reserved keyword `True`, `False`, or `None` or the original string, `huh`.
300
300
 
301
301
  """
302
302
  if not isinstance(huh, str):
@@ -0,0 +1,10 @@
1
+ """Pytest tests you can use in your package to test some hunterMakesPy functions.
2
+
3
+ Each function in this module returns a list of test functions that can be used with `pytest.parametrize`.
4
+
5
+ Note: These test functions are now in `hunterMakesPy.tests` with all other tests.
6
+ """
7
+
8
+ from hunterMakesPy.tests.test_parseParameters import (
9
+ PytestFor_defineConcurrencyLimit as PytestFor_defineConcurrencyLimit, PytestFor_intInnit as PytestFor_intInnit,
10
+ PytestFor_oopsieKwargsie as PytestFor_oopsieKwargsie)