hunterMakesPy 0.7.5__tar.gz → 0.8.1__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.5 → huntermakespy-0.8.1}/PKG-INFO +3 -3
  2. {huntermakespy-0.7.5 → huntermakespy-0.8.1}/hunterMakesPy/__init__.py +4 -2
  3. {huntermakespy-0.7.5 → huntermakespy-0.8.1}/hunterMakesPy/coping.py +6 -12
  4. {huntermakespy-0.7.5 → huntermakespy-0.8.1}/hunterMakesPy/dataStructures.py +100 -53
  5. {huntermakespy-0.7.5 → huntermakespy-0.8.1}/hunterMakesPy/filesystemToolkit.py +78 -23
  6. {huntermakespy-0.7.5 → huntermakespy-0.8.1}/hunterMakesPy/parseParameters.py +100 -79
  7. {huntermakespy-0.7.5 → huntermakespy-0.8.1}/hunterMakesPy/semiotics.py +3 -1
  8. {huntermakespy-0.7.5 → huntermakespy-0.8.1}/hunterMakesPy/tests/conftest.py +6 -6
  9. huntermakespy-0.8.1/hunterMakesPy/tests/conftestAnnex.py +22 -0
  10. {huntermakespy-0.7.5 → huntermakespy-0.8.1}/hunterMakesPy/tests/test_dataStructures.py +2 -2
  11. {huntermakespy-0.7.5 → huntermakespy-0.8.1}/hunterMakesPy/tests/test_theTypes.py +1 -2
  12. {huntermakespy-0.7.5 → huntermakespy-0.8.1}/hunterMakesPy/theTypes.py +5 -3
  13. {huntermakespy-0.7.5 → huntermakespy-0.8.1}/hunterMakesPy/theTypesCallableFunction.py +2 -12
  14. {huntermakespy-0.7.5 → huntermakespy-0.8.1}/hunterMakesPy.egg-info/PKG-INFO +3 -3
  15. {huntermakespy-0.7.5 → huntermakespy-0.8.1}/hunterMakesPy.egg-info/SOURCES.txt +1 -0
  16. {huntermakespy-0.7.5 → huntermakespy-0.8.1}/pyproject.toml +3 -3
  17. {huntermakespy-0.7.5 → huntermakespy-0.8.1}/LICENSE +0 -0
  18. {huntermakespy-0.7.5 → huntermakespy-0.8.1}/README.md +0 -0
  19. {huntermakespy-0.7.5 → huntermakespy-0.8.1}/hunterMakesPy/_theSSOT.py +0 -0
  20. {huntermakespy-0.7.5 → huntermakespy-0.8.1}/hunterMakesPy/py.typed +0 -0
  21. {huntermakespy-0.7.5 → huntermakespy-0.8.1}/hunterMakesPy/tests/__init__.py +0 -0
  22. {huntermakespy-0.7.5 → huntermakespy-0.8.1}/hunterMakesPy/tests/test_coping.py +0 -0
  23. {huntermakespy-0.7.5 → huntermakespy-0.8.1}/hunterMakesPy/tests/test_filesystemToolkit.py +0 -0
  24. {huntermakespy-0.7.5 → huntermakespy-0.8.1}/hunterMakesPy/tests/test_parseParameters.py +0 -0
  25. {huntermakespy-0.7.5 → huntermakespy-0.8.1}/hunterMakesPy.egg-info/dependency_links.txt +0 -0
  26. {huntermakespy-0.7.5 → huntermakespy-0.8.1}/hunterMakesPy.egg-info/requires.txt +0 -0
  27. {huntermakespy-0.7.5 → huntermakespy-0.8.1}/hunterMakesPy.egg-info/top_level.txt +0 -0
  28. {huntermakespy-0.7.5 → huntermakespy-0.8.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hunterMakesPy
3
- Version: 0.7.5
3
+ Version: 0.8.1
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,12 +17,12 @@ 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
20
+ Classifier: Programming Language :: Python :: 3.11
21
21
  Classifier: Programming Language :: Python :: 3.14
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.11
26
26
  Description-Content-Type: text/markdown
27
27
  License-File: LICENSE
28
28
  Requires-Dist: Z0Z_tools>=2.2.1
@@ -69,11 +69,13 @@ References
69
69
  from __future__ import annotations
70
70
 
71
71
  from hunterMakesPy.semiotics import (
72
- decreasing as decreasing, errorL33T as errorL33T, inclusive as inclusive, oneIndexed as oneIndexed, zeroIndexed as zeroIndexed)
72
+ ansiColor as ansiColor, ansiColorReset as ansiColorReset, decreasing as decreasing, errorL33T as errorL33T, inclusive as inclusive,
73
+ oneIndexed as oneIndexed, zeroIndexed as zeroIndexed)
73
74
 
