python-backpack 2.0.4__py3-none-any.whl → 2.0.5__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.
backpack/json_utils.py CHANGED
@@ -1,53 +1,53 @@
1
- import json
2
- import os
3
-
4
- from backpack.logger import get_logger
5
-
6
- log = get_logger('Python Backpack - JsonUtils')
7
-
8
-
9
- def json_load(json_file: str) -> dict:
10
- """Reads a json file.
11
-
12
- Args:
13
- json_file: (string filepath) json file to read data.
14
-
15
- Returns:
16
- dict (file content)
17
- """
18
- if not os.path.exists(json_file):
19
- raise OSError(f'json_load: File not found: {json_file}.')
20
-
21
- with open(json_file, encoding='utf-8') as json_file_opened:
22
- try:
23
- value = json.load(json_file_opened)
24
- except ValueError as e:
25
- raise OSError(f'{json_file} \n JSON File issue: {str(e)}') from e
26
-
27
- return value
28
-
29
-
30
- def json_save(data: object, json_file: str) -> bool:
31
- """Saves a dictionary into a json file.
32
-
33
- Args:
34
- data: value to serialize as JSON
35
- json_file: (string filepath) json file to save data.
36
-
37
- Returns:
38
- bool (True if success)
39
- """
40
-
41
- parent_dir = os.path.dirname(json_file)
42
- if parent_dir and not os.path.exists(parent_dir):
43
- os.makedirs(parent_dir, exist_ok=True)
44
-
45
- try:
46
- with open(json_file, 'w', encoding='utf-8') as f:
47
- json.dump(data, f, sort_keys=True, indent=4)
48
- return True
49
-
50
- except (OSError, TypeError) as e:
51
- log.error(f'{e} - {json_file}')
52
-
53
- return False
1
+ import json
2
+ import os
3
+
4
+ from backpack.logger import get_logger
5
+
6
+ log = get_logger('Python Backpack - JsonUtils')
7
+
8
+
9
+ def json_load(json_file: str) -> dict:
10
+ """Reads a json file.
11
+
12
+ Args:
13
+ json_file: (string filepath) json file to read data.
14
+
15
+ Returns:
16
+ dict (file content)
17
+ """
18
+ if not os.path.exists(json_file):
19
+ raise OSError(f'json_load: File not found: {json_file}.')
20
+
21
+ with open(json_file, encoding='utf-8') as json_file_opened:
22
+ try:
23
+ value = json.load(json_file_opened)
24
+ except ValueError as e:
25
+ raise OSError(f'{json_file} \n JSON File issue: {str(e)}') from e
26
+
27
+ return value
28
+
29
+
30
+ def json_save(data: object, json_file: str) -> bool:
31
+ """Saves a dictionary into a json file.
32
+
33
+ Args:
34
+ data: value to serialize as JSON
35
+ json_file: (string filepath) json file to save data.
36
+
37
+ Returns:
38
+ bool (True if success)
39
+ """
40
+
41
+ parent_dir = os.path.dirname(json_file)
42
+ if parent_dir and not os.path.exists(parent_dir):
43
+ os.makedirs(parent_dir, exist_ok=True)
44
+
45
+ try:
46
+ with open(json_file, 'w', encoding='utf-8') as f:
47
+ json.dump(data, f, sort_keys=True, indent=4)
48
+ return True
49
+
50
+ except (OSError, TypeError) as e:
51
+ log.error(f'{e} - {json_file}')
52
+
53
+ return False
backpack/logger.py CHANGED
@@ -1,16 +1,16 @@
1
- import logging
2
-
3
-
4
- def get_logger(name: str) -> logging.Logger:
5
- """Returns a logger object with a stream handler."""
6
- _log = logging.getLogger(name)
7
- _log.propagate = False
8
- _log.setLevel(logging.DEBUG)
9
- if len(_log.handlers) == 0:
10
- stream_handler = logging.StreamHandler()
11
- format_str = '%(levelname)-7s | %(message)s'
12
- formatter = logging.Formatter(format_str)
13
- stream_handler.setFormatter(formatter)
14
- _log.addHandler(stream_handler)
15
-
16
- return _log
1
+ import logging
2
+
3
+
4
+ def get_logger(name: str) -> logging.Logger:
5
+ """Returns a logger object with a stream handler."""
6
+ _log = logging.getLogger(name)
7
+ _log.propagate = False
8
+ _log.setLevel(logging.DEBUG)
9
+ if len(_log.handlers) == 0:
10
+ stream_handler = logging.StreamHandler()
11
+ format_str = '%(levelname)-7s | %(message)s'
12
+ formatter = logging.Formatter(format_str)
13
+ stream_handler.setFormatter(formatter)
14
+ _log.addHandler(stream_handler)
15
+
16
+ return _log
backpack/patterns.py CHANGED
@@ -1,28 +1,28 @@
1
- # ----------------------------------------------------------------------------------------
2
- # Python-Backpack - Pattern Utilities
3
- # Maximiliano Rocamora / maxirocamora@gmail.com
4
- # https://github.com/MaxRocamora/python-backpack
5
- # ----------------------------------------------------------------------------------------
6
-
7
-
8
- class Singleton:
9
- """Python Singleton BaseClass.
10
-
11
- Usage:
12
-
13
- class MyClass(Singleton, otherClass):
14
- pass
15
-
16
- obj = MyClass() # any new object will be taken from the same instance
17
-
18
- """
19
-
20
- _instance = None
21
-
22
- def __new__(cls, *args, **kwargs):
23
- """Singleton __new__ method."""
24
- if not isinstance(cls._instance, cls):
25
- # print(f'Initializing {cls.__name__}')
26
- cls._instance = object.__new__(cls)
27
- # print(f'Restoring {class_.__name__}')
28
- return cls._instance
1
+ # ----------------------------------------------------------------------------------------
2
+ # Python-Backpack - Pattern Utilities
3
+ # Maximiliano Rocamora / maxirocamora@gmail.com
4
+ # https://github.com/MaxRocamora/python-backpack
5
+ # ----------------------------------------------------------------------------------------
6
+
7
+
8
+ class Singleton:
9
+ """Python Singleton BaseClass.
10
+
11
+ Usage:
12
+
13
+ class MyClass(Singleton, otherClass):
14
+ pass
15
+
16
+ obj = MyClass() # any new object will be taken from the same instance
17
+
18
+ """
19
+
20
+ _instance = None
21
+
22
+ def __new__(cls, *args, **kwargs):
23
+ """Singleton __new__ method."""
24
+ if not isinstance(cls._instance, cls):
25
+ # print(f'Initializing {cls.__name__}')
26
+ cls._instance = object.__new__(cls)
27
+ # print(f'Restoring {class_.__name__}')
28
+ return cls._instance
backpack/strings.py CHANGED
@@ -1,123 +1,123 @@
1
- # ----------------------------------------------------------------------------------------
2
- # Python-Backpack - String Verification Methods
3
- # Maximiliano Rocamora / maxirocamora@gmail.com
4
- # https://github.com/MaxRocamora/python-backpack
5
- # ----------------------------------------------------------------------------------------
6
-
7
- import re
8
-
9
-
10
- def normalize_input_string(
11
- input_string: str,
12
- under_spaces: bool = True,
13
- under_hyphen: bool = True,
14
- replacer: str = '_',
15
- ) -> str:
16
- """Reformat the user input string.
17
-
18
- leaves only alphanumeric chars
19
- replaces spaces and hyphens for underscores.
20
-
21
- Args:
22
- input_string (str): input string to reformat
23
- under_spaces (bool, optional): replaces spaces. Defaults to True.
24
- under_hyphen (bool, optional): replaces hyphens. Defaults to True.
25
- replacer (str, optional): char to replace spaces and hyphens. Defaults to '_'.
26
-
27
- Returns:
28
- str: reformatted string
29
- """
30
-
31
- regex = re.sub(r'[^A-Za-z0-9 _-]+', '', input_string)
32
- if under_spaces:
33
- regex = regex.replace(' ', replacer)
34
- if under_hyphen:
35
- regex = regex.replace('-', replacer)
36
-
37
- return regex
38
-
39
-
40
- def begin_or_end_with_numbers(input_string: str) -> bool:
41
- """Returns true if the input string begins or ends with number."""
42
- return bool(input_string) and (
43
- _char_is_number(input_string[0]) or _char_is_number(input_string[-1])
44
- )
45
-
46
-
47
- def begin_with_number(input_string: str) -> bool:
48
- """Returns true if the input string begins with numbers."""
49
- return bool(input_string) and _char_is_number(input_string[0])
50
-
51
-
52
- def has_numbers(input_string: str) -> bool:
53
- """Returns true if the input string has numbers."""
54
- return any(_char_is_number(char) for char in input_string)
55
-
56
-
57
- def _char_is_number(char: str) -> bool:
58
- """Check if a string is a number.
59
-
60
- Args:
61
- char (str): character to evaluate
62
-
63
- Returns:
64
- bool: True if is a int
65
- """
66
-
67
- try:
68
- int(char)
69
- return True
70
- except ValueError:
71
- return False
72
-
73
-
74
- def camelcase_to_snakecase(input_string: str) -> str:
75
- """Convert a CamelCase string to snake_case string.
76
-
77
- Args:
78
- input_string (str): The CamelCase string to be converted.
79
-
80
- Returns:
81
- str: The converted snake_case string.
82
-
83
- Examples:
84
- OneDayCaseChar > one_day_case_char
85
- oneCamelCaseChar > one_camel_case_char
86
- HTTPServer > http_server
87
- _TwoCamel_CaseChar > _two_camel_case_char
88
- _LeadingUnderscore_case_ > _leading_underscore_case_
89
- """
90
-
91
- snake_str = ''
92
-
93
- # iterate over the string
94
- for index, char in enumerate(input_string):
95
- if not char.isalnum():
96
- snake_str += char
97
- continue
98
-
99
- if not char.isupper():
100
- snake_str += char
101
- continue
102
-
103
- prev_char = input_string[index - 1] if index > 0 else ''
104
- next_char = input_string[index + 1] if index + 1 < len(input_string) else ''
105
-
106
- # Add underscore only at real word boundaries.
107
- # This keeps acronyms together: HTTPServer -> http_server.
108
- should_add_underscore = (
109
- index > 0
110
- and prev_char != '_'
111
- and (
112
- prev_char.islower()
113
- or prev_char.isdigit()
114
- or (prev_char.isupper() and next_char.islower())
115
- )
116
- )
117
-
118
- if should_add_underscore:
119
- snake_str += '_'
120
-
121
- snake_str += char.lower()
122
-
123
- return snake_str
1
+ # ----------------------------------------------------------------------------------------
2
+ # Python-Backpack - String Verification Methods
3
+ # Maximiliano Rocamora / maxirocamora@gmail.com
4
+ # https://github.com/MaxRocamora/python-backpack
5
+ # ----------------------------------------------------------------------------------------
6
+
7
+ import re
8
+
9
+
10
+ def normalize_input_string(
11
+ input_string: str,
12
+ under_spaces: bool = True,
13
+ under_hyphen: bool = True,
14
+ replacer: str = '_',
15
+ ) -> str:
16
+ """Reformat the user input string.
17
+
18
+ leaves only alphanumeric chars
19
+ replaces spaces and hyphens for underscores.
20
+
21
+ Args:
22
+ input_string (str): input string to reformat
23
+ under_spaces (bool, optional): replaces spaces. Defaults to True.
24
+ under_hyphen (bool, optional): replaces hyphens. Defaults to True.
25
+ replacer (str, optional): char to replace spaces and hyphens. Defaults to '_'.
26
+
27
+ Returns:
28
+ str: reformatted string
29
+ """
30
+
31
+ regex = re.sub(r'[^A-Za-z0-9 _-]+', '', input_string)
32
+ if under_spaces:
33
+ regex = regex.replace(' ', replacer)
34
+ if under_hyphen:
35
+ regex = regex.replace('-', replacer)
36
+
37
+ return regex
38
+
39
+
40
+ def begin_or_end_with_numbers(input_string: str) -> bool:
41
+ """Returns true if the input string begins or ends with number."""
42
+ return bool(input_string) and (
43
+ _char_is_number(input_string[0]) or _char_is_number(input_string[-1])
44
+ )
45
+
46
+
47
+ def begin_with_number(input_string: str) -> bool:
48
+ """Returns true if the input string begins with numbers."""
49
+ return bool(input_string) and _char_is_number(input_string[0])
50
+
51
+
52
+ def has_numbers(input_string: str) -> bool:
53
+ """Returns true if the input string has numbers."""
54
+ return any(_char_is_number(char) for char in input_string)
55
+
56
+
57
+ def _char_is_number(char: str) -> bool:
58
+ """Check if a string is a number.
59
+
60
+ Args:
61
+ char (str): character to evaluate
62
+
63
+ Returns:
64
+ bool: True if is a int
65
+ """
66
+
67
+ try:
68
+ int(char)
69
+ return True
70
+ except ValueError:
71
+ return False
72
+
73
+
74
+ def camelcase_to_snakecase(input_string: str) -> str:
75
+ """Convert a CamelCase string to snake_case string.
76
+
77
+ Args:
78
+ input_string (str): The CamelCase string to be converted.
79
+
80
+ Returns:
81
+ str: The converted snake_case string.
82
+
83
+ Examples:
84
+ OneDayCaseChar > one_day_case_char
85
+ oneCamelCaseChar > one_camel_case_char
86
+ HTTPServer > http_server
87
+ _TwoCamel_CaseChar > _two_camel_case_char
88
+ _LeadingUnderscore_case_ > _leading_underscore_case_
89
+ """
90
+
91
+ snake_str = ''
92
+
93
+ # iterate over the string
94
+ for index, char in enumerate(input_string):
95
+ if not char.isalnum():
96
+ snake_str += char
97
+ continue
98
+
99
+ if not char.isupper():
100
+ snake_str += char
101
+ continue
102
+
103
+ prev_char = input_string[index - 1] if index > 0 else ''
104
+ next_char = input_string[index + 1] if index + 1 < len(input_string) else ''
105
+
106
+ # Add underscore only at real word boundaries.
107
+ # This keeps acronyms together: HTTPServer -> http_server.
108
+ should_add_underscore = (
109
+ index > 0
110
+ and prev_char != '_'
111
+ and (
112
+ prev_char.islower()
113
+ or prev_char.isdigit()
114
+ or (prev_char.isupper() and next_char.islower())
115
+ )
116
+ )
117
+
118
+ if should_add_underscore:
119
+ snake_str += '_'
120
+
121
+ snake_str += char.lower()
122
+
123
+ return snake_str
backpack/test_utils.py CHANGED
@@ -1,48 +1,48 @@
1
- # ----------------------------------------------------------------------------------------
2
- # Python-Backpack - Test Utilities
3
- # Maximiliano Rocamora / maxirocamora@gmail.com
4
- # https://github.com/MaxRocamora/python-backpack
5
- # ----------------------------------------------------------------------------------------
6
- import random
7
- import string
8
- import time
9
- from functools import wraps
10
- from typing import Callable, ParamSpec, TypeVar
11
-
12
- from backpack.logger import get_logger
13
-
14
- log = get_logger('Python Backpack - TestUtils')
15
-
16
- P = ParamSpec('P')
17
- R = TypeVar('R')
18
-
19
-
20
- def random_string(length: int = 10) -> str:
21
- """Generates a random string of fixed length.
22
-
23
- Args:
24
- length (int, optional): max string length. Defaults to 10.
25
-
26
- Returns:
27
- str: random string
28
- """
29
-
30
- letters = string.ascii_lowercase
31
- return ''.join(random.choice(letters) for _ in range(length))
32
-
33
-
34
- def time_function_decorator(method: Callable[P, R]) -> Callable[P, R]:
35
- """Decorator to measure methods execution time."""
36
-
37
- @wraps(method)
38
- def timed(*args: P.args, **kw: P.kwargs) -> R:
39
- ts = time.perf_counter()
40
- result = method(*args, **kw)
41
- te = time.perf_counter()
42
-
43
- message = f'{method.__name__!r} {(te - ts) * 1000:2.2f} ms'
44
- log.info(message)
45
-
46
- return result
47
-
48
- return timed
1
+ # ----------------------------------------------------------------------------------------
2
+ # Python-Backpack - Test Utilities
3
+ # Maximiliano Rocamora / maxirocamora@gmail.com
4
+ # https://github.com/MaxRocamora/python-backpack
5
+ # ----------------------------------------------------------------------------------------
6
+ import random
7
+ import string
8
+ import time
9
+ from functools import wraps
10
+ from typing import Callable, ParamSpec, TypeVar
11
+
12
+ from backpack.logger import get_logger
13
+
14
+ log = get_logger('Python Backpack - TestUtils')
15
+
16
+ P = ParamSpec('P')
17
+ R = TypeVar('R')
18
+
19
+
20
+ def random_string(length: int = 10) -> str:
21
+ """Generates a random string of fixed length.
22
+
23
+ Args:
24
+ length (int, optional): max string length. Defaults to 10.
25
+
26
+ Returns:
27
+ str: random string
28
+ """
29
+
30
+ letters = string.ascii_lowercase
31
+ return ''.join(random.choice(letters) for _ in range(length))
32
+
33
+
34
+ def time_function_decorator(method: Callable[P, R]) -> Callable[P, R]:
35
+ """Decorator to measure methods execution time."""
36
+
37
+ @wraps(method)
38
+ def timed(*args: P.args, **kw: P.kwargs) -> R:
39
+ ts = time.perf_counter()
40
+ result = method(*args, **kw)
41
+ te = time.perf_counter()
42
+
43
+ message = f'{method.__name__!r} {(te - ts) * 1000:2.2f} ms'
44
+ log.info(message)
45
+
46
+ return result
47
+
48
+ return timed
backpack/version.py CHANGED
@@ -1,24 +1,25 @@
1
- # ----------------------------------------------------------------------------------------
2
- # 1.0.0 07/2022 - Initial Release
3
- # 1.0.2 07/2022 - More Methods and tests
4
- # 1.0.3 07/2022 - JsonMetaFile, JsonUserSettings
5
- # 1.0.4 04/2023 - Added camelcase_to_snakecase function
6
- # 1.0.5 05/2023 - Refactor JsonMetaFile, Added Cache, Some type hints / docstrings
7
- # 1.1.0 01/2024 - Ruff Formatting
8
- # 1.1.1 07/2025 - Update Python version matrix to include 3.10 and 3.11, update actions versions
9
- # 1.1.2 07/2025 - Update actions versions, remove unused badge from README
10
- # 1.1.3 07/2025 - Improve test class names and docstrings for clarity, ruff formatting
11
- # 1.1.4 07/2025 - Moved from pipenv to poetry for dependency management.toml
12
- # 2.0.1 05/2026 - Remove tox workflow and setup.py, standardize uv + coverage tooling
13
- # 2.0.2 05/2026 - Add PyPI long description metadata and release polish
14
- # 2.0.3 06/2026 - Add type hints, refactor backpack.cache to use functools.lru_cache, added tests.
15
- # 2.0.4 08/2026 - Fixed get_version_from_filename().
16
- # ----------------------------------------------------------------------------------------
17
-
18
- __version__ = '2.0.4'
19
-
20
- VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH = map(int, __version__.split('.'))
21
-
22
- version = __version__
23
-
24
- app_name = 'python-backpack'
1
+ # ----------------------------------------------------------------------------------------
2
+ # 1.0.0 07/2022 - Initial Release
3
+ # 1.0.2 07/2022 - More Methods and tests
4
+ # 1.0.3 07/2022 - JsonMetaFile, JsonUserSettings
5
+ # 1.0.4 04/2023 - Added camelcase_to_snakecase function
6
+ # 1.0.5 05/2023 - Refactor JsonMetaFile, Added Cache, Some type hints / docstrings
7
+ # 1.1.0 01/2024 - Ruff Formatting
8
+ # 1.1.1 07/2025 - Update Python version matrix to include 3.10 and 3.11, update actions versions
9
+ # 1.1.2 07/2025 - Update actions versions, remove unused badge from README
10
+ # 1.1.3 07/2025 - Improve test class names and docstrings for clarity, ruff formatting
11
+ # 1.1.4 07/2025 - Moved from pipenv to poetry for dependency management.toml
12
+ # 2.0.1 05/2026 - Remove tox workflow and setup.py, standardize uv + coverage tooling
13
+ # 2.0.2 05/2026 - Add PyPI long description metadata and release polish
14
+ # 2.0.3 06/2026 - Add type hints, refactor backpack.cache to use functools.lru_cache, added tests.
15
+ # 2.0.4 08/2026 - Fixed get_version_from_filename().
16
+ # 2.0.5 09/2026 - Add PyPI publish workflow.
17
+ # ----------------------------------------------------------------------------------------
18
+
19
+ __version__ = '2.0.5'
20
+
21
+ VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH = map(int, __version__.split('.'))
22
+
23
+ version = __version__
24
+
25
+ app_name = 'python-backpack'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: python-backpack
3
- Version: 2.0.4
3
+ Version: 2.0.5
4
4
  Summary: Lightweight utilities for JSON, filesystem operations, strings, caching, and scripting.
