python-backpack 2.0.3__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 -135
- 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 -23
- {python_backpack-2.0.3.dist-info → python_backpack-2.0.5.dist-info}/METADATA +2 -2
- python_backpack-2.0.5.dist-info/RECORD +17 -0
- {python_backpack-2.0.3.dist-info → python_backpack-2.0.5.dist-info}/WHEEL +1 -1
- {python_backpack-2.0.3.dist-info → python_backpack-2.0.5.dist-info}/licenses/LICENSE +674 -674
- python_backpack-2.0.3.dist-info/RECORD +0 -17
backpack/cache.py
CHANGED
|
@@ -1,93 +1,93 @@
|
|
|
1
|
-
# ----------------------------------------------------------------------------------------
|
|
2
|
-
# Python-Backpack - Pattern Utilities
|
|
3
|
-
# Maximiliano Rocamora / maxirocamora@gmail.com
|
|
4
|
-
# https://github.com/MaxRocamora/python-backpack
|
|
5
|
-
# ----------------------------------------------------------------------------------------
|
|
6
|
-
from datetime import datetime, timedelta, timezone
|
|
7
|
-
from functools import lru_cache, wraps
|
|
8
|
-
from typing import Any, Callable, Protocol, TypeVar, cast
|
|
9
|
-
|
|
10
|
-
from backpack.logger import get_logger
|
|
11
|
-
|
|
12
|
-
log = get_logger('Python Backpack - Cache')
|
|
13
|
-
|
|
14
|
-
R = TypeVar('R', covariant=True)
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class TimedCachedCallable(Protocol[R]):
|
|
18
|
-
"""Callable returned by timed_lru_cache with cache control kwargs."""
|
|
19
|
-
|
|
20
|
-
def __call__(
|
|
21
|
-
self,
|
|
22
|
-
*args: Any,
|
|
23
|
-
force_clear: bool = False,
|
|
24
|
-
show_log: bool = False,
|
|
25
|
-
**kwargs: Any,
|
|
26
|
-
) -> R:
|
|
27
|
-
"""Call the cached function, optionally forcing a cache clear first."""
|
|
28
|
-
...
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def timed_lru_cache(
|
|
32
|
-
seconds: int,
|
|
33
|
-
maxsize: int = 128,
|
|
34
|
-
) -> Callable[[Callable[..., R]], TimedCachedCallable[R]]:
|
|
35
|
-
"""Lru_cache with expiration time.
|
|
36
|
-
|
|
37
|
-
Args:
|
|
38
|
-
seconds (int): expiration time in seconds
|
|
39
|
-
maxsize (int): maxsize for lru_cache
|
|
40
|
-
Note:
|
|
41
|
-
Wrapped function can be forced to clear cache with: force_clear=True
|
|
42
|
-
Wrapped function can show log on clear with: show_log=True
|
|
43
|
-
Returns:
|
|
44
|
-
function result
|
|
45
|
-
|
|
46
|
-
# * Usage:
|
|
47
|
-
|
|
48
|
-
# * Add the decorator to your function
|
|
49
|
-
|
|
50
|
-
@timed_lru_cache(seconds=60)
|
|
51
|
-
def my_function():
|
|
52
|
-
return 'Hello World'
|
|
53
|
-
|
|
54
|
-
# * to clear the cache, use force_clear=True on the function call
|
|
55
|
-
my_function(force_clear=True)
|
|
56
|
-
|
|
57
|
-
"""
|
|
58
|
-
|
|
59
|
-
def wrapper_cache(func: Callable[..., R]) -> TimedCachedCallable[R]:
|
|
60
|
-
cached_func = cast(Any, lru_cache(maxsize=maxsize)(func))
|
|
61
|
-
cached_func.lifetime = timedelta(seconds=seconds)
|
|
62
|
-
cached_func.expiration = datetime.now(timezone.utc) + cached_func.lifetime
|
|
63
|
-
|
|
64
|
-
@wraps(func)
|
|
65
|
-
def wrapped_func(
|
|
66
|
-
*args: Any,
|
|
67
|
-
force_clear: bool = False,
|
|
68
|
-
show_log: bool = False,
|
|
69
|
-
**kwargs: Any,
|
|
70
|
-
) -> R:
|
|
71
|
-
"""Wrapper function for lru_cache with expiration time.
|
|
72
|
-
|
|
73
|
-
Args:
|
|
74
|
-
*args: function arguments
|
|
75
|
-
force_clear (bool): forces a clear cache
|
|
76
|
-
show_log (bool): show log on clear
|
|
77
|
-
**kwargs: function keyword arguments
|
|
78
|
-
Returns:
|
|
79
|
-
function result
|
|
80
|
-
"""
|
|
81
|
-
|
|
82
|
-
if force_clear or datetime.now(timezone.utc) >= cached_func.expiration:
|
|
83
|
-
if show_log:
|
|
84
|
-
log.debug(f'Cache cleared for {cached_func.__name__}')
|
|
85
|
-
|
|
86
|
-
cached_func.cache_clear()
|
|
87
|
-
cached_func.expiration = datetime.now(timezone.utc) + cached_func.lifetime
|
|
88
|
-
|
|
89
|
-
return cached_func(*args, **kwargs)
|
|
90
|
-
|
|
91
|
-
return cast(TimedCachedCallable[R], wrapped_func)
|
|
92
|
-
|
|
93
|
-
return wrapper_cache
|
|
1
|
+
# ----------------------------------------------------------------------------------------
|
|
2
|
+
# Python-Backpack - Pattern Utilities
|
|
3
|
+
# Maximiliano Rocamora / maxirocamora@gmail.com
|
|
4
|
+
# https://github.com/MaxRocamora/python-backpack
|
|
5
|
+
# ----------------------------------------------------------------------------------------
|
|
6
|
+
from datetime import datetime, timedelta, timezone
|
|
7
|
+
from functools import lru_cache, wraps
|
|
8
|
+
from typing import Any, Callable, Protocol, TypeVar, cast
|
|
9
|
+
|
|
10
|
+
from backpack.logger import get_logger
|
|
11
|
+
|
|
12
|
+
log = get_logger('Python Backpack - Cache')
|
|
13
|
+
|
|
14
|
+
R = TypeVar('R', covariant=True)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class TimedCachedCallable(Protocol[R]):
|
|
18
|
+
"""Callable returned by timed_lru_cache with cache control kwargs."""
|
|
19
|
+
|
|
20
|
+
def __call__(
|
|
21
|
+
self,
|
|
22
|
+
*args: Any,
|
|
23
|
+
force_clear: bool = False,
|
|
24
|
+
show_log: bool = False,
|
|
25
|
+
**kwargs: Any,
|
|
26
|
+
) -> R:
|
|
27
|
+
"""Call the cached function, optionally forcing a cache clear first."""
|
|
28
|
+
...
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def timed_lru_cache(
|
|
32
|
+
seconds: int,
|
|
33
|
+
maxsize: int = 128,
|
|
34
|
+
) -> Callable[[Callable[..., R]], TimedCachedCallable[R]]:
|
|
35
|
+
"""Lru_cache with expiration time.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
seconds (int): expiration time in seconds
|
|
39
|
+
maxsize (int): maxsize for lru_cache
|
|
40
|
+
Note:
|
|
41
|
+
Wrapped function can be forced to clear cache with: force_clear=True
|
|
42
|
+
Wrapped function can show log on clear with: show_log=True
|
|
43
|
+
Returns:
|
|
44
|
+
function result
|
|
45
|
+
|
|
46
|
+
# * Usage:
|
|
47
|
+
|
|
48
|
+
# * Add the decorator to your function
|
|
49
|
+
|
|
50
|
+
@timed_lru_cache(seconds=60)
|
|
51
|
+
def my_function():
|
|
52
|
+
return 'Hello World'
|
|
53
|
+
|
|
54
|
+
# * to clear the cache, use force_clear=True on the function call
|
|
55
|
+
my_function(force_clear=True)
|
|
56
|
+
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
def wrapper_cache(func: Callable[..., R]) -> TimedCachedCallable[R]:
|
|
60
|
+
cached_func = cast(Any, lru_cache(maxsize=maxsize)(func))
|
|
61
|
+
cached_func.lifetime = timedelta(seconds=seconds)
|
|
62
|
+
cached_func.expiration = datetime.now(timezone.utc) + cached_func.lifetime
|
|
63
|
+
|
|
64
|
+
@wraps(func)
|
|
65
|
+
def wrapped_func(
|
|
66
|
+
*args: Any,
|
|
67
|
+
force_clear: bool = False,
|
|
68
|
+
show_log: bool = False,
|
|
69
|
+
**kwargs: Any,
|
|
70
|
+
) -> R:
|
|
71
|
+
"""Wrapper function for lru_cache with expiration time.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
*args: function arguments
|
|
75
|
+
force_clear (bool): forces a clear cache
|
|
76
|
+
show_log (bool): show log on clear
|
|
77
|
+
**kwargs: function keyword arguments
|
|
78
|
+
Returns:
|
|
79
|
+
function result
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
if force_clear or datetime.now(timezone.utc) >= cached_func.expiration:
|
|
83
|
+
if show_log:
|
|
84
|
+
log.debug(f'Cache cleared for {cached_func.__name__}')
|
|
85
|
+
|
|
86
|
+
cached_func.cache_clear()
|
|
87
|
+
cached_func.expiration = datetime.now(timezone.utc) + cached_func.lifetime
|
|
88
|
+
|
|
89
|
+
return cached_func(*args, **kwargs)
|
|
90
|
+
|
|
91
|
+
return cast(TimedCachedCallable[R], wrapped_func)
|
|
92
|
+
|
|
93
|
+
return wrapper_cache
|
backpack/custom_errors.py
CHANGED
|
@@ -1,37 +1,37 @@
|
|
|
1
|
-
# ----------------------------------------------------------------------------------------
|
|
2
|
-
# Python-Backpack - Custom Exceptions
|
|
3
|
-
# Maximiliano Rocamora / maxirocamora@gmail.com
|
|
4
|
-
# https://github.com/MaxRocamora/python-backpack
|
|
5
|
-
# ----------------------------------------------------------------------------------------
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class EnvironmentVariableNotFoundError(Exception):
|
|
9
|
-
def __init__(self, var_name: str) -> None:
|
|
10
|
-
"""Error Raised when a required environment variable is missing from os.
|
|
11
|
-
|
|
12
|
-
Args:
|
|
13
|
-
var_name (str): name of the required variable missing
|
|
14
|
-
"""
|
|
15
|
-
self.var_name = var_name
|
|
16
|
-
self.message = f'Required Environment Variable [{var_name}] not found.'
|
|
17
|
-
super().__init__(self.message)
|
|
18
|
-
|
|
19
|
-
def __str__(self):
|
|
20
|
-
"""Return the error message."""
|
|
21
|
-
return self.message
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
class ApplicationNotFoundError(Exception):
|
|
25
|
-
def __init__(self, app_name: str) -> None:
|
|
26
|
-
"""Error Raised when an Application Name required is not found.
|
|
27
|
-
|
|
28
|
-
Args:
|
|
29
|
-
app_name (str): name of the required application missing
|
|
30
|
-
"""
|
|
31
|
-
self.app_name = app_name
|
|
32
|
-
self.message = f'Application ({app_name}) not found.'
|
|
33
|
-
super().__init__(self.message)
|
|
34
|
-
|
|
35
|
-
def __str__(self):
|
|
36
|
-
"""Return the error message."""
|
|
37
|
-
return self.message
|
|
1
|
+
# ----------------------------------------------------------------------------------------
|
|
2
|
+
# Python-Backpack - Custom Exceptions
|
|
3
|
+
# Maximiliano Rocamora / maxirocamora@gmail.com
|
|
4
|
+
# https://github.com/MaxRocamora/python-backpack
|
|
5
|
+
# ----------------------------------------------------------------------------------------
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class EnvironmentVariableNotFoundError(Exception):
|
|
9
|
+
def __init__(self, var_name: str) -> None:
|
|
10
|
+
"""Error Raised when a required environment variable is missing from os.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
var_name (str): name of the required variable missing
|
|
14
|
+
"""
|
|
15
|
+
self.var_name = var_name
|
|
16
|
+
self.message = f'Required Environment Variable [{var_name}] not found.'
|
|
17
|
+
super().__init__(self.message)
|
|
18
|
+
|
|
19
|
+
def __str__(self):
|
|
20
|
+
"""Return the error message."""
|
|
21
|
+
return self.message
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ApplicationNotFoundError(Exception):
|
|
25
|
+
def __init__(self, app_name: str) -> None:
|
|
26
|
+
"""Error Raised when an Application Name required is not found.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
app_name (str): name of the required application missing
|
|
30
|
+
"""
|
|
31
|
+
self.app_name = app_name
|
|
32
|
+
self.message = f'Application ({app_name}) not found.'
|
|
33
|
+
super().__init__(self.message)
|
|
34
|
+
|
|
35
|
+
def __str__(self):
|
|
36
|
+
"""Return the error message."""
|
|
37
|
+
return self.message
|
backpack/file_utils.py
CHANGED
|
@@ -1,135 +1,140 @@
|
|
|
1
|
-
# ----------------------------------------------------------------------------------------
|
|
2
|
-
# Python-Backpack - File Utilities - Ascii Files Manipulation
|
|
3
|
-
# Maximiliano Rocamora / maxirocamora@gmail.com
|
|
4
|
-
# https://github.com/MaxRocamora/python-backpack
|
|
5
|
-
# ----------------------------------------------------------------------------------------
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
log.info('
|
|
40
|
-
log.info('
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
log.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
get_version_from_filename('
|
|
109
|
-
get_version_from_filename('
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
1
|
+
# ----------------------------------------------------------------------------------------
|
|
2
|
+
# Python-Backpack - File Utilities - Ascii Files Manipulation
|
|
3
|
+
# Maximiliano Rocamora / maxirocamora@gmail.com
|
|
4
|
+
# https://github.com/MaxRocamora/python-backpack
|
|
5
|
+
# ----------------------------------------------------------------------------------------
|
|
6
|
+
|
|
7
|
+
import re
|
|
8
|
+
from collections.abc import Sequence
|
|
9
|
+
|
|
10
|
+
from backpack.logger import get_logger
|
|
11
|
+
|
|
12
|
+
log = get_logger('Python Backpack - FileUtils')
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def replace_strings_in_file(
|
|
16
|
+
ascii_file: str,
|
|
17
|
+
strings: Sequence[str],
|
|
18
|
+
new_string: str,
|
|
19
|
+
) -> None:
|
|
20
|
+
"""Opens ascii file and replaces all occurrences from strings into new_string.
|
|
21
|
+
|
|
22
|
+
In this class we use a full path to avoid use of os.dirname, which
|
|
23
|
+
causes string encode problems.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
ascii_file: (fullpath string) file to open
|
|
27
|
+
strings: (list) strings to replace
|
|
28
|
+
new_string: (string) new string or path to set
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
# force forward slashes
|
|
32
|
+
new_string = new_string.replace('\\', '/')
|
|
33
|
+
|
|
34
|
+
log.info(f'Replacing Strings, Opening File: {ascii_file}')
|
|
35
|
+
|
|
36
|
+
with open(ascii_file, newline='') as f:
|
|
37
|
+
file_data = f.read()
|
|
38
|
+
for i in strings:
|
|
39
|
+
log.info('Finding: %s', i)
|
|
40
|
+
log.info('Replacing for: %s', new_string)
|
|
41
|
+
log.info('-' * 50)
|
|
42
|
+
file_data = file_data.replace(i, new_string)
|
|
43
|
+
|
|
44
|
+
with open(ascii_file, 'w', newline='') as f:
|
|
45
|
+
f.write(file_data)
|
|
46
|
+
log.info(f'Closing File: {ascii_file}')
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def remove_line_from_file(
|
|
50
|
+
ascii_file: str,
|
|
51
|
+
strings: Sequence[str],
|
|
52
|
+
verbose: bool = False,
|
|
53
|
+
) -> None:
|
|
54
|
+
"""Removes given lines from ascii file.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
ascii_file: (string path) ASCII file to process
|
|
58
|
+
strings: (list) match lines to remove
|
|
59
|
+
verbose: (bool) if true, prints removed lines
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
retained_lines = []
|
|
63
|
+
with open(ascii_file, newline='') as f:
|
|
64
|
+
file_content = f.readlines()
|
|
65
|
+
|
|
66
|
+
for line in file_content:
|
|
67
|
+
value = line.rstrip('\r\n')
|
|
68
|
+
if verbose:
|
|
69
|
+
log.info(f'Checking line: {value}')
|
|
70
|
+
if value in strings:
|
|
71
|
+
if verbose:
|
|
72
|
+
log.info(f'Removing line: {value}')
|
|
73
|
+
else:
|
|
74
|
+
retained_lines.append(line)
|
|
75
|
+
|
|
76
|
+
with open(ascii_file, 'w', newline='') as f:
|
|
77
|
+
f.writelines(retained_lines)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def file_is_writeable(filepath: str) -> bool:
|
|
81
|
+
"""Checks if a file is locked and can be overwritten.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
filepath: (str) file to check
|
|
85
|
+
Returns:
|
|
86
|
+
bool : true if file is writeable
|
|
87
|
+
"""
|
|
88
|
+
try:
|
|
89
|
+
with open(filepath, 'r+') as _:
|
|
90
|
+
return True
|
|
91
|
+
except OSError as x:
|
|
92
|
+
log.warning(f'{filepath} is locked ')
|
|
93
|
+
log.info(x.strerror)
|
|
94
|
+
|
|
95
|
+
return False
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def get_version_from_filename(filename: str, separator: str | None = None) -> str:
|
|
99
|
+
"""Extracts version from filename.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
filename: (str) file name to extract version from
|
|
103
|
+
separator: delimiter that must precede the version; when omitted, use supported delimiters
|
|
104
|
+
Returns:
|
|
105
|
+
str : version extracted from filename
|
|
106
|
+
|
|
107
|
+
Examples:
|
|
108
|
+
get_version_from_filename('myfile_23.txt') -> '23'
|
|
109
|
+
get_version_from_filename('myfile-9.txt') -> '9'
|
|
110
|
+
get_version_from_filename('myfile.130.txt') -> '130'
|
|
111
|
+
get_version_from_filename('myfile_v1002.txt') -> '1002'
|
|
112
|
+
get_version_from_filename('XD_shot_0000.0003.ma') -> '0003'
|
|
113
|
+
get_version_from_filename('ANM_shot_0010.0003.ma') -> '0003'
|
|
114
|
+
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
filename_no_ext = filename.rsplit('.', 1)[0]
|
|
118
|
+
|
|
119
|
+
if separator is not None:
|
|
120
|
+
if not separator:
|
|
121
|
+
raise ValueError('separator must not be empty')
|
|
122
|
+
|
|
123
|
+
version = filename_no_ext.rsplit(separator, 1)[-1]
|
|
124
|
+
if version.isdigit():
|
|
125
|
+
log.info(
|
|
126
|
+
f'Extracted version: {version} from filename: {filename} using separator: {separator}'
|
|
127
|
+
)
|
|
128
|
+
return version
|
|
129
|
+
|
|
130
|
+
log.info(f'No numeric version found in filename: {filename} using separator: {separator}')
|
|
131
|
+
return '0'
|
|
132
|
+
|
|
133
|
+
match = re.search(r'(?:^|[_.-])v?(\d+)$', filename_no_ext)
|
|
134
|
+
if match:
|
|
135
|
+
version = match.group(1)
|
|
136
|
+
log.info(f'Extracted version: {version} from filename: {filename}')
|
|
137
|
+
return version
|
|
138
|
+
|
|
139
|
+
log.info(f'No numeric version found in filename: {filename}')
|
|
140
|
+
return '0'
|