python-backpack 2.0.3__tar.gz → 2.0.4__tar.gz

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.
Files changed (45) hide show
  1. {python_backpack-2.0.3 → python_backpack-2.0.4}/PKG-INFO +2 -2
  2. {python_backpack-2.0.3 → python_backpack-2.0.4}/backpack/file_utils.py +27 -22
  3. {python_backpack-2.0.3 → python_backpack-2.0.4}/backpack/version.py +3 -2
  4. {python_backpack-2.0.3 → python_backpack-2.0.4}/tests/test_file_utils.py +23 -0
  5. {python_backpack-2.0.3 → python_backpack-2.0.4}/tests/test_json_user_settings.py +8 -0
  6. {python_backpack-2.0.3 → python_backpack-2.0.4}/.github/workflows/codecov_tests.yml +0 -0
  7. {python_backpack-2.0.3 → python_backpack-2.0.4}/.gitignore +0 -0
  8. {python_backpack-2.0.3 → python_backpack-2.0.4}/LICENSE +0 -0
  9. {python_backpack-2.0.3 → python_backpack-2.0.4}/README.md +0 -0
  10. {python_backpack-2.0.3 → python_backpack-2.0.4}/__init__.py +0 -0
  11. {python_backpack-2.0.3 → python_backpack-2.0.4}/backpack/__init__.py +0 -0
  12. {python_backpack-2.0.3 → python_backpack-2.0.4}/backpack/cache.py +0 -0
  13. {python_backpack-2.0.3 → python_backpack-2.0.4}/backpack/custom_errors.py +0 -0
  14. {python_backpack-2.0.3 → python_backpack-2.0.4}/backpack/folder_utils.py +0 -0
  15. {python_backpack-2.0.3 → python_backpack-2.0.4}/backpack/json_metadata.py +0 -0
  16. {python_backpack-2.0.3 → python_backpack-2.0.4}/backpack/json_user_settings.py +0 -0
  17. {python_backpack-2.0.3 → python_backpack-2.0.4}/backpack/json_utils.py +0 -0
  18. {python_backpack-2.0.3 → python_backpack-2.0.4}/backpack/logger.py +0 -0
  19. {python_backpack-2.0.3 → python_backpack-2.0.4}/backpack/patterns.py +0 -0
  20. {python_backpack-2.0.3 → python_backpack-2.0.4}/backpack/strings.py +0 -0
  21. {python_backpack-2.0.3 → python_backpack-2.0.4}/backpack/test_utils.py +0 -0
  22. {python_backpack-2.0.3 → python_backpack-2.0.4}/backpack.code-workspace +0 -0
  23. {python_backpack-2.0.3 → python_backpack-2.0.4}/codecov.yml +0 -0
  24. {python_backpack-2.0.3 → python_backpack-2.0.4}/coverage.bat +0 -0
  25. {python_backpack-2.0.3 → python_backpack-2.0.4}/pyproject.toml +0 -0
  26. {python_backpack-2.0.3 → python_backpack-2.0.4}/ruff.toml +0 -0
  27. {python_backpack-2.0.3 → python_backpack-2.0.4}/tests/__init__.py +0 -0
  28. {python_backpack-2.0.3 → python_backpack-2.0.4}/tests/test_cache.py +0 -0
  29. {python_backpack-2.0.3 → python_backpack-2.0.4}/tests/test_errors.py +0 -0
  30. {python_backpack-2.0.3 → python_backpack-2.0.4}/tests/test_files/edited.txt +0 -0
  31. {python_backpack-2.0.3 → python_backpack-2.0.4}/tests/test_files/edited_remove.txt +0 -0
  32. {python_backpack-2.0.3 → python_backpack-2.0.4}/tests/test_files/locked_file.txt +0 -0
  33. {python_backpack-2.0.3 → python_backpack-2.0.4}/tests/test_files/origin.txt +0 -0
  34. {python_backpack-2.0.3 → python_backpack-2.0.4}/tests/test_files/origin_remove.txt +0 -0
  35. {python_backpack-2.0.3 → python_backpack-2.0.4}/tests/test_files/unlocked_file.txt +0 -0
  36. {python_backpack-2.0.3 → python_backpack-2.0.4}/tests/test_folder_utils.py +0 -0
  37. {python_backpack-2.0.3 → python_backpack-2.0.4}/tests/test_json/data.json +0 -0
  38. {python_backpack-2.0.3 → python_backpack-2.0.4}/tests/test_json/data_broken.json +0 -0
  39. {python_backpack-2.0.3 → python_backpack-2.0.4}/tests/test_json/save/data_saved.json +0 -0
  40. {python_backpack-2.0.3 → python_backpack-2.0.4}/tests/test_json_utils.py +0 -0
  41. {python_backpack-2.0.3 → python_backpack-2.0.4}/tests/test_jsonmd.py +0 -0
  42. {python_backpack-2.0.3 → python_backpack-2.0.4}/tests/test_misc.py +0 -0
  43. {python_backpack-2.0.3 → python_backpack-2.0.4}/tests/test_strings.py +0 -0
  44. {python_backpack-2.0.3 → python_backpack-2.0.4}/tests/test_test_utils.py +0 -0
  45. {python_backpack-2.0.3 → python_backpack-2.0.4}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: python-backpack
