Coverage for src / toolbox_python / strings.py: 100%
26 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-02 22:56 +0000
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-02 22:56 +0000
1# ============================================================================ #
2# #
3# Title : Strings #
4# Purpose : Manipulate and check strings. #
5# #
6# ============================================================================ #
9# ---------------------------------------------------------------------------- #
10# #
11# Overview ####
12# #
13# ---------------------------------------------------------------------------- #
16# ---------------------------------------------------------------------------- #
17# Description ####
18# ---------------------------------------------------------------------------- #
21"""
22!!! note "Summary"
23 The `strings` module is for manipulating and checking certain string objects.
24"""
27# ---------------------------------------------------------------------------- #
28# #
29# Setup ####
30# #
31# ---------------------------------------------------------------------------- #
34# ---------------------------------------------------------------------------- #
35# Imports ####
36# ---------------------------------------------------------------------------- #
39# ## Python StdLib Imports ----
40import re
41import string
42from typing import Any, Union, overload
44# ## Python Third Party Imports ----
45from typeguard import typechecked
47# ## Local First Party Imports ----
48from toolbox_python.checkers import is_type
51# ---------------------------------------------------------------------------- #
52# Exports ####
53# ---------------------------------------------------------------------------- #
56__all__: list[str] = [
57 "str_replace",
58 "str_contains",
59 "str_contains_any",
60 "str_contains_all",
61 "str_separate_number_chars",
62 "str_to_list",
63]
66# ---------------------------------------------------------------------------- #
67# #
68# Functions ####
69# #
70# ---------------------------------------------------------------------------- #
73@typechecked
74def str_replace(
75 old_string: str,
76 replace_chars: str = string.punctuation + string.whitespace,
77 replace_with: str = "",
78) -> str:
79 """
80 !!! note "Summary"
81 Replace the characters with a given string.
83 ???+ abstract "Details"
84 Similar to the Python `#!py str.replace()` method, but provides more customisation through the use of the [`re`](https://docs.python.org/3/library/re.html) package.
86 Params:
87 old_string (str):
88 The old string to be replaced.
89 replace_chars (str, optional):
90 The characters that need replacing.<br>
91 Defaults to `#!py string.punctuation + string.whitespace`.
92 replace_with (str, optional):
93 The value to replace the characters with.<br>
94 Defaults to `""`.
96 Raises:
97 (TypeCheckError):
98 If any of the inputs parsed to the parameters of this function are not the correct type. Uses the [`@typeguard.typechecked`](https://typeguard.readthedocs.io/en/stable/api.html#typeguard.typechecked) decorator.
100 Returns:
101 (str):
102 The new formatted string.
104 ???+ example "Examples"
106 ```pycon {.py .python linenums="1" title="Set up"}
107 >>> from toolbox_python.strings import str_replace
108 >>> long_string = "This long string"
109 >>> complex_sentence = "Because my pizza was cold, I put it in the microwave."
110 ```
112 ```pycon {.py .python linenums="1" title="Example 1: Replace all spaces (` `) with underscore (`_`)"}
113 >>> print(str_replace(long_string, " ", "_"))
114 ```
115 <div class="result" markdown>
116 ```{.sh .shell title="Terminal"}
117 "This_long_string"
118 ```
119 !!! success "Conclusion: Successful conversion."
120 </div>
122 ```pycon {.py .python linenums="1" title="Example 2: Remove all punctuation and white space"}
123 >>> print(str_replace(complex_sentence))
124 ```
125 <div class="result" markdown>
126 ```{.sh .shell title="Terminal"}
127 "BecausemylunchwascoldIputitinthemicrowave"
128 ```
129 !!! success "Conclusion: Successful conversion."
130 </div>
132 ```pycon {.py .python linenums="1" title="Example 3: Invalid `old_string` input"}
133 >>> print(str_replace(123))
134 ```
135 <div class="result" markdown>
136 ```{.sh .shell title="Terminal"}
137 TypeError: ...
138 ```
139 !!! failure "Conclusion: Invalid input."
140 !!! observation "Note: The same error will occur if `replace_chars` or `replace_with` are not of type `str`."
141 </div>
143 ??? success "Credit"
144 Full credit goes to:<br>
145 https://stackoverflow.com/questions/23996118/replace-special-characters-in-a-string-python#answer-23996414
147 ??? tip "See Also"
148 - [`re`](https://docs.python.org/3/library/re.html)
149 """
150 chars: str = re.escape(replace_chars)
151 return re.sub(rf"[{chars}]", replace_with, old_string)
154@typechecked
155def str_contains(check_string: str, sub_string: str) -> bool:
156 """
157 !!! note "Summary"
158 Check whether one string contains another string.
160 ???+ abstract "Details"
161 This is a super simple one-line function.
163 ```py linenums="1" title="Example"
164 return True if sub_string in check_string else False
165 ```
167 Params:
168 check_string (str):
169 The main string to check.
170 sub_string (str):
171 The substring to check.
173 Raises:
174 (TypeCheckError):
175 If any of the inputs parsed to the parameters of this function are not the correct type. Uses the [`@typeguard.typechecked`](https://typeguard.readthedocs.io/en/stable/api.html#typeguard.typechecked) decorator.
177 Returns:
178 (bool):
179 `#!py True` if `#!py sub_string` in `#!py check_string`
181 ???+ example "Examples"
183 ```pycon {.py .python linenums="1" title="Set up"}
184 >>> from toolbox_python.strings import str_contains
185 >>> long_string = "This long string"
186 ```
188 ```pycon {.py .python linenums="1" title="Example 1: String is contained"}
189 >>> print(str_contains(long_string, "long"))
190 ```
191 <div class="result" markdown>
192 ```{.sh .shell title="Terminal"}
193 True
194 ```
195 !!! success "Conclusion: `#!py long_string` contains `#!py "long"`."
196 </div>
198 ```pycon {.py .python linenums="1" title="Example 2: String is not contained"}
199 >>> print(str_contains(long_string, "short"))
200 ```
201 <div class="result" markdown>
202 ```{.sh .shell title="Terminal"}
203 False
204 ```
205 !!! success "Conclusion: `#!py long_string` does not contain `#!py "short"`."
206 </div>
208 ```pycon {.py .python linenums="1" title="Example 3: Invalid `check_string` input"}
209 >>> print(str_contains(123, "short"))
210 ```
211 <div class="result" markdown>
212 ```{.sh .shell title="Terminal"}
213 TypeError: ...
214 ```
215 !!! failure "Conclusion: Invalid input."
216 !!! observation "Note: The same error will occur if `sub_string` is not of type `str`."
217 </div>
219 ??? tip "See Also"
220 - [`str_contains_any()`][toolbox_python.strings.str_contains_any]
221 - [`str_contains_all()`][toolbox_python.strings.str_contains_all]
222 """
223 return sub_string in check_string
226@typechecked
227def str_contains_any(
228 check_string: str,
229 sub_strings: Union[list[str], tuple[str, ...]],
230) -> bool:
231 """
232 !!! note "Summary"
233 Check whether any one of a number of strings are contained within a main string.
235 Params:
236 check_string (str):
237 The main string to check.
238 sub_strings (Union[list[str], tuple[str, ...]]):
239 The collection of substrings to check.
241 Raises:
242 (TypeCheckError):
243 If any of the inputs parsed to the parameters of this function are not the correct type. Uses the [`@typeguard.typechecked`](https://typeguard.readthedocs.io/en/stable/api.html#typeguard.typechecked) decorator.
245 Returns:
246 (bool):
247 `#!py True` if `#!py any` of the strings in `#!py sub_strings` are contained within `#!py check_string`.
249 ???+ example "Examples"
251 ```pycon {.py .python linenums="1" title="Set up"}
252 >>> from toolbox_python.strings import str_contains_any
253 >>> long_string = "This long string"
254 ```
256 ```pycon {.py .python linenums="1" title="Example 1: Contains any"}
257 >>> print(str_contains_any(long_string, ["long", "short"]))
258 ```
259 <div class="result" markdown>
260 ```{.sh .shell title="Terminal"}
261 True
262 ```
263 !!! success "Conclusion: `#!py long_string` contains either `#!py "long"` or `#!py "short"`."
264 </div>
266 ```pycon {.py .python linenums="1" title="Example 2: Contains none"}
267 >>> print(str_contains_any(long_string, ["this", "that"]))
268 ```
269 <div class="result" markdown>
270 ```{.sh .shell title="Terminal"}
271 False
272 ```
273 !!! success "Conclusion: `#!py long_string` contains neither `#!py "this"` nor `#!py "that"`."
274 </div>
276 ```pycon {.py .python linenums="1" title="Example 3: Invalid `check_string` input"}
277 >>> print(str_contains_any(123, ["short", "long"]))
278 ```
279 <div class="result" markdown>
280 ```{.sh .shell title="Terminal"}
281 TypeError: ...
282 ```
283 !!! failure "Conclusion: Invalid input."
284 !!! observation "Note: The same error will occur if any of the elements in `sub_strings` are not of type `str`."
285 </div>
287 ??? tip "See Also"
288 - [`str_contains()`][toolbox_python.strings.str_contains]
289 - [`str_contains_all()`][toolbox_python.strings.str_contains_all]
290 """
291 return any(
292 str_contains(
293 check_string=check_string,
294 sub_string=sub_string,
295 )
296 for sub_string in sub_strings
297 )
300@typechecked
301def str_contains_all(
302 check_string: str,
303 sub_strings: Union[list[str], tuple[str, ...]],
304) -> bool:
305 """
306 !!! note "Summary"
307 Check to ensure that all sub-strings are contained within a main string.
309 Params:
310 check_string (str):
311 The main string to check.
312 sub_strings (Union[list[str], tuple[str, ...]]):
313 The collection of substrings to check.
315 Raises:
316 (TypeCheckError):
317 If any of the inputs parsed to the parameters of this function are not the correct type. Uses the [`@typeguard.typechecked`](https://typeguard.readthedocs.io/en/stable/api.html#typeguard.typechecked) decorator.
319 Returns:
320 (bool):
321 `#!py True` if `#!py all` of the strings in `#!py sub_strings` are contained within `#!py check_string`.
323 ???+ example "Examples"
325 ```pycon {.py .python linenums="1" title="Set up"}
326 >>> from toolbox_python.strings import str_contains_all
327 >>> long_string = "This long string"
328 ```
330 ```pycon {.py .python linenums="1" title="Example 1: Contains all"}
331 >>> print(str_contains_all(long_string, ["long", "string"]))
332 ```
333 <div class="result" markdown>
334 ```{.sh .shell title="Terminal"}
335 True
336 ```
337 !!! success "Conclusion: `#!py long_string` contains both `#!py "long"` and `#!py "string"`."
338 </div>
340 ```pycon {.py .python linenums="1" title="Example 2: Contains some"}
341 >>> print(str_contains_all(long_string, ["long", "something"]))
342 ```
343 <div class="result" markdown>
344 ```{.sh .shell title="Terminal"}
345 False
346 ```
347 !!! failure "Conclusion: `#!py long_string` contains `#!py "long"` but not `#!py "something"`."
348 </div>
350 ```pycon {.py .python linenums="1" title="Example 3: Contains none"}
351 >>> print(str_contains_all(long_string, ["this", "that"]))
352 ```
353 <div class="result" markdown>
354 ```{.sh .shell title="Terminal"}
355 False
356 ```
357 !!! failure "Conclusion: `#!py long_string` contains neither `#!py "this"` nor `#!py "that"`."
358 </div>
360 ```pycon {.py .python linenums="1" title="Example 4: Invalid `check_string` input"}
361 >>> print(str_contains_all(123, ["short", "long"]))
362 ```
363 <div class="result" markdown>
364 ```{.sh .shell title="Terminal"}
365 TypeError: ...
366 ```
367 !!! failure "Conclusion: Invalid input."
368 !!! observation "Note: The same error will occur if any of the elements in `sub_strings` are not of type `str`."
369 </div>
371 ??? tip "See Also"
372 - [`str_contains()`][toolbox_python.strings.str_contains]
373 - [`str_contains_any()`][toolbox_python.strings.str_contains_any]
374 """
375 return all(
376 str_contains(
377 check_string=check_string,
378 sub_string=sub_string,
379 )
380 for sub_string in sub_strings
381 )
384@typechecked
385def str_separate_number_chars(text: str) -> list[str]:
386 """
387 !!! note "Summary"
388 Take in a string that contains both numbers and letters, and output a list of strings, separated to have each element containing either entirely number or entirely letters.
390 ???+ abstract "Details"
391 Uses regex ([`re.split()`](https://docs.python.org/3/library/re.html#re.split)) to perform the actual splitting.<br>
392 Note, it _will_ preserve special characters & punctuation, but it _will not_ preserve whitespaces.
394 Params:
395 text (str):
396 The string to split.
398 Raises:
399 (TypeCheckError):
400 If any of the inputs parsed to the parameters of this function are not the correct type. Uses the [`@typeguard.typechecked`](https://typeguard.readthedocs.io/en/stable/api.html#typeguard.typechecked) decorator.
402 Returns:
403 (list[str]):
404 The updated list, with each element of the list containing either entirely characters or entirely numbers.
406 ???+ example "Examples"
408 ```pycon {.py .python linenums="1" title="Set up"}
409 >>> from toolbox_python.strings import str_contains_all
410 >>> simple_string = "-12.1grams"
411 >>> complex_string = "abcd2343 abw34324 abc3243-23A 123"
412 ```
414 ```pycon {.py .python linenums="1" title="Example 1: Simple split"}
415 >>> print(str_separate_number_chars(simple_string))
416 ```
417 <div class="result" markdown>
418 ```{.sh .shell title="Terminal"}
419 ["-12.1", "grams"]
420 ```
421 !!! success "Conclusion: Successful split."
422 </div>
424 ```pycon {.py .python linenums="1" title="Example 2: Complex split"}
425 >>> print(str_separate_number_chars(complex_string))
426 ```
427 <div class="result" markdown>
428 ```{.sh .shell title="Terminal"}
429 [
430 "abcd",
431 "2343",
432 "abw",
433 "34324",
434 "abc",
435 "3243",
436 "-23",
437 "A",
438 "123",
439 ]
440 ```
441 !!! success "Conclusion: Successful split."
442 </div>
444 ```pycon {.py .python linenums="1" title="Example 3: `text` does not contain any numbers"}
445 >>> print(str_separate_number_chars("abcd"))
446 ```
447 <div class="result" markdown>
448 ```{.sh .shell title="Terminal"}
449 ["abcd"]
450 ```
451 !!! success "Conclusion: No numbers in `#!py text`, so returns a single-element long list."
452 </div>
454 ```pycon {.py .python linenums="1" title="Example 4: Invalid `text` input"}
455 >>> print(str_separate_number_chars(123))
456 ```
457 <div class="result" markdown>
458 ```{.sh .shell title="Terminal"}
459 TypeError: ...
460 ```
461 !!! failure "Conclusion: Invalid input."
462 </div>
464 ??? success "Credit"
465 Full credit goes to:<br>
466 https://stackoverflow.com/questions/3340081/product-code-looks-like-abcd2343-how-to-split-by-letters-and-numbers#answer-63362709.
468 ??? tip "See Also"
469 - [`re`](https://docs.python.org/3/library/re.html)
470 """
471 res = re.split(r"([-+]?\d+\.\d+)|([-+]?\d+)", text.strip())
472 return [r.strip() for r in res if r is not None and r.strip() != ""]
475@overload
476@typechecked
477def str_to_list(obj: str) -> list[str]: ...
478@overload
479@typechecked
480def str_to_list(obj: Any) -> Any: ...
481@typechecked
482def str_to_list(obj: Any) -> Union[list[str], Any]:
483 """
484 !!! note "Summary"
485 Convert a string to a list containing that string as the only element.
487 ???+ abstract "Details"
488 This function is useful when you want to ensure that a string is treated as a list, even if it is a single string. If the input is already a list, it will return it unchanged.
490 Params:
491 obj (Any):
492 The object to convert to a list if it is a string.
494 Raises:
495 (TypeCheckError):
496 If `obj` is not a string or a list.
498 Returns:
499 (Union[list[str], Any]):
500 If `obj` is a string, returns a list containing that string as the only element. If `obj` is not a string, returns it unchanged.
502 ???+ example "Examples"
503 ```pycon {.py .python linenums="1" title="Set up"}
504 >>> from toolbox_python.strings import str_to_list
505 ```
507 ```pycon {.py .python linenums="1" title="Example 1: Convert string to list"}
508 >>> print(str_to_list("hello"))
509 ```
510 <div class="result" markdown>
511 ```{.sh .shell title="Terminal"}
512 ["hello"]
513 ```
514 !!! success "Conclusion: Successfully converted string to list."
515 </div>
517 ```pycon {.py .python linenums="1" title="Example 2: Input is already a list"}
518 >>> print(str_to_list(["hello", "world"]))
519 ```
520 <div class="result" markdown>
521 ```{.sh .shell title="Terminal"}
522 ["hello", "world"]
523 ```
524 !!! success "Conclusion: Input was already a list, so returned unchanged."
525 </div>
526 """
527 return [obj] if is_type(obj, str) else obj