hunterMakesPy 0.1.1__tar.gz → 0.1.2__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.1.1 → huntermakespy-0.1.2}/PKG-INFO +10 -11
  2. {huntermakespy-0.1.1 → huntermakespy-0.1.2}/hunterMakesPy/__init__.py +1 -3
  3. huntermakespy-0.1.2/hunterMakesPy/_theSSOT.py +4 -0
  4. huntermakespy-0.1.2/hunterMakesPy/coping.py +149 -0
  5. {huntermakespy-0.1.1 → huntermakespy-0.1.2}/hunterMakesPy/dataStructures.py +2 -2
  6. {huntermakespy-0.1.1 → huntermakespy-0.1.2}/hunterMakesPy.egg-info/PKG-INFO +10 -11
  7. {huntermakespy-0.1.1 → huntermakespy-0.1.2}/hunterMakesPy.egg-info/requires.txt +5 -4
  8. {huntermakespy-0.1.1 → huntermakespy-0.1.2}/pyproject.toml +20 -21
  9. huntermakespy-0.1.2/tests/test_coping.py +216 -0
  10. huntermakespy-0.1.1/hunterMakesPy/_theSSOT.py +0 -40
  11. huntermakespy-0.1.1/hunterMakesPy/coping.py +0 -73
  12. huntermakespy-0.1.1/tests/test_coping.py +0 -56
  13. {huntermakespy-0.1.1 → huntermakespy-0.1.2}/LICENSE +0 -0
  14. {huntermakespy-0.1.1 → huntermakespy-0.1.2}/README.md +0 -0
  15. {huntermakespy-0.1.1 → huntermakespy-0.1.2}/hunterMakesPy/filesystemToolkit.py +0 -0
  16. {huntermakespy-0.1.1 → huntermakespy-0.1.2}/hunterMakesPy/parseParameters.py +0 -0
  17. {huntermakespy-0.1.1 → huntermakespy-0.1.2}/hunterMakesPy/py.typed +0 -0
  18. {huntermakespy-0.1.1 → huntermakespy-0.1.2}/hunterMakesPy/pytestForYourUse.py +0 -0
  19. {huntermakespy-0.1.1 → huntermakespy-0.1.2}/hunterMakesPy/theTypes.py +0 -0
  20. {huntermakespy-0.1.1 → huntermakespy-0.1.2}/hunterMakesPy.egg-info/SOURCES.txt +0 -0
  21. {huntermakespy-0.1.1 → huntermakespy-0.1.2}/hunterMakesPy.egg-info/dependency_links.txt +0 -0
  22. {huntermakespy-0.1.1 → huntermakespy-0.1.2}/hunterMakesPy.egg-info/top_level.txt +0 -0
  23. {huntermakespy-0.1.1 → huntermakespy-0.1.2}/setup.cfg +0 -0
  24. {huntermakespy-0.1.1 → huntermakespy-0.1.2}/tests/__init__.py +0 -0
  25. {huntermakespy-0.1.1 → huntermakespy-0.1.2}/tests/conftest.py +0 -0
  26. {huntermakespy-0.1.1 → huntermakespy-0.1.2}/tests/test_dataStructures.py +0 -0
  27. {huntermakespy-0.1.1 → huntermakespy-0.1.2}/tests/test_filesystemToolkit.py +0 -0
  28. {huntermakespy-0.1.1 → huntermakespy-0.1.2}/tests/test_parseParameters.py +0 -0
@@ -1,14 +1,14 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hunterMakesPy
3
- Version: 0.1.1
4
- Summary: A modular Python toolkit for defensive programming, parameter validation, file system utilities, and flexible data structure manipulation. Provides helpers for error propagation, input validation, concurrency limits, safe directory creation, dynamic module loading, string extraction from nested structures, and dictionary merging.
3
+ Version: 0.1.2
4
+ Summary: Easy Python functions making making functional Python functions easier.
5
5
  Author-email: Hunter Hogan <HunterHogan@pm.me>
6
6
  License: CC-BY-NC-4.0
7
7
  Project-URL: Donate, https://www.patreon.com/integrated
8
8
  Project-URL: Homepage, https://github.com/hunterhogan/
9
9
  Project-URL: Issues, https://github.com/hunterhogan/
10
10
  Project-URL: Repository, https://github.com/hunterhogan/
11
- Keywords: defensive programming,parameter validation,input validation,error propagation,concurrency limit,integer parsing,file system utilities,directory creation,dynamic import,module loading,attribute loading,string extraction,nested data structures,dictionary merging,package settings,configuration,pytest,test utilities
11
+ Keywords: attribute loading,concurrency limit,configuration,defensive programming,dictionary merging,directory creation,dynamic import,error propagation,file system utilities,input validation,integer parsing,module loading,nested data structures,package settings,parameter validation,pytest,string extraction,test utilities
12
12
  Classifier: Development Status :: 4 - Beta
13
13
  Classifier: Environment :: Console
14
14
  Classifier: Framework :: Pytest
@@ -17,31 +17,30 @@ Classifier: Intended Audience :: End Users/Desktop
17
17
  Classifier: Intended Audience :: Other Audience
18
18
  Classifier: Natural Language :: English
19
19
  Classifier: Operating System :: OS Independent
20
- Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python
21
+ Classifier: Programming Language :: Python :: 3
21
22
  Classifier: Programming Language :: Python :: 3.11