3
- Version: 2.0.3
3
+ Version: 2.0.4
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
@@ -4,6 +4,7 @@
4
4
  # https://github.com/MaxRocamora/python-backpack
5
5
  # ----------------------------------------------------------------------------------------
6
6
 
7
+ import re
7
8
  from collections.abc import Sequence
8
9
 
9
10
  from backpack.logger import get_logger
@@ -94,11 +95,12 @@ def file_is_writeable(filepath: str) -> bool:
94
95
  return False
95
96
 
96
97
 
97
- def get_version_from_filename(filename: str) -> str:
98
+ def get_version_from_filename(filename: str, separator: str | None = None) -> str:
98
99
  """Extracts version from filename.
99
100
 
100
101
  Args:
101
102
  filename: (str) file name to extract version from
103
+ separator: delimiter that must precede the version; when omitted, use supported delimiters
102
104
  Returns:
103
105
  str : version extracted from filename
104
106
 
@@ -107,29 +109,32 @@ def get_version_from_filename(filename: str) -> str:
107
109
  get_version_from_filename('myfile-9.txt') -> '9'
108
110
  get_version_from_filename('myfile.130.txt') -> '130'
109
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'
110
114
 
111
115
  """
112
116
 
113
117
  filename_no_ext = filename.rsplit('.', 1)[0]
114
118
 
115
- # guess is the separator is an underscore, dash, dot or v, and the version is the last part before the extension
116
- separators = ['_', '-', '.', 'v']
117
- if not any(sep in filename_no_ext for sep in separators):
118
- log.info(f'No separator found in filename: {filename_no_ext}. Using fallback extraction.')
119
- else:
120
- for sep in separators:
121
- if sep in filename_no_ext:
122
- parts = filename_no_ext.split(sep)
123
- version_part = parts[-1].split('.')[0] # Get the last part before the extension
124
- if version_part.replace('.', '').isdigit(): # Check if it's a valid version number
125
- log.info(
126
- f'Extracted version: {version_part} from filename: {filename} using separator: {sep}'
127
- )
128
- return version_part
129
-
130
- # Fallback: extract from the entire filename
131
- version = filename_no_ext.lstrip('v')
132
- version = version.replace('_', '.').replace('-', '.')
133
- version = version if version.replace('.', '').isdigit() else '0'
134
- log.info(f'Extracted version: {version} from filename: {filename}')
135
- return version
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'
@@ -11,10 +11,11 @@
11
11
  # 1.1.4 07/2025 - Moved from pipenv to poetry for dependency management.toml
12
12
  # 2.0.1 05/2026 - Remove tox workflow and setup.py, standardize uv + coverage tooling
13
13
  # 2.0.2 05/2026 - Add PyPI long description metadata and release polish
14
- # 2.0.3 06/2026 - Add type hints to backpack.cache, backpack.folder_utils, backpack.json_user_settings, backpack.json_utils, and backpack.logger, refactor backpack.cache to use functools.lru_cache, and add tests for backpack.cache and backpack.folder_utils.
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().
15
16
  # ----------------------------------------------------------------------------------------
16
17
 
17
- __version__ = '2.0.3'
18
+ __version__ = '2.0.4'
18
19
 
19
20
  VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH = map(int, __version__.split('.'))
20
21
 
@@ -79,8 +79,31 @@ def test_file_is_writeable(tmp_path):
79
79
  ('myfile.txt', '0'),
80
80
  ('mod_asset.1004.ma', '1004'),
81
81
  ('script_nuke_s100_v1005.ma', '1005'),
82
+ ('XD_shot_0000.0003.ma', '0003'),
83
+ ('ANM_shot_0010.0003.ma', '0003'),
82
84
  ],
83
85
  )
84
86
  def test_get_version_from_filename(filename: str, expected_version: str):
85
87
  """Version extraction should handle supported separators and invalid values."""
86
88
  assert get_version_from_filename(filename) == expected_version
89
+
90
+
91
+ @pytest.mark.parametrize(
92
+ ('filename', 'separator', 'expected_version'),
93
+ [
94
+ ('XD_shot_0000.0003.ma', '.', '0003'),
95
+ ('XD_shot_0000.0003.ma', '_', '0'),
96
+ ('myfile_v1002.txt', 'v', '1002'),
97
+ ],
98
+ )
99
+ def test_get_version_from_filename_with_separator(
100
+ filename: str, separator: str, expected_version: str
101
+ ):
102
+ """An explicit separator constrains version extraction to its final token."""
103
+ assert get_version_from_filename(filename, separator) == expected_version
104
+
105
+
106
+ def test_get_version_from_filename_rejects_empty_separator():
107
+ """An empty separator is not a meaningful version delimiter."""
108
+ with pytest.raises(ValueError, match='separator must not be empty'):
109
+ get_version_from_filename('myfile_23.txt', '')
@@ -65,3 +65,11 @@ def test_user_settings_creates_missing_directory(settings_home):
65
65
 
66
66
  assert target_dir.exists() is True
67
67
  assert Path(js.filepath).parent == target_dir
68
+
69
+
70
+ def test_os_user_folder_returns_home_directory(monkeypatch):
71
+ """The unpatched property should resolve the current user's home directory."""
72
+ monkeypatch.undo()
73
+ settings = object.__new__(JsonUserSettings)
74
+
75
+ assert settings.os_user_folder == str(Path.home())
File without changes
File without changes