hunterMakesPy 0.1.0__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.
@@ -0,0 +1,16 @@
1
+ """`import hunterMakesPy as humpy`."""
2
+ from hunterMakesPy.theTypes import identifierDotAttribute as identifierDotAttribute
3
+
4
+ from hunterMakesPy.coping import raiseIfNone as raiseIfNone
5
+
6
+ from hunterMakesPy.parseParameters import (defineConcurrencyLimit as defineConcurrencyLimit, intInnit as intInnit,
7
+ oopsieKwargsie as oopsieKwargsie)
8
+
9
+ from hunterMakesPy.filesystemToolkit import (importLogicalPath2Identifier as importLogicalPath2Identifier,
10
+ importPathFilename2Identifier as importPathFilename2Identifier, makeDirsSafely as makeDirsSafely,
11
+ writeStringToHere as writeStringToHere)
12
+
13
+ from hunterMakesPy.dataStructures import (autoDecodingRLE as autoDecodingRLE, stringItUp as stringItUp,
14
+ updateExtendPolishDictionaryLists as updateExtendPolishDictionaryLists)
15
+
16
+ from hunterMakesPy._theSSOT import settingsPackage
@@ -0,0 +1,40 @@
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())["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()
@@ -0,0 +1,73 @@
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
@@ -0,0 +1,265 @@
1
+ """Provides utilities for string extraction from nested data structures and merges multiple dictionaries containing lists into one dictionary."""
2
+
3
+ from collections.abc import Iterator, Mapping
4
+ from numpy import integer
5
+ from numpy.typing import NDArray
6
+ from typing import Any
7
+ import more_itertools
8
+ import python_minifier
9
+ import re as regex
10
+
11
+ def autoDecodingRLE(arrayTarget: NDArray[integer[Any]], *, assumeAddSpaces: bool = False) -> str: # noqa: C901, PLR0915
12
+ """Transform a NumPy array into a compact, self-decoding run-length encoded string representation.
13
+
14
+ This function converts a NumPy array into a string that, when evaluated as Python code,
15
+ recreates the original array structure. The function employs two compression strategies:
16
+ 1. Python's `range` syntax for consecutive integer sequences
17
+ 2. Multiplication syntax for repeated elements
18
+
19
+ The resulting string representation is designed to be both human-readable and space-efficient,
20
+ especially for large cartesian mappings with repetitive patterns. When this string is used
21
+ as a data source, Python will automatically decode it into Python `list`, which if used as an
22
+ argument to `numpy.array()`, will recreate the original array structure.
23
+
24
+ Parameters
25
+ ----------
26
+ arrayTarget : NDArray[integer[Any]]
27
+ (array2target) The NumPy array to be encoded.
28
+ assumeAddSpaces : bool = False
29
+ (assume2add2spaces) Affects internal length comparison during compression decisions.
30
+ This parameter doesn't directly change output format but influences whether
31
+ `range` or multiplication syntax is preferred in certain cases. The parameter
32
+ exists because the Abstract Syntax Tree (AST) inserts spaces in its string
33
+ representation.
34
+
35
+ Returns
36
+ -------
37
+ rleString : str
38
+ (rle2string) A string representation of the array using run-length encoding that,
39
+ when evaluated as Python code, reproduces the original array structure.
40
+
41
+ Notes
42
+ -----
43
+ The "autoDecoding" feature means that the string representation evaluates directly
44
+ to the desired data structure without explicit decompression steps.
45
+
46
+ """
47
+ def sliceNDArrayToNestedLists(arraySlice: NDArray[integer[Any]]) -> Any:
48
+ def getLengthOption(optionAsStr: str) -> int:
49
+ # `assumeAddSpaces` characters: `,` 1; `]*` 2 # noqa: ERA001
50
+ return assumeAddSpaces * (optionAsStr.count(',') + optionAsStr.count(']*') * 2) + len(optionAsStr)
51
+
52
+ if arraySlice.ndim > 1:
53
+ axisOfOperation = 0
54
+ return [sliceNDArrayToNestedLists(arraySlice[index]) for index in range(arraySlice.shape[axisOfOperation])]
55
+ if arraySlice.ndim == 1:
56
+ arraySliceAsList: list[int | range] = []
57
+ cache_consecutiveGroup_addMe: dict[Iterator[Any], list[int] | list[range]] = {}
58
+ for consecutiveGroup in more_itertools.consecutive_groups(arraySlice.tolist()):
59
+ if consecutiveGroup in cache_consecutiveGroup_addMe:
60
+ addMe = cache_consecutiveGroup_addMe[consecutiveGroup]
61
+ else:
62
+ ImaSerious: list[int] = list(consecutiveGroup)
63
+ ImaRange = [range(ImaSerious[0], ImaSerious[-1] + 1)]
64
+ ImaRangeAsStr = python_minifier.minify(str(ImaRange)).replace('range(0,', 'range(').replace('range', '*range')
65
+
66
+ option1 = ImaRange
67
+ option1AsStr = ImaRangeAsStr
68
+ option2 = ImaSerious
69
+ option2AsStr = None
70
+
71
+ # alpha, potential function
72
+ option1AsStr = option1AsStr or python_minifier.minify(str(option1))
73
+ lengthOption1 = getLengthOption(option1AsStr)
74
+
75
+ option2AsStr = option2AsStr or python_minifier.minify(str(option2))
76
+ lengthOption2 = getLengthOption(option2AsStr)
77
+
78
+ if lengthOption1 < lengthOption2:
79
+ addMe = option1
80
+ else:
81
+ addMe = option2
82
+
83
+ cache_consecutiveGroup_addMe[consecutiveGroup] = addMe
84
+
85
+ arraySliceAsList += addMe
86
+
87
+ listRangeAndTuple: list[int | range | tuple[int | range, int]] = []
88
+ cache_malkovichGrouped_addMe: dict[tuple[int | range, int], list[tuple[int | range, int]] | list[int | range]] = {}
89
+ for malkovichGrouped in more_itertools.run_length.encode(arraySliceAsList):
90
+ if malkovichGrouped in cache_malkovichGrouped_addMe:
91
+ addMe = cache_malkovichGrouped_addMe[malkovichGrouped]
92
+ else:
93
+ lengthMalkovich = malkovichGrouped[-1]
94
+ malkovichAsList = list(more_itertools.run_length.decode([malkovichGrouped]))
95
+ malkovichMalkovich = f"[{malkovichGrouped[0]}]*{lengthMalkovich}"
96
+
97
+ option1 = [malkovichGrouped]
98
+ option1AsStr = malkovichMalkovich
99
+ option2 = malkovichAsList
100
+ option2AsStr = None
101
+
102
+ # beta, potential function
103
+ option1AsStr = option1AsStr or python_minifier.minify(str(option1))
104
+ lengthOption1 = getLengthOption(option1AsStr)
105
+
106
+ option2AsStr = option2AsStr or python_minifier.minify(str(option2))
107
+ lengthOption2 = getLengthOption(option2AsStr)
108
+
109
+ if lengthOption1 < lengthOption2:
110
+ addMe = option1
111
+ else:
112
+ addMe = option2
113
+
114
+ cache_malkovichGrouped_addMe[malkovichGrouped] = addMe
115
+
116
+ listRangeAndTuple += addMe
117
+
118
+ return listRangeAndTuple
119
+ return arraySlice
120
+
121
+ arrayAsNestedLists = sliceNDArrayToNestedLists(arrayTarget)
122
+
123
+ arrayAsStr = python_minifier.minify(str(arrayAsNestedLists))
124
+
125
+ patternRegex = regex.compile(
126
+ "(?<!rang)(?:"
127
+ # Pattern 1: Comma ahead, bracket behind # noqa: ERA001
128
+ "(?P<joinAhead>,)\\((?P<malkovich>\\d+),(?P<multiple>\\d+)\\)(?P<bracketBehind>])|"
129
+ # Pattern 2: Bracket or start ahead, comma behind # noqa: ERA001
130
+ "(?P<bracketOrStartAhead>\\[|^.)\\((?P<malkovichmalkovich>\\d+),(?P<multiple_fml>\\d+)\\)(?P<joinBehind>,)|"
131
+ # Pattern 3: Bracket ahead, bracket behind # noqa: ERA001
132
+ "(?P<bracketAhead>\\[)\\((?P<malkovichmalkovichmalkovich>\\d+),(?P<multiple_whatever>\\d+)\\)(?P<bracketBehindbracketBehind>])|"
133
+ # Pattern 4: Comma ahead, comma behind # noqa: ERA001
134
+ "(?P<joinAhead_prayharder>,)\\((?P<malkovichmalkovichmalkovichmalkovich>\\d+),(?P<multiple_prayharder>\\d+)\\)(?P<joinBehind_prayharder>,)"
135
+ ")"
136
+ )
137
+
138
+ def replacementByContext(match: regex.Match[str]) -> str:
139
+ """Generate replacement string based on context patterns."""
140
+ yourIdentifiersSuck = match.groupdict()
141
+ joinAhead = yourIdentifiersSuck.get('joinAhead') or yourIdentifiersSuck.get('joinAhead_prayharder')
142
+ malkovich = yourIdentifiersSuck.get('malkovich') or yourIdentifiersSuck.get('malkovichmalkovich') or yourIdentifiersSuck.get('malkovichmalkovichmalkovich') or yourIdentifiersSuck.get('malkovichmalkovichmalkovichmalkovich')
143
+ multiple = yourIdentifiersSuck.get('multiple') or yourIdentifiersSuck.get('multiple_fml') or yourIdentifiersSuck.get('multiple_whatever') or yourIdentifiersSuck.get('multiple_prayharder')
144
+ joinBehind = yourIdentifiersSuck.get('joinBehind') or yourIdentifiersSuck.get('joinBehind_prayharder')
145
+
146
+ replaceAhead = "]+[" if joinAhead == "," else "["
147
+
148
+ replaceBehind = "+[" if joinBehind == "," else ""
149
+
150
+ return f"{replaceAhead}{malkovich}]*{multiple}{replaceBehind}"
151
+
152
+ arrayAsStr = patternRegex.sub(replacementByContext, arrayAsStr)
153
+ arrayAsStr = patternRegex.sub(replacementByContext, arrayAsStr)
154
+
155
+ # Replace `range(0,stop)` syntax with `range(stop)` syntax. # noqa: ERA001
156
+ # Add unpack operator `*` for automatic decoding when evaluated.
157
+ return arrayAsStr.replace('range(0,', 'range(').replace('range', '*range')
158
+
159
+ def stringItUp(*scrapPile: Any) -> list[str]: # noqa: C901
160
+ """Convert, if possible, every element in the input data structure to a string.
161
+
162
+ Order is not preserved or readily predictable.
163
+
164
+ Parameters
165
+ ----------
166
+ *scrapPile : Any
167
+ (scrap2pile) One or more data structures to unpack and convert to strings.
168
+
169
+ Returns
170
+ -------
171
+ listStrungUp : list[str]
172
+ (list2strung2up) A `list` of string versions of all convertible elements.
173
+
174
+ """
175
+ scrap = None
176
+ listStrungUp: list[str] = []
177
+
178
+ def drill(KitKat: Any) -> None: # noqa: C901, PLR0912
179
+ match KitKat:
180
+ case str():
181
+ listStrungUp.append(KitKat)
182
+ case bool() | bytearray() | bytes() | complex() | float() | int() | memoryview() | None:
183
+ listStrungUp.append(str(KitKat)) # pyright: ignore [reportUnknownArgumentType]
184
+ case dict():
185
+ for broken, piece in KitKat.items(): # pyright: ignore [reportUnknownVariableType]
186
+ drill(broken)
187
+ drill(piece)
188
+ case list() | tuple() | set() | frozenset() | range():
189
+ for kit in KitKat: # pyright: ignore [reportUnknownVariableType]
190
+ drill(kit)
191
+ case _:
192
+ if hasattr(KitKat, '__iter__'): # Unpack other iterables
193
+ for kat in KitKat:
194
+ drill(kat)
195
+ else:
196
+ try:
197
+ sharingIsCaring = KitKat.__str__()
198
+ listStrungUp.append(sharingIsCaring)
199
+ except AttributeError:
200
+ pass
201
+ except TypeError: # "The error traceback provided indicates that there is an issue when calling the __str__ method on an object that does not have this method properly defined, leading to a TypeError."
202
+ pass
203
+ except:
204
+ print(f"\nWoah! I received '{repr(KitKat)}'.\nTheir report card says, 'Plays well with others: Needs improvement.'\n") # noqa: RUF010, T201
205
+ raise
206
+ try:
207
+ for scrap in scrapPile:
208
+ drill(scrap)
209
+ except RecursionError:
210
+ listStrungUp.append(repr(scrap))
211
+ return listStrungUp
212
+
213
+ def updateExtendPolishDictionaryLists(*dictionaryLists: Mapping[str, list[Any] | set[Any] | tuple[Any, ...]], destroyDuplicates: bool = False, reorderLists: bool = False, killErroneousDataTypes: bool = False) -> dict[str, list[Any]]:
214
+ """Merge multiple dictionaries containing `list` into a single dictionary.
215
+
216
+ With options to handle duplicates, `list` ordering, and erroneous data types.
217
+
218
+ Parameters
219
+ ----------
220
+ *dictionaryLists : Mapping[str, list[Any] | set[Any] | tuple[Any, ...]]
221
+ (dictionary2lists) Variable number of dictionaries to be merged. If only one dictionary is passed, it will be processed based on the provided options.
222
+ destroyDuplicates : bool = False
223
+ (destroy2duplicates) If `True`, removes duplicate elements from the `list`. Defaults to `False`.
224
+ reorderLists : bool = False
225
+ (reorder2lists) If `True`, sorts the `list`. Defaults to `False`.
226
+ killErroneousDataTypes : bool = False
227
+ (kill2erroneous2data2types) If `True`, skips dictionary keys or dictionary values that cause a `TypeError` during merging. Defaults to `False`.
228
+
229
+ Returns
230
+ -------
231
+ ePluribusUnum : dict[str, list[Any]]
232
+ (e2pluribus2unum) A single dictionary with merged `list` based on the provided options. If only one dictionary is passed,
233
+ it will be cleaned up based on the options.
234
+
235
+ Notes
236
+ -----
237
+ The returned value, `ePluribusUnum`, is a so-called primitive dictionary (`dict`). Furthermore, every dictionary key is a
238
+ so-called primitive string (cf. `str()`) and every dictionary value is a so-called primitive `list` (`list`). If
239
+ `dictionaryLists` has other data types, the data types will not be preserved. That could have unexpected consequences.
240
+ Conversion from the original data type to a `list`, for example, may not preserve the order even if you want the order to be
241
+ preserved.
242
+
243
+ """
244
+ ePluribusUnum: dict[str, list[Any]] = {}
245
+
246
+ for dictionaryListTarget in dictionaryLists:
247
+ for keyName, keyValue in dictionaryListTarget.items():
248
+ try:
249
+ ImaStr = str(keyName)
250
+ ImaList = list(keyValue)
251
+ ePluribusUnum.setdefault(ImaStr, []).extend(ImaList)
252
+ except TypeError: # noqa: PERF203
253
+ if killErroneousDataTypes:
254
+ continue
255
+ else:
256
+ raise
257
+
258
+ if destroyDuplicates:
259
+ for ImaStr, ImaList in ePluribusUnum.items():
260
+ ePluribusUnum[ImaStr] = list(dict.fromkeys(ImaList))
261
+ if reorderLists:
262
+ for ImaStr, ImaList in ePluribusUnum.items():
263
+ ePluribusUnum[ImaStr] = sorted(ImaList)
264
+
265
+ return ePluribusUnum
@@ -0,0 +1,114 @@
1
+ """File system and module import utilities.
2
+
3
+ This module provides basic file I/O utilities such as writing tabular data to files, computing canonical relative paths, importing
4
+ callables from modules, and safely creating directories.
5
+
6
+ """
7
+
8
+ from hunterMakesPy import identifierDotAttribute
9
+ from os import PathLike
10
+ from pathlib import Path, PurePath
11
+ from typing import Any, TYPE_CHECKING, TypeVar
12
+ import contextlib
13
+ import importlib
14
+ import importlib.util
15
+ import io
16
+
17
+ if TYPE_CHECKING:
18
+ from types import ModuleType
19
+
20
+ 归个 = TypeVar('归个')
21
+
22
+ def importLogicalPath2Identifier(logicalPathModule: identifierDotAttribute, identifier: str, packageIdentifierIfRelative: str | None = None) -> 归个:
23
+ """Import an `identifier`, such as a function or `class`, from a module using its logical path.
24
+
25
+ This function imports a module and retrieves a specific attribute (function, class, or other object) from that module.
26
+
27
+ Parameters
28
+ ----------
29
+ logicalPathModule : identifierDotAttribute
30
+ The logical path to the module, using dot notation (e.g., 'scipy.signal.windows').
31
+ identifier : str
32
+ The identifier of the object to retrieve from the module.
33
+ packageIdentifierIfRelative : str | None = None
34
+ The package name to use as the anchor point if `logicalPathModule` is a relative import. `None` means an absolute import.
35
+
36
+ Returns
37
+ -------
38
+ identifierImported : 归个
39
+ The identifier (function, class, or object) retrieved from the module.
40
+
41
+ """
42
+ moduleImported: ModuleType = importlib.import_module(logicalPathModule, packageIdentifierIfRelative)
43
+ return getattr(moduleImported, identifier)
44
+
45
+ def importPathFilename2Identifier(pathFilename: PathLike[Any] | PurePath, identifier: str, moduleIdentifier: str | None = None) -> 归个:
46
+ """Load an identifier from a Python file.
47
+
48
+ This function imports a specified Python file as a module, extracts an identifier from it by name, and returns that
49
+ identifier.
50
+
51
+ Parameters
52
+ ----------
53
+ pathFilename : PathLike[Any] | PurePath
54
+ Path to the Python file to import.
55
+ identifier : str
56
+ Name of the identifier to extract from the imported module.
57
+ moduleIdentifier : str | None = None
58
+ Name to use for the imported module. If `None`, the filename stem is used.
59
+
60
+ Returns
61
+ -------
62
+ identifierImported : 归个
63
+ The identifier extracted from the imported module.
64
+
65
+ Raises
66
+ ------
67
+ ImportError
68
+ If the file cannot be imported or the importlib specification is invalid.
69
+ AttributeError
70
+ If the identifier does not exist in the imported module.
71
+
72
+ """
73
+ pathFilename = Path(pathFilename)
74
+
75
+ importlibSpecification = importlib.util.spec_from_file_location(moduleIdentifier or pathFilename.stem, pathFilename)
76
+ if importlibSpecification is None or importlibSpecification.loader is None:
77
+ 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
+ raise ImportError(message)
79
+
80
+ moduleImported_jk_hahaha: ModuleType = importlib.util.module_from_spec(importlibSpecification)
81
+ importlibSpecification.loader.exec_module(moduleImported_jk_hahaha)
82
+ return getattr(moduleImported_jk_hahaha, identifier)
83
+
84
+ def makeDirsSafely(pathFilename: Any) -> None:
85
+ """Create parent directories for a given path safely.
86
+
87
+ This function attempts to create all necessary parent directories for a given path. If the directory already exists or if
88
+ there's an `OSError` during creation, it will silently continue without raising an exception.
89
+
90
+ Parameters
91
+ ----------
92
+ pathFilename : Any
93
+ A path-like object or file object representing the path for which to create parent directories. If it's an IO stream
94
+ object, no directories will be created.
95
+
96
+ """
97
+ if not isinstance(pathFilename, io.IOBase):
98
+ with contextlib.suppress(OSError):
99
+ Path(pathFilename).parent.mkdir(parents=True, exist_ok=True)
100
+
101
+ def writeStringToHere(this: str, pathFilename: PathLike[Any] | PurePath) -> None:
102
+ """Write a string to a file, creating parent directories as needed.
103
+
104
+ Parameters
105
+ ----------
106
+ this : str
107
+ The string content to write to the file.
108
+ pathFilename : PathLike[Any] | PurePath
109
+ The path and filename where the string will be written.
110
+
111
+ """
112
+ pathFilename = Path(pathFilename)
113
+ makeDirsSafely(pathFilename)
114
+ pathFilename.write_text(str(this), encoding='utf-8')