5
5
  Project-URL: Homepage, https://github.com/MaxRocamora/python-backpack
6
6
  Project-URL: Repository, https://github.com/MaxRocamora/python-backpack.git
@@ -0,0 +1,17 @@
1
+ backpack/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ backpack/cache.py,sha256=tKGtakURa5xkqoip9LPZta2g0Rufca1uh29rdkRcdKU,2978
3
+ backpack/custom_errors.py,sha256=wQbhNcLl2f3YBoXQ_XIXZi4xqHbCjXa7ONw8-nmgidQ,1321
4
+ backpack/file_utils.py,sha256=b7KXy4XXzsGJfTPrPiNclL1FLH_1dwDlUfE0n-Mi5R4,4311
5
+ backpack/folder_utils.py,sha256=Kmb5R0tHIi7e-_iAyDTbBdHWJi_AP7rmtqdXUzGBko0,3160
6
+ backpack/json_metadata.py,sha256=VrNO-d2QtHJ2YZDBOBukJie2TIgjYPVnbwcz8mSITH0,4503
7
+ backpack/json_user_settings.py,sha256=nTnYF5uzHAnIc3ojf7Y5Av8btD0g8JJUBHo_YpQcKzM,2559
8
+ backpack/json_utils.py,sha256=kq5eNJYPQ0i_6JKnP4pTyVAXm8D9u4ZoZnwzosUowlE,1315
9
+ backpack/logger.py,sha256=jvkiRaLmOUn7MV-g8gYWeI7mXXXmdrOY4lTe38QIiVE,502
10
+ backpack/patterns.py,sha256=LBayEl9MtYwHqSvoiRGgxaDcXW1P6cHbjRNO5BvDGDQ,861
11
+ backpack/strings.py,sha256=PBB47E4G2Rd1kRuqqNDHIc_O2kai0ZVcn8obLVC3dCg,3488
12
+ backpack/test_utils.py,sha256=G6M6da2cB7bgC_MxiClbMGC0HOzIPlICm2oQqPAKUrI,1322
13
+ backpack/version.py,sha256=pwSBZZi0FOevw34Q_xBGS7v3Hci5EVIQrciOLV6bRDc,1326
14
+ python_backpack-2.0.5.dist-info/METADATA,sha256=qlgRuWmUzMpGA0KB7U4AvdWHP8vLhohU6IwMsvYOWAo,6665
15
+ python_backpack-2.0.5.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
16
+ python_backpack-2.0.5.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
17
+ python_backpack-2.0.5.dist-info/RECORD,,