22
23
  Classifier: Programming Language :: Python :: 3.12
23
24
  Classifier: Programming Language :: Python :: 3.13
24
- Classifier: Programming Language :: Python :: 3
25
25
  Classifier: Programming Language :: Python :: Implementation :: CPython
26
- Classifier: Programming Language :: Python
27
26
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
28
27
  Classifier: Topic :: Utilities
29
28
  Classifier: Typing :: Typed
30
- Requires-Python: >=3.10
29
+ Requires-Python: >=3.11
31
30
  Description-Content-Type: text/markdown
32
31
  License-File: LICENSE
33
32
  Requires-Dist: charset_normalizer
34
33
  Requires-Dist: more_itertools
35
34
  Requires-Dist: numpy
36
35
  Requires-Dist: python_minifier
37
- Requires-Dist: tomli
36
+ Provides-Extra: development
37
+ Requires-Dist: mypy; extra == "development"
38
+ Requires-Dist: pyupgrade; extra == "development"
39
+ Requires-Dist: setuptools-scm; extra == "development"
38
40
  Provides-Extra: testing
39
- Requires-Dist: mypy; extra == "testing"
40
41
  Requires-Dist: pytest; extra == "testing"
41
42
  Requires-Dist: pytest-cov; extra == "testing"
42
43
  Requires-Dist: pytest-xdist; extra == "testing"
43
- Requires-Dist: pyupgrade; extra == "testing"
44
- Requires-Dist: setuptools-scm; extra == "testing"
45
44
  Dynamic: license-file
46
45
 
47
46
  # hunterMakesPy
@@ -1,7 +1,5 @@
1
1
  """A modular toolkit for defensive programming, parameter validation, file system utilities, and data structure manipulation.
2
2
 
3
- (AI generated docstring)
4
-
5
3
  This package provides:
6
4
  - Defensive programming helpers for handling `None` values and error propagation.
7
5
  - Parameter and input validation, integer parsing, and concurrency limit utilities.
@@ -11,7 +9,7 @@ This package provides:
11
9
  """
12
10
  from hunterMakesPy.theTypes import identifierDotAttribute as identifierDotAttribute
13
11
 
14
- from hunterMakesPy.coping import raiseIfNone as raiseIfNone
12
+ from hunterMakesPy.coping import PackageSettings as PackageSettings, raiseIfNone as raiseIfNone
15
13
 
16
14
  from hunterMakesPy.parseParameters import (defineConcurrencyLimit as defineConcurrencyLimit, intInnit as intInnit,
17
15
  oopsieKwargsie as oopsieKwargsie)
