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/cache.py +93 -93
- backpack/custom_errors.py +37 -37
- backpack/file_utils.py +140 -140
- backpack/folder_utils.py +105 -105
- backpack/json_metadata.py +135 -135
- backpack/json_user_settings.py +85 -85
- backpack/json_utils.py +53 -53
- backpack/logger.py +16 -16
- backpack/patterns.py +28 -28
- backpack/strings.py +123 -123
- backpack/test_utils.py +48 -48
- backpack/version.py +25 -24
- {python_backpack-2.0.4.dist-info → python_backpack-2.0.5.dist-info}/METADATA +1 -1
- python_backpack-2.0.5.dist-info/RECORD +17 -0
- {python_backpack-2.0.4.dist-info → python_backpack-2.0.5.dist-info}/licenses/LICENSE +674 -674
- python_backpack-2.0.4.dist-info/RECORD +0 -17
- {python_backpack-2.0.4.dist-info → python_backpack-2.0.5.dist-info}/WHEEL +0 -0
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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.
|
|
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,,
|