hunterMakesPy 0.2.4__py3-none-any.whl → 0.3.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
hunterMakesPy/__init__.py CHANGED
@@ -7,23 +7,29 @@ This package provides:
7
7
  - Utilities for string extraction from nested data structures and merging dictionaries of lists.
8
8
 
9
9
  """
10
- # pyright: reportUnusedImport=false
10
+
11
+ # isort: split
11
12
  from hunterMakesPy.theTypes import identifierDotAttribute as identifierDotAttribute
12
13
 
14
+ # isort: split
13
15
  from hunterMakesPy.coping import PackageSettings as PackageSettings, raiseIfNone as raiseIfNone
14
16
 
15
- from hunterMakesPy.parseParameters import (defineConcurrencyLimit as defineConcurrencyLimit, intInnit as intInnit,
16
- oopsieKwargsie as oopsieKwargsie)
17
+ # isort: split
18
+ from hunterMakesPy.parseParameters import (
19
+ defineConcurrencyLimit as defineConcurrencyLimit, intInnit as intInnit, oopsieKwargsie as oopsieKwargsie)
17
20
 
18
- from hunterMakesPy.filesystemToolkit import (importLogicalPath2Identifier as importLogicalPath2Identifier,
21
+ # isort: split
22
+ from hunterMakesPy.filesystemToolkit import (
23
+ importLogicalPath2Identifier as importLogicalPath2Identifier,
19
24
  importPathFilename2Identifier as importPathFilename2Identifier, makeDirsSafely as makeDirsSafely,
20
- writeStringToHere as writeStringToHere)
21
-
22
- from hunterMakesPy.dataStructures import stringItUp as stringItUp, updateExtendPolishDictionaryLists as updateExtendPolishDictionaryLists
25
+ writePython as writePython, writeStringToHere as writeStringToHere)
23
26
 
24
- import sys
27
+ # isort: split
28
+ from hunterMakesPy.dataStructures import (
29
+ stringItUp as stringItUp, updateExtendPolishDictionaryLists as updateExtendPolishDictionaryLists)
25
30
 
26
- if sys.version_info < (3, 14):
27
- from hunterMakesPy.dataStructures import autoDecodingRLE as autoDecodingRLE
31
+ # isort: split
32
+ from hunterMakesPy.dataStructures import autoDecodingRLE as autoDecodingRLE
28
33
 
29
- from hunterMakesPy._theSSOT import settingsPackage
34
+ # isort: split
35
+ from hunterMakesPy._theSSOT import settingsPackage # pyright: ignore[reportUnusedImport]
hunterMakesPy/_theSSOT.py CHANGED
@@ -1,4 +1,4 @@
1
1
  """Settings for this package."""
2
2
  from hunterMakesPy import PackageSettings
3
3
 
4
- settingsPackage = PackageSettings(identifierPackageFALLBACK="hunterMakesPy")
4
+ settingsPackage: PackageSettings = PackageSettings(identifierPackageFALLBACK='hunterMakesPy')
hunterMakesPy/coping.py CHANGED
@@ -22,7 +22,7 @@ def getPathPackageINSTALLING(identifierPackage: str) -> Path:
22
22
  try:
23
23
  moduleSpecification: ModuleSpec | None = find_spec(identifierPackage)
24
24
  if moduleSpecification and moduleSpecification.origin:
25
- pathFilename = Path(moduleSpecification.origin)
25
+ pathFilename: Path = Path(moduleSpecification.origin)
26
26
  return pathFilename.parent if pathFilename.is_file() else pathFilename
27
27
  except ModuleNotFoundError:
28
28
  pass
@@ -148,6 +148,6 @@ def raiseIfNone(expression: TypeSansNone | None, errorMessage: str | None = None
148
148
 
149
149
  """
150
150
  if expression is None:
151
- message = errorMessage or 'A function unexpectedly returned `None`. Hint: look at the traceback immediately before `raiseIfNone`.'
151
+ message: str = errorMessage or 'A function unexpectedly returned `None`. Hint: look at the traceback immediately before `raiseIfNone`.'
152
152
  raise ValueError(message)
153
153
  return expression
@@ -1,19 +1,17 @@
1
1
  """Provides utilities for string extraction from nested data structures and merges multiple dictionaries containing lists into one dictionary."""
2
2
 
3
- from collections.abc import Iterator, Mapping
3
+ from collections.abc import Mapping
4
4
  from numpy import integer
5
5
  from numpy.typing import NDArray
6
- from typing import Any
6
+ from typing import Any, Protocol, TYPE_CHECKING, TypeVar
7
7
  import more_itertools
8
8
  import re as regex
9
9
 
10
- def removeExtraWhitespace(text: str) -> str:
11
- """Remove extra whitespace from string representation of Python data structures.
10
+ if TYPE_CHECKING:
11
+ from collections.abc import Iterator
12
12
 
13
- This function replaces python_minifier.minify() for the specific use case of
14
- minimizing string representations of lists, tuples, ranges, etc. It removes
15
- spaces after commas, around brackets and parentheses.
16
- """
13
+ def removeExtraWhitespace(text: str) -> str:
14
+ """Remove extra whitespace from string representation of Python data structures."""
17
15
  # Remove spaces after commas
18
16
  text = regex.sub(r',\s+', ',', text)
19
17
  # Remove spaces after opening brackets/parens
@@ -193,13 +191,13 @@ def stringItUp(*scrapPile: Any) -> list[str]:
193
191
  case str():
194
192
  listStrungUp.append(KitKat)
195
193
  case bool() | bytearray() | bytes() | complex() | float() | int() | memoryview() | None:
196
- listStrungUp.append(str(KitKat)) # pyright: ignore [reportUnknownArgumentType]
194
+ listStrungUp.append(str(KitKat))
197
195
  case dict():
198
- for broken, piece in KitKat.items(): # pyright: ignore [reportUnknownVariableType]
196
+ for broken, piece in KitKat.items():
199
197
  drill(broken)
200
198
  drill(piece)
201
199
  case list() | tuple() | set() | frozenset() | range():
202
- for kit in KitKat: # pyright: ignore [reportUnknownVariableType]
200
+ for kit in KitKat:
203
201
  drill(kit)
204
202
  case _:
205
203
  if hasattr(KitKat, '__iter__'): # Unpack other iterables
@@ -223,38 +221,50 @@ def stringItUp(*scrapPile: Any) -> list[str]:
223
221
  listStrungUp.append(repr(scrap))