@@ -0,0 +1,4 @@
1
+ """Settings for this package."""
2
+ from hunterMakesPy import PackageSettings
3
+
4
+ settingsPackage = PackageSettings(identifierPackageFALLBACK="hunterMakesPy")
@@ -0,0 +1,149 @@
1
+ """Package configuration and defensive programming utilities for Python projects."""
2
+ from importlib.util import find_spec
3
+ from pathlib import Path
4
+ from tomllib import loads as tomllib_loads
5
+ from typing import TYPE_CHECKING, TypeVar
6
+ import dataclasses
7
+
8
+ if TYPE_CHECKING:
9
+ from importlib.machinery import ModuleSpec
10
+
11
+ TypeSansNone = TypeVar('TypeSansNone')
12
+
13
+ def getIdentifierPackagePACKAGING(identifierPackageFALLBACK: str) -> str:
14
+ """Get package name from pyproject.toml or fallback to provided value."""
15
+ try:
16
+ return tomllib_loads(Path('pyproject.toml').read_text(encoding='utf-8'))['project']['name']
17
+ except Exception: # noqa: BLE001
18
+ return identifierPackageFALLBACK
19
+
20
+ def getPathPackageINSTALLING(identifierPackage: str) -> Path:
21
+ """Return the root directory of the installed package."""
22
+ try:
23
+ moduleSpecification: ModuleSpec | None = find_spec(identifierPackage)
24
+ if moduleSpecification and moduleSpecification.origin:
25
+ pathFilename = Path(moduleSpecification.origin)
26
+ return pathFilename.parent if pathFilename.is_file() else pathFilename
27
+ except ModuleNotFoundError:
28
+ pass
29
+ return Path.cwd()
30
+
31
+ @dataclasses.dataclass
32
+ class PackageSettings:
33
+ """Configuration container for Python package metadata and runtime settings.
34
+
35
+ This `class` provides a simple way to store and access basic information about a Python package, It will automatically resolve
36
+ package identifiers and installation paths if they are not passed to the `class` constructor. Python `dataclasses` are easy to
37
+ subtype and extend.
38
+
39
+ Parameters
40
+ ----------
41
+ identifierPackageFALLBACK : str = ''
42
+ Fallback package identifier used only during initialization when automatic discovery fails.
43
+ pathPackage : Path = Path()
44
+ Absolute path to the installed package directory. Automatically resolved from `identifierPackage` if not provided.
45
+ identifierPackage : str = ''
46
+ Canonical name of the package. Automatically extracted from `pyproject.toml`.
47
+ fileExtension : str = '.py'
48
+ Default file extension.
49
+
50
+ Examples
51
+ --------
52
+ Automatic package discovery from development environment:
53
+
54
+ ```python
55
+ settings = PackageSettings(identifierPackageFALLBACK='cobraPy')
56
+ # Automatically discovers package name from pyproject.toml
57
+ # Resolves installation path from package identifier
58
+ ```
59
+
60
+ Explicit configuration for specific deployment:
61
+
62
+ ```python
63
+ settings = PackageSettings(
64
+ identifierPackage='cobraPy',
65
+ pathPackage=Path('/opt/tenEx/packages/cobraPy'),
66
+ fileExtension='.pyx'
67
+ )
68
+ ```
69
+
70
+ """
71
+
72
+ identifierPackageFALLBACK: dataclasses.InitVar[str] = ''
73
+ """Fallback package identifier used during initialization only."""
74
+ pathPackage: Path = dataclasses.field(default_factory=Path, metadata={'evaluateWhen': 'installing'})
75
+ """Absolute path to the installed package."""
76
+ identifierPackage: str = dataclasses.field(default='', metadata={'evaluateWhen': 'packaging'})
77
+ """Name of this package."""
78
+ fileExtension: str = dataclasses.field(default='.py', metadata={'evaluateWhen': 'installing'})
79
+ """Default file extension for files."""
80
+
81
+ def __post_init__(self, identifierPackageFALLBACK: str) -> None:
82
+ """Initialize computed fields after dataclass initialization."""
83
+ if not self.identifierPackage and identifierPackageFALLBACK:
84
+ self.identifierPackage = getIdentifierPackagePACKAGING(identifierPackageFALLBACK)
85
+ if self.pathPackage == Path() and self.identifierPackage:
86
+ self.pathPackage = getPathPackageINSTALLING(self.identifierPackage)
87
+
88
+ def raiseIfNone(returnTarget: TypeSansNone | None, errorMessage: str | None = None) -> TypeSansNone:
89
+ """Raise a `ValueError` if the target value is `None`, otherwise return the value: tell the type checker that the return value is not `None`.
90
+
91
+ (AI generated docstring)
92
+
93
+ This is a defensive programming function that converts unexpected `None` values into explicit errors with context. It is useful for asserting that functions that might return `None` have actually returned a meaningful value.
94
+
95
+ Parameters
96
+ ----------
97
+ returnTarget : TypeSansNone | None
98
+ The value to check for `None`. If not `None`, this value is returned unchanged.
99
+ errorMessage : str | None = None
100
+ Custom error message to include in the `ValueError`. If `None`, a default message with debugging hints is used.
101
+
102
+ Returns
103
+ -------
104
+ returnTarget : TypeSansNone
105
+ The original `returnTarget` value, guaranteed to not be `None`.
106
+
107
+ Raises
108
+ ------
109
+ ValueError
110
+ If `returnTarget` is `None`.
111
+
112
+ Examples
113
+ --------
114
+ Ensure a function result is not `None`:
115
+
116
+ ```python
117
+ def findFirstMatch(listItems: list[str], pattern: str) -> str | None:
118
+ for item in listItems:
119
+ if pattern in item:
120
+ return item
121
+ return None
122
+
123
+ listFiles = ['document.txt', 'image.png', 'data.csv']
124
+ filename = raiseIfNone(findFirstMatch(listFiles, '.txt'))
125
+ # Returns 'document.txt'
126
+ ```
127
+
128
+ Handle dictionary lookups with custom error messages:
129
+
130
+ ```python
131
+ configurationMapping = {'host': 'localhost', 'port': 8080}
132
+ host = raiseIfNone(configurationMapping.get('host'),
133
+ "Configuration must include 'host' setting")
134
+ # Returns 'localhost'
135
+
136
+ # This would raise ValueError with custom message:
137
+ # database = raiseIfNone(configurationMapping.get('database'),
138
+ # "Configuration must include 'database' setting")
139
+ ```
140
+
141
+ Thanks
142
+ ------
143
+ sobolevn, https://github.com/sobolevn, for the seed of the function. https://github.com/python/typing/discussions/1997#discussioncomment-13108399
144
+
145
+ """
146
+ if returnTarget is None:
147
+ message = errorMessage or 'A function unexpectedly returned `None`. Hint: look at the traceback immediately before `raiseIfNone`.'
148
+ raise ValueError(message)
149
+ return returnTarget
@@ -46,7 +46,7 @@ def autoDecodingRLE(arrayTarget: NDArray[integer[Any]], *, assumeAddSpaces: bool
46
46
  """
47
47
  def sliceNDArrayToNestedLists(arraySlice: NDArray[integer[Any]]) -> Any:
48
48
  def getLengthOption(optionAsStr: str) -> int:
49
- # `assumeAddSpaces` characters: `,` 1; `]*` 2 # noqa: ERA001
49
+ """`assumeAddSpaces` characters: `,` 1; `]*` 2."""
50
50
  return assumeAddSpaces * (optionAsStr.count(',') + optionAsStr.count(']*') * 2) + len(optionAsStr)
51
51
 
52
52
  if arraySlice.ndim > 1:
@@ -249,7 +249,7 @@ def updateExtendPolishDictionaryLists(*dictionaryLists: Mapping[str, list[Any] |
249
249
  ImaStr = str(keyName)
250
250
  ImaList = list(keyValue)
251
251
  ePluribusUnum.setdefault(ImaStr, []).extend(ImaList)
252
- except TypeError: # noqa: PERF203
252
+ except TypeError:
253
253
  if killErroneousDataTypes:
254
254
  continue
255
255
  else:
@@ -1,14 +1,14 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hunterMakesPy
3
- Version: 0.1.1
4
- Summary: A modular Python toolkit for defensive programming, parameter validation, file system utilities, and flexible data structure manipulation. Provides helpers for error propagation, input validation, concurrency limits, safe directory creation, dynamic module loading, string extraction from nested structures, and dictionary merging.
3
+ Version: 0.1.2
4
+ Summary: Easy Python functions making making functional Python functions easier.
5
5
  Author-email: Hunter Hogan <HunterHogan@pm.me>
6
6
  License: CC-BY-NC-4.0
7
7
  Project-URL: Donate, https://www.patreon.com/integrated
8
8
  Project-URL: Homepage, https://github.com/hunterhogan/
9
9
  Project-URL: Issues, https://github.com/hunterhogan/
10
10
  Project-URL: Repository, https://github.com/hunterhogan/
11
- Keywords: defensive programming,parameter validation,input validation,error propagation,concurrency limit,integer parsing,file system utilities,directory creation,dynamic import,module loading,attribute loading,string extraction,nested data structures,dictionary merging,package settings,configuration,pytest,test utilities
11
+ Keywords: attribute loading,concurrency limit,configuration,defensive programming,dictionary merging,directory creation,dynamic import,error propagation,file system utilities,input validation,integer parsing,module loading,nested data structures,package settings,parameter validation,pytest,string extraction,test utilities
12
12
  Classifier: Development Status :: 4 - Beta
13
13
  Classifier: Environment :: Console
14
14
  Classifier: Framework :: Pytest
@@ -17,31 +17,30 @@ Classifier: Intended Audience :: End Users/Desktop
17
17
  Classifier: Intended Audience :: Other Audience
18
18
  Classifier: Natural Language :: English
19
19
  Classifier: Operating System :: OS Independent
20
- Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python
21
+ Classifier: Programming Language :: Python :: 3
21
22
  Classifier: Programming Language :: Python :: 3.11
22
23
  Classifier: Programming Language :: Python :: 3.12
23
24
  Classifier: Programming Language :: Python :: 3.13
24
- Classifier: Programming Language :: Python :: 3
25
25
  Classifier: Programming Language :: Python :: Implementation :: CPython
26
- Classifier: Programming Language :: Python
27
26
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
28
27
  Classifier: Topic :: Utilities
29
28
  Classifier: Typing :: Typed
30
- Requires-Python: >=3.10
29
+ Requires-Python: >=3.11
31
30
  Description-Content-Type: text/markdown
32
31
  License-File: LICENSE
33
32
  Requires-Dist: charset_normalizer
34
33
  Requires-Dist: more_itertools
35
34
  Requires-Dist: numpy
36
35
  Requires-Dist: python_minifier
37
- Requires-Dist: tomli
36
+ Provides-Extra: development
37
+ Requires-Dist: mypy; extra == "development"
38
+ Requires-Dist: pyupgrade; extra == "development"
39
+ Requires-Dist: setuptools-scm; extra == "development"
38
40
  Provides-Extra: testing
39
- Requires-Dist: mypy; extra == "testing"
40
41
  Requires-Dist: pytest; extra == "testing"
41
42
  Requires-Dist: pytest-cov; extra == "testing"
42
43
  Requires-Dist: pytest-xdist; extra == "testing"
43
- Requires-Dist: pyupgrade; extra == "testing"
44
- Requires-Dist: setuptools-scm; extra == "testing"
45
44
  Dynamic: license-file
46
45
 
47
46
  # hunterMakesPy
@@ -2,12 +2,13 @@ charset_normalizer
2
2
  more_itertools
3
3
  numpy
4
4
  python_minifier
5
- tomli
6
5
 
7
- [testing]
6
+ [development]
8
7
  mypy
8
+ pyupgrade
9
+ setuptools-scm
10
+
11
+ [testing]
9
12
  pytest
10
13
  pytest-cov
11
14
  pytest-xdist
12
- pyupgrade
13
- setuptools-scm
@@ -1,30 +1,30 @@
1
1
  [project]
2
2
  name = "hunterMakesPy"
3
- version = "0.1.1"
4
- description = "A modular Python toolkit for defensive programming, parameter validation, file system utilities, and flexible data structure manipulation. Provides helpers for error propagation, input validation, concurrency limits, safe directory creation, dynamic module loading, string extraction from nested structures, and dictionary merging."
3
+ version = "0.1.2"
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 = { 'text' = "CC-BY-NC-4.0" }
8
8
  authors = [{ name = "Hunter Hogan", email = "HunterHogan@pm.me" }]
9
9
  keywords = [
10
- "defensive programming",
11
- "parameter validation",
12
- "input validation",
13
- "error propagation",
10
+ "attribute loading",
14
11
  "concurrency limit",
15
- "integer parsing",
16
- "file system utilities",
12
+ "configuration",
13
+ "defensive programming",
14
+ "dictionary merging",
17
15
  "directory creation",
18
16
  "dynamic import",
17
+ "error propagation",
18
+ "file system utilities",
19
+ "input validation",
20
+ "integer parsing",
19
21
  "module loading",
20
- "attribute loading",
21
- "string extraction",
22
22
  "nested data structures",
23
- "dictionary merging",
24
23
  "package settings",
25
- "configuration",
24
+ "parameter validation",
26
25
  "pytest",
27
- "test utilities"
26
+ "string extraction",
27
+ "test utilities",
28
28
  ]
29
29
  classifiers = [
30
30
  "Development Status :: 4 - Beta",
@@ -35,13 +35,12 @@ classifiers = [
35
35
  "Intended Audience :: Other Audience",
36
36
  "Natural Language :: English",
37
37
  "Operating System :: OS Independent",
38
- "Programming Language :: Python :: 3.10",
38
+ "Programming Language :: Python",
39
+ "Programming Language :: Python :: 3",
39
40
  "Programming Language :: Python :: 3.11",
40
41
  "Programming Language :: Python :: 3.12",
41
42
  "Programming Language :: Python :: 3.13",
42
- "Programming Language :: Python :: 3",
43
43
  "Programming Language :: Python :: Implementation :: CPython",
44
- "Programming Language :: Python",
45
44
  "Topic :: Software Development :: Libraries :: Python Modules",
46
45
  "Topic :: Utilities",
47
46
  "Typing :: Typed",
@@ -52,15 +51,15 @@ dependencies = [
52
51
  "more_itertools",
53
52
  "numpy",
54
53
  "python_minifier",
55
- "tomli",
56
54
  ]
57
- optional-dependencies = { testing = [
55
+ optional-dependencies = { development = [
58
56
  "mypy",
57
+ "pyupgrade",
58
+ "setuptools-scm",
59
+ ], testing = [
59
60
  "pytest",
60
61
  "pytest-cov",
61
62
  "pytest-xdist",
62
- "pyupgrade",
63
- "setuptools-scm",
64
63
  ] }
65
64
 
66
65
  [build-system]
@@ -0,0 +1,216 @@
1
+ from hunterMakesPy import PackageSettings, raiseIfNone
2
+ from hunterMakesPy.coping import getIdentifierPackagePACKAGING, getPathPackageINSTALLING
3
+ from pathlib import Path
4
+ from tests.conftest import uniformTestFailureMessage
5
+ import pytest
6
+
7
+ @pytest.mark.parametrize(
8
+ "returnTarget, expected",
9
+ [
10
+ (13, 13),
11
+ (17, 17),
12
+ ("fibonacci", "fibonacci"),
13
+ ("prime", "prime"),
14
+ ([], []),
15
+ ({}, {}),
16
+ (False, False),
17
+ (0, 0),
18
+ ]
19
+ )
20
+ def testRaiseIfNoneReturnsNonNoneValues(returnTarget: object, expected: object) -> None:
21
+ actual = raiseIfNone(returnTarget)
22
+ assert actual == expected, uniformTestFailureMessage(expected, actual, "testRaiseIfNoneReturnsNonNoneValues", returnTarget)
23
+ assert actual is returnTarget, uniformTestFailureMessage(returnTarget, actual, "testRaiseIfNoneReturnsNonNoneValues identity check", returnTarget)
24
+
25
+
26
+ def testRaiseIfNoneRaisesValueErrorWhenGivenNone() -> None:
27
+ with pytest.raises(ValueError, match="A function unexpectedly returned `None`. Hint: look at the traceback immediately before `raiseIfNone`."):
28
+ raiseIfNone(None)
29
+
30
+
31
+ @pytest.mark.parametrize(
32
+ "customMessage",
33
+ [
34
+ "Configuration must include 'host' setting",
35
+ "Database connection failed",
36
+ "User input is required",
37
+ "Network request returned empty response",
38
+ ]
39
+ )
40
+ def testRaiseIfNoneRaisesValueErrorWithCustomMessage(customMessage: str) -> None:
41
+ with pytest.raises(ValueError, match=customMessage):
42
+ raiseIfNone(None, customMessage)
43
+
44
+
45
+ def testRaiseIfNoneWithEmptyStringMessage() -> None:
46
+ with pytest.raises(ValueError, match="A function unexpectedly returned `None`. Hint: look at the traceback immediately before `raiseIfNone`."):
47
+ raiseIfNone(None, "")
48
+
49
+
50
+ def testRaiseIfNonePreservesTypeAnnotations() -> None:
51
+ integerValue: int = raiseIfNone(23)
52
+ assert isinstance(integerValue, int), uniformTestFailureMessage(int, type(integerValue), "testRaiseIfNonePreservesTypeAnnotations", integerValue)
53
+
54
+ stringValue: str = raiseIfNone("cardinal")
55
+ assert isinstance(stringValue, str), uniformTestFailureMessage(str, type(stringValue), "testRaiseIfNonePreservesTypeAnnotations", stringValue)
56
+
57
+ listValue: list[int] = raiseIfNone([29, 31])
58
+ assert isinstance(listValue, list), uniformTestFailureMessage(list, type(listValue), "testRaiseIfNonePreservesTypeAnnotations", listValue)
59
+
60
+ # Tests for PackageSettings dataclass
61
+ @pytest.mark.parametrize(
62
+ "identifierPackageFALLBACK, expectedIdentifierPackage",
63
+ [
64
+ ("astToolFactory", "hunterMakesPy"), # Should read from pyproject.toml
65
+ ("nonExistentPackage", "hunterMakesPy"), # Should read from pyproject.toml
66
+ ("customPackage", "hunterMakesPy"), # Should read from pyproject.toml
67
+ ]
68
+ )
69
+ def testPackageSettingsWithFallbackUsesProjectToml(identifierPackageFALLBACK: str, expectedIdentifierPackage: str) -> None:
70
+ """Test that PackageSettings reads package name from pyproject.toml when using fallback."""
71
+ packageSettings = PackageSettings(identifierPackageFALLBACK)
72
+ assert packageSettings.identifierPackage == expectedIdentifierPackage, uniformTestFailureMessage(
73
+ expectedIdentifierPackage, packageSettings.identifierPackage, "PackageSettings fallback", identifierPackageFALLBACK
74
+ )
75
+
76
+ @pytest.mark.parametrize(
77
+ "explicitIdentifierPackage, expectedIdentifierPackage",
78
+ [
79
+ ("customPackageName", "customPackageName"),
80
+ ("fibonacci", "fibonacci"),
81
+ ("prime", "prime"),
82
+ ("astToolFactory", "astToolFactory"),
83
+ ]
84
+ )
85
+ def testPackageSettingsWithExplicitIdentifierPackage(explicitIdentifierPackage: str, expectedIdentifierPackage: str) -> None:
86
+ """Test that PackageSettings respects explicitly provided identifierPackage."""
87
+ packageSettings = PackageSettings(identifierPackage=explicitIdentifierPackage)
88
+ assert packageSettings.identifierPackage == expectedIdentifierPackage, uniformTestFailureMessage(
89
+ expectedIdentifierPackage, packageSettings.identifierPackage, "PackageSettings explicit identifierPackage", explicitIdentifierPackage
90
+ )
91
+
92
+ @pytest.mark.parametrize(
93
+ "explicitPathPackage, expectedPathPackage",
94
+ [
95
+ (Path("C:/fibonacci/path"), Path("C:/fibonacci/path")),
96
+ (Path("C:/prime/directory"), Path("C:/prime/directory")),
97
+ (Path("/usr/local/lib/package"), Path("/usr/local/lib/package")),
98
+ (Path("relative/path"), Path("relative/path")),
99
+ ]
100
+ )
101
+ def testPackageSettingsWithExplicitPathPackage(explicitPathPackage: Path, expectedPathPackage: Path) -> None:
102
+ """Test that PackageSettings respects explicitly provided pathPackage."""
103
+ packageSettings = PackageSettings(pathPackage=explicitPathPackage)
104
+ assert packageSettings.pathPackage == expectedPathPackage, uniformTestFailureMessage(
105
+ expectedPathPackage, packageSettings.pathPackage, "PackageSettings explicit pathPackage", explicitPathPackage
106
+ )
107
+
108
+ @pytest.mark.parametrize(
109
+ "fileExtension, expectedFileExtension",
110
+ [
111
+ (".fibonacci", ".fibonacci"),
112
+ (".prime", ".prime"),
113
+ (".txt", ".txt"),
114
+ (".md", ".md"),
115
+ (".json", ".json"),
116
+ ]
117
+ )
118
+ def testPackageSettingsWithCustomFileExtension(fileExtension: str, expectedFileExtension: str) -> None:
119
+ """Test that PackageSettings respects custom file extensions."""
120
+ packageSettings = PackageSettings(fileExtension=fileExtension)
121
+ assert packageSettings.fileExtension == expectedFileExtension, uniformTestFailureMessage(
122
+ expectedFileExtension, packageSettings.fileExtension, "PackageSettings custom fileExtension", fileExtension
123
+ )
124
+
125
+ def testPackageSettingsDefaultValues() -> None:
126
+ """Test that PackageSettings has correct default values when no arguments provided."""
127
+ packageSettings = PackageSettings()
128
+
129
+ # Should have default file extension
130
+ assert packageSettings.fileExtension == '.py', uniformTestFailureMessage(
131
+ '.py', packageSettings.fileExtension, "PackageSettings default fileExtension"
132
+ )
133
+
134
+ # identifierPackage should be empty when no fallback provided
135
+ assert packageSettings.identifierPackage == '', uniformTestFailureMessage(
136
+ '', packageSettings.identifierPackage, "PackageSettings default identifierPackage"
137
+ )
138
+
139
+ # pathPackage should remain as Path() when identifierPackage is empty
140
+ expectedPath = Path()
141
+ assert packageSettings.pathPackage == expectedPath, uniformTestFailureMessage(
142
+ expectedPath, packageSettings.pathPackage, "PackageSettings default pathPackage"
143
+ )
144
+
145
+ @pytest.mark.parametrize(
146
+ "identifierPackageFALLBACK, identifierPackage, pathPackage, fileExtension",
147
+ [
148
+ ("fallback", "custom", Path("C:/custom/path"), ".txt"),
149
+ ("fibonacci", "prime", Path("C:/fibonacci/lib"), ".md"),
150
+ ("defaultFallback", "overridePackage", Path("/usr/local/override"), ".json"),
151
+ ]
152
+ )
153
+ def testPackageSettingsAllParametersCombined(
154
+ identifierPackageFALLBACK: str,
155
+ identifierPackage: str,
156
+ pathPackage: Path,
157
+ fileExtension: str
158
+ ) -> None:
159
+ """Test PackageSettings with all parameters provided."""
160
+ packageSettings = PackageSettings(
161
+ identifierPackageFALLBACK,
162
+ identifierPackage=identifierPackage,
163
+ pathPackage=pathPackage,
164
+ fileExtension=fileExtension
165
+ )
166
+
167
+ assert packageSettings.identifierPackage == identifierPackage, uniformTestFailureMessage(
168
+ identifierPackage, packageSettings.identifierPackage, "PackageSettings combined identifierPackage"
169
+ )
170
+ assert packageSettings.pathPackage == pathPackage, uniformTestFailureMessage(
171
+ pathPackage, packageSettings.pathPackage, "PackageSettings combined pathPackage"
172
+ )
173
+ assert packageSettings.fileExtension == fileExtension, uniformTestFailureMessage(
174
+ fileExtension, packageSettings.fileExtension, "PackageSettings combined fileExtension"
175
+ )
176
+
177
+ def testPackageSettingsFallbackIgnoredWhenExplicitIdentifierProvided() -> None:
178
+ """Test that fallback is ignored when explicit identifierPackage is provided."""
179
+ packageSettings = PackageSettings("shouldBeIgnored", identifierPackage="explicit")
180
+ assert packageSettings.identifierPackage == "explicit", uniformTestFailureMessage(
181
+ "explicit", packageSettings.identifierPackage, "PackageSettings fallback ignored"
182
+ )
183
+
184
+ # Tests for helper functions
185
+ @pytest.mark.parametrize(
186
+ "identifierPackageFALLBACK, expectedResult",
187
+ [
188
+ ("fibonacci", "hunterMakesPy"), # Should read from pyproject.toml
189
+ ("prime", "hunterMakesPy"), # Should read from pyproject.toml
190
+ ("nonExistentPackage", "hunterMakesPy"), # Should read from pyproject.toml
191
+ ]
192
+ )
193
+ def testGetIdentifierPackagePACKAGING(identifierPackageFALLBACK: str, expectedResult: str) -> None:
194
+ """Test that getIdentifierPackagePACKAGING reads from pyproject.toml correctly."""
195
+ actual = getIdentifierPackagePACKAGING(identifierPackageFALLBACK)
196
+ assert actual == expectedResult, uniformTestFailureMessage(
197
+ expectedResult, actual, "getIdentifierPackagePACKAGING", identifierPackageFALLBACK
198
+ )
199
+
200
+ @pytest.mark.parametrize(
201
+ "identifierPackage",
202
+ [
203
+ "hunterMakesPy", # This package exists
204
+ "fibonacci", # Non-existent package should fallback to cwd
205
+ "prime", # Non-existent package should fallback to cwd
206
+ ]
207
+ )
208
+ def testGetPathPackageINSTALLING(identifierPackage: str) -> None:
209
+ """Test that getPathPackageINSTALLING returns valid Path objects."""
210
+ actual = getPathPackageINSTALLING(identifierPackage)
211
+ assert isinstance(actual, Path), uniformTestFailureMessage(
212
+ Path, type(actual), "getPathPackageINSTALLING type", identifierPackage
213
+ )
214
+ assert actual.exists() or actual == Path.cwd(), uniformTestFailureMessage(
215
+ "existing path or cwd", actual, "getPathPackageINSTALLING existence", identifierPackage
216
+ )
@@ -1,40 +0,0 @@
1
- """Primary: settings for this package.
2
-
3
- Secondary: settings for manufacturing.
4
- Tertiary: hardcoded values until I implement a dynamic solution.
5
- """
6
- from importlib.util import find_spec
7
- from pathlib import Path
8
- from tomli import loads as tomli_loads
9
- from typing import TYPE_CHECKING
10
- import dataclasses
11
-
12
- if TYPE_CHECKING:
13
- from importlib.machinery import ModuleSpec
14
-
15
- try:
16
- identifierPackagePACKAGING: str = tomli_loads(Path("pyproject.toml").read_text(encoding="utf-8"))["project"]["name"]
17
- except Exception: # noqa: BLE001
18
- identifierPackagePACKAGING = "hunterMakesPy"
19
-
20
- def getPathPackageINSTALLING() -> Path:
21
- """Return the root directory of the installed package."""
22
- try:
23
- moduleSpecification: ModuleSpec | None = find_spec(identifierPackagePACKAGING)
24
- if moduleSpecification and moduleSpecification.origin:
25
- pathFilename = Path(moduleSpecification.origin)
26
- return pathFilename.parent if pathFilename.is_file() else pathFilename
27
- except ModuleNotFoundError:
28
- pass
29
- return Path.cwd()
30
-
31
- @dataclasses.dataclass
32
- class PackageSettings:
33
- fileExtension: str = dataclasses.field(default='.py', metadata={'evaluateWhen': 'installing'})
34
- """Default file extension for generated code files."""
35
- identifierPackage: str = dataclasses.field(default = identifierPackagePACKAGING, metadata={'evaluateWhen': 'packaging'})
36
- """Name of this package, used for import paths and configuration."""
37
- pathPackage: Path = dataclasses.field(default_factory=getPathPackageINSTALLING, metadata={'evaluateWhen': 'installing'})
38
- """Absolute path to the installed package directory."""
39
-
40
- settingsPackage = PackageSettings()
@@ -1,73 +0,0 @@
1
- """Utility functions for handling `None` values and coping with common programming patterns.
2
-
3
- (AI generated docstring)
4
-
5
- This module provides helper functions for defensive programming and error handling, particularly for dealing with `None` values that should not occur in correct program flow.
6
-
7
- """
8
- from typing import TypeVar
9
-
10
- TypeSansNone = TypeVar('TypeSansNone')
11
-
12
- def raiseIfNone(returnTarget: TypeSansNone | None, errorMessage: str | None = None) -> TypeSansNone:
13
- """Raise a `ValueError` if the target value is `None`, otherwise return the value: tell the type checker that the return value is not `None`.
14
-
15
- (AI generated docstring)
16
-
17
- This is a defensive programming function that converts unexpected `None` values into explicit errors with context. It is useful for asserting that functions that might return `None` have actually returned a meaningful value.
18
-
19
- Parameters
20
- ----------
21
- returnTarget : TypeSansNone | None
22
- The value to check for `None`. If not `None`, this value is returned unchanged.
23
- errorMessage : str | None = None
24
- Custom error message to include in the `ValueError`. If `None`, a default message with debugging hints is used.
25
-
26
- Returns
27
- -------
28
- returnTarget : TypeSansNone
29
- The original `returnTarget` value, guaranteed to not be `None`.
30
-
31
- Raises
32
- ------
33
- ValueError
34
- If `returnTarget` is `None`.
35
-
36
- Examples
37
- --------
38
- Ensure a function result is not `None`:
39
-
40
- ```python
41
- def findFirstMatch(listItems: list[str], pattern: str) -> str | None:
42
- for item in listItems:
43
- if pattern in item:
44
- return item
45
- return None
46
-
47
- listFiles = ['document.txt', 'image.png', 'data.csv']
48
- filename = raiseIfNone(findFirstMatch(listFiles, '.txt'))
49
- # Returns 'document.txt'
50
- ```
51
-
52
- Handle dictionary lookups with custom error messages:
53
-
54
- ```python
55
- configurationMapping = {'host': 'localhost', 'port': 8080}
56
- host = raiseIfNone(configurationMapping.get('host'),
57
- "Configuration must include 'host' setting")
58
- # Returns 'localhost'
59
-
60
- # This would raise ValueError with custom message:
61
- # database = raiseIfNone(configurationMapping.get('database'),
62
- # "Configuration must include 'database' setting")
63
- ```
64
-
65
- Thanks
66
- ------
67
- sobolevn, https://github.com/sobolevn, for the seed of the function. https://github.com/python/typing/discussions/1997#discussioncomment-13108399
68
-
69
- """
70
- if returnTarget is None:
71
- message = errorMessage or 'A function unexpectedly returned `None`. Hint: look at the traceback immediately before `raiseIfNone`.'
72
- raise ValueError(message)
73
- return returnTarget
@@ -1,56 +0,0 @@
1
- from hunterMakesPy import raiseIfNone
2
- from tests.conftest import uniformTestFailureMessage
3
- import pytest
4
-
5
- @pytest.mark.parametrize(
6
- "returnTarget, expected",
7
- [
8
- (13, 13),
9
- (17, 17),
10
- ("fibonacci", "fibonacci"),
11
- ("prime", "prime"),
12
- ([], []),
13
- ({}, {}),
14
- (False, False),
15
- (0, 0),
16
- ]
17
- )
18
- def testRaiseIfNoneReturnsNonNoneValues(returnTarget: object, expected: object) -> None:
19
- actual = raiseIfNone(returnTarget)
20
- assert actual == expected, uniformTestFailureMessage(expected, actual, "testRaiseIfNoneReturnsNonNoneValues", returnTarget)
21
- assert actual is returnTarget, uniformTestFailureMessage(returnTarget, actual, "testRaiseIfNoneReturnsNonNoneValues identity check", returnTarget)
22
-
23
-
24
- def testRaiseIfNoneRaisesValueErrorWhenGivenNone() -> None:
25
- with pytest.raises(ValueError, match="A function unexpectedly returned `None`. Hint: look at the traceback immediately before `raiseIfNone`."):
26
- raiseIfNone(None)
27
-
28
-
29
- @pytest.mark.parametrize(
30
- "customMessage",
31
- [
32
- "Configuration must include 'host' setting",
33
- "Database connection failed",
34
- "User input is required",
35
- "Network request returned empty response",
36
- ]
37
- )
38
- def testRaiseIfNoneRaisesValueErrorWithCustomMessage(customMessage: str) -> None:
39
- with pytest.raises(ValueError, match=customMessage):
40
- raiseIfNone(None, customMessage)
41
-
42
-
43
- def testRaiseIfNoneWithEmptyStringMessage() -> None:
44
- with pytest.raises(ValueError, match="A function unexpectedly returned `None`. Hint: look at the traceback immediately before `raiseIfNone`."):
45
- raiseIfNone(None, "")
46
-
47
-
48
- def testRaiseIfNonePreservesTypeAnnotations() -> None:
49
- integerValue: int = raiseIfNone(23)
50
- assert isinstance(integerValue, int), uniformTestFailureMessage(int, type(integerValue), "testRaiseIfNonePreservesTypeAnnotations", integerValue)
51
-
52
- stringValue: str = raiseIfNone("cardinal")
53
- assert isinstance(stringValue, str), uniformTestFailureMessage(str, type(stringValue), "testRaiseIfNonePreservesTypeAnnotations", stringValue)
54
-
55
- listValue: list[int] = raiseIfNone([29, 31])
56
- assert isinstance(listValue, list), uniformTestFailureMessage(list, type(listValue), "testRaiseIfNonePreservesTypeAnnotations", listValue)
File without changes
File without changes
File without changes