hunterMakesPy 0.7.6__tar.gz → 0.9.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 (28) hide show
  1. {huntermakespy-0.7.6 → huntermakespy-0.9.0}/PKG-INFO +11 -6
  2. {huntermakespy-0.7.6 → huntermakespy-0.9.0}/README.md +6 -1
  3. {huntermakespy-0.7.6 → huntermakespy-0.9.0}/hunterMakesPy/__init__.py +2 -1
  4. {huntermakespy-0.7.6 → huntermakespy-0.9.0}/hunterMakesPy/coping.py +4 -14
  5. {huntermakespy-0.7.6 → huntermakespy-0.9.0}/hunterMakesPy/dataStructures.py +95 -53
  6. {huntermakespy-0.7.6 → huntermakespy-0.9.0}/hunterMakesPy/filesystemToolkit.py +74 -49
  7. {huntermakespy-0.7.6 → huntermakespy-0.9.0}/hunterMakesPy/parseParameters.py +71 -63
  8. {huntermakespy-0.7.6 → huntermakespy-0.9.0}/hunterMakesPy/semiotics.py +3 -5
  9. {huntermakespy-0.7.6 → huntermakespy-0.9.0}/hunterMakesPy/tests/conftest.py +6 -6
  10. huntermakespy-0.9.0/hunterMakesPy/tests/conftestAnnex.py +22 -0
  11. {huntermakespy-0.7.6 → huntermakespy-0.9.0}/hunterMakesPy/tests/test_dataStructures.py +2 -2
  12. {huntermakespy-0.7.6 → huntermakespy-0.9.0}/hunterMakesPy/tests/test_theTypes.py +1 -2
  13. {huntermakespy-0.7.6 → huntermakespy-0.9.0}/hunterMakesPy/theTypes.py +5 -3
  14. {huntermakespy-0.7.6 → huntermakespy-0.9.0}/hunterMakesPy/theTypesCallableFunction.py +2 -12
  15. {huntermakespy-0.7.6 → huntermakespy-0.9.0}/hunterMakesPy.egg-info/PKG-INFO +11 -6
  16. {huntermakespy-0.7.6 → huntermakespy-0.9.0}/hunterMakesPy.egg-info/SOURCES.txt +1 -0
  17. {huntermakespy-0.7.6 → huntermakespy-0.9.0}/hunterMakesPy.egg-info/requires.txt +1 -1
  18. {huntermakespy-0.7.6 → huntermakespy-0.9.0}/pyproject.toml +5 -5
  19. {huntermakespy-0.7.6 → huntermakespy-0.9.0}/LICENSE +0 -0
  20. {huntermakespy-0.7.6 → huntermakespy-0.9.0}/hunterMakesPy/_theSSOT.py +0 -0
  21. {huntermakespy-0.7.6 → huntermakespy-0.9.0}/hunterMakesPy/py.typed +0 -0
  22. {huntermakespy-0.7.6 → huntermakespy-0.9.0}/hunterMakesPy/tests/__init__.py +0 -0
  23. {huntermakespy-0.7.6 → huntermakespy-0.9.0}/hunterMakesPy/tests/test_coping.py +0 -0
  24. {huntermakespy-0.7.6 → huntermakespy-0.9.0}/hunterMakesPy/tests/test_filesystemToolkit.py +0 -0
  25. {huntermakespy-0.7.6 → huntermakespy-0.9.0}/hunterMakesPy/tests/test_parseParameters.py +0 -0
  26. {huntermakespy-0.7.6 → huntermakespy-0.9.0}/hunterMakesPy.egg-info/dependency_links.txt +0 -0
  27. {huntermakespy-0.7.6 → huntermakespy-0.9.0}/hunterMakesPy.egg-info/top_level.txt +0 -0
  28. {huntermakespy-0.7.6 → huntermakespy-0.9.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hunterMakesPy
3
- Version: 0.7.6
3
+ Version: 0.9.0
4
4
  Summary: Easy Python functions making making functional Python functions easier.
5
5
  Author-email: Hunter Hogan <HunterHogan@pm.me>
6
6
  License-Expression: CC-BY-NC-4.0
@@ -17,15 +17,15 @@ Classifier: Intended Audience :: Developers
17
17
  Classifier: Natural Language :: English
18
18
  Classifier: Operating System :: OS Independent
19
19
  Classifier: Programming Language :: Python
20
- Classifier: Programming Language :: Python :: 3.10
21
- Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.15
22
22
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
23
  Classifier: Topic :: Utilities
24
24
  Classifier: Typing :: Typed
25
- Requires-Python: >=3.10
25
+ Requires-Python: >=3.12
26
26
  Description-Content-Type: text/markdown
27
27
  License-File: LICENSE
28
- Requires-Dist: Z0Z_tools>=2.2.1
28
+ Requires-Dist: Z0Z_tools>=4.0.0
29
29
  Requires-Dist: autoflake
30
30
  Requires-Dist: charset_normalizer
31
31
  Requires-Dist: isort
@@ -185,6 +185,11 @@ def test_my_integer_validator(test_name, test_func):
185
185
  ## My recovery
186
186
 
187
187
  [![Static Badge](https://img.shields.io/badge/2011_August-Homeless_since-blue?style=flat)](https://HunterThinks.com/support)
188
- [![YouTube Channel Subscribers](https://img.shields.io/youtube/channel/subscribers/UC3Gx7kz61009NbhpRtPP7tw)](https://www.youtube.com/@HunterHogan)
189
188
 
190
189
  [![CC-BY-NC-4.0](https://raw.githubusercontent.com/hunterhogan/hunterMakesPy/refs/heads/main/.github/CC-BY-NC-4.0.png)](https://creativecommons.org/licenses/by-nc/4.0/)
190
+
191
+ By law, authors retain their copyrights unless they grant exceptions. The [Attribution-NonCommercial 4.0 International Deed](https://github.com/hunterhogan/hunterMakesPy#License-1-ov-file) outlines how you may use this work.
192
+
193
+ The restriction on commercial use (without a license) limits advantage-taking, unjust enrichment, and exploitation. You would be morally wrong if you were to enrich yourself by using this work while I am denied healthcare, housing, security of property, and the safety of my person.
194
+
195
+ To obtain and retain the right to use this work, you incur the cost of attributing the work to me. Your nominal cost might accrue as a benefit that mitigates my suffering or saves my life.
@@ -143,6 +143,11 @@ def test_my_integer_validator(test_name, test_func):
143
143
  ## My recovery
144
144
 
145
145
  [![Static Badge](https://img.shields.io/badge/2011_August-Homeless_since-blue?style=flat)](https://HunterThinks.com/support)
146
- [![YouTube Channel Subscribers](https://img.shields.io/youtube/channel/subscribers/UC3Gx7kz61009NbhpRtPP7tw)](https://www.youtube.com/@HunterHogan)
147
146
 
148
147
  [![CC-BY-NC-4.0](https://raw.githubusercontent.com/hunterhogan/hunterMakesPy/refs/heads/main/.github/CC-BY-NC-4.0.png)](https://creativecommons.org/licenses/by-nc/4.0/)
148
+
149
+ By law, authors retain their copyrights unless they grant exceptions. The [Attribution-NonCommercial 4.0 International Deed](https://github.com/hunterhogan/hunterMakesPy#License-1-ov-file) outlines how you may use this work.
150
+
151
+ The restriction on commercial use (without a license) limits advantage-taking, unjust enrichment, and exploitation. You would be morally wrong if you were to enrich yourself by using this work while I am denied healthcare, housing, security of property, and the safety of my person.
152
+
153
+ To obtain and retain the right to use this work, you incur the cost of attributing the work to me. Your nominal cost might accrue as a benefit that mitigates my suffering or saves my life.
@@ -74,7 +74,8 @@ from hunterMakesPy.semiotics import (
74
74
 
75
75
  # isort: split
76
76
  from hunterMakesPy.theTypes import (
77
- CallableFunction as CallableFunction, identifierDotAttribute as identifierDotAttribute, Ordinals as Ordinals, 小于 as 小于)
77
+ CallableFunction as CallableFunction, ConcurrencyLimit as ConcurrencyLimit, identifierDotAttribute as identifierDotAttribute,
78
+ Ordinals as Ordinals, 小于 as 小于, 文义 as 文义, 文件 as 文件)
78
79
 
79
80
  # isort: split
80
81
  from hunterMakesPy.coping import PackageSettings as PackageSettings, raiseIfNone as raiseIfNone
@@ -12,14 +12,15 @@ if TYPE_CHECKING:
12
12
  from importlib.machinery import ModuleSpec
13
13
 
14
14
  def getIdentifierPackagePACKAGING(identifierPackageFALLBACK: str) -> str:
15
- """Get package name from pyproject.toml or fallback to provided value.""" # noqa: DOC201
15
+ """Get package name from pyproject.toml or fallback to provided value."""
16
16
  try:
17
17
  return tomli_loads(Path('pyproject.toml').read_text(encoding='utf-8'))['project']['name']
18
- except Exception: # noqa: BLE001
18
+ #ruff: ignore[blind-except]
19
+ except Exception:
19
20
  return identifierPackageFALLBACK
20
21
 
21
22
  def getPathPackageINSTALLING(identifierPackage: str) -> Path:
22
- """Return the root directory of the installed package.""" # noqa: DOC201
23
+ """Return the root directory of the installed package."""
23
24
  try:
24
25
  moduleSpecification: ModuleSpec | None = find_spec(identifierPackage)
25
26
  if moduleSpecification and moduleSpecification.origin:
@@ -54,8 +55,6 @@ class PackageSettings:
54
55
 
55
56
  ```python
56
57
  settings = PackageSettings(identifierPackageFALLBACK='cobraPy')
57
- # Automatically discovers package name from pyproject.toml
58
- # Resolves installation path from package identifier
59
58
  ```
60
59
 
61
60
  Explicit configuration for specific deployment:
@@ -67,7 +66,6 @@ class PackageSettings:
67
66
  fileExtension='.pyx'
68
67
  )
69
68
  ```
70
-
71
69
  """
72
70
 
73
71
  identifierPackageFALLBACK: dataclasses.InitVar[str] = ''
@@ -117,7 +115,6 @@ def raiseIfNone(expression: TypeSansNone | None, errorMessage: str | None = None
117
115
  Basic usage with attribute access:
118
116
  ```python
119
117
  annotation = raiseIfNone(ast_arg.annotation)
120
- # Raises ValueError if ast_arg.annotation is None
121
118
  ```
122
119
 
123
120
  Function return value validation:
@@ -130,7 +127,6 @@ def raiseIfNone(expression: TypeSansNone | None, errorMessage: str | None = None
130
127
 
131
128
  listFiles = ['document.txt', 'image.png', 'data.csv']
132
129
  filename = raiseIfNone(findFirstMatch(listFiles, '.txt'))
133
- # Returns 'document.txt' when match exists
134
130
  ```
135
131
 
136
132
  Dictionary value retrieval with custom message:
@@ -138,17 +134,11 @@ def raiseIfNone(expression: TypeSansNone | None, errorMessage: str | None = None
138
134
  configurationMapping = {'host': 'localhost', 'port': 8080}
139
135
  host = raiseIfNone(configurationMapping.get('host'),
140
136
  "Configuration must include 'host' setting")
141
- # Returns 'localhost' when key exists
142
-
143
- # This would raise ValueError with custom message:
144
- # database = raiseIfNone(configurationMapping.get('database'),
145
- # "Configuration must include 'database' setting")
146
137
  ```
147
138
 
148
139
  Thanks
149
140
  ------
150
141
  sobolevn, https://github.com/sobolevn, for the seed of this function. https://github.com/python/typing/discussions/1997#discussioncomment-13108399
151
-
152
142
  """
153
143
  if expression is None:
154
144
  message: str = errorMessage or 'A function unexpectedly returned `None`. Hint: look at the traceback immediately before `raiseIfNone`.'
@@ -1,41 +1,42 @@
1
1
  """Manipulate data structures with encoding, extraction, and merging utilities.
2
2
 
3
- You can use this module to transform arrays (in the general sense) into compact run-length encoded
4
- strings, extract strings from arbitrarily nested data structures, or merge dictionaries of lists.
3
+ You can use this module to encode integer arrays as compact Python expressions, extract strings
4
+ from nested data, normalize whitespace in representations of Python data, and merge mappings of
5
+ iterable values into dictionaries of lists.
5
6
 
6
7
  Contents
7
8
  --------
8
9
  Functions
9
10
  autoDecodingRLE
10
- Transform a NumPy array into a compact, self-decoding run-length encoded string
11
- representation.
11
+ Encode an integer array as a compact Python expression.
12
+ removeExtraWhitespace
13
+ Remove whitespace after commas and inside parentheses or square brackets.
12
14
  stringItUp
13
15
  Convert every element in input data structures to strings.
14
16
  updateExtendPolishDictionaryLists
15
- Merge multiple dictionaries with list values into a single dictionary.
17
+ Merge iterable values into lists with optional deduplication and sorting.
16
18
  """
17
19
  from __future__ import annotations
18
20
 
19
21
  from charset_normalizer import CharsetMatch
20
- from collections.abc import Hashable
21
- from humpy_cytoolz.dicttoolz import valmap
22
+ from humpy_cytoolz.dicttoolz import valmap as mapValues
22
23
  from humpy_cytoolz.functoolz import compose
23
24
  from types import FunctionType
24
- from typing import cast, TYPE_CHECKING, TypeVar
25
+ from typing import cast, overload, TYPE_CHECKING
25
26
  import charset_normalizer
26
27
  import more_itertools
27
28
  import re as regex
28
29
  import sys
29
30
 
30
31
  if TYPE_CHECKING:
31
- from collections.abc import Iterator, Mapping
32
- from hunterMakesPy import 小于
32
+ from collections.abc import Callable, Iterable, Iterator, Mapping
33
+ from hunterMakesPy import 小于, 文义, 文件
33
34
  from numpy import integer
34
35
  from numpy.typing import NDArray
35
- from typing import Any
36
+ from typing import Any, Literal
36
37
 
37
38
  def removeExtraWhitespace(string: str) -> str:
38
- """Remove extra whitespace from string representation of Python data structures.""" # noqa: DOC201
39
+ """Remove extra whitespace from string representation of Python data structures."""
39
40
  commas: str = regex.sub(r',\s+', ',', string)
40
41
  bracketsOpening: str = regex.sub(r'([\[\(])\s+', r'\1', commas)
41
42
  return regex.sub(r'\s+([\]\)])', r'\1', bracketsOpening)
@@ -70,7 +71,7 @@ def autoDecodingRLE(arrayTarget: NDArray[integer[Any]], *, assumeAddSpaces: bool
70
71
  """
71
72
  def sliceNDArrayToNestedLists(arraySlice: NDArray[integer[Any]]) -> Any:
72
73
  def getLengthOption(optionAsStr: str) -> int:
73
- """`assumeAddSpaces` characters: `,` 1 space; `]*` 2 spaces.""" # noqa: DOC201
74
+ """`assumeAddSpaces` characters: `,` 1 space; `]*` 2 spaces."""
74
75
  return assumeAddSpaces * (optionAsStr.count(',') + optionAsStr.count(']*') * 2) + len(optionAsStr)
75
76
 
76
77
  if 1 < arraySlice.ndim:
@@ -92,7 +93,6 @@ def autoDecodingRLE(arrayTarget: NDArray[integer[Any]], *, assumeAddSpaces: bool
92
93
  option2 = ImaSerious
93
94
  option2AsStr: str | None = None
94
95
 
95
- # alpha, potential function
96
96
  option1AsStr: str = option1AsStr or removeExtraWhitespace(str(option1))
97
97
  lengthOption1: int = getLengthOption(option1AsStr)
98
98
 
@@ -123,7 +123,6 @@ def autoDecodingRLE(arrayTarget: NDArray[integer[Any]], *, assumeAddSpaces: bool
123
123
  option2 = malkovichAsList
124
124
  option2AsStr = None
125
125
 
126
- # beta, potential function
127
126
  option1AsStr = option1AsStr or removeExtraWhitespace(str(option1))
128
127
  lengthOption1 = getLengthOption(option1AsStr)
129
128
 
@@ -148,19 +147,19 @@ def autoDecodingRLE(arrayTarget: NDArray[integer[Any]], *, assumeAddSpaces: bool
148
147
 
149
148
  patternRegex: regex.Pattern[str] = regex.compile(
150
149
  r"(?<!rang)(?:"
151
- # Pattern 1: Comma ahead, bracket behind
150
+ #Pattern 1: Comma ahead, bracket behind
152
151
  r"(?P<joinAhead>,)\((?P<malkovich>\d+),(?P<multiply>\d+)\)(?P<bracketBehind>])|"
153
- # Pattern 2: Bracket or start ahead, comma behind
152
+ #Pattern 2: Bracket or start ahead, comma behind
154
153
  r"(?P<bracketOrStartAhead>\[|^.)\((?P<malkovichMalkovich>\d+),(?P<multiplyIDK>\d+)\)(?P<joinBehind>,)|"
155
- # Pattern 3: Bracket ahead, bracket behind
154
+ #Pattern 3: Bracket ahead, bracket behind
156
155
  r"(?P<bracketAhead>\[)\((?P<malkovichMalkovichMalkovich>\d+),(?P<multiply_whatever>\d+)\)(?P<bracketBehindBracketBehind>])|"
157
- # Pattern 4: Comma ahead, comma behind
156
+ #Pattern 4: Comma ahead, comma behind
158
157
  r"(?P<joinAheadJoinAhead>,)\((?P<malkovichMalkovichMalkovichMalkovich>\d+),(?P<multiplyOrSomething>\d+)\)(?P<joinBehindJoinBehind>,)"
159
158
  r")"
160
159
  )
161
160
 
162
161
  def replacementByContext(match: regex.Match[str]) -> str:
163
- """Generate replacement string based on context patterns.""" # noqa: DOC201
162
+ """Generate replacement string based on context patterns."""
164
163
  elephino: dict[str, str | None] = match.groupdict()
165
164
  joinAhead: str | None = elephino.get('joinAhead') or elephino.get('joinAheadJoinAhead')
166
165
  malkovich: str | None = elephino.get('malkovich') or elephino.get('malkovichMalkovich') or elephino.get('malkovichMalkovichMalkovich') or elephino.get('malkovichMalkovichMalkovichMalkovich')
@@ -176,8 +175,8 @@ def autoDecodingRLE(arrayTarget: NDArray[integer[Any]], *, assumeAddSpaces: bool
176
175
  arrayAsStr = patternRegex.sub(replacementByContext, arrayAsStr)
177
176
  arrayAsStr = patternRegex.sub(replacementByContext, arrayAsStr)
178
177
 
179
- # .replace `range(0,stop)` syntax with `range(stop)` syntax.
180
- # Add unpack operator `*` for automatic decoding when evaluated.
178
+ #.replace `range(0,stop)` syntax with `range(stop)` syntax.
179
+ #Add unpack operator `*` for automatic decoding when evaluated.
181
180
  return arrayAsStr.replace('range(0,', 'range(').replace('range', '*range')
182
181
 
183
182
  def stringItUp(*scrapPile: Any) -> list[str]:
@@ -220,16 +219,18 @@ def stringItUp(*scrapPile: Any) -> list[str]:
220
219
  elif isinstance(KitKat, (list, tuple, set, frozenset, range)):
221
220
  for kit in KitKat: # pyright: ignore[reportUnknownVariableType]
222
221
  drill(kit)
223
- elif hasattr(KitKat, '__iter__'): # Unpack other iterables
222
+ elif hasattr(KitKat, '__iter__'):
224
223
  for kat in KitKat:
225
224
  drill(kat)
226
225
  else:
227
226
  try:
228
- sharingIsCaring: str = KitKat.__str__() # noqa: PLC2801
227
+ #ruff: ignore[unnecessary-dunder-call]
228
+ sharingIsCaring: str = KitKat.__str__()
229
229
  listStrungUp.append(sharingIsCaring)
230
230
  except AttributeError:
231
231
  pass
232
- 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."
232
+ except TypeError:
233
+ #"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."
233
234
  pass
234
235
  except:
235
236
  message: str = (f"\nWoah! I received '{repr(KitKat)}'.\nTheir report card says, 'Plays well with others: Needs improvement.'\n")
@@ -242,55 +243,96 @@ def stringItUp(*scrapPile: Any) -> list[str]:
242
243
  listStrungUp.append(repr(scrap))
243
244
  return listStrungUp
244
245
 
245
- 文件 = TypeVar('文件', bound=Hashable)
246
+ @overload
247
+ def updateExtendPolishDictionaryLists(*dictionaryLists: Mapping[文件, Iterable[文义]], destroyDuplicates: bool = False, reorderLists: Literal[False] = False, killErroneousDataTypes: bool = False) -> dict[文件, list[文义]]: ...
246
248
 
247
- # TODO if `reorderLists: bool = False`, the elements do not need to be ordinals, and they should be covariant.
248
- def updateExtendPolishDictionaryLists(*dictionaryLists: Mapping[文件, list[小于] | set[小于] | tuple[小于, ...]], destroyDuplicates: bool = False, reorderLists: bool = False, killErroneousDataTypes: bool = False) -> dict[文件, list[小于]]:
249
- """Merge multiple dictionaries with `list` values into a single dictionary with the `list` values merged.
249
+ @overload
250
+ def updateExtendPolishDictionaryLists(*dictionaryLists: Mapping[文件, Iterable[小于]], destroyDuplicates: bool = False, reorderLists: Literal[True], killErroneousDataTypes: bool = False) -> dict[文件, list[小于]]: ...
250
251
 
251
- Plus options to destroy duplicates, sort `list` values, and handle erroneous data types.
252
+ @overload
253
+ def updateExtendPolishDictionaryLists(*dictionaryLists: Mapping[文件, Iterable[小于]], destroyDuplicates: bool = False, reorderLists: bool, killErroneousDataTypes: bool = False) -> dict[文件, list[小于]]: ...
254
+
255
+ def updateExtendPolishDictionaryLists(*dictionaryLists: Mapping[文件, Iterable[文义]], destroyDuplicates: bool = False, reorderLists: bool = False, killErroneousDataTypes: bool = False) -> dict[文件, list[文义]]:
256
+ """Merge mappings of iterable values into a dictionary of lists.
257
+
258
+ You can use this function to combine values under matching keys across `dictionaryLists`.
259
+ This function returns a new dictionary with new lists and can remove duplicate elements,
260
+ sort elements, or skip entries that raise `TypeError` during merging.
261
+
262
+ Iteration and Ordering
263
+ ----------------------
264
+ This function consumes each value's iterable in mapping argument order. Within each value,
265
+ the list follows the iterable's order, which may be unpredictable for sets. Strings contribute
266
+ individual characters, and mapping values contribute their keys.
267
+
268
+ Deduplication uses `dict.fromkeys` [1] to preserve first occurrences. Sorting uses `sorted`
269
+ and runs after deduplication. Every retained value becomes a list regardless of the input type.
252
270
 
253
271
  Parameters
254
272
  ----------
255
- *dictionaryLists : MutableMapping[文件, list[Any] | set[Any] | tuple[Any, ...]]
256
- Variable number of dictionaries to be merged. If only one dictionary is passed, it will be
257
- "polished".
273
+ *dictionaryLists : Mapping[文件, Iterable[文义]]
274
+ The mappings to merge, with hashable keys and iterable values. A single mapping receives
275
+ the same conversion and optional processing. No mappings produce an empty dictionary.
258
276
  destroyDuplicates : bool = False
259
- If `True`, removes duplicate elements from the `list`. Defaults to `False`.
277
+ If `True`, remove duplicate elements from each merged list, preserving the first occurrence.
278
+ Elements must be hashable when this option is enabled.
260
279
  reorderLists : bool = False
261
- If `True`, sorts each `list` value. Defaults to `False`. The elements must be comparable;
262
- otherwise, a `TypeError` will be raised.
280
+ If `True`, sort each merged list in ascending order after optional deduplication.
281
+ Elements within each list must be mutually comparable.
263
282
  killErroneousDataTypes : bool = False
264
- If `True`, suppresses any `TypeError` `Exception` and omits the dictionary key or value that
265
- caused the `Exception`. Defaults to `False`.
283
+ If `True`, skip an entry when converting the entry's value to a list or merging the entry
284
+ raises `TypeError`. Successfully merged values for the same key remain in the result.
285
+ This option does not suppress `TypeError` from deduplication or sorting.
266
286
 
267
287
  Returns
268
288
  -------
269
- ePluribusUnum : dict[文件, list[Any]]
270
- A single dictionary with merged and optionally "polished" `list` values.
271
-
272
- Notes
273
- -----
274
- The returned value, `ePluribusUnum`, is a dictionary (`dict`). Furthermore, every dictionary value
275
- is a `list`. If `dictionaryLists` has values that are not data type `list`, the data type will not
276
- be preserved. That could have unexpected consequences. Conversion from the original data type to a
277
- `list`, for example, may not preserve the order even if you want the order to be preserved.
278
- """ # noqa: DOC501
279
- ePluribusUnum: dict[文件, list[小于]] = {}
289
+ ePluribusUnum : dict[文件, list[文义]]
290
+ The merged dictionary, with a list for every retained key. This function does not modify
291
+ the input mappings or their containers, but the lists retain references to the original elements.
292
+
293
+ Raises
294
+ ------
295
+ TypeError
296
+ If a value cannot be iterated or an entry cannot be merged and `killErroneousDataTypes`
297
+ is `False`. Also raised for unhashable elements during deduplication or incomparable
298
+ elements during sorting, regardless of `killErroneousDataTypes`.
299
+
300
+ Examples
301
+ --------
302
+ The README merges server lists while preserving the first occurrence of each server name.
303
+
304
+ ```python
305
+ from hunterMakesPy.dataStructures import updateExtendPolishDictionaryLists
306
+
307
+ merged = updateExtendPolishDictionaryLists(
308
+ {"servers": ["chicago"]},
309
+ {"servers": ["tokyo", "chicago"]},
310
+ destroyDuplicates=True,
311
+ )
312
+ # Returns {"servers": ["chicago", "tokyo"]}.
313
+ ```
314
+
315
+ References
316
+ ----------
317
+ [1] Python dictionaries and `dict.fromkeys`.
318
+ https://docs.python.org/3/library/stdtypes.html#dict.fromkeys
319
+ """
320
+ ePluribusUnum: dict[文件, list[文义]] = {}
280
321
 
281
322
  for dictionaryListTarget in dictionaryLists:
282
323
  for keyName, keyValue in dictionaryListTarget.items():
283
324
  try:
284
- ImaList: list[小于] = list(keyValue)
325
+ ImaList: list[文义] = list(keyValue)
285
326
  ePluribusUnum.setdefault(keyName, []).extend(ImaList)
286
- except TypeError as error: # noqa: PERF203
327
+ except TypeError as error:
287
328
  if killErroneousDataTypes:
288
329
  continue
289
330
  raise TypeError from error
290
331
 
291
332
  if destroyDuplicates:
292
- ePluribusUnum = valmap(compose(list, dict.fromkeys), ePluribusUnum)
333
+ ePluribusUnum = mapValues(compose(list, dict.fromkeys), ePluribusUnum)
293
334
  if reorderLists:
294
- ePluribusUnum = valmap(sorted, ePluribusUnum)
335
+ #=SIN= `sorted` is broken.
336
+ ePluribusUnum = mapValues(cast('Callable[[list[文义]], list[文义]]', sorted), ePluribusUnum)
295
337
 
296
338
  return ePluribusUnum
@@ -1,41 +1,43 @@
1
- """Access dynamic import utilities and text-writing utilities.
1
+ """Import Python identifiers and write or append text to files.
2
2
 
3
3
  (AI generated docstring)
4
4
 
5
5
  You can use this module to import `identifier` values from logical module paths or Python files,
6
- create parent directories for output paths, and write text or formatted Python source to files and
7
- text streams. The writing functions can normalize Python imports with `autoflake` [1] and `isort` [2]
8
- before the destination receives the final text.
6
+ create parent directories for output paths, append text to files, and write text or formatted Python
7
+ source to files and text streams. The writing functions can normalize Python imports with
8
+ `autoflake` [1] and `isort` [2] before the destination receives the final text.
9
9
 
10
10
  Contents
11
11
  --------
12
12
  Functions
13
- importLogicalPath2Identifier
14
- Import `identifier` from the module named by `logicalPathModule`.
15
- importPathFilename2Identifier
16
- Import `identifier` from the Python file at `pathFilename`.
17
- makeDirectorySafely
18
- Create parent directories for `pathFilename` when `pathFilename` is a filesystem path.
19
- makeDirsSafely
20
- Temporary alias for `makeDirectorySafely`.
21
- writePython
22
- Format `pythonSource` and write `pythonSource` to `pathFilename`.
23
- writeStringToHere
24
- Write `this` to `pathFilename`.
13
+ appendStringToHere
14
+ Append `this` to `pathFilename` without changing existing text.
15
+ importLogicalPath2Identifier
16
+ Import `identifier` from the module named by `logicalPathModule`.
17
+ importPathFilename2Identifier
18
+ Import `identifier` from the Python file at `pathFilename`.
19
+ makeDirectorySafely
20
+ Create parent directories for `pathFilename` when `pathFilename` is a filesystem path.
21
+ makeDirsSafely
22
+ Temporary alias for `makeDirectorySafely`.
23
+ writePython
24
+ Format `pythonSource` and write `pythonSource` to `pathFilename`.
25
+ writeStringToHere
26
+ Write `this` to `pathFilename`.
25
27
 
26
28
  Variables
27
29
  ---------
28
- settings_autoflakeDEFAULT
29
- Default settings dictionary for Python source cleanup.
30
- settings_isortDEFAULT
31
- Default settings dictionary for Python import sorting.
30
+ settings_autoflakeDEFAULT
31
+ Default settings dictionary for Python source cleanup.
32
+ settings_isortDEFAULT
33
+ Default settings dictionary for Python import sorting.
32
34
 
33
35
  References
34
36
  ----------
35
37
  [1] autoflake
36
- https://github.com/PyCQA/autoflake
38
+ https://github.com/PyCQA/autoflake
37
39
  [2] isort
38
- https://pycqa.github.io/isort/
40
+ https://pycqa.github.io/isort/
39
41
  """
40
42
  from __future__ import annotations
41
43
 
@@ -55,6 +57,49 @@ if TYPE_CHECKING:
55
57
  from types import ModuleType
56
58
  from typing import Any
57
59
 
60
+ def appendStringToHere(this: str, pathFilename: PathLike[Any] | PurePath) -> Path:
61
+ """Append `this` to the UTF-8 file at `pathFilename`.
62
+
63
+ (AI generated docstring)
64
+
65
+ You can use this function to add text to the end of a file while preserving existing text. This
66
+ function creates missing parent directories when possible and creates the file when needed.
67
+ This function returns the destination path after writing and closing the file.
68
+
69
+ Parameters
70
+ ----------
71
+ this : str
72
+ The text to append. This function does not add a separator or trailing newline.
73
+ pathFilename : PathLike[Any] | PurePath
74
+ The destination file path. This function accepts a filesystem path rather than an open stream.
75
+
76
+ Returns
77
+ -------
78
+ destinationAppended : Path
79
+ The destination path as a `Path`, without resolving the path to an absolute path.
80
+
81
+ See Also
82
+ --------
83
+ `writeStringToHere`
84
+ Replace a file's contents or write to an open text stream.
85
+
86
+ File Writing
87
+ ------------
88
+ This function prepares parent directories with `makeDirectorySafely`, then opens the file in
89
+ append mode with UTF-8 encoding and `newline=''`. Newline characters in `this` are written
90
+ without translation. This function writes and flushes the text with `writeStringToHere`.
91
+ Directory creation suppresses `OSError`, but errors from opening or writing the file propagate.
92
+
93
+ Examples
94
+ --------
95
+ No invocation of this function exists elsewhere in the codebase.
96
+ """
97
+ pathFilename = Path(pathFilename)
98
+ makeDirectorySafely(pathFilename)
99
+ with pathFilename.open(encoding='utf-8', mode='a', newline='') as streamAppend:
100
+ writeStringToHere(this, streamAppend)
101
+ return pathFilename
102
+
58
103
  def importLogicalPath2Identifier(logicalPathModule: identifierDotAttribute, identifier: str, packageIdentifierIfRelative: str | None = None) -> Any:
59
104
  """Import `identifier` from the module named by `logicalPathModule`.
60
105
 
@@ -82,12 +127,13 @@ def importLogicalPath2Identifier(logicalPathModule: identifierDotAttribute, iden
82
127
  return getattr(moduleImported, identifier)
83
128
 
84
129
  def importPathFilename2Identifier(pathFilename: PathLike[Any] | PurePath, identifier: str, moduleIdentifier: str | None = None) -> Any:
130
+ #ruff: ignore[docstring-extraneous-exception]
85
131
  """Import `identifier` from the Python file at `pathFilename`.
86
132
 
87
133
  You can use this function to load a Python source file as a module and retrieve a named attribute
88
134
  from that module. This function builds a module specification with
89
- `importlib.util.spec_from_file_location` [1], executes the loaded module with
90
- `importlib.util.module_from_spec` [2], and returns the attribute selected by `identifier`.
135
+ `importlib.util.spec_from_file_location`, executes the loaded module with
136
+ `importlib.util.module_from_spec`, and returns the attribute selected by `identifier`.
91
137
 
92
138
  Parameters
93
139
  ----------
@@ -111,14 +157,7 @@ def importPathFilename2Identifier(pathFilename: PathLike[Any] | PurePath, identi
111
157
  Raised when the loader cannot read `pathFilename`.
112
158
  AttributeError
113
159
  Raised when the loaded module does not define `identifier`.
114
-
115
- References
116
- ----------
117
- [1] `importlib.util.spec_from_file_location`
118
- https://docs.python.org/3/library/importlib.html#importlib.util.spec_from_file_location
119
- [2] `importlib.util.module_from_spec`
120
- https://docs.python.org/3/library/importlib.html#importlib.util.module_from_spec
121
- """ # noqa: DOC502
160
+ """
122
161
  pathFilename = Path(pathFilename)
123
162
 
124
163
  importlibSpecification: ModuleSpec | None = importlib.util.spec_from_file_location(moduleIdentifier or pathFilename.stem, pathFilename)
@@ -134,21 +173,14 @@ def makeDirectorySafely(pathFilename: Any) -> None:
134
173
  """Create parent directories for `pathFilename` when `pathFilename` is a filesystem path.
135
174
 
136
175
  You can use this function to prepare an output location before a later write operation. This
137
- function ignores `OSError` from `Path.mkdir` [1] and does nothing when `pathFilename` is an
138
- `IOBase` [2] stream.
176
+ function ignores `OSError` from `Path.mkdir` and does nothing when `pathFilename` is an
177
+ `IOBase` stream.
139
178
 
140
179
  Parameters
141
180
  ----------
142
181
  pathFilename : Any
143
182
  The target path or open stream. When `pathFilename` is not an `IOBase` instance, the
144
183
  function creates the parent directory of `pathFilename`.
145
-
146
- References
147
- ----------
148
- [1] `pathlib.Path.mkdir`
149
- https://docs.python.org/3/library/pathlib.html#pathlib.Path.mkdir
150
- [2] `IOBase`
151
- https://docs.python.org/3/library/io.html#io.IOBase
152
184
  """
153
185
  if not isinstance(pathFilename, IOBase):
154
186
  with contextlib.suppress(OSError):
@@ -241,7 +273,7 @@ def writeStringToHere(this: str, pathFilename: PathLike[Any] | PurePath | TextIO
241
273
 
242
274
  You can use this function to send text to a filesystem path or an open text stream. This function
243
275
  creates the parent directory when `pathFilename` is path-like, writes UTF-8 text with
244
- `Path.write_text` [1], or writes and flushes a `TextIOBase` [2] stream.
276
+ `Path.write_text`, or writes and flushes a `TextIOBase` stream.
245
277
 
246
278
  Parameters
247
279
  ----------
@@ -271,13 +303,6 @@ def writeStringToHere(this: str, pathFilename: PathLike[Any] | PurePath | TextIO
271
303
  pathFilename.relative_to(pathTransformee)
272
304
  )
273
305
  ```
274
-
275
- References
276
- ----------
277
- [1] `pathlib.Path.write_text`
278
- https://docs.python.org/3/library/pathlib.html#pathlib.Path.write_text
279
- [2] `TextIOBase`
280
- https://docs.python.org/3/library/io.html#io.TextIOBase
281
306
  """
282
307
  if isinstance(pathFilename, TextIOBase):
283
308
  pathFilename.write(str(this))
@@ -1,20 +1,13 @@
1
- # ruff: file-ignore[raise-within-try, docstring-missing-exception, too-many-statements-in-try-clause, blind-except]
2
- """Validate parameters and parse input with defensive error handling.
1
+ # ruff: file-ignore[raise-within-try]
2
+ """Validate integer inputs, calculate worker counts, and interpret string parameters.
3
3
 
4
4
  (AI generated docstring)
5
5
 
6
- You can use this module to validate and convert input sequences to integers, determine
7
- concurrency limits based on flexible parameter specifications, and interpret string values
8
- as boolean or None types. The module provides strict validation functions that follow
9
- fail-early principles with descriptive error messages for debugging.
10
-
11
- The integer validation function accepts various numeric types including strings, floats,
12
- complex numbers, and binary data, converting them to integers while detecting ambiguous
13
- or incompatible values. The concurrency limit function interprets boolean, integer, and
14
- float values to compute processor counts with support for absolute limits, fractional
15
- allocation, and reserved-processor specifications. The string interpretation function
16
- attempts to parse string values as True, False, or None to handle parameter type mismatches
17
- gracefully.
6
+ You can use this module to convert integer-compatible inputs into lists of integers, calculate
7
+ worker counts from absolute or fractional limits, and interpret strings as `True`, `False`, or
8
+ `None`. Integer validation reports incompatible values with context about the caller's parameter.
9
+ Worker-count calculation uses the system CPU count from `multiprocessing` by default and also
10
+ accepts numeric strings. Binary integer inputs use `charset_normalizer` for text decoding.
18
11
 
19
12
  Contents
20
13
  --------
@@ -26,13 +19,9 @@ Functions
26
19
  oopsieKwargsie
27
20
  Interpret a string as True, False, or None to avoid exceptions.
28
21
 
29
- References
30
- ----------
31
- [1] multiprocessing - Context7
32
- https://docs.python.org/3/library/multiprocessing.html
33
- [2] charset-normalizer - Context7
34
- https://github.com/Ousret/charset_normalizer
35
-
22
+ Classes
23
+ ErrorMessageContext
24
+ Store value, type, and container information for integer-validation error messages.
36
25
  """
37
26
  from __future__ import annotations
38
27
 
@@ -46,7 +35,7 @@ import sys
46
35
  if TYPE_CHECKING:
47
36
  from charset_normalizer.models import CharsetMatch
48
37
  from collections.abc import Collection
49
- from hunterMakesPy.theTypes import Limitation
38
+ from hunterMakesPy.theTypes import ConcurrencyLimit
50
39
  from typing import Any
51
40
 
52
41
  @dataclass
@@ -71,49 +60,67 @@ class ErrorMessageContext:
71
60
  containerType: str | None = None
72
61
  isElement: bool = False
73
62
 
74
- def defineConcurrencyLimit(*, limit: Limitation, cpuTotal: int = multiprocessing.cpu_count()) -> int:
75
- """Determine the concurrency limit based on the provided parameter.
63
+ def defineConcurrencyLimit(*, limit: ConcurrencyLimit, cpuTotal: int = multiprocessing.cpu_count()) -> int:
64
+ """Calculate a worker count from an absolute, fractional, or reserved-CPU limit.
65
+
66
+ You can use this function to select a worker count from `limit` and `cpuTotal`. This function
67
+ accepts explicit counts, fractions of `cpuTotal`, counts to reserve, and boolean choices.
68
+ This function also accepts numeric strings and strings representing `True`, `False`, or `None`.
69
+ The result is at least one worker and is at most 61 workers on Windows.
70
+
71
+ String Parsing
72
+ --------------
73
+ This function first uses `oopsieKwargsie` to interpret strings representing `True`, `False`,
74
+ or `None`, ignoring capitalization and surrounding whitespace. Other strings are converted
75
+ with `float`. An invalid numeric string raises `ValueError` with the conversion error as the cause.
76
+
77
+ Processor Counts
78
+ ----------------
79
+ This function rounds floating-point counts whose absolute value is at least one before applying
80
+ the count rules. Rounding uses Python `round`, including rounding ties to the nearest even
81
+ integer. This function applies the following rules before enforcing the platform bounds.
82
+
83
+ - `None`, `False`, or zero select `cpuTotal`.
84
+ - `True` selects one worker.
85
+ - A positive count of at least one selects that count.
86
+ - A positive fraction below one selects `round(limit * cpuTotal)` workers.
87
+ - A negative fraction above negative one reserves `abs(round(limit * cpuTotal))` CPUs.
88
+ - A negative count of at most negative one reserves `abs(int(limit))` CPUs.
89
+
90
+ This function caps the result at 61 on Windows, matching the restriction for
91
+ `concurrent.futures.ProcessPoolExecutor`. This function then enforces the minimum of one.
76
92
 
77
93
  Parameters
78
94
  ----------
79
- limit : bool | float | int | None
80
- Whether and how to limit CPU usage. See notes and examples for details how to describe the options to your users.
95
+ limit : ConcurrencyLimit
96
+ The worker-count specification. `None`, `False`, or zero select `cpuTotal`; `True` selects
97
+ one worker. Positive counts select workers to use, and negative counts select CPUs to reserve.
98
+ Fractions between negative one and one select proportions to reserve or use, respectively.
99
+ Strings are also accepted at runtime, although the type annotation excludes strings.
81
100
  cpuTotal : int = multiprocessing.cpu_count()
82
- The total number of CPUs available in the system. Default is `multiprocessing.cpu_count()`.
101
+ The CPU count used for fractional limits and reserved-CPU calculations. The default is the
102
+ value of `multiprocessing.cpu_count` evaluated when this module is imported.
83
103
 
84
104
  Returns
85
105
  -------
86
106
  concurrencyLimit : int
87
- The calculated concurrency limit, ensuring it is at least 1.
107
+ The calculated worker count, with a minimum of one and a Windows maximum of 61.
108
+ An explicit positive count can exceed `cpuTotal`.
88
109
 
89
- Example parameters
90
- ------------------
91
- ```python
92
- CPUlimit: bool | float | int | None
93
- CPUlimit: Limitation = None
94
- ```
110
+ Raises
111
+ ------
112
+ ValueError
113
+ If a string does not represent a number, `True`, `False`, or `None`.
95
114
 
96
- Example docstring
97
- -----------------
98
- ```python
115
+ Examples
116
+ --------
117
+ The README selects three quarters of the default CPU count.
99
118
 
100
- Arguments
101
- ---------
102
- CPUlimit: bool | float | int | None
103
- Whether and how to limit the the number of available processors used by the function. See notes for details.
104
-
105
- Notes
106
- -----
107
- Limits on CPU usage, `CPUlimit`:
108
- - `False`, `None`, or `0`: No limits on processor usage; uses all available processors. All other values will potentially limit processor usage.
109
- - `True`: Yes, limit the processor usage; limits to 1 processor.
110
- - `int >= 1`: The maximum number of available processors to use.
111
- - `0 < float < 1`: The maximum number of processors to use expressed as a fraction of available processors.
112
- - `-1 < float < 0`: The number of processors to *not* use expressed as a fraction of available processors.
113
- - `int <= -1`: The number of available processors to *not* use.
114
- - 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`.
115
- ```
119
+ ```python
120
+ from hunterMakesPy.parseParameters import defineConcurrencyLimit
116
121
 
122
+ workers = defineConcurrencyLimit(limit=0.75)
123
+ ```
117
124
  """
118
125
  if isinstance(limit, str):
119
126
  limitFromString: bool | str | None = oopsieKwargsie(limit)
@@ -142,7 +149,7 @@ def defineConcurrencyLimit(*, limit: Limitation, cpuTotal: int = multiprocessing
142
149
  elif limit <= -1:
143
150
  concurrencyLimit = cpuTotal - abs(int(limit))
144
151
 
145
- # https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.ProcessPoolExecutor
152
+ #https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.ProcessPoolExecutor
146
153
  if sys.platform == "win32":
147
154
  concurrencyLimit = min(concurrencyLimit, 61)
148
155
  return max(int(concurrencyLimit), 1)
@@ -170,7 +177,6 @@ def _constructErrorMessage(context: ErrorMessageContext, parameterName: str, par
170
177
  -------
171
178
  errorMessage : str
172
179
  The constructed error message string.
173
-
174
180
  """
175
181
  messageParts: list[str] = ["I received "]
176
182
 
@@ -239,8 +245,9 @@ def intInnit(listInt_Allegedly: Collection[Any], parameterName: str | None = Non
239
245
  message: str = f"I did not receive a value for `{parameterName}`, but I must have it."
240
246
  raise ValueError(message)
241
247
 
242
- # Be nice, and assume the input container is valid and every element is valid.
243
- # Nevertheless, this is a "fail-early" step, so reject ambiguity and try to induce errors now that could be catastrophic later.
248
+ #Be nice, and assume the input container is valid and every element is valid.
249
+ #Nevertheless, this is a "fail-early" step, so reject ambiguity and try to induce errors now that could be catastrophic later.
250
+ #ruff: ignore[too-many-statements-in-try-clause]
244
251
  try:
245
252
  iter(listInt_Allegedly)
246
253
  lengthInitial: int | None = None
@@ -256,12 +263,12 @@ def intInnit(listInt_Allegedly: Collection[Any], parameterName: str | None = Non
256
263
  , isElement=True
257
264
  )
258
265
 
259
- # Always rejected as ambiguous
266
+ #Always rejected as ambiguous
260
267
  if isinstance(allegedInt, bool):
261
268
  raise TypeError(errorMessageContext)
262
269
 
263
- # In this section, we know the Python type is not `int`, but maybe the value is clearly an integer.
264
- # Through a series of conversions, allow data to cascade down into either an `int` or a meaningful error message.
270
+ #In this section, we know the Python type is not `int`, but maybe the value is clearly an integer.
271
+ #Through a series of conversions, allow data to cascade down into either an `int` or a meaningful error message.
265
272
 
266
273
  if isinstance(allegedInt, (bytes, bytearray, memoryview)):
267
274
  errorMessageContext.parameterValue = None # Don't expose potentially garbled binary data in error messages
@@ -288,7 +295,7 @@ def intInnit(listInt_Allegedly: Collection[Any], parameterName: str | None = Non
288
295
 
289
296
  listValidated.append(allegedInt)
290
297
 
291
- if lengthInitial is not None and isinstance(listInt_Allegedly, Sized) and len(listInt_Allegedly) != lengthInitial:
298
+ if lengthInitial is not None and isinstance(listInt_Allegedly, Sized) and len(listInt_Allegedly) != lengthInitial: # pyright: ignore[reportUnnecessaryComparison]
292
299
  raise RuntimeError((lengthInitial, len(listInt_Allegedly)))
293
300
 
294
301
  except (TypeError, ValueError) as ERROR:
@@ -298,8 +305,8 @@ def intInnit(listInt_Allegedly: Collection[Any], parameterName: str | None = Non
298
305
  context.containerType = type(listInt_Allegedly).__name__
299
306
  message = _constructErrorMessage(context, parameterName, parameterType)
300
307
  raise type(ERROR)(message) from None
301
- # If it's not our Exception, don't molest it
302
- raise
308
+ #If it's not our Exception, don't molest it
309
+ raise ValueError from ERROR
303
310
 
304
311
  except RuntimeError as ERROR:
305
312
  lengthInitial, lengthCurrent = ERROR.args[0]
@@ -331,6 +338,7 @@ def oopsieKwargsie(huh: Any) -> bool | str | None:
331
338
  if not isinstance(huh, str):
332
339
  try:
333
340
  huh = str(huh)
341
+ #ruff: ignore[blind-except]
334
342
  except BaseException:
335
343
  return huh
336
344
  formatted: str = huh.strip().title()
@@ -117,7 +117,7 @@ expects negative error indicators. The leetspeak encoding makes `errorL33T` imme
117
117
  in debugger output, log files, or printed state, reducing the risk of overlooking error conditions
118
118
  that might be missed with generic values like `-1`.
119
119
 
120
- In rare cases when code execution does not raise `Exception` [2] but produces incorrect output,
120
+ In rare cases when code execution does not raise `Exception` but produces incorrect output,
121
121
  seeing `31212012` or `-31212012` in the output provides a stronger diagnostic signal than `-1` or
122
122
  other conventional error values. The distinctive value helps identify which variables hold error
123
123
  states without requiring careful inspection of every numeric field.
@@ -150,9 +150,6 @@ References
150
150
  ----------
151
151
  [1] Leet - Wikipedia
152
152
  https://en.wikipedia.org/wiki/Leet
153
- [2] Built-in Exceptions - `Exception` (Python documentation)
154
- https://docs.python.org/3/library/exceptions.html#Exception
155
-
156
153
  """
157
154
 
158
155
  inclusive: Final[int] = 1
@@ -323,10 +320,11 @@ References
323
320
 
324
321
  """
325
322
 
323
+ # TODO Can I use Python's color system? It is shockingly similar in structure and even identifiers.
326
324
  #======== Some colors for printing text to the terminal. ========
327
325
  # Many values and options at https://gist.github.com/JBlond/2fea43a3049b38287e5e9cefc87b2124
328
326
  # Many, many, many options at https://stackoverflow.com/a/33206814/4403878
329
- # NOTE Always define color and background color at the same time.
327
+ # Always define color and background color at the same time.
330
328
 
331
329
  ansiColorReset: Final[str] = '\x1b[0m'
332
330
  """Reset terminal text color and background to default settings.
@@ -41,7 +41,7 @@ def pathTmpTesting(tmp_path: pathlib.Path) -> pathlib.Path:
41
41
  # Fixture for predictable Python source code samples
42
42
  @pytest.fixture
43
43
  def dictionaryPythonSourceSamples() -> dict[str, str]:
44
- """Provide predictable Python source code samples for testing.""" # noqa: DOC201
44
+ """Provide predictable Python source code samples for testing."""
45
45
  return {
46
46
  'functionFibonacci': "def fibonacciNumber():\n return 13\n",
47
47
  'functionPrime': "def primeNumber():\n return 17\n",
@@ -53,19 +53,19 @@ def dictionaryPythonSourceSamples() -> dict[str, str]:
53
53
  # Fixture for IO stream objects
54
54
  @pytest.fixture
55
55
  def streamMemoryString() -> io.StringIO:
56
- """Provide a StringIO object for testing stream operations.""" # noqa: DOC201
56
+ """Provide a StringIO object for testing stream operations."""
57
57
  return io.StringIO()
58
58
 
59
59
  # Fixture for predictable directory names using cardinal directions
60
60
  @pytest.fixture
61
61
  def listDirectoryNamesCardinal() -> list[str]:
62
- """Provide predictable directory names using cardinal directions.""" # noqa: DOC201
62
+ """Provide predictable directory names using cardinal directions."""
63
63
  return ['north', 'south', 'east', 'west']
64
64
 
65
65
  # Fixture for predictable file content using Fibonacci numbers
66
66
  @pytest.fixture
67
67
  def listFileContentsFibonacci() -> list[str]:
68
- """Provide predictable file contents using Fibonacci sequence.""" # noqa: DOC201
68
+ """Provide predictable file contents using Fibonacci sequence."""
69
69
  return ['fibonacci8', 'fibonacci13', 'fibonacci21', 'fibonacci34']
70
70
 
71
71
  def uniformTestFailureMessage(expected: Any, actual: Any, functionName: str, *arguments: Any, **keywordArguments: Any) -> str:
@@ -115,7 +115,7 @@ def standardizedEqualTo(expected: Any, functionTarget: Callable[..., Any], *argu
115
115
  Keyword arguments to pass to `functionTarget`.
116
116
 
117
117
  """
118
- if type(expected) == type[Exception]: # noqa: E721
118
+ if type(expected) == type[Exception]: # ruff: ignore[type-comparison]
119
119
  messageExpected: str = expected.__name__
120
120
  else:
121
121
  messageExpected = expected
@@ -130,7 +130,7 @@ def standardizedEqualTo(expected: Any, functionTarget: Callable[..., Any], *argu
130
130
  assert actual == expected, uniformTestFailureMessage(messageExpected, messageActual, functionName, *arguments, **keywordArguments)
131
131
 
132
132
  # Why I wish I could figure out how to implement standardized* test functions.
133
- # ruff: noqa: ERA001
133
+ # ruff: file-ignore[commented-out-code]
134
134
  # standardizedEqualTo(expected, updateExtendPolishDictionaryLists, *value_dictionaryLists, **keywordArguments)
135
135
  # NOTE one line of code with `standardizedEqualTo` (above) replaced the following ten lines of code. Use `standardizedEqualTo`.
136
136
  # if isinstance(expected, type) and issubclass(expected, Exception):
@@ -0,0 +1,22 @@
1
+ from __future__ import annotations
2
+
3
+ from itertools import starmap
4
+ from typing import TYPE_CHECKING
5
+ import pytest
6
+
7
+ if TYPE_CHECKING:
8
+ from typing import Any
9
+
10
+ #================== Assert scalar and built-in containers ========================================================================
11
+
12
+ def assertEqualTo[个](actual: 个, expected: 个, function: str, *arguments: Any, **keywordArguments: Any) -> None:
13
+ """Assert that two objects are equal, and if not, raise an AssertionError with a detailed message."""
14
+ assert actual == expected, messageTestFailure(actual, expected, function, *arguments, **keywordArguments)
15
+
16
+ def assert_approx[个](actual: 个, expected: 个, pytest_rel: float, pytest_abs: float, function: str, *arguments: Any, **keywordArguments: Any) -> None:
17
+ assert actual == pytest.approx(expected, pytest_rel, pytest_abs, nan_ok=True), messageTestFailure(actual, expected, function, *arguments, **keywordArguments)
18
+
19
+ def messageTestFailure(actual: Any, expected: Any, function: str, *arguments: Any, **keywordArguments: Any) -> str:
20
+ """Format assertion message for any test comparison."""
21
+ parameters: list[str] = [*map(repr, arguments), *starmap('{}={!r}'.format, keywordArguments.items())]
22
+ return f'{function}({", ".join(parameters)}) = {actual!r}, but {expected = }.'
@@ -161,7 +161,7 @@ def test_autoDecodingRLE(
161
161
  "range": range,
162
162
  "list": list,
163
163
  }
164
- value_arrayTarget: NDArray[numpy.integer[Any]] = eval(arrayExpression, evaluationContext) # noqa: S307
164
+ value_arrayTarget: NDArray[numpy.integer[Any]] = eval(arrayExpression, evaluationContext) # ruff: ignore[suspicious-eval-usage]
165
165
 
166
166
  resultRLE: str = autoDecodingRLE(value_arrayTarget, assumeAddSpaces=assumeAddSpaces)
167
167
 
@@ -179,7 +179,7 @@ def test_autoDecodingRLE(
179
179
 
180
180
  assert resultRLE == expected, (f"autoDecodingRLE made {resultRLE!r}, expected {expected!r} for {description=} and {assumeAddSpaces=}.")
181
181
 
182
- decodedData = eval(resultRLE) # noqa: S307
182
+ decodedData = eval(resultRLE) # ruff: ignore[suspicious-eval-usage]
183
183
  reconstructedArray: NDArray[numpy.integer[Any]] = numpy.array(decodedData)
184
184
  numpy.testing.assert_array_equal(
185
185
  reconstructedArray
@@ -10,8 +10,7 @@ from __future__ import annotations
10
10
  from dataclasses import dataclass
11
11
  from hunterMakesPy import CallableFunction as CallableFunction
12
12
  from hunterMakesPy.tests.conftest import uniformTestFailureMessage
13
- from typing import TYPE_CHECKING
14
- from typing_extensions import Self
13
+ from typing import Self, TYPE_CHECKING
15
14
  import pytest
16
15
 
17
16
  if TYPE_CHECKING:
@@ -1,9 +1,9 @@
1
1
  """I type, you type, we all `type` for `theTypes`."""
2
2
  from __future__ import annotations
3
3
 
4
+ from collections.abc import Hashable
4
5
  from hunterMakesPy.theTypesCallableFunction import CallableFunction as CallableFunction
5
- from typing import Protocol, TypeAlias
6
- from typing_extensions import Self, TypeVar
6
+ from typing import Protocol, Self, TypeAlias, TypeVar
7
7
 
8
8
  identifierDotAttribute: TypeAlias = str
9
9
  """`str` (***str***ing) representing a dotted attribute identifier.
@@ -11,7 +11,7 @@ identifierDotAttribute: TypeAlias = str
11
11
  `TypeAlias` for a `str` `object` using dot notation to access an attribute, such as 'scipy.signal.windows'.
12
12
  """
13
13
 
14
- Limitation: TypeAlias = bool | float | int | None
14
+ ConcurrencyLimit: TypeAlias = bool | float | int | None
15
15
 
16
16
  class Ordinals(Protocol):
17
17
  """Any Python `object` `type` that may be ordered before or after a comparable `object` `type` by comparison operators."""
@@ -25,3 +25,5 @@ class Ordinals(Protocol):
25
25
  ...
26
26
 
27
27
  小于 = TypeVar('小于', bound=Ordinals)
28
+ 文件 = TypeVar('文件', bound=Hashable)
29
+ 文义 = TypeVar('文义')
@@ -7,17 +7,8 @@ import sys
7
7
 
8
8
  if TYPE_CHECKING:
9
9
  from types import CodeType, MethodType
10
- from typing import TypeAlias
11
- from typing_extensions import Self, TypeVarTuple
12
-
13
- # TODO explore the following
14
- """
15
- I've added new test cases using a lambda function and a standard user-defined imported function
16
- (stringItUp itself). Built-in functions like len don't trigger the FunctionType check because their
17
- type is technically a builtin_function_or_method in CPython, whereas functions defined via def or
18
- lambda correspond exactly to types.FunctionType. Your coverage should now reflect this branch as
19
- expected reflect activation of that branch.
20
- """
10
+ from typing import Self, TypeAlias
11
+ from typing_extensions import TypeVarTuple
21
12
 
22
13
  #======== Copied from typeshed:stdlib\_typeshed\__init__.pyi ========
23
14
  AnnotationForm: TypeAlias = Any
@@ -25,7 +16,6 @@ AnnotationForm: TypeAlias = Any
25
16
  if (3, 14) <= sys.version_info:
26
17
  from annotationlib import Format
27
18
 
28
- # NOTE These return annotations, which can be arbitrary objects
29
19
  AnnotateFunc: TypeAlias = Callable[[Format], dict[str, AnnotationForm]]
30
20
  EvaluateFunc: TypeAlias = Callable[[Format], AnnotationForm]
31
21
  #======== End Copied from typeshed:stdlib\_typeshed\__init__.pyi ========
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hunterMakesPy
3
- Version: 0.7.6
3
+ Version: 0.9.0
4
4
  Summary: Easy Python functions making making functional Python functions easier.
5
5
  Author-email: Hunter Hogan <HunterHogan@pm.me>
6
6
  License-Expression: CC-BY-NC-4.0
@@ -17,15 +17,15 @@ Classifier: Intended Audience :: Developers
17
17
  Classifier: Natural Language :: English
18
18
  Classifier: Operating System :: OS Independent
19
19
  Classifier: Programming Language :: Python
20
- Classifier: Programming Language :: Python :: 3.10
21
- Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.15
22
22
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
23
  Classifier: Topic :: Utilities
24
24
  Classifier: Typing :: Typed
25
- Requires-Python: >=3.10
25
+ Requires-Python: >=3.12
26
26
  Description-Content-Type: text/markdown
27
27
  License-File: LICENSE
28
- Requires-Dist: Z0Z_tools>=2.2.1
28
+ Requires-Dist: Z0Z_tools>=4.0.0
29
29
  Requires-Dist: autoflake
30
30
  Requires-Dist: charset_normalizer
31
31
  Requires-Dist: isort
@@ -185,6 +185,11 @@ def test_my_integer_validator(test_name, test_func):
185
185
  ## My recovery
186
186
 
187
187
  [![Static Badge](https://img.shields.io/badge/2011_August-Homeless_since-blue?style=flat)](https://HunterThinks.com/support)
188
- [![YouTube Channel Subscribers](https://img.shields.io/youtube/channel/subscribers/UC3Gx7kz61009NbhpRtPP7tw)](https://www.youtube.com/@HunterHogan)
189
188
 
190
189
  [![CC-BY-NC-4.0](https://raw.githubusercontent.com/hunterhogan/hunterMakesPy/refs/heads/main/.github/CC-BY-NC-4.0.png)](https://creativecommons.org/licenses/by-nc/4.0/)
190
+
191
+ By law, authors retain their copyrights unless they grant exceptions. The [Attribution-NonCommercial 4.0 International Deed](https://github.com/hunterhogan/hunterMakesPy#License-1-ov-file) outlines how you may use this work.
192
+
193
+ The restriction on commercial use (without a license) limits advantage-taking, unjust enrichment, and exploitation. You would be morally wrong if you were to enrich yourself by using this work while I am denied healthcare, housing, security of property, and the safety of my person.
194
+
195
+ To obtain and retain the right to use this work, you incur the cost of attributing the work to me. Your nominal cost might accrue as a benefit that mitigates my suffering or saves my life.
@@ -18,6 +18,7 @@ hunterMakesPy.egg-info/requires.txt
18
18
  hunterMakesPy.egg-info/top_level.txt
19
19
  hunterMakesPy/tests/__init__.py
20
20
  hunterMakesPy/tests/conftest.py
21
+ hunterMakesPy/tests/conftestAnnex.py
21
22
  hunterMakesPy/tests/test_coping.py
22
23
  hunterMakesPy/tests/test_dataStructures.py
23
24
  hunterMakesPy/tests/test_filesystemToolkit.py
@@ -1,4 +1,4 @@
1
- Z0Z_tools>=2.2.1
1
+ Z0Z_tools>=4.0.0
2
2
  autoflake
3
3
  charset_normalizer
4
4
  isort
@@ -1,9 +1,9 @@
1
1
  [project]
2
2
  name = "hunterMakesPy"
3
- version = "0.7.6"
3
+ version = "0.9.0"
4
4
  description = "Easy Python functions making making functional Python functions easier."
5
5
  readme = "README.md"
6
- requires-python = ">=3.10"
6
+ requires-python = ">=3.12"
7
7
  license = "CC-BY-NC-4.0"
8
8
  authors = [{ name = "Hunter Hogan", email = "HunterHogan@pm.me" }]
9
9
  keywords = [
@@ -20,14 +20,14 @@ classifiers = [
20
20
  "Natural Language :: English",
21
21
  "Operating System :: OS Independent",
22
22
  "Programming Language :: Python",
23
- "Programming Language :: Python :: 3.10",
24
- "Programming Language :: Python :: 3.14",
23
+ "Programming Language :: Python :: 3.12",
24
+ "Programming Language :: Python :: 3.15",
25
25
  "Topic :: Software Development :: Libraries :: Python Modules",
26
26
  "Topic :: Utilities",
27
27
  "Typing :: Typed",
28
28
  ]
29
29
  dependencies = [
30
- "Z0Z_tools>=2.2.1",
30
+ "Z0Z_tools>=4.0.0",
31
31
  "autoflake",
32
32
  "charset_normalizer",
33
33
  "isort",
File without changes
File without changes