74
75
  # isort: split
75
76
  from hunterMakesPy.theTypes import (
76
- 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 文件)
77
79
 
78
80
  # isort: split
79
81
  from hunterMakesPy.coping import PackageSettings as PackageSettings, raiseIfNone as raiseIfNone
@@ -12,14 +12,17 @@ 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
+ #ruff: ignore[docstring-missing-returns]
16
+ """Get package name from pyproject.toml or fallback to provided value."""
16
17
  try:
17
18
  return tomli_loads(Path('pyproject.toml').read_text(encoding='utf-8'))['project']['name']
18
- except Exception: # noqa: BLE001
19
+ #ruff: ignore[blind-except]
20
+ except Exception:
19
21
  return identifierPackageFALLBACK
20
22
 
21
23
  def getPathPackageINSTALLING(identifierPackage: str) -> Path:
22
- """Return the root directory of the installed package.""" # noqa: DOC201
24
+ #ruff: ignore[docstring-missing-returns]
25
+ """Return the root directory of the installed package."""
23
26
  try:
24
27
  moduleSpecification: ModuleSpec | None = find_spec(identifierPackage)
25
28
  if moduleSpecification and moduleSpecification.origin:
@@ -54,8 +57,6 @@ class PackageSettings:
54
57
 
55
58
  ```python
56
59
  settings = PackageSettings(identifierPackageFALLBACK='cobraPy')
57
- # Automatically discovers package name from pyproject.toml
58
- # Resolves installation path from package identifier
59
60
  ```
60
61
 
61
62
  Explicit configuration for specific deployment:
