hunterMakesPy 0.3.3__tar.gz → 0.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. {huntermakespy-0.3.3 → huntermakespy-0.4.0}/PKG-INFO +2 -3
  2. huntermakespy-0.4.0/hunterMakesPy/Z0Z_CallableFunction.py +70 -0
  3. {huntermakespy-0.3.3 → huntermakespy-0.4.0}/hunterMakesPy/__init__.py +8 -1
  4. {huntermakespy-0.3.3 → huntermakespy-0.4.0}/hunterMakesPy/coping.py +2 -4
  5. {huntermakespy-0.3.3 → huntermakespy-0.4.0}/hunterMakesPy/pytestForYourUse.py +1 -2
  6. huntermakespy-0.4.0/hunterMakesPy/tests/conftest.py +143 -0
  7. huntermakespy-0.4.0/hunterMakesPy/tests/test_coping.py +321 -0
  8. {huntermakespy-0.3.3 → huntermakespy-0.4.0}/hunterMakesPy/tests/test_dataStructures.py +250 -69
  9. {huntermakespy-0.3.3 → huntermakespy-0.4.0}/hunterMakesPy/tests/test_filesystemToolkit.py +245 -131
  10. {huntermakespy-0.3.3 → huntermakespy-0.4.0}/hunterMakesPy/tests/test_parseParameters.py +63 -7
  11. huntermakespy-0.4.0/hunterMakesPy/tests/test_theTypes.py +359 -0
  12. {huntermakespy-0.3.3 → huntermakespy-0.4.0}/hunterMakesPy/theTypes.py +5 -6
  13. {huntermakespy-0.3.3 → huntermakespy-0.4.0}/hunterMakesPy.egg-info/PKG-INFO +2 -3
  14. {huntermakespy-0.3.3 → huntermakespy-0.4.0}/hunterMakesPy.egg-info/SOURCES.txt +3 -1
  15. {huntermakespy-0.3.3 → huntermakespy-0.4.0}/pyproject.toml +2 -3
  16. huntermakespy-0.3.3/hunterMakesPy/tests/conftest.py +0 -69
  17. huntermakespy-0.3.3/hunterMakesPy/tests/test_coping.py +0 -216
  18. {huntermakespy-0.3.3 → huntermakespy-0.4.0}/LICENSE +0 -0
  19. {huntermakespy-0.3.3 → huntermakespy-0.4.0}/README.md +0 -0
  20. {huntermakespy-0.3.3 → huntermakespy-0.4.0}/hunterMakesPy/_theSSOT.py +0 -0
  21. {huntermakespy-0.3.3 → huntermakespy-0.4.0}/hunterMakesPy/dataStructures.py +0 -0
  22. {huntermakespy-0.3.3 → huntermakespy-0.4.0}/hunterMakesPy/filesystemToolkit.py +0 -0
  23. {huntermakespy-0.3.3 → huntermakespy-0.4.0}/hunterMakesPy/parseParameters.py +0 -0
  24. {huntermakespy-0.3.3 → huntermakespy-0.4.0}/hunterMakesPy/py.typed +0 -0
  25. {huntermakespy-0.3.3 → huntermakespy-0.4.0}/hunterMakesPy/tests/__init__.py +0 -0
  26. {huntermakespy-0.3.3 → huntermakespy-0.4.0}/hunterMakesPy.egg-info/dependency_links.txt +0 -0
  27. {huntermakespy-0.3.3 → huntermakespy-0.4.0}/hunterMakesPy.egg-info/requires.txt +0 -0
  28. {huntermakespy-0.3.3 → huntermakespy-0.4.0}/hunterMakesPy.egg-info/top_level.txt +0 -0
  29. {huntermakespy-0.3.3 → huntermakespy-0.4.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hunterMakesPy
3
- Version: 0.3.3
3
+ Version: 0.4.0
4
4
  Summary: Easy Python functions making making functional Python functions easier.
5
5
  Author-email: Hunter Hogan <HunterHogan@pm.me>
6
6
  License: CC-BY-NC-4.0
@@ -19,7 +19,6 @@ Classifier: Natural Language :: English
19
19
  Classifier: Operating System :: OS Independent
20
20
  Classifier: Programming Language :: Python
21
21
  Classifier: Programming Language :: Python :: 3
22
- Classifier: Programming Language :: Python :: 3.11
23
22
  Classifier: Programming Language :: Python :: 3.12
24
23
  Classifier: Programming Language :: Python :: 3.13
25
24
  Classifier: Programming Language :: Python :: 3.14
@@ -27,7 +26,7 @@ Classifier: Programming Language :: Python :: Implementation :: CPython
27
26
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
28
27
  Classifier: Topic :: Utilities
29
28
  Classifier: Typing :: Typed
30
- Requires-Python: >=3.11
29
+ Requires-Python: >=3.12
31
30
  Description-Content-Type: text/markdown
32
31
  License-File: LICENSE
33
32
  Requires-Dist: autoflake
@@ -0,0 +1,70 @@
1
+ """Protocols for callable functions with full type safety."""
2
+ from collections.abc import Callable
3
+ from types import CellType, CodeType, MethodType
4
+ from typing import Any, overload, ParamSpec, Protocol, runtime_checkable, Self, TypeVar, TypeVarTuple
5
+ import sys
6
+
7
+ #======== Stolen, uh, I mean copied from typeshed:stdlib\_typeshed\__init__.pyi ========
8
+ type AnnotationForm = Any
9
+
10
+ if sys.version_info >= (3, 14):
11
+ from annotationlib import Format
12
+
13
+ # NOTE These return annotations, which can be arbitrary objects
14
+ type AnnotateFunc = Callable[[Format], dict[str, AnnotationForm]]
15
+ type EvaluateFunc = Callable[[Format], AnnotationForm]
16
+ #======== End stolen, uh, I mean copied from typeshed:stdlib\_typeshed\__init__.pyi ========
17
+
18
+ @runtime_checkable
19
+ class CallableFunction[**P, R](Protocol):
20
+ """A Protocol representing callable functions with descriptor support.
21
+
22
+ Mimics types.FunctionType while being a drop-in replacement for `collections.abc.Callable`. Includes all standard function
23
+ attributes and the descriptor protocol for proper method binding.
24
+
25
+ Note: @runtime_checkable only validates attribute presence, not signatures.
26
+ """
27
+
28
+ # NOTE: The eehhhh, IDK... section
29
+ __doc__: str | None
30
+ __wrapped__: Any # For functools.wraps support
31
+ # NOTE: End eehhhh, IDK... section
32
+
33
+ @property
34
+ def __closure__(self) -> tuple[CellType, ...] | None:
35
+ """Tuple of cells that contain bindings for the function's free variables."""
36
+ ...
37
+ __code__: CodeType
38
+ __defaults__: tuple[Any, ...] | None
39
+ __dict__: dict[str, Any]
40
+ @property
41
+ def __globals__(self) -> dict[str, Any]:
42
+ """The global namespace in which the function was defined."""
43
+ ...
44
+ __name__: str
45
+ __qualname__: str
46
+ __annotations__: dict[str, AnnotationForm]
47
+ if sys.version_info >= (3, 14):
48
+ __annotate__: AnnotateFunc | None
49
+ __kwdefaults__: dict[str, Any] | None
50
+ @property
51
+ def __builtins__(self) -> dict[str, Any]:
52
+ """The built-in namespace in which the function was defined."""
53
+ ...
54
+ __type_params__: tuple[TypeVar | ParamSpec | TypeVarTuple, ...]
55
+ __module__: str
56
+
57
+ def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R:
58
+ """Execute the callable with the given arguments."""
59
+ ...
60
+
61
+ @overload
62
+ def __get__(self, instance: None, owner: type, /) -> Self: ...
63
+ @overload
64
+ def __get__(self, instance: object, owner: type | None = None, /) -> MethodType: ...
65
+ def __get__(self, instance: object | None, owner: type | None = None) -> Self | MethodType:
66
+ """Descriptor protocol for method binding.
67
+
68
+ Returns self when accessed on the class, or a bound MethodType when accessed on an instance.
69
+ """
70
+ ...
@@ -8,15 +8,22 @@ This package provides:
8
8
 
9
9
  """
10
10
 
11
- from hunterMakesPy.theTypes import identifierDotAttribute as identifierDotAttribute, Ordinals as Ordinals
11
+ # isort: split
12
+ from hunterMakesPy.theTypes import (
13
+ CallableFunction as CallableFunction, identifierDotAttribute as identifierDotAttribute, Ordinals as Ordinals)
12
14
 
15
+ # isort: split
13
16
  from hunterMakesPy.coping import PackageSettings as PackageSettings, raiseIfNone as raiseIfNone
14
17
 
18
+ # isort: split
15
19
  from hunterMakesPy.parseParameters import defineConcurrencyLimit, intInnit, oopsieKwargsie
16
20
 
21
+ # isort: split
17
22
  from hunterMakesPy.filesystemToolkit import (
18
23
  importLogicalPath2Identifier, importPathFilename2Identifier, makeDirsSafely, writePython, writeStringToHere)
19
24
 
25
+ # isort: split
20
26
  from hunterMakesPy.dataStructures import autoDecodingRLE, stringItUp, updateExtendPolishDictionaryLists
21
27
 
28
+ # isort: split
22
29
  from hunterMakesPy._theSSOT import settingsPackage # pyright: ignore[reportUnusedImport]
@@ -2,14 +2,12 @@
2
2
  from importlib.util import find_spec
3
3
  from pathlib import Path
4
4
  from tomllib import loads as tomllib_loads
5
- from typing import TYPE_CHECKING, TypeVar
5
+ from typing import TYPE_CHECKING
6
6
  import dataclasses
7
7
 
8
8
  if TYPE_CHECKING:
9
9
  from importlib.machinery import ModuleSpec
10
10
 
11
- TypeSansNone = TypeVar('TypeSansNone')
12
-
13
11
  def getIdentifierPackagePACKAGING(identifierPackageFALLBACK: str) -> str:
14
12
  """Get package name from pyproject.toml or fallback to provided value."""
15
13
  try:
@@ -85,7 +83,7 @@ class PackageSettings:
85
83
  if self.pathPackage == Path() and self.identifierPackage:
86
84
  self.pathPackage = getPathPackageINSTALLING(self.identifierPackage)
87
85
 
88
- def raiseIfNone(expression: TypeSansNone | None, errorMessage: str | None = None) -> TypeSansNone:
86
+ def raiseIfNone[TypeSansNone](expression: TypeSansNone | None, errorMessage: str | None = None) -> TypeSansNone:
89
87
  """Convert the `expression` return annotation from '`cerPytainty | None`' to '`cerPytainty`' because `expression` cannot be `None`; `raise` an `Exception` if you're wrong.
90
88
 
91
89
  The Python interpreter evaluates `expression` to a value: think of a function call or an attribute access. You can use
@@ -6,5 +6,4 @@ Note: These test functions are now in `hunterMakesPy.tests` with all other tests
6
6
  """
7
7
 
8
8
  from hunterMakesPy.tests.test_parseParameters import (
9
- PytestFor_defineConcurrencyLimit as PytestFor_defineConcurrencyLimit, PytestFor_intInnit as PytestFor_intInnit,
10
- PytestFor_oopsieKwargsie as PytestFor_oopsieKwargsie)
9
+ PytestFor_defineConcurrencyLimit, PytestFor_intInnit, PytestFor_oopsieKwargsie)
@@ -0,0 +1,143 @@
1
+ """Configuration and fixtures for pytest.
2
+
3
+ (AI generated docstring)
4
+
5
+ This module provides shared fixtures and utility functions for the test suite,
6
+ including data paths, source code samples, and standardized assertion helpers.
7
+
8
+ """
9
+ # pyright: standard
10
+ from collections.abc import Callable
11
+ from typing import Any
12
+ import io
13
+ import pathlib
14
+ import pytest
15
+
16
+ # SSOT for test data paths and filenames
17
+ pathDataSamples: pathlib.Path = pathlib.Path("hunterMakesPy/tests/dataSamples")
18
+
19
+ # Fixture to provide a temporary directory for filesystem tests
20
+ @pytest.fixture
21
+ def pathTmpTesting(tmp_path: pathlib.Path) -> pathlib.Path:
22
+ """Provide a temporary directory for filesystem tests.
23
+
24
+ (AI generated docstring)
25
+
26
+ Parameters
27
+ ----------
28
+ tmp_path : pathlib.Path
29
+ The pytest built-in temporary path fixture.
30
+
31
+ Returns
32
+ -------
33
+ pathTmpTesting : pathlib.Path
34
+ The path to the temporary directory.
35
+
36
+ """
37
+ return tmp_path
38
+
39
+ # Fixture for predictable Python source code samples
40
+ @pytest.fixture
41
+ def dictionaryPythonSourceSamples() -> dict[str, str]:
42
+ """Provide predictable Python source code samples for testing."""
43
+ return {
44
+ 'functionFibonacci': "def fibonacciNumber():\n return 13\n",
45
+ 'functionPrime': "def primeNumber():\n return 17\n",
46
+ 'variablePrime': "prime = 19\n",
47
+ 'variableFibonacci': "fibonacci = 21\n",
48
+ 'classCardinal': "class CardinalDirection:\n north = 'N'\n south = 'S'\n",
49
+ }
50
+
51
+ # Fixture for IO stream objects
52
+ @pytest.fixture
53
+ def streamMemoryString() -> io.StringIO:
54
+ """Provide a StringIO object for testing stream operations."""
55
+ return io.StringIO()
56
+
57
+ # Fixture for predictable directory names using cardinal directions
58
+ @pytest.fixture
59
+ def listDirectoryNamesCardinal() -> list[str]:
60
+ """Provide predictable directory names using cardinal directions."""
61
+ return ['north', 'south', 'east', 'west']
62
+
63
+ # Fixture for predictable file content using Fibonacci numbers
64
+ @pytest.fixture
65
+ def listFileContentsFibonacci() -> list[str]:
66
+ """Provide predictable file contents using Fibonacci sequence."""
67
+ return ['fibonacci8', 'fibonacci13', 'fibonacci21', 'fibonacci34']
68
+
69
+ def uniformTestFailureMessage(expected: Any, actual: Any, functionName: str, *arguments: Any, **keywordArguments: Any) -> str:
70
+ """Format assertion message for any test comparison.
71
+
72
+ Parameters
73
+ ----------
74
+ expected : Any
75
+ The expected value or outcome.
76
+ actual : Any
77
+ The actual value or outcome received.
78
+ functionName : str
79
+ The name of the function or test case being executed.
80
+ *arguments : Any
81
+ Positional arguments passed to the function having its return value checked.
82
+ **keywordArguments : Any
83
+ Keyword arguments passed to the function having its return value checked.
84
+
85
+ Returns
86
+ -------
87
+ message : str
88
+ A formatted failure message detailing the expectation vs reality.
89
+
90
+ """
91
+ listArgumentComponents: list[str] = [str(parameter) for parameter in arguments]
92
+ listKeywordComponents: list[str] = [f"{key}={value}" for key, value in keywordArguments.items()]
93
+ joinedArguments: str = ', '.join(listArgumentComponents + listKeywordComponents)
94
+
95
+ return (f"\nTesting: `{functionName}({joinedArguments})`\n"
96
+ f"Expected: {expected}\n"
97
+ f"Got: {actual}")
98
+
99
+ def standardizedEqualTo(expected: Any, functionTarget: Callable[..., Any], *arguments: Any, **keywordArguments: Any) -> None:
100
+ """Template for most tests to compare actual outcome with expected outcome.
101
+
102
+ Includes handling for expected errors/exceptions.
103
+
104
+ Parameters
105
+ ----------
106
+ expected : Any
107
+ The expected return value, or an Exception type if an error is expected.
108
+ functionTarget : Callable[..., Any]
109
+ The function to call and test.
110
+ *arguments : Any
111
+ Positional arguments to pass to `functionTarget`.
112
+ **keywordArguments : Any
113
+ Keyword arguments to pass to `functionTarget`.
114
+
115
+ """
116
+ if type(expected) == type[Exception]: # noqa: E721
117
+ messageExpected: str = expected.__name__
118
+ else:
119
+ messageExpected = expected
120
+
121
+ try:
122
+ messageActual = actual = functionTarget(*arguments, **keywordArguments)
123
+ except Exception as actualError:
124
+ messageActual = type(actualError).__name__
125
+ actual = type(actualError)
126
+
127
+ functionName: str = getattr(functionTarget, "__name__", functionTarget.__class__.__name__)
128
+ assert actual == expected, uniformTestFailureMessage(messageExpected, messageActual, functionName, *arguments, **keywordArguments)
129
+
130
+ # Why I wish I could figure out how to implement standardized* test functions.
131
+ # ruff: noqa: ERA001
132
+ # standardizedEqualTo(expected, updateExtendPolishDictionaryLists, *value_dictionaryLists, **keywordArguments)
133
+ # NOTE one line of code with `standardizedEqualTo` (above) replaced the following ten lines of code. Use `standardizedEqualTo`.
134
+ # if isinstance(expected, type) and issubclass(expected, Exception):
135
+ # with pytest.raises(expected):
136
+ # updateExtendPolishDictionaryLists(*value_dictionaryLists, **keywordArguments)
137
+ # else:
138
+ # result = updateExtendPolishDictionaryLists(*value_dictionaryLists, **keywordArguments)
139
+ # if description == "Set values": # Special handling for unordered sets
140
+ # for key in result:
141
+ # assert sorted(result[key]) == sorted(expected[key])
142
+ # else:
143
+ # assert result == expected
@@ -0,0 +1,321 @@
1
+ """Tests for the coping mechanism module.
2
+
3
+ (AI generated docstring)
4
+
5
+ This module validates the behavior of package setting retrieval,
6
+ null-check utilities, and installation path resolution.
7
+
8
+ """
9
+ from hunterMakesPy import PackageSettings, raiseIfNone
10
+ from hunterMakesPy.coping import getIdentifierPackagePACKAGING, getPathPackageINSTALLING
11
+ from hunterMakesPy.tests.conftest import uniformTestFailureMessage
12
+ from pathlib import Path
13
+ import pytest
14
+
15
+ @pytest.mark.parametrize(
16
+ "returnTarget, expected",
17
+ [
18
+ (13, 13),
19
+ (17, 17),
20
+ ("fibonacci", "fibonacci"),
21
+ ("prime", "prime"),
22
+ ([], []),
23
+ ({}, {}),
24
+ (False, False),
25
+ (0, 0),
26
+ ]
27
+ )
28
+ def testRaiseIfNoneReturnsNonNoneValues(returnTarget: object, expected: object) -> None:
29
+ """Verify that non-None values are returned exactly as provided.
30
+
31
+ (AI generated docstring)
32
+
33
+ Parameters
34
+ ----------
35
+ returnTarget : object
36
+ The value to pass to `raiseIfNone`.
37
+ expected : object
38
+ The expected return value (should be identical to `returnTarget`).
39
+
40
+ """
41
+ actual: object = raiseIfNone(returnTarget)
42
+ assert actual == expected, uniformTestFailureMessage(expected, actual, "testRaiseIfNoneReturnsNonNoneValues", returnTarget)
43
+ assert actual is returnTarget, uniformTestFailureMessage(returnTarget, actual, "testRaiseIfNoneReturnsNonNoneValues identity check", returnTarget)
44
+
45
+ def testRaiseIfNoneRaisesValueErrorWhenGivenNone() -> None:
46
+ """Verify that ValueError is raised when input is None.
47
+
48
+ (AI generated docstring)
49
+
50
+ """
51
+ with pytest.raises(ValueError, match=r"A function unexpectedly returned `None`.*"):
52
+ raiseIfNone(None)
53
+
54
+ @pytest.mark.parametrize(
55
+ "customMessage",
56
+ [
57
+ "Configuration must include 'host' setting",
58
+ "Database connection failed",
59
+ "User input is required",
60
+ "Network request returned empty response",
61
+ ]
62
+ )
63
+ def testRaiseIfNoneRaisesValueErrorWithCustomMessage(customMessage: str) -> None:
64
+ """Verify that custom error messages are used when provided.
65
+
66
+ (AI generated docstring)
67
+
68
+ Parameters
69
+ ----------
70
+ customMessage : str
71
+ The custom error message to expect.
72
+
73
+ """
74
+ with pytest.raises(ValueError, match=customMessage):
75
+ raiseIfNone(None, customMessage)
76
+
77
+ def testRaiseIfNoneWithEmptyStringMessage() -> None:
78
+ """Verify that empty custom message string triggers default message.
79
+
80
+ (AI generated docstring)
81
+
82
+ """
83
+ with pytest.raises(ValueError, match=r"A function unexpectedly returned `None`.*"):
84
+ raiseIfNone(None, "")
85
+
86
+ def testRaiseIfNonePreservesTypeAnnotations() -> None:
87
+ """Verify that type info is preserved through the pass-through.
88
+
89
+ (AI generated docstring)
90
+
91
+ """
92
+ integerValue: int = raiseIfNone(23)
93
+ assert isinstance(integerValue, int), uniformTestFailureMessage(int, type(integerValue), "testRaiseIfNonePreservesTypeAnnotations", integerValue)
94
+
95
+ stringValue: str = raiseIfNone("cardinal")
96
+ assert isinstance(stringValue, str), uniformTestFailureMessage(str, type(stringValue), "testRaiseIfNonePreservesTypeAnnotations", stringValue)
97
+
98
+ listValue: list[int] = raiseIfNone([29, 31])
99
+ assert isinstance(listValue, list), uniformTestFailureMessage(list, type(listValue), "testRaiseIfNonePreservesTypeAnnotations", listValue)
100
+
101
+ # Tests for PackageSettings dataclass
102
+ @pytest.mark.parametrize(
103
+ "identifierPackageFALLBACK, expectedIdentifierPackage",
104
+ [
105
+ ("astToolFactory", "hunterMakesPy"), # Should read from pyproject.toml
106
+ ("nonExistentPackage", "hunterMakesPy"), # Should read from pyproject.toml
107
+ ("customPackage", "hunterMakesPy"), # Should read from pyproject.toml
108
+ ]
109
+ )
110
+ def testPackageSettingsWithFallbackUsesProjectToml(identifierPackageFALLBACK: str, expectedIdentifierPackage: str) -> None:
111
+ """Test that PackageSettings reads package name from pyproject.toml when using fallback.
112
+
113
+ Parameters
114
+ ----------
115
+ identifierPackageFALLBACK : str
116
+ The fallback identifier to use if retrieval fails (or to trigger lookup).
117
+ expectedIdentifierPackage : str
118
+ The expected resolved package identifier.
119
+
120
+ """
121
+ packageSettings: PackageSettings = PackageSettings(identifierPackageFALLBACK)
122
+ assert packageSettings.identifierPackage == expectedIdentifierPackage, uniformTestFailureMessage(
123
+ expectedIdentifierPackage, packageSettings.identifierPackage, "PackageSettings fallback", identifierPackageFALLBACK
124
+ )
125
+
126
+ @pytest.mark.parametrize(
127
+ "explicitIdentifierPackage, expectedIdentifierPackage",
128
+ [
129
+ ("customPackageName", "customPackageName"),
130
+ ("fibonacci", "fibonacci"),
131
+ ("prime", "prime"),
132
+ ("astToolFactory", "astToolFactory"),
133
+ ]
134
+ )
135
+ def testPackageSettingsWithExplicitIdentifierPackage(explicitIdentifierPackage: str, expectedIdentifierPackage: str) -> None:
136
+ """Test that PackageSettings respects explicitly provided identifierPackage.
137
+
138
+ Parameters
139
+ ----------
140
+ explicitIdentifierPackage : str
141
+ The explicitly provided package identifier.
142
+ expectedIdentifierPackage : str
143
+ The expected resolved package identifier.
144
+
145
+ """
146
+ packageSettings: PackageSettings = PackageSettings(identifierPackage=explicitIdentifierPackage)
147
+ assert packageSettings.identifierPackage == expectedIdentifierPackage, uniformTestFailureMessage(
148
+ expectedIdentifierPackage, packageSettings.identifierPackage, "PackageSettings explicit identifierPackage", explicitIdentifierPackage
149
+ )
150
+
151
+ @pytest.mark.parametrize(
152
+ "explicitPathPackage, expectedPathPackage",
153
+ [
154
+ (Path("C:/fibonacci/path"), Path("C:/fibonacci/path")),
155
+ (Path("C:/prime/directory"), Path("C:/prime/directory")),
156
+ (Path("/usr/local/lib/package"), Path("/usr/local/lib/package")),
157
+ (Path("relative/path"), Path("relative/path")),
158
+ ]
159
+ )
160
+ def testPackageSettingsWithExplicitPathPackage(explicitPathPackage: Path, expectedPathPackage: Path) -> None:
161
+ """Test that PackageSettings respects explicitly provided pathPackage.
162
+
163
+ Parameters
164
+ ----------
165
+ explicitPathPackage : Path
166
+ The explicitly provided package path.
167
+ expectedPathPackage : Path
168
+ The expected resolved package path.
169
+
170
+ """
171
+ packageSettings: PackageSettings = PackageSettings(pathPackage=explicitPathPackage)
172
+ assert packageSettings.pathPackage == expectedPathPackage, uniformTestFailureMessage(
173
+ expectedPathPackage, packageSettings.pathPackage, "PackageSettings explicit pathPackage", explicitPathPackage
174
+ )
175
+
176
+ @pytest.mark.parametrize(
177
+ "fileExtension, expectedFileExtension",
178
+ [
179
+ (".fibonacci", ".fibonacci"),
180
+ (".prime", ".prime"),
181
+ (".txt", ".txt"),
182
+ (".md", ".md"),
183
+ (".json", ".json"),
184
+ ]
185
+ )
186
+ def testPackageSettingsWithCustomFileExtension(fileExtension: str, expectedFileExtension: str) -> None:
187
+ """Test that PackageSettings respects custom file extensions.
188
+
189
+ Parameters
190
+ ----------
191
+ fileExtension : str
192
+ The custom file extension to set.
193
+ expectedFileExtension : str
194
+ The expected file extension in the settings.
195
+
196
+ """
197
+ packageSettings: PackageSettings = PackageSettings(fileExtension=fileExtension)
198
+ assert packageSettings.fileExtension == expectedFileExtension, uniformTestFailureMessage(
199
+ expectedFileExtension, packageSettings.fileExtension, "PackageSettings custom fileExtension", fileExtension
200
+ )
201
+
202
+ def testPackageSettingsDefaultValues() -> None:
203
+ """Test that PackageSettings has correct default values when no arguments provided."""
204
+ packageSettings: PackageSettings = PackageSettings()
205
+
206
+ # Should have default file extension
207
+ assert packageSettings.fileExtension == '.py', uniformTestFailureMessage(
208
+ '.py', packageSettings.fileExtension, "PackageSettings default fileExtension"
209
+ )
210
+
211
+ # identifierPackage should be empty when no fallback provided
212
+ assert packageSettings.identifierPackage == '', uniformTestFailureMessage(
213
+ '', packageSettings.identifierPackage, "PackageSettings default identifierPackage"
214
+ )
215
+
216
+ expectedPath: Path = Path()
217
+ assert packageSettings.pathPackage == expectedPath, uniformTestFailureMessage(
218
+ expectedPath, packageSettings.pathPackage, "PackageSettings default pathPackage, pathPackage should remain as Path() when identifierPackage is empty"
219
+ )
220
+
221
+ @pytest.mark.parametrize(
222
+ "identifierPackageFALLBACK, identifierPackage, pathPackage, fileExtension",
223
+ [
224
+ ("fallback", "custom", Path("C:/custom/path"), ".txt"),
225
+ ("fibonacci", "prime", Path("C:/fibonacci/lib"), ".md"),
226
+ ("defaultFallback", "overridePackage", Path("/usr/local/override"), ".json"),
227
+ ]
228
+ )
229
+ def testPackageSettingsAllParametersCombined(
230
+ identifierPackageFALLBACK: str,
231
+ identifierPackage: str,
232
+ pathPackage: Path,
233
+ fileExtension: str
234
+ ) -> None:
235
+ """Test PackageSettings with all parameters provided.
236
+
237
+ Parameters
238
+ ----------
239
+ identifierPackageFALLBACK : str
240
+ The fallback identifier.
241
+ identifierPackage : str
242
+ The explicit package identifier.
243
+ pathPackage : Path
244
+ The explicit package path.
245
+ fileExtension : str
246
+ The explicit file extension.
247
+
248
+ """
249
+ packageSettings: PackageSettings = PackageSettings(
250
+ identifierPackageFALLBACK
251
+ , identifierPackage=identifierPackage
252
+ , pathPackage=pathPackage
253
+ , fileExtension=fileExtension
254
+ )
255
+
256
+ assert packageSettings.identifierPackage == identifierPackage, uniformTestFailureMessage(
257
+ identifierPackage, packageSettings.identifierPackage, "PackageSettings combined identifierPackage"
258
+ )
259
+ assert packageSettings.pathPackage == pathPackage, uniformTestFailureMessage(
260
+ pathPackage, packageSettings.pathPackage, "PackageSettings combined pathPackage"
261
+ )
262
+ assert packageSettings.fileExtension == fileExtension, uniformTestFailureMessage(
263
+ fileExtension, packageSettings.fileExtension, "PackageSettings combined fileExtension"
264
+ )
265
+
266
+ def testPackageSettingsFallbackIgnoredWhenExplicitIdentifierProvided() -> None:
267
+ """Test that fallback is ignored when explicit identifierPackage is provided."""
268
+ packageSettings: PackageSettings = PackageSettings("shouldBeIgnored", identifierPackage="explicit")
269
+ assert packageSettings.identifierPackage == "explicit", uniformTestFailureMessage(
270
+ "explicit", packageSettings.identifierPackage, "PackageSettings fallback ignored"
271
+ )
272
+
273
+ # Tests for helper functions
274
+ @pytest.mark.parametrize(
275
+ "identifierPackageFALLBACK, expectedResult",
276
+ [
277
+ ("fibonacci", "hunterMakesPy"), # Should read from pyproject.toml
278
+ ("prime", "hunterMakesPy"), # Should read from pyproject.toml
279
+ ("nonExistentPackage", "hunterMakesPy"), # Should read from pyproject.toml
280
+ ]
281
+ )
282
+ def testGetIdentifierPackagePACKAGING(identifierPackageFALLBACK: str, expectedResult: str) -> None:
283
+ """Test that getIdentifierPackagePACKAGING reads from pyproject.toml correctly.
284
+
285
+ Parameters
286
+ ----------
287
+ identifierPackageFALLBACK : str
288
+ The fallback identifier to provide.
289
+ expectedResult : str
290
+ The expected package identifier result.
291
+
292
+ """
293
+ actual: str = getIdentifierPackagePACKAGING(identifierPackageFALLBACK)
294
+ assert actual == expectedResult, uniformTestFailureMessage(
295
+ expectedResult, actual, "getIdentifierPackagePACKAGING", identifierPackageFALLBACK
296
+ )
297
+
298
+ @pytest.mark.parametrize(
299
+ "identifierPackage",
300
+ [
301
+ "hunterMakesPy", # This package exists
302
+ "fibonacci", # Non-existent package should fallback to cwd
303
+ "prime", # Non-existent package should fallback to cwd
304
+ ]
305
+ )
306
+ def testGetPathPackageINSTALLING(identifierPackage: str) -> None:
307
+ """Test that getPathPackageINSTALLING returns valid Path objects.
308
+
309
+ Parameters
310
+ ----------
311
+ identifierPackage : str
312
+ The package identifier to look up.
313
+
314
+ """
315
+ actual: Path = getPathPackageINSTALLING(identifierPackage)
316
+ assert isinstance(actual, Path), uniformTestFailureMessage(
317
+ Path, type(actual), "getPathPackageINSTALLING type", identifierPackage
318
+ )
319
+ assert actual.exists() or actual == Path.cwd(), uniformTestFailureMessage(
320
+ "existing path or cwd", actual, "getPathPackageINSTALLING existence", identifierPackage
321
+ )