224
222
  return listStrungUp
225
223
 
226
- def updateExtendPolishDictionaryLists(*dictionaryLists: Mapping[str, list[Any] | set[Any] | tuple[Any, ...]], destroyDuplicates: bool = False, reorderLists: bool = False, killErroneousDataTypes: bool = False) -> dict[str, list[Any]]:
227
- """Merge multiple dictionaries containing `list` into a single dictionary.
224
+ class Ordinals(Protocol):
225
+ """Protocol for types that support ordering comparisons."""
226
+
227
+ def __le__(self, other: "Ordinals", /) -> bool:
228
+ """Less than or equal to comparison."""
229
+ ...
230
+ def __ge__(self, other: "Ordinals", /) -> bool:
231
+ """Greater than or equal to comparison."""
232
+ ...
228
233
 
229
- With options to handle duplicates, `list` ordering, and erroneous data types.
234
+ 小于 = TypeVar('小于', bound=Ordinals)
235
+
236
+ def updateExtendPolishDictionaryLists(*dictionaryLists: Mapping[str, list[小于] | set[小于] | tuple[小于, ...]], destroyDuplicates: bool = False, reorderLists: bool = False, killErroneousDataTypes: bool = False) -> dict[str, list[小于]]:
237
+ """Merge multiple dictionaries with `list` values into a single dictionary with the `list` values merged.
238
+
239
+ Plus options to destroy duplicates, sort `list` values, and handle erroneous data types.
230
240
 
231
241
  Parameters
232
242
  ----------
233
243
  *dictionaryLists : Mapping[str, list[Any] | set[Any] | tuple[Any, ...]]
234
- (dictionary2lists) Variable number of dictionaries to be merged. If only one dictionary is passed, it will be processed based on the provided options.
244
+ Variable number of dictionaries to be merged. If only one dictionary is passed, it will be "polished".
235
245
  destroyDuplicates : bool = False
236
- (destroy2duplicates) If `True`, removes duplicate elements from the `list`. Defaults to `False`.
246
+ If `True`, removes duplicate elements from the `list`. Defaults to `False`.
237
247
  reorderLists : bool = False
238
- (reorder2lists) If `True`, sorts the `list`. Defaults to `False`.
248
+ If `True`, sorts each `list` value. Defaults to `False`. The elements must be comparable; otherwise, a `TypeError` will be raised.
239
249
  killErroneousDataTypes : bool = False
240
- (kill2erroneous2data2types) If `True`, skips dictionary keys or dictionary values that cause a `TypeError` during merging. Defaults to `False`.
250
+ If `True`, suppresses any `TypeError` `Exception` and omits the dictionary key or value that caused the `Exception`.
251
+ Defaults to `False`.
241
252
 
242
253
  Returns
243
254
  -------
244
255
  ePluribusUnum : dict[str, list[Any]]
245
- (e2pluribus2unum) A single dictionary with merged `list` based on the provided options. If only one dictionary is passed,
246
- it will be cleaned up based on the options.
256
+ A single dictionary with merged and optionally "polished" `list` values.
247
257
 
248
258
  Notes
249
259
  -----
250
260
  The returned value, `ePluribusUnum`, is a so-called primitive dictionary (`dict`). Furthermore, every dictionary key is a
251
- so-called primitive string (cf. `str()`) and every dictionary value is a so-called primitive `list` (`list`). If
261
+ so-called primitive string (*cf.* `str()`) and every dictionary value is a so-called primitive `list` (`list`). If
252
262
  `dictionaryLists` has other data types, the data types will not be preserved. That could have unexpected consequences.
253
263
  Conversion from the original data type to a `list`, for example, may not preserve the order even if you want the order to be
254
264
  preserved.
255
265
 
256
266
  """
257
- ePluribusUnum: dict[str, list[Any]] = {}
267
+ ePluribusUnum: dict[str, list[小于]] = {}
258
268
 
259
269
  for dictionaryListTarget in dictionaryLists:
260
270
  for keyName, keyValue in dictionaryListTarget.items():
@@ -272,7 +282,7 @@ def updateExtendPolishDictionaryLists(*dictionaryLists: Mapping[str, list[Any] |
272
282
  for ImaStr, ImaList in ePluribusUnum.items():
273
283
  ePluribusUnum[ImaStr] = list(dict.fromkeys(ImaList))
274
284
  if reorderLists:
275
- for ImaStr, ImaList in ePluribusUnum.items():
276
- ePluribusUnum[ImaStr] = sorted(ImaList)
285
+ for ImaStr, ImaRichComparisonSupporter in ePluribusUnum.items():
286
+ ePluribusUnum[ImaStr] = sorted(ImaRichComparisonSupporter)
277
287
 
278
288
  return ePluribusUnum
@@ -3,8 +3,9 @@
3
3
  This module provides basic file I/O utilities such as importing callables from modules, safely creating directories, and writing to files or streams (pipes).
4
4
 
5
5
  """
6
-
6
+ from autoflake import fix_code as autoflake_fix_code
7
7
  from hunterMakesPy import identifierDotAttribute
8
+ from isort import code as isort_code
8
9
  from os import PathLike
9
10
  from pathlib import Path, PurePath
10
11
  from typing import Any, TYPE_CHECKING, TypeVar
@@ -14,6 +15,7 @@ import importlib.util
14
15
  import io
15
16
 
16
17
  if TYPE_CHECKING:
18
+ from importlib.machinery import ModuleSpec
17
19
  from types import ModuleType
18
20
 
19
21
  归个 = TypeVar('归个')