@@ -117,7 +118,6 @@ def raiseIfNone(expression: TypeSansNone | None, errorMessage: str | None = None
117
118
  Basic usage with attribute access:
118
119
  ```python
119
120
  annotation = raiseIfNone(ast_arg.annotation)
120
- # Raises ValueError if ast_arg.annotation is None
121
121
  ```
122
122
 
123
123
  Function return value validation:
@@ -130,7 +130,6 @@ def raiseIfNone(expression: TypeSansNone | None, errorMessage: str | None = None
130
130
 
131
131
  listFiles = ['document.txt', 'image.png', 'data.csv']
132
132
  filename = raiseIfNone(findFirstMatch(listFiles, '.txt'))
133
- # Returns 'document.txt' when match exists
134
133
  ```
135
134
 
136
135
  Dictionary value retrieval with custom message:
@@ -138,11 +137,6 @@ def raiseIfNone(expression: TypeSansNone | None, errorMessage: str | None = None
138
137
  configurationMapping = {'host': 'localhost', 'port': 8080}
139
138
  host = raiseIfNone(configurationMapping.get('host'),
140
139
  "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
140
  ```
147
141
 
148
142
  Thanks
@@ -1,41 +1,43 @@
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
+ #ruff: ignore[docstring-missing-returns]
40
+ """Remove extra whitespace from string representation of Python data structures."""
39
41
  commas: str = regex.sub(r',\s+', ',', string)
40
42
  bracketsOpening: str = regex.sub(r'([\[\(])\s+', r'\1', commas)
41
43
  return regex.sub(r'\s+([\]\)])', r'\1', bracketsOpening)
@@ -70,7 +72,8 @@ def autoDecodingRLE(arrayTarget: NDArray[integer[Any]], *, assumeAddSpaces: bool
70
72
  """
71
73
  def sliceNDArrayToNestedLists(arraySlice: NDArray[integer[Any]]) -> Any:
72
74
  def getLengthOption(optionAsStr: str) -> int:
73
- """`assumeAddSpaces` characters: `,` 1 space; `]*` 2 spaces.""" # noqa: DOC201
75
+ #ruff: ignore[docstring-missing-returns]
76
+ """`assumeAddSpaces` characters: `,` 1 space; `]*` 2 spaces."""
74
77
  return assumeAddSpaces * (optionAsStr.count(',') + optionAsStr.count(']*') * 2) + len(optionAsStr)
75
78
 
76
79
  if 1 < arraySlice.ndim:
@@ -92,7 +95,6 @@ def autoDecodingRLE(arrayTarget: NDArray[integer[Any]], *, assumeAddSpaces: bool
92
95
  option2 = ImaSerious
93
96
  option2AsStr: str | None = None
94
97
 
95
- # alpha, potential function
96
98
  option1AsStr: str = option1AsStr or removeExtraWhitespace(str(option1))
97
99
  lengthOption1: int = getLengthOption(option1AsStr)
98
100
 
@@ -123,7 +125,6 @@ def autoDecodingRLE(arrayTarget: NDArray[integer[Any]], *, assumeAddSpaces: bool
123
125
  option2 = malkovichAsList
124
126
  option2AsStr = None
125
127
 
126
- # beta, potential function
127
128
  option1AsStr = option1AsStr or removeExtraWhitespace(str(option1))
128
129
  lengthOption1 = getLengthOption(option1AsStr)
129
130
 
@@ -148,19 +149,20 @@ def autoDecodingRLE(arrayTarget: NDArray[integer[Any]], *, assumeAddSpaces: bool
148
149
 
149
150
  patternRegex: regex.Pattern[str] = regex.compile(
150
151
  r"(?<!rang)(?:"
151
- # Pattern 1: Comma ahead, bracket behind
152
+ #Pattern 1: Comma ahead, bracket behind
152
153
  r"(?P<joinAhead>,)\((?P<malkovich>\d+),(?P<multiply>\d+)\)(?P<bracketBehind>])|"
153
- # Pattern 2: Bracket or start ahead, comma behind
154
+ #Pattern 2: Bracket or start ahead, comma behind
154
155
  r"(?P<bracketOrStartAhead>\[|^.)\((?P<malkovichMalkovich>\d+),(?P<multiplyIDK>\d+)\)(?P<joinBehind>,)|"
155
- # Pattern 3: Bracket ahead, bracket behind
156
+ #Pattern 3: Bracket ahead, bracket behind
156
157
  r"(?P<bracketAhead>\[)\((?P<malkovichMalkovichMalkovich>\d+),(?P<multiply_whatever>\d+)\)(?P<bracketBehindBracketBehind>])|"
157
- # Pattern 4: Comma ahead, comma behind
158
+ #Pattern 4: Comma ahead, comma behind
158
159
  r"(?P<joinAheadJoinAhead>,)\((?P<malkovichMalkovichMalkovichMalkovich>\d+),(?P<multiplyOrSomething>\d+)\)(?P<joinBehindJoinBehind>,)"
159
160
  r")"
160
161
  )
161
162
 
162
163
  def replacementByContext(match: regex.Match[str]) -> str:
163
- """Generate replacement string based on context patterns.""" # noqa: DOC201
164
+ #ruff: ignore[docstring-missing-returns]
165
+ """Generate replacement string based on context patterns."""
164
166
  elephino: dict[str, str | None] = match.groupdict()
165
167
  joinAhead: str | None = elephino.get('joinAhead') or elephino.get('joinAheadJoinAhead')
166
168
  malkovich: str | None = elephino.get('malkovich') or elephino.get('malkovichMalkovich') or elephino.get('malkovichMalkovichMalkovich') or elephino.get('malkovichMalkovichMalkovichMalkovich')
@@ -176,8 +178,8 @@ def autoDecodingRLE(arrayTarget: NDArray[integer[Any]], *, assumeAddSpaces: bool
176
178
  arrayAsStr = patternRegex.sub(replacementByContext, arrayAsStr)
177
179
  arrayAsStr = patternRegex.sub(replacementByContext, arrayAsStr)
178
180
 
179
- # .replace `range(0,stop)` syntax with `range(stop)` syntax.
180
- # Add unpack operator `*` for automatic decoding when evaluated.
181
+ #.replace `range(0,stop)` syntax with `range(stop)` syntax.
182
+ #Add unpack operator `*` for automatic decoding when evaluated.
181
183
  return arrayAsStr.replace('range(0,', 'range(').replace('range', '*range')
182
184
 
183
185
  def stringItUp(*scrapPile: Any) -> list[str]:
@@ -220,16 +222,18 @@ def stringItUp(*scrapPile: Any) -> list[str]:
220
222
  elif isinstance(KitKat, (list, tuple, set, frozenset, range)):
221
223
  for kit in KitKat: # pyright: ignore[reportUnknownVariableType]
222
224
  drill(kit)
223
- elif hasattr(KitKat, '__iter__'): # Unpack other iterables
225
+ elif hasattr(KitKat, '__iter__'):
224
226
  for kat in KitKat:
225
227
  drill(kat)
226
228
  else:
227
229
  try:
228
- sharingIsCaring: str = KitKat.__str__() # noqa: PLC2801
230
+ #ruff: ignore[unnecessary-dunder-call]
231
+ sharingIsCaring: str = KitKat.__str__()
229
232
  listStrungUp.append(sharingIsCaring)
230
233
  except AttributeError:
231
234
  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."
235
+ except TypeError:
236
+ #"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
237
  pass
234
238
  except:
235
239
  message: str = (f"\nWoah! I received '{repr(KitKat)}'.\nTheir report card says, 'Plays well with others: Needs improvement.'\n")
@@ -242,55 +246,98 @@ def stringItUp(*scrapPile: Any) -> list[str]:
242
246
  listStrungUp.append(repr(scrap))
243
247
  return listStrungUp
244
248
 
245
- 文件 = TypeVar('文件', bound=Hashable)
249
+ @overload
250
+ def updateExtendPolishDictionaryLists(*dictionaryLists: Mapping[文件, Iterable[文义]], destroyDuplicates: bool = False, reorderLists: Literal[False] = False, killErroneousDataTypes: bool = False) -> dict[文件, list[文义]]: ...
246
251
 
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.
252
+ @overload
253
+ def updateExtendPolishDictionaryLists(*dictionaryLists: Mapping[文件, Iterable[小于]], destroyDuplicates: bool = False, reorderLists: Literal[True], killErroneousDataTypes: bool = False) -> dict[文件, list[小于]]: ...
250
254
 
251
- Plus options to destroy duplicates, sort `list` values, and handle erroneous data types.
255
+ @overload
256
+ def updateExtendPolishDictionaryLists(*dictionaryLists: Mapping[文件, Iterable[小于]], destroyDuplicates: bool = False, reorderLists: bool, killErroneousDataTypes: bool = False) -> dict[文件, list[小于]]: ...
257
+
258
+ def updateExtendPolishDictionaryLists(*dictionaryLists: Mapping[文件, Iterable[文义]], destroyDuplicates: bool = False, reorderLists: bool = False, killErroneousDataTypes: bool = False) -> dict[文件, list[文义]]:
259
+ """Merge mappings of iterable values into a dictionary of lists.
260
+
261
+ You can use this function to combine values under matching keys across `dictionaryLists`.
262
+ This function returns a new dictionary with new lists and can remove duplicate elements,
263
+ sort elements, or skip entries that raise `TypeError` during merging.
264
+
265
+ Iteration and Ordering
266
+ ----------------------
267
+ This function consumes each value's iterable in mapping argument order. Within each value,
268
+ the list follows the iterable's order, which may be unpredictable for sets. Strings contribute
269
+ individual characters, and mapping values contribute their keys.
270
+
271
+ Deduplication uses `dict.fromkeys` [1] to preserve first occurrences. Sorting uses `sorted` [2]
272
+ and runs after deduplication. Every retained value becomes a list regardless of the input type.
252
273
 
253
274
  Parameters
254
275
  ----------
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".
276
+ *dictionaryLists : Mapping[文件, Iterable[文义]]
277
+ The mappings to merge, with hashable keys and iterable values. A single mapping receives
278
+ the same conversion and optional processing. No mappings produce an empty dictionary.
258
279
  destroyDuplicates : bool = False
259
- If `True`, removes duplicate elements from the `list`. Defaults to `False`.
280
+ If `True`, remove duplicate elements from each merged list, preserving the first occurrence.
281
+ Elements must be hashable when this option is enabled.
260
282
  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.
283
+ If `True`, sort each merged list in ascending order after optional deduplication.
284
+ Elements within each list must be mutually comparable.
263
285
  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`.
286
+ If `True`, skip an entry when converting the entry's value to a list or merging the entry
287
+ raises `TypeError`. Successfully merged values for the same key remain in the result.
288
+ This option does not suppress `TypeError` from deduplication or sorting.
266
289
 
267
290
  Returns
268
291
  -------
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[小于]] = {}
292
+ ePluribusUnum : dict[文件, list[文义]]
293
+ The merged dictionary, with a list for every retained key. This function does not modify
294
+ the input mappings or their containers, but the lists retain references to the original elements.
295
+
296
+ Raises
297
+ ------
298
+ TypeError
299
+ If a value cannot be iterated or an entry cannot be merged and `killErroneousDataTypes`
300
+ is `False`. Also raised for unhashable elements during deduplication or incomparable
301
+ elements during sorting, regardless of `killErroneousDataTypes`.
302
+
303
+ Examples
304
+ --------
305
+ The README merges server lists while preserving the first occurrence of each server name.
306
+
307
+ ```python
308
+ from hunterMakesPy.dataStructures import updateExtendPolishDictionaryLists
309
+
310
+ merged = updateExtendPolishDictionaryLists(
311
+ {"servers": ["chicago"]},
312
+ {"servers": ["tokyo", "chicago"]},
313
+ destroyDuplicates=True,
314
+ )
315
+ # Returns {"servers": ["chicago", "tokyo"]}.
316
+ ```
317
+
318
+ References
319
+ ----------
320
+ [1] Python dictionaries and `dict.fromkeys`.
321
+ https://docs.python.org/3/library/stdtypes.html#dict.fromkeys
322
+ [2] Python `sorted`.
323
+ https://docs.python.org/3/library/functions.html#sorted
324
+ """
325
+ ePluribusUnum: dict[文件, list[文义]] = {}
280
326
 
281
327
  for dictionaryListTarget in dictionaryLists:
282
328
  for keyName, keyValue in dictionaryListTarget.items():
283
329
  try:
284
- ImaList: list[小于] = list(keyValue)
330
+ ImaList: list[文义] = list(keyValue)
285
331
  ePluribusUnum.setdefault(keyName, []).extend(ImaList)
286
- except TypeError as error: # noqa: PERF203
332
+ except TypeError as error:
287
333
  if killErroneousDataTypes:
288
334
  continue
289
335
  raise TypeError from error
290
336
 
291
337
  if destroyDuplicates:
292
- ePluribusUnum = valmap(compose(list, dict.fromkeys), ePluribusUnum)
338
+ ePluribusUnum = mapValues(compose(list, dict.fromkeys), ePluribusUnum)
293
339
  if reorderLists:
294
- ePluribusUnum = valmap(sorted, ePluribusUnum)
340
+ #=SIN= `sorted` is broken.
341
+ ePluribusUnum = mapValues(cast('Callable[[list[文义]], list[文义]]', sorted), ePluribusUnum)
295
342
 
296
343
  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,58 @@ 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` [1], then opens the file in
89
+ append mode with UTF-8 encoding and `newline=''` [2]. Newline characters in `this` are written
90
+ without translation. This function writes and flushes the text with `writeStringToHere` [3].
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
+ References
98
+ ----------
99
+ [1] `makeDirectorySafely`
100
+
101
+ [2] Python `open` modes, encoding, and newline handling.
102
+ https://docs.python.org/3/library/functions.html#open
103
+ [3] `writeStringToHere`
104
+
105
+ """
106
+ pathFilename = Path(pathFilename)
107
+ makeDirectorySafely(pathFilename)
108
+ with pathFilename.open(encoding='utf-8', mode='a', newline='') as streamAppend:
109
+ writeStringToHere(this, streamAppend)
110
+ return pathFilename
111
+
58
112
  def importLogicalPath2Identifier(logicalPathModule: identifierDotAttribute, identifier: str, packageIdentifierIfRelative: str | None = None) -> Any:
59
113
  """Import `identifier` from the module named by `logicalPathModule`.
60
114
 
@@ -82,6 +136,7 @@ def importLogicalPath2Identifier(logicalPathModule: identifierDotAttribute, iden
82
136
  return getattr(moduleImported, identifier)
83
137
 
84
138
  def importPathFilename2Identifier(pathFilename: PathLike[Any] | PurePath, identifier: str, moduleIdentifier: str | None = None) -> Any:
139
+ #ruff: ignore[docstring-extraneous-exception]
85
140
  """Import `identifier` from the Python file at `pathFilename`.
86
141
 
87
142
  You can use this function to load a Python source file as a module and retrieve a named attribute
@@ -118,7 +173,7 @@ def importPathFilename2Identifier(pathFilename: PathLike[Any] | PurePath, identi
118
173
  https://docs.python.org/3/library/importlib.html#importlib.util.spec_from_file_location
119
174
  [2] `importlib.util.module_from_spec`
120
175
  https://docs.python.org/3/library/importlib.html#importlib.util.module_from_spec
121
- """ # noqa: DOC502
176
+ """
122
177
  pathFilename = Path(pathFilename)
123
178
 
124
179
  importlibSpecification: ModuleSpec | None = importlib.util.spec_from_file_location(moduleIdentifier or pathFilename.stem, pathFilename)
@@ -1,20 +1,13 @@
1
- # ruff: noqa: TRY301, DOC501, PLW0717 BLE001
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` [1] by default and also
10
+ accepts numeric strings. Binary integer inputs use `charset_normalizer` [2] for text decoding.
18
11
 
19
12
  Contents
20
13
  --------
@@ -26,11 +19,15 @@ Functions
26
19
  oopsieKwargsie
27
20
  Interpret a string as True, False, or None to avoid exceptions.
28
21
 
22
+ Classes
23
+ ErrorMessageContext
24
+ Store value, type, and container information for integer-validation error messages.
25
+
29
26
  References
30
27
  ----------
31
- [1] multiprocessing - Context7
28
+ [1] Python `multiprocessing`.
32
29
  https://docs.python.org/3/library/multiprocessing.html
33
- [2] charset-normalizer - Context7
30
+ [2] charset-normalizer.
34
31
  https://github.com/Ousret/charset_normalizer
35
32
 
36
33
  """
@@ -45,8 +42,8 @@ import sys
45
42
 
46
43
  if TYPE_CHECKING:
47
44
  from charset_normalizer.models import CharsetMatch
48
- from collections.abc import Iterable
49
- from hunterMakesPy.theTypes import Limitation
45
+ from collections.abc import Collection
46
+ from hunterMakesPy.theTypes import ConcurrencyLimit
50
47
  from typing import Any
51
48
 
52
49
  @dataclass
@@ -71,49 +68,78 @@ class ErrorMessageContext:
71
68
  containerType: str | None = None
72
69
  isElement: bool = False
73
70
 
74
- def defineConcurrencyLimit(*, limit: Limitation, cpuTotal: int = multiprocessing.cpu_count()) -> int:
75
- """Determine the concurrency limit based on the provided parameter.
71
+ def defineConcurrencyLimit(*, limit: ConcurrencyLimit, cpuTotal: int = multiprocessing.cpu_count()) -> int:
72
+ """Calculate a worker count from an absolute, fractional, or reserved-CPU limit.
73
+
74
+ You can use this function to select a worker count from `limit` and `cpuTotal`. This function
75
+ accepts explicit counts, fractions of `cpuTotal`, counts to reserve, and boolean choices.
76
+ This function also accepts numeric strings and strings representing `True`, `False`, or `None`.
77
+ The result is at least one worker and is at most 61 workers on Windows.
78
+
79
+ String Parsing
80
+ --------------
81
+ This function first uses `oopsieKwargsie` [1] to interpret strings representing `True`, `False`,
82
+ or `None`, ignoring capitalization and surrounding whitespace. Other strings are converted
83
+ with `float`. An invalid numeric string raises `ValueError` with the conversion error as the cause.
84
+
85
+ Processor Counts
86
+ ----------------
87
+ This function rounds floating-point counts whose absolute value is at least one before applying
88
+ the count rules. Rounding uses Python `round` [2], including rounding ties to the nearest even
89
+ integer. This function applies the following rules before enforcing the platform bounds.
90
+
91
+ - `None`, `False`, or zero select `cpuTotal`.
92
+ - `True` selects one worker.
93
+ - A positive count of at least one selects that count.
94
+ - A positive fraction below one selects `round(limit * cpuTotal)` workers.
95
+ - A negative fraction above negative one reserves `abs(round(limit * cpuTotal))` CPUs.
96
+ - A negative count of at most negative one reserves `abs(int(limit))` CPUs.
97
+
98
+ This function caps the result at 61 on Windows, matching the restriction for
99
+ `concurrent.futures.ProcessPoolExecutor` [3]. This function then enforces the minimum of one.
76
100
 
77
101
  Parameters
78
102
  ----------
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.
103
+ limit : ConcurrencyLimit
104
+ The worker-count specification. `None`, `False`, or zero select `cpuTotal`; `True` selects
105
+ one worker. Positive counts select workers to use, and negative counts select CPUs to reserve.
106
+ Fractions between negative one and one select proportions to reserve or use, respectively.
107
+ Strings are also accepted at runtime, although the type annotation excludes strings.
81
108
  cpuTotal : int = multiprocessing.cpu_count()
82
- The total number of CPUs available in the system. Default is `multiprocessing.cpu_count()`.
109
+ The CPU count used for fractional limits and reserved-CPU calculations. The default is the
110
+ value of `multiprocessing.cpu_count` [4] evaluated when this module is imported.
83
111
 
84
112
  Returns
85
113
  -------
86
114
  concurrencyLimit : int
87
- The calculated concurrency limit, ensuring it is at least 1.
115
+ The calculated worker count, with a minimum of one and a Windows maximum of 61.
116
+ An explicit positive count can exceed `cpuTotal`.
88
117
 
89
- Example parameters
90
- ------------------
91
- ```python
92
- CPUlimit: bool | float | int | None
93
- CPUlimit: Limitation = None
94
- ```
118
+ Raises
119
+ ------
120
+ ValueError
121
+ If a string does not represent a number, `True`, `False`, or `None`.
95
122
 
96
- Example docstring
97
- -----------------
98
- ```python
123
+ Examples
124
+ --------
125
+ The README selects three quarters of the default CPU count.
99
126
 
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.
127
+ ```python
128
+ from hunterMakesPy.parseParameters import defineConcurrencyLimit
104
129
 
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
- ```
130
+ workers = defineConcurrencyLimit(limit=0.75)
131
+ ```
116
132
 
133
+ References
134
+ ----------
135
+ [1] `oopsieKwargsie`
136
+
137
+ [2] Python `round`.
138
+ https://docs.python.org/3/library/functions.html#round
139
+ [3] Python `concurrent.futures.ProcessPoolExecutor`.
140
+ https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.ProcessPoolExecutor
141
+ [4] Python `multiprocessing.cpu_count`.
142
+ https://docs.python.org/3/library/multiprocessing.html#multiprocessing.cpu_count
117
143
  """
118
144
  if isinstance(limit, str):
119
145
  limitFromString: bool | str | None = oopsieKwargsie(limit)
@@ -142,7 +168,7 @@ def defineConcurrencyLimit(*, limit: Limitation, cpuTotal: int = multiprocessing
142
168
  elif limit <= -1:
143
169
  concurrencyLimit = cpuTotal - abs(int(limit))
144
170
 
145
- # https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.ProcessPoolExecutor
171
+ #https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.ProcessPoolExecutor
146
172
  if sys.platform == "win32":
147
173
  concurrencyLimit = min(concurrencyLimit, 61)
148
174
  return max(int(concurrencyLimit), 1)
@@ -195,7 +221,7 @@ def _constructErrorMessage(context: ErrorMessageContext, parameterName: str, par
195
221
 
196
222
  return "".join(messageParts)
197
223
 
198
- def intInnit(listInt_Allegedly: Iterable[Any], parameterName: str | None = None, parameterType: type[Any] | None = None) -> list[int]:
224
+ def intInnit(listInt_Allegedly: Collection[Any], parameterName: str | None = None, parameterType: type[Any] | None = None) -> list[int]:
199
225
  """Validate and convert input values to a `list` of integers.
200
226
 
201
227
  Accepts various numeric types and attempts to convert them into integers while providing descriptive error messages. This
@@ -204,7 +230,7 @@ def intInnit(listInt_Allegedly: Iterable[Any], parameterName: str | None = None,
204
230
 
205
231
  Parameters
206
232
  ----------
207
- listInt_Allegedly : Iterable[Any]
233
+ listInt_Allegedly : Collection[Any]
208
234
  The input sequence that should contain integer-compatible values. Accepts integers, strings, floats, complex numbers, and
209
235
  binary data. Rejects boolean values and non-integer numeric values.
210
236
  parameterName : str | None = None
@@ -236,11 +262,12 @@ def intInnit(listInt_Allegedly: Iterable[Any], parameterName: str | None = None,
236
262
  parameterType = parameterType or list
237
263
 
238
264
  if not listInt_Allegedly:
239
- message: str = f"I did not receive a value for {parameterName}, but it is required."
265
+ message: str = f"I did not receive a value for `{parameterName}`, but I must have it."
240
266
  raise ValueError(message)
241
267
 
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.
268
+ #Be nice, and assume the input container is valid and every element is valid.
269
+ #Nevertheless, this is a "fail-early" step, so reject ambiguity and try to induce errors now that could be catastrophic later.
270
+ #ruff: ignore[too-many-statements-in-try-clause]
244
271
  try:
245
272
  iter(listInt_Allegedly)
246
273
  lengthInitial: int | None = None
@@ -250,19 +277,18 @@ def intInnit(listInt_Allegedly: Iterable[Any], parameterName: str | None = None,
250
277
  listValidated: list[int] = []
251
278
 
252
279
  for allegedInt in listInt_Allegedly:
253
-
254
280
  errorMessageContext: ErrorMessageContext = ErrorMessageContext(
255
- parameterValue=allegedInt,
256
- parameterValueType=type(allegedInt).__name__,
257
- isElement=True
281
+ parameterValue=allegedInt
282
+ , parameterValueType=type(allegedInt).__name__
283
+ , isElement=True
258
284
  )
259
285
 
260
- # Always rejected as ambiguous
286
+ #Always rejected as ambiguous
261
287
  if isinstance(allegedInt, bool):
262
288
  raise TypeError(errorMessageContext)
263
289
 
264
- # In this section, we know the Python type is not `int`, but maybe the value is clearly an integer.
265
- # Through a series of conversions, allow data to cascade down into either an `int` or a meaningful error message.
290
+ #In this section, we know the Python type is not `int`, but maybe the value is clearly an integer.
291
+ #Through a series of conversions, allow data to cascade down into either an `int` or a meaningful error message.
266
292
 
267
293
  if isinstance(allegedInt, (bytes, bytearray, memoryview)):
268
294
  errorMessageContext.parameterValue = None # Don't expose potentially garbled binary data in error messages
@@ -289,29 +315,23 @@ def intInnit(listInt_Allegedly: Iterable[Any], parameterName: str | None = None,
289
315
 
290
316
  listValidated.append(allegedInt)
291
317
 
292
- if lengthInitial is not None and isinstance(listInt_Allegedly, Sized) and len(listInt_Allegedly) != lengthInitial:
318
+ if lengthInitial is not None and isinstance(listInt_Allegedly, Sized) and len(listInt_Allegedly) != lengthInitial: # pyright: ignore[reportUnnecessaryComparison]
293
319
  raise RuntimeError((lengthInitial, len(listInt_Allegedly)))
294
320
 
295
- except (TypeError, ValueError) as ERRORmessage:
296
- if isinstance(ERRORmessage.args[0], ErrorMessageContext):
297
- context: ErrorMessageContext = ERRORmessage.args[0]
321
+ except (TypeError, ValueError) as ERROR:
322
+ if isinstance(ERROR.args[0], ErrorMessageContext):
323
+ context: ErrorMessageContext = ERROR.args[0]
298
324
  if not context.containerType:
299
325
  context.containerType = type(listInt_Allegedly).__name__
300
326
  message = _constructErrorMessage(context, parameterName, parameterType)
301
- raise type(ERRORmessage)(message) from None
302
- # If it's not our Exception, don't molest it
303
- raise
304
-
305
- except RuntimeError as ERRORruntime:
306
- lengthCurrent: int
307
- lengthInitial, lengthCurrent = ERRORruntime.args[0]
308
- ERRORmessage = (
309
- f"The input sequence {parameterName} was modified during iteration. "
310
- f"Initial length {lengthInitial}, current length {lengthCurrent}."
311
- )
312
- raise RuntimeError(
313
- ERRORmessage
314
- ) from None
327
+ raise type(ERROR)(message) from None
328
+ #If it's not our Exception, don't molest it
329
+ raise ValueError from ERROR
330
+
331
+ except RuntimeError as ERROR:
332
+ lengthInitial, lengthCurrent = ERROR.args[0]
333
+ message = (f"`{parameterName}` was modified during iteration: {lengthInitial = }, current length {lengthCurrent = }.")
334
+ raise RuntimeError(message) from None
315
335
 
316
336
  else:
317
337
  return listValidated
@@ -338,6 +358,7 @@ def oopsieKwargsie(huh: Any) -> bool | str | None:
338
358
  if not isinstance(huh, str):
339
359
  try:
340
360
  huh = str(huh)
361
+ #ruff: ignore[blind-except]
341
362
  except BaseException:
342
363
  return huh
343
364
  formatted: str = huh.strip().title()
@@ -326,7 +326,7 @@ References
326
326
  #======== Some colors for printing text to the terminal. ========
327
327
  # Many values and options at https://gist.github.com/JBlond/2fea43a3049b38287e5e9cefc87b2124
328
328
  # Many, many, many options at https://stackoverflow.com/a/33206814/4403878
329
- # NOTE Always define color and background color at the same time.
329
+ # Always define color and background color at the same time.
330
330
 
331
331
  ansiColorReset: Final[str] = '\x1b[0m'
332
332
  """Reset terminal text color and background to default settings.
@@ -503,3 +503,5 @@ class AnsiColors(NamedTuple):
503
503
  YellowOnBlack: str = '\x1b[33;40m'
504
504
  YellowOnBlue: str = '\x1b[33;44m'
505
505
  YellowOnRed: str = '\x1b[33;41m'
506
+
507
+ ansiColor = AnsiColors()
@@ -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.5
3
+ Version: 0.8.1
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,12 +17,12 @@ 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
20
+ Classifier: Programming Language :: Python :: 3.11
21
21
  Classifier: Programming Language :: Python :: 3.14
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.11
26
26
  Description-Content-Type: text/markdown
27
27
  License-File: LICENSE
28
28
  Requires-Dist: Z0Z_tools>=2.2.1
@@ -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,9 +1,9 @@
1
1
  [project]
2
2
  name = "hunterMakesPy"
3
- version = "0.7.5"
3
+ version = "0.8.1"
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.11"
7
7
  license = "CC-BY-NC-4.0"
8
8
  authors = [{ name = "Hunter Hogan", email = "HunterHogan@pm.me" }]
9
9
  keywords = [
@@ -20,7 +20,7 @@ classifiers = [
20
20
  "Natural Language :: English",
21
21
  "Operating System :: OS Independent",
22
22
  "Programming Language :: Python",
23
- "Programming Language :: Python :: 3.10",
23
+ "Programming Language :: Python :: 3.11",
24
24
  "Programming Language :: Python :: 3.14",
25
25
  "Topic :: Software Development :: Libraries :: Python Modules",
26
26
  "Topic :: Utilities",
File without changes
File without changes
File without changes