@@ -71,9 +73,9 @@ def importPathFilename2Identifier(pathFilename: PathLike[Any] | PurePath, identi
71
73
  """
72
74
  pathFilename = Path(pathFilename)
73
75
 
74
- importlibSpecification = importlib.util.spec_from_file_location(moduleIdentifier or pathFilename.stem, pathFilename)
76
+ importlibSpecification: ModuleSpec | None = importlib.util.spec_from_file_location(moduleIdentifier or pathFilename.stem, pathFilename)
75
77
  if importlibSpecification is None or importlibSpecification.loader is None:
76
- message = f"I received\n\t`{pathFilename = }`,\n\t`{identifier = }`, and\n\t`{moduleIdentifier = }`.\n\tAfter loading, \n\t`importlibSpecification` {'is `None`' if importlibSpecification is None else 'has a value'} and\n\t`importlibSpecification.loader` is unknown."
78
+ message: str = f"I received\n\t`{pathFilename = }`,\n\t`{identifier = }`, and\n\t`{moduleIdentifier = }`.\n\tAfter loading, \n\t`importlibSpecification` {'is `None`' if importlibSpecification is None else 'has a value'} and\n\t`importlibSpecification.loader` is unknown."
77
79
  raise ImportError(message)
78
80
 
79
81
  moduleImported_jk_hahaha: ModuleType = importlib.util.module_from_spec(importlibSpecification)
@@ -97,6 +99,59 @@ def makeDirsSafely(pathFilename: Any) -> None:
97
99
  with contextlib.suppress(OSError):
98
100
  Path(pathFilename).parent.mkdir(parents=True, exist_ok=True)
99
101
 
102
+ settings_autoflakeDEFAULT: dict[str, list[str] | bool] = {
103
+ 'additional_imports': [],
104
+ 'expand_star_imports': True,
105
+ 'remove_all_unused_imports': True,
106
+ 'remove_duplicate_keys': False,
107
+ 'remove_unused_variables': False,
108
+ }
109
+
110
+ settings_isortDEFAULT: dict[str, bool | int | str | list[str]] = {
111
+ "combine_as_imports": True,
112
+ "force_alphabetical_sort_within_sections": True,
113
+ "from_first": True,
114
+ "honor_noqa": True,
115
+ "indent": "\t",
116
+ "line_length": 120,
117
+ "lines_after_imports": 1,
118
+ "lines_between_types": 0,
119
+ "multi_line_output": 4,
120
+ "no_sections": True,
121
+ "skip": ["__init__.py"], # TODO think
122
+ "use_parentheses": True,
123
+ }
124
+
125
+ def writePython(pythonSource: str, pathFilename: PathLike[Any] | PurePath | io.TextIOBase, settings: dict[str, dict[str, Any]] | None = None) -> None:
126
+ """Format and write Python source code to a file or text stream.
127
+
128
+ (AI generated docstring)
129
+
130
+ This function processes Python source code through autoflake and isort formatters before writing to the specified destination.
131
+ The formatters remove unused imports, sort imports, and apply consistent code style according to the provided or default
132
+ settings.
133
+
134
+ Parameters
135
+ ----------
136
+ pythonSource : str
137
+ The Python source code to format and write.
138
+ pathFilename : PathLike[Any] | PurePath | io.TextIOBase
139
+ The target destination: either a file path or an open text stream.
140
+ settings : dict[str, dict[str, Any]] | None = None
141
+ Configuration for the formatters. Keys are `'autoflake'` and `'isort'`, each mapping to a dictionary of formatter-specific
142
+ settings. If `None`, default settings are used for both formatters.
143
+
144
+ """
145
+ if settings is None:
146
+ settings = {}
147
+
148
+ settings_autoflake: dict[str, Any] = settings.get('autoflake', settings_autoflakeDEFAULT)
149
+ pythonSource = autoflake_fix_code(pythonSource, **settings_autoflake)
150
+
151
+ settings_isort: dict[str, Any] = settings.get('isort', settings_isortDEFAULT)
152
+ pythonSource = isort_code(pythonSource, **settings_isort)
153
+ writeStringToHere(pythonSource + '\n', pathFilename)
154
+
100
155
  def writeStringToHere(this: str, pathFilename: PathLike[Any] | PurePath | io.TextIOBase) -> None:
101
156
  """Write a string to a file or text stream.
102
157
 
@@ -1,10 +1,13 @@
1
1
  """Provides parameter and input validation, integer parsing, and concurrency handling utilities."""
2
2
  from collections.abc import Iterable, Sized
3
3
  from dataclasses import dataclass
4
- from typing import Any
4
+ from typing import Any, TYPE_CHECKING
5
5
  import charset_normalizer
6
6
  import multiprocessing
7
7
 
8
+ if TYPE_CHECKING:
9
+ from charset_normalizer.models import CharsetMatch
10
+
8
11
  @dataclass
9
12
  class ErrorMessageContext:
10
13
  """Context information for constructing error messages.
@@ -52,7 +55,7 @@ def _constructErrorMessage(context: ErrorMessageContext, parameterName: str, par
52
55
  The constructed error message string.
53
56
 
54
57
  """
55
- messageParts = ["I received "]
58
+ messageParts: list[str] = ["I received "]
56
59
 
57
60
  if context.parameterValue is not None and not isinstance(context.parameterValue, (bytes, bytearray, memoryview)):
58
61
  messageParts.append(f'"{context.parameterValue}"')
@@ -131,15 +134,15 @@ def defineConcurrencyLimit(*, limit: bool | float | int | None, cpuTotal: int =
131
134
  ```
132
135
 
133
136
  """
134
- concurrencyLimit = cpuTotal
137
+ concurrencyLimit: int = cpuTotal
135
138
 
136
139
  if isinstance(limit, str):
137
- limitFromString = oopsieKwargsie(limit)
140
+ limitFromString: bool | None | str = oopsieKwargsie(limit)
138
141
  if isinstance(limitFromString, str):
139
142
  try:
140
143
  limit = float(limitFromString)
141
144
  except ValueError as ERRORmessage:
142
- message = f"I received '{limitFromString}', but it must be a number, `True`, `False`, or `None`."
145
+ message: str = f"I received '{limitFromString}', but it must be a number, `True`, `False`, or `None`."
143
146
  raise ValueError(message) from ERRORmessage
144
147
  else:
145
148
  limit = limitFromString
@@ -208,19 +211,19 @@ def intInnit(listInt_Allegedly: Iterable[Any], parameterName: str | None = None,
208
211
  message = f"I did not receive a value for {parameterName}, but it is required."
209
212
  raise ValueError(message)
210
213
 
211
- # Be nice: assume the input container is valid and every element is valid. # noqa: ERA001
214
+ # Be nice, and assume the input container is valid and every element is valid.
212
215
  # Nevertheless, this is a "fail-early" step, so reject ambiguity and try to induce errors now that could be catastrophic later.
213
216
  try:
214
217
  iter(listInt_Allegedly)
215
- lengthInitial = None
218
+ lengthInitial: int | None = None
216
219
  if isinstance(listInt_Allegedly, Sized):
217
220
  lengthInitial = len(listInt_Allegedly)
218
221
 
219
222
  listValidated: list[int] = []
220
223
 
221
224
  for allegedInt in listInt_Allegedly:
222
- # ruff: noqa: PLW2901
223
- errorMessageContext = ErrorMessageContext(
225
+
226
+ errorMessageContext: ErrorMessageContext = ErrorMessageContext(
224
227
  parameterValue = allegedInt,
225
228
  parameterValueType = type(allegedInt).__name__,
226
229
  isElement = True
@@ -237,7 +240,7 @@ def intInnit(listInt_Allegedly: Iterable[Any], parameterName: str | None = None,
237
240
  errorMessageContext.parameterValue = None # Don't expose potentially garbled binary data in error messages
238
241
  if isinstance(allegedInt, memoryview):
239
242
  allegedInt = allegedInt.tobytes()
240
- decodedString = charset_normalizer.from_bytes(allegedInt).best()
243
+ decodedString: CharsetMatch | None = charset_normalizer.from_bytes(allegedInt).best()
241
244
  if not decodedString:
242
245
  raise ValueError(errorMessageContext)
243
246
  allegedInt = errorMessageContext.parameterValue = str(decodedString)
@@ -308,7 +311,7 @@ def oopsieKwargsie(huh: Any) -> bool | None | str:
308
311
  huh = str(huh)
309
312
  except BaseException: # noqa: BLE001
310
313
  return huh
311
- formatted = huh.strip().title()
314
+ formatted: str = huh.strip().title()
312
315
  if formatted == str(True):
313
316
  return True
314
317
  if formatted == str(False):
@@ -1,5 +1,6 @@
1
1
  # pyright: standard
2
- from hunterMakesPy import importLogicalPath2Identifier, importPathFilename2Identifier, makeDirsSafely, writeStringToHere
2
+ from hunterMakesPy import (
3
+ importLogicalPath2Identifier, importPathFilename2Identifier, makeDirsSafely, writePython, writeStringToHere)
3
4
  from hunterMakesPy.tests.conftest import standardizedEqualTo
4
5
  import io
5
6
  import math
@@ -261,3 +262,188 @@ def testImportPathFilename2IdentifierWithValidFileInvalidIdentifier(
261
262
  identifierTarget
262
263
  )
263
264
 
265
+
266
+ @pytest.mark.parametrize(
267
+ "pythonSourceTarget, expectedFormattedContent",
268
+ [
269
+ (
270
+ "import sys\nimport os\nimport math\n\ndef fibonacciFunction():\n return 13\n",
271
+ "\ndef fibonacciFunction():\n return 13\n\n"
272
+ ),
273
+ (
274
+ "from pathlib import Path\nimport sys\nimport os\n\ndef primeFunction():\n return 17\n",
275
+ "\ndef primeFunction():\n return 17\n\n"
276
+ ),
277
+ (
278
+ "import unused\nimport sys\n\nvalueCardinal = sys.version\n",
279
+ "import sys\n\nvalueCardinal = sys.version\n\n"
280
+ ),
281
+ (
282
+ "import math\n\nvalueFibonacci = math.sqrt(21)\n",
283
+ "import math\n\nvalueFibonacci = math.sqrt(21)\n\n"
284
+ ),
285
+ ]
286
+ )
287
+ def testWritePythonFormatsAndWritesToFile(
288
+ pathTmpTesting: pathlib.Path,
289
+ pythonSourceTarget: str,
290
+ expectedFormattedContent: str
291
+ ) -> None:
292
+ """Test that writePython formats Python source code and writes it to files."""
293
+ pathFilenameTarget = pathTmpTesting / "formattedModule.py"
294
+ writePython(pythonSourceTarget, pathFilenameTarget)
295
+
296
+ assert pathFilenameTarget.exists(), (
297
+ f"\nTesting: `writePython(..., {pathFilenameTarget})`\n"
298
+ f"Expected: File {pathFilenameTarget} to exist\n"
299
+ f"Got: exists={pathFilenameTarget.exists()}"
300
+ )
301
+
302
+ contentActual = pathFilenameTarget.read_text(encoding="utf-8")
303
+ assert contentActual == expectedFormattedContent, (
304
+ f"\nTesting: `writePython(...)`\n"
305
+ f"Expected content:\n{repr(expectedFormattedContent)}\n"
306
+ f"Got content:\n{repr(contentActual)}"
307
+ )
308
+
309
+
310
+ @pytest.mark.parametrize(
311
+ "pythonSourceTarget, expectedFormattedContent",
312
+ [
313
+ (
314
+ "import sys\nimport unused\n\ndef cardinalFunction():\n return sys.version\n",
315
+ "import sys\n\ndef cardinalFunction():\n return sys.version\n\n"
316
+ ),
317
+ (
318
+ "from pathlib import Path\nfrom os import getcwd\nimport math\n\nvalueSequence = getcwd()\n",
319
+ "from os import getcwd\n\nvalueSequence = getcwd()\n\n"
320
+ ),
321
+ ]
322
+ )
323
+ def testWritePythonFormatsAndWritesToStream(
324
+ pythonSourceTarget: str,
325
+ expectedFormattedContent: str
326
+ ) -> None:
327
+ """Test that writePython formats Python source code and writes it to IO streams."""
328
+ streamMemory = io.StringIO()
329
+ writePython(pythonSourceTarget, streamMemory)
330
+
331
+ contentActual = streamMemory.getvalue()
332
+ assert contentActual == expectedFormattedContent, (
333
+ f"\nTesting: `writePython(..., StringIO)`\n"
334
+ f"Expected content:\n{repr(expectedFormattedContent)}\n"
335
+ f"Got content:\n{repr(contentActual)}"
336
+ )
337
+
338
+
339
+ @pytest.mark.parametrize(
340
+ "pythonSourceTarget, settingsCustom, expectedFormattedContent",
341
+ [
342
+ (
343
+ "import math\nimport unused\n\nvalueFibonacci = 34\n\ndef fibonacciFunction():\n return math.sqrt(13)\n",
344
+ {'autoflake': {'remove_all_unused_imports': True, 'remove_unused_variables': False}},
345
+ "import math\n\nvalueFibonacci = 34\n\ndef fibonacciFunction():\n return math.sqrt(13)\n\n"
346
+ ),
347
+ (
348
+ "from pathlib import Path\nimport sys\n\nvaluePrime = Path.cwd()\n",
349
+ {'isort': {'force_alphabetical_sort_within_sections': False, 'from_first': False}},
350
+ "from pathlib import Path\n\nvaluePrime = Path.cwd()\n\n"
351
+ ),
352
+ ]
353
+ )
354
+ def testWritePythonWithCustomSettings(
355
+ pathTmpTesting: pathlib.Path,
356
+ pythonSourceTarget: str,
357
+ settingsCustom: dict[str, dict[str, object]],
358
+ expectedFormattedContent: str
359
+ ) -> None:
360
+ """Test that writePython respects custom formatter settings."""
361
+ pathFilenameTarget = pathTmpTesting / "customFormattedModule.py"
362
+ writePython(pythonSourceTarget, pathFilenameTarget, settingsCustom)
363
+
364
+ contentActual = pathFilenameTarget.read_text(encoding="utf-8")
365
+ assert contentActual == expectedFormattedContent, (
366
+ f"\nTesting: `writePython(..., custom settings)`\n"
367
+ f"Expected content:\n{repr(expectedFormattedContent)}\n"
368
+ f"Got content:\n{repr(contentActual)}"
369
+ )
370
+
371
+
372
+ @pytest.mark.parametrize(
373
+ "pythonSourceTarget",
374
+ [
375
+ "import math\nimport sys\n\nvalueFibonacci = 34\n",
376
+ "from pathlib import Path\n\nvalueCardinal = 'SW'\n",
377
+ "def primeFunction():\n return 37\n",
378
+ ]
379
+ )
380
+ def testWritePythonCreatesNestedDirectories(
381
+ pathTmpTesting: pathlib.Path,
382
+ pythonSourceTarget: str
383
+ ) -> None:
384
+ """Test that writePython creates nested directories when writing to files."""
385
+ pathFilenameTarget = pathTmpTesting / "nested" / "directories" / "module.py"
386
+ writePython(pythonSourceTarget, pathFilenameTarget)
387
+
388
+ assert pathFilenameTarget.exists(), (
389
+ f"\nTesting: `writePython(..., {pathFilenameTarget})`\n"
390
+ f"Expected: File {pathFilenameTarget} to exist\n"
391
+ f"Got: exists={pathFilenameTarget.exists()}"
392
+ )
393
+
394
+ assert pathFilenameTarget.parent.exists(), (
395
+ f"\nTesting: `writePython(..., {pathFilenameTarget})`\n"
396
+ f"Expected: Parent directory {pathFilenameTarget.parent} to exist\n"
397
+ f"Got: exists={pathFilenameTarget.parent.exists()}"
398
+ )
399
+
400
+
401
+ @pytest.mark.parametrize(
402
+ "pythonSourceTarget, expectedContainsImport",
403
+ [
404
+ ("import math\n\nvaluePrime = math.sqrt(41)\n", "import math"),
405
+ ("from os import getcwd\n\nvalueSequence = getcwd()\n", "from os import getcwd"),
406
+ ("import sys\n\nvalueFibonacci = sys.version\n", "import sys"),
407
+ ]
408
+ )
409
+ def testWritePythonPreservesUsedImports(
410
+ pathTmpTesting: pathlib.Path,
411
+ pythonSourceTarget: str,
412
+ expectedContainsImport: str
413
+ ) -> None:
414
+ """Test that writePython preserves imports that are actually used in the code."""
415
+ pathFilenameTarget = pathTmpTesting / "preservedImports.py"
416
+ writePython(pythonSourceTarget, pathFilenameTarget)
417
+
418
+ contentActual = pathFilenameTarget.read_text(encoding="utf-8")
419
+ assert expectedContainsImport in contentActual, (
420
+ f"\nTesting: `writePython(...)` preserves used imports\n"
421
+ f"Expected content to contain: {expectedContainsImport}\n"
422
+ f"Got content:\n{contentActual}"
423
+ )
424
+
425
+
426
+ @pytest.mark.parametrize(
427
+ "pythonSourceTarget, expectedNotContainsImport",
428
+ [
429
+ ("import math\nimport unused\n\nvaluePrime = 43\n", "import unused"),
430
+ ("from os import getcwd, unused\n\nvalueSequence = getcwd()\n", "unused"),
431
+ ("import sys\nimport collections\n\nvalueFibonacci = sys.version\n", "import collections"),
432
+ ]
433
+ )
434
+ def testWritePythonRemovesUnusedImports(
435
+ pathTmpTesting: pathlib.Path,
436
+ pythonSourceTarget: str,
437
+ expectedNotContainsImport: str
438
+ ) -> None:
439
+ """Test that writePython removes imports that are not used in the code."""
440
+ pathFilenameTarget = pathTmpTesting / "removedImports.py"
441
+ writePython(pythonSourceTarget, pathFilenameTarget)
442
+
443
+ contentActual = pathFilenameTarget.read_text(encoding="utf-8")
444
+ assert expectedNotContainsImport not in contentActual, (
445
+ f"\nTesting: `writePython(...)` removes unused imports\n"
446
+ f"Expected content to NOT contain: {expectedNotContainsImport}\n"
447
+ f"Got content:\n{contentActual}"
448
+ )
449
+
@@ -8,7 +8,7 @@ import pytest
8
8
  parameters = ParamSpec('parameters')
9
9
  returnType = TypeVar('returnType')
10
10
 
11
- def PytestFor_defineConcurrencyLimit(callableToTest: Callable[..., int] = defineConcurrencyLimit, cpuCount: int = 8) -> list[tuple[str, Callable[[], None]]]: # noqa: C901
11
+ def PytestFor_defineConcurrencyLimit(callableToTest: Callable[..., int] = defineConcurrencyLimit, cpuCount: int = 8) -> list[tuple[str, Callable[[], None]]]:
12
12
  """Return a list of test functions to validate concurrency limit behavior.
13
13
 
14
14
  This function provides a comprehensive test suite for validating concurrency limit parsing
@@ -80,15 +80,17 @@ def PytestFor_defineConcurrencyLimit(callableToTest: Callable[..., int] = define
80
80
  @patch('multiprocessing.cpu_count', return_value=cpuCount)
81
81
  def testBooleanTrue(_mockCpu: Mock) -> None:
82
82
  assert callableToTest(limit=True, cpuTotal=cpuCount) == 1
83
- assert callableToTest(limit='True', cpuTotal=cpuCount) == 1 # pyright: ignore[reportArgumentType]
84
- assert callableToTest(limit='TRUE', cpuTotal=cpuCount) == 1 # pyright: ignore[reportArgumentType]
85
- assert callableToTest(limit=' true ', cpuTotal=cpuCount) == 1 # pyright: ignore[reportArgumentType]
83
+ # pyright: reportArgumentType=false
84
+ assert callableToTest(limit='True', cpuTotal=cpuCount) == 1
85
+ assert callableToTest(limit='TRUE', cpuTotal=cpuCount) == 1
86
+ assert callableToTest(limit=' true ', cpuTotal=cpuCount) == 1
87
+ # pyright: reportArgumentType=true
86
88
 
87
89
  @patch('multiprocessing.cpu_count', return_value=cpuCount)
88
90
  def testInvalidStrings(_mockCpu: Mock) -> None:
89
91
  for stringInput in ["invalid", "True but not quite", "None of the above"]:
90
92
  with pytest.raises(ValueError, match="must be a number, `True`, `False`, or `None`"):
91
- callableToTest(limit=stringInput, cpuTotal=cpuCount) # pyright: ignore[reportArgumentType]
93
+ callableToTest(limit=stringInput, cpuTotal=cpuCount)
92
94
 
93
95
  @patch('multiprocessing.cpu_count', return_value=cpuCount)
94
96
  def testStringNumbers(_mockCpu: Mock) -> None:
@@ -100,7 +102,7 @@ def PytestFor_defineConcurrencyLimit(callableToTest: Callable[..., int] = define
100
102
  ("-0.25", 6),
101
103
  ]
102
104
  for stringNumber, expectedLimit in testCases:
103
- assert callableToTest(limit=stringNumber, cpuTotal=cpuCount) == expectedLimit # pyright: ignore[reportArgumentType]
105
+ assert callableToTest(limit=stringNumber, cpuTotal=cpuCount) == expectedLimit
104
106
 
105
107
  return [
106
108
  ('testDefaults', testDefaults),
@@ -112,7 +114,7 @@ def PytestFor_defineConcurrencyLimit(callableToTest: Callable[..., int] = define
112
114
  ('testStringNumbers', testStringNumbers)
113
115
  ]
114
116
 
115
- def PytestFor_intInnit(callableToTest: Callable[[Iterable[int], str | None, type[Any] | None], list[int]] = intInnit) -> list[tuple[str, Callable[[], None]]]: # noqa: C901
117
+ def PytestFor_intInnit(callableToTest: Callable[[Iterable[int], str | None, type[Any] | None], list[int]] = intInnit) -> list[tuple[str, Callable[[], None]]]:
116
118
  """Return a list of test functions to validate integer initialization behavior.
117
119
 
118
120
  This function provides a comprehensive test suite for validating integer parsing
@@ -156,64 +158,64 @@ def PytestFor_intInnit(callableToTest: Callable[[Iterable[int], str | None, type
156
158
  """
157
159
  def testHandlesValidIntegers() -> None:
158
160
  assert callableToTest([2, 3, 5, 8], 'test', None) == [2, 3, 5, 8]
159
- assert callableToTest([13.0, 21.0, 34.0], 'test', None) == [13, 21, 34] # pyright: ignore[reportArgumentType]
160
- assert callableToTest(['55', '89', '144'], 'test', None) == [55, 89, 144] # pyright: ignore[reportArgumentType]
161
- assert callableToTest([' 233 ', '377', '-610'], 'test', None) == [233, 377, -610] # pyright: ignore[reportArgumentType]
161
+ assert callableToTest([13.0, 21.0, 34.0], 'test', None) == [13, 21, 34]
162
+ assert callableToTest(['55', '89', '144'], 'test', None) == [55, 89, 144]
163
+ assert callableToTest([' 233 ', '377', '-610'], 'test', None) == [233, 377, -610]
162
164
 
163
165
  def testRejectsNonWholeNumbers() -> None:
164
166
  listInvalidNumbers: list[float] = [13.7, 21.5, 34.8, -55.9]
165
167
  for invalidNumber in listInvalidNumbers:
166
168
  with pytest.raises(ValueError):
167
- callableToTest([invalidNumber], 'test', None) # pyright: ignore[reportArgumentType]
168
-
169
- def testRejectsBooleans() -> None:
170
- with pytest.raises(TypeError):
171
- callableToTest([True, False], 'test', None)
169
+ callableToTest([invalidNumber], 'test', None)
172
170
 
173
171
  def testRejectsInvalidStrings() -> None:
174
172
  for invalidString in ['NW', '', ' ', 'SE.SW']:
175
173
  with pytest.raises(ValueError):
176
- callableToTest([invalidString], 'test', None) # pyright: ignore[reportArgumentType]
174
+ callableToTest([invalidString], 'test', None)
175
+
176
+ def testHandlesMixedValidTypes() -> None:
177
+ assert callableToTest([13, '21', 34.0], 'test', None) == [13, 21, 34]
178
+
179
+ def testRejectsBooleans() -> None:
180
+ with pytest.raises(TypeError):
181
+ callableToTest([True, False], 'test', None)
177
182
 
178
183
  def testRejectsEmptyList() -> None:
179
184
  with pytest.raises(ValueError):
180
185
  callableToTest([], 'test', None)
181
186
 
182
- def testHandlesMixedValidTypes() -> None:
183
- assert callableToTest([13, '21', 34.0], 'test', None) == [13, 21, 34] # pyright: ignore[reportArgumentType]
184
-
185
187
  def testHandlesBytes() -> None:
186
188
  validCases: list[tuple[list[bytes], str, list[int]]] = [
187
189
  ([b'123'], '123', [123]),
188
190
  ]
189
191
  for inputData, testName, expected in validCases:
190
- assert callableToTest(inputData, testName, None) == expected # pyright: ignore[reportArgumentType]
192
+ assert callableToTest(inputData, testName, None) == expected
191
193
 
192
194
  extendedCases: list[tuple[list[bytes], str, list[int]]] = [
193
195
  ([b'123456789'], '123456789', [123456789]),
194
196
  ]
195
197
  for inputData, testName, expected in extendedCases:
196
- assert callableToTest(inputData, testName, None) == expected # pyright: ignore[reportArgumentType]
198
+ assert callableToTest(inputData, testName, None) == expected
197
199
 
198
200
  invalidCases: list[list[bytes]] = [[b'\x00']]
199
201
  for inputData in invalidCases:
200
202
  with pytest.raises(ValueError):
201
- callableToTest(inputData, 'test', None) # pyright: ignore[reportArgumentType]
203
+ callableToTest(inputData, 'test', None)
202
204
 
203
205
  def testHandlesMemoryview() -> None:
204
206
  validCases: list[tuple[list[memoryview], str, list[int]]] = [
205
207
  ([memoryview(b'123')], '123', [123]),
206
208
  ]
207
209
  for inputData, testName, expected in validCases:
208
- assert callableToTest(inputData, testName, None) == expected # pyright: ignore[reportArgumentType]
210
+ assert callableToTest(inputData, testName, None) == expected
209
211
 
210
212
  largeMemoryviewCase: list[memoryview] = [memoryview(b'9999999999')]
211
- assert callableToTest(largeMemoryviewCase, 'test', None) == [9999999999] # pyright: ignore[reportArgumentType]
213
+ assert callableToTest(largeMemoryviewCase, 'test', None) == [9999999999]
212
214
 
213
215
  invalidMemoryviewCases: list[list[memoryview]] = [[memoryview(b'\x00')]]
214
216
  for inputData in invalidMemoryviewCases:
215
217
  with pytest.raises(ValueError):
216
- callableToTest(inputData, 'test', None) # pyright: ignore[reportArgumentType]
218
+ callableToTest(inputData, 'test', None)
217
219
 
218
220
  def testRejectsMutableSequence() -> None:
219
221
  class MutableList(list[int]):
@@ -229,12 +231,12 @@ def PytestFor_intInnit(callableToTest: Callable[[Iterable[int], str | None, type
229
231
  ([21+0j, 34+0j], [21, 34])
230
232
  ]
231
233
  for inputData, expected in testCases:
232
- assert callableToTest(inputData, 'test', None) == expected # pyright: ignore[reportArgumentType]
234
+ assert callableToTest(inputData, 'test', None) == expected
233
235
 
234
236
  def testRejectsInvalidComplex() -> None:
235
237
  for invalidComplex in [13+1j, 21+0.5j, 34.5+0j]:
236
238
  with pytest.raises(ValueError):
237
- callableToTest([invalidComplex], 'test', None) # pyright: ignore[reportArgumentType]
239
+ callableToTest([invalidComplex], 'test', None)
238
240
 
239
241
  return [
240
242
  ('testHandlesValidIntegers', testHandlesValidIntegers),
@@ -250,7 +252,7 @@ def PytestFor_intInnit(callableToTest: Callable[[Iterable[int], str | None, type
250
252
  ('testRejectsInvalidComplex', testRejectsInvalidComplex)
251
253
  ]
252
254
 
253
- def PytestFor_oopsieKwargsie(callableToTest: Callable[[str], bool | None | str] = oopsieKwargsie) -> list[tuple[str, Callable[[], None]]]: # noqa: C901
255
+ def PytestFor_oopsieKwargsie(callableToTest: Callable[[str], bool | None | str] = oopsieKwargsie) -> list[tuple[str, Callable[[], None]]]:
254
256
  """Return a list of test functions to validate string-to-boolean/None conversion behavior.
255
257
 
256
258
  This function provides a comprehensive test suite for validating string parsing and conversion
@@ -312,10 +314,10 @@ def PytestFor_oopsieKwargsie(callableToTest: Callable[[str], bool | None | str]
312
314
  message = "Cannot be stringified"
313
315
  raise TypeError(message)
314
316
 
315
- assert callableToTest(123) == "123" # pyright: ignore[reportArgumentType]
317
+ assert callableToTest(123) == "123"
316
318
 
317
319
  neverGonnaStringIt = NeverGonnaStringIt()
318
- result = callableToTest(neverGonnaStringIt) # pyright: ignore[reportArgumentType]
320
+ result = callableToTest(neverGonnaStringIt)
319
321
  assert result is neverGonnaStringIt
320
322
 
321
323
  return [
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hunterMakesPy
3
- Version: 0.2.4
3
+ Version: 0.3.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: CC-BY-NC-4.0
@@ -9,7 +9,7 @@ Project-URL: Homepage, https://github.com/hunterhogan/hunterMakesPy
9
9
  Project-URL: Issues, https://github.com/hunterhogan/hunterMakesPy/issues
10
10
  Project-URL: Repository, https://github.com/hunterhogan/hunterMakesPy
11
11
  Keywords: attribute loading,concurrency limit,configuration,defensive programming,dictionary merging,directory creation,dynamic import,error propagation,file system utilities,input validation,integer parsing,module loading,nested data structures,package settings,parameter validation,pytest,string extraction,test utilities
12
- Classifier: Development Status :: 4 - Beta
12
+ Classifier: Development Status :: 5 - Production/Stable
13
13
  Classifier: Environment :: Console
14
14
  Classifier: Framework :: Pytest
15
15
  Classifier: Intended Audience :: Developers
@@ -30,12 +30,12 @@ Classifier: Typing :: Typed
30
30
  Requires-Python: >=3.11
31
31
  Description-Content-Type: text/markdown
32
32
  License-File: LICENSE
33
+ Requires-Dist: autoflake
33
34
  Requires-Dist: charset_normalizer
35
+ Requires-Dist: isort
34
36
  Requires-Dist: more_itertools
35
37
  Requires-Dist: numpy
36
38
  Provides-Extra: development
37
- Requires-Dist: mypy; extra == "development"
38
- Requires-Dist: pyupgrade; extra == "development"
39
39
  Requires-Dist: setuptools-scm; extra == "development"
40
40
  Provides-Extra: testing
41
41
  Requires-Dist: pytest; extra == "testing"
@@ -164,13 +164,4 @@ def test_myFunction(nameOfTest, callablePytest):
164
164
  [![Static Badge](https://img.shields.io/badge/2011_August-Homeless_since-blue?style=flat)](https://HunterThinks.com/support)
165
165
  [![YouTube Channel Subscribers](https://img.shields.io/youtube/channel/subscribers/UC3Gx7kz61009NbhpRtPP7tw)](https://www.youtube.com/@HunterHogan)
166
166
 
167
- ## How to code
168
-
169
- Coding One Step at a Time:
170
-
171
- 0. WRITE CODE.
172
- 1. Don't write stupid code that's hard to revise.
173
- 2. Write good code.
174
- 3. When revising, write better code.
175
-
176
- [![CC-BY-NC-4.0](https://github.com/hunterhogan/hunterMakesPy/blob/main/CC-BY-NC-4.0.png)](https://creativecommons.org/licenses/by-nc/4.0/)
167
+ [![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/)
@@ -0,0 +1,20 @@
1
+ hunterMakesPy/__init__.py,sha256=N2Bvmacvs9z8ITWWjQr6-5LQHZ1FG5KL6v-pR1m6fCY,1547
2
+ hunterMakesPy/_theSSOT.py,sha256=x9Rdmw0qeAqgmlMFyFYRTRV5kEDYXcN4aBZ4KjlnKEU,170
3
+ hunterMakesPy/coping.py,sha256=42a_1kB6zHeRpfbpPnjmhrgWTPvUtqE5W9z3tqu-K8w,6068
4
+ hunterMakesPy/dataStructures.py,sha256=0-BRFriADFKX9pvjYj03ukj021ZII61wet2Wq2yAEdM,11776
5
+ hunterMakesPy/filesystemToolkit.py,sha256=8Yx8SH56w7g9wxpLjsdCaC1RvsW8Ur_cDrxhHaY9cLM,6848
6
+ hunterMakesPy/parseParameters.py,sha256=uQXoD89BfWAmRVT2yabyHtW7qITwAuCC9eh0sQFIV5Q,11966
7
+ hunterMakesPy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ hunterMakesPy/pytestForYourUse.py,sha256=GiN1C1gTTM0ZunRPEMnrKlLQLMdH0wF_ZGr_RPgRjjA,500
9
+ hunterMakesPy/theTypes.py,sha256=C2d0uLn1VIx6_2CK41it3IP7iplSQqe51tzWc-RT320,306
10
+ hunterMakesPy/tests/__init__.py,sha256=C_FzfKDi_VrGVxlenWHyOYtKShAKlt3KW14jeRx1mQI,224
11
+ hunterMakesPy/tests/conftest.py,sha256=NZQPRiwvGhP16hJ6WGGm9eKLxfQArYV8E9X12YzSpP0,2827
12
+ hunterMakesPy/tests/test_coping.py,sha256=mH89TUAL6fJanBLlhdVlCNNQqm5OpdcQMP_p5W2JJwo,9860
13
+ hunterMakesPy/tests/test_dataStructures.py,sha256=OouddHjN-Km26U92jYwnjYeP6_Y2DrJLgq3qD_8GvGw,16393
14
+ hunterMakesPy/tests/test_filesystemToolkit.py,sha256=_CoSMzstJwWZ_tkNyIqclOIIqTaY2tYfUIgxGFfC0Jk,15335
15
+ hunterMakesPy/tests/test_parseParameters.py,sha256=UKf1hwhLtXYL96VVJNf8NpJOyzRLO204pd-9PdDjufA,13267
16
+ huntermakespy-0.3.1.dist-info/licenses/LICENSE,sha256=NxH5Y8BdC-gNU-WSMwim3uMbID2iNDXJz7fHtuTdXhk,19346
17
+ huntermakespy-0.3.1.dist-info/METADATA,sha256=_rbA9J-buuKyZvp8vkiYMhSipTCPdLFGdjRsRNtK5oU,6319
18
+ huntermakespy-0.3.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
19
+ huntermakespy-0.3.1.dist-info/top_level.txt,sha256=Uh4bj8EDTdsRpqY1VlK_his_B4HDfZ6Tqrwhoj75P_w,14
20
+ huntermakespy-0.3.1.dist-info/RECORD,,
@@ -1,20 +0,0 @@
1
- hunterMakesPy/__init__.py,sha256=bVF1F2Mdo5AOiioEfxKvNrnsa3vCFI16eMK7Oy5O5TU,1450
2
- hunterMakesPy/_theSSOT.py,sha256=lkLOG3oTIWNKD_ULX55chlUGNqCHgqVIrBvolvK1vbQ,153
3
- hunterMakesPy/coping.py,sha256=7NBwaGutEr6Q-2mIz65M69NkrbpG24u1I5HXx6VaAWI,6057
4
- hunterMakesPy/dataStructures.py,sha256=7CxCBpQmHJzGxTq_AfkAeh2QdJDkzBr5lfSpPaA1GkE,11722
5
- hunterMakesPy/filesystemToolkit.py,sha256=jd7H5UtrIrPiCYWcvNBNa6DAy-2Ewcf21-jbGXg_IVI,4702
6
- hunterMakesPy/parseParameters.py,sha256=1mwGNVIZ1x23k_di3lhOhQb8QMXMUCHgt7xw3NRzDXs,11814
7
- hunterMakesPy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- hunterMakesPy/pytestForYourUse.py,sha256=GiN1C1gTTM0ZunRPEMnrKlLQLMdH0wF_ZGr_RPgRjjA,500
9
- hunterMakesPy/theTypes.py,sha256=C2d0uLn1VIx6_2CK41it3IP7iplSQqe51tzWc-RT320,306
10
- hunterMakesPy/tests/__init__.py,sha256=C_FzfKDi_VrGVxlenWHyOYtKShAKlt3KW14jeRx1mQI,224
11
- hunterMakesPy/tests/conftest.py,sha256=NZQPRiwvGhP16hJ6WGGm9eKLxfQArYV8E9X12YzSpP0,2827
12
- hunterMakesPy/tests/test_coping.py,sha256=mH89TUAL6fJanBLlhdVlCNNQqm5OpdcQMP_p5W2JJwo,9860
13
- hunterMakesPy/tests/test_dataStructures.py,sha256=OouddHjN-Km26U92jYwnjYeP6_Y2DrJLgq3qD_8GvGw,16393
14
- hunterMakesPy/tests/test_filesystemToolkit.py,sha256=q2voXjCbQPIT8l8VF9iuWX1Bs2ZieABItWoVkITj_fo,8841
15
- hunterMakesPy/tests/test_parseParameters.py,sha256=80npsoWcCackjxvoW2dMXMpHeale7fuRXyXp78MibLs,14037
16
- huntermakespy-0.2.4.dist-info/licenses/LICENSE,sha256=NxH5Y8BdC-gNU-WSMwim3uMbID2iNDXJz7fHtuTdXhk,19346
17
- huntermakespy-0.2.4.dist-info/METADATA,sha256=UeGvfHoO_DMo_K5yy8daea6K7Ska8X6hHbC7MuCh7p8,6491
18
- huntermakespy-0.2.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
19
- huntermakespy-0.2.4.dist-info/top_level.txt,sha256=Uh4bj8EDTdsRpqY1VlK_his_B4HDfZ6Tqrwhoj75P_w,14
20
- huntermakespy-0.2.4.dist-info/RECORD,,