python-backpack 2.0.0__tar.gz → 2.0.1__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 (47) hide show
  1. python_backpack-2.0.1/.github/workflows/codecov_tests.yml +51 -0
  2. python_backpack-2.0.1/.gitignore +131 -0
  3. python_backpack-2.0.1/PKG-INFO +7 -0
  4. python_backpack-2.0.1/README.md +144 -0
  5. python_backpack-2.0.1/backpack/__init__.py +0 -0
  6. {python_backpack-2.0.0 → python_backpack-2.0.1}/backpack/file_utils.py +41 -0
  7. {python_backpack-2.0.0 → python_backpack-2.0.1}/backpack/json_utils.py +1 -1
  8. {python_backpack-2.0.0 → python_backpack-2.0.1}/backpack/version.py +2 -2
  9. python_backpack-2.0.1/backpack.code-workspace +63 -0
  10. python_backpack-2.0.1/codecov.yml +2 -0
  11. python_backpack-2.0.1/coverage.bat +9 -0
  12. python_backpack-2.0.1/pyproject.toml +29 -0
  13. python_backpack-2.0.1/ruff.toml +26 -0
  14. python_backpack-2.0.1/tests/__init__.py +0 -0
  15. python_backpack-2.0.1/tests/test_cache.py +20 -0
  16. python_backpack-2.0.1/tests/test_errors.py +47 -0
  17. python_backpack-2.0.1/tests/test_file_utils.py +71 -0
  18. python_backpack-2.0.1/tests/test_files/edited.txt +9 -0
  19. python_backpack-2.0.1/tests/test_files/edited_remove.txt +5 -0
  20. python_backpack-2.0.1/tests/test_files/locked_file.txt +1 -0
  21. python_backpack-2.0.1/tests/test_files/origin.txt +9 -0
  22. python_backpack-2.0.1/tests/test_files/origin_remove.txt +8 -0
  23. python_backpack-2.0.1/tests/test_files/unlocked_file.txt +0 -0
  24. python_backpack-2.0.1/tests/test_folder_utils.py +105 -0
  25. python_backpack-2.0.1/tests/test_json/data.json +13 -0
  26. python_backpack-2.0.1/tests/test_json/data_broken.json +13 -0
  27. python_backpack-2.0.1/tests/test_json/save/data_saved.json +0 -0
  28. python_backpack-2.0.1/tests/test_json_user_settings.py +90 -0
  29. python_backpack-2.0.1/tests/test_json_utils.py +46 -0
  30. python_backpack-2.0.1/tests/test_jsonmd.py +88 -0
  31. python_backpack-2.0.1/tests/test_misc.py +47 -0
  32. python_backpack-2.0.1/tests/test_strings.py +125 -0
  33. python_backpack-2.0.1/tests/test_test_utils.py +35 -0
  34. python_backpack-2.0.1/uv.lock +295 -0
  35. python_backpack-2.0.0/PKG-INFO +0 -12
  36. python_backpack-2.0.0/pyproject.toml +0 -22
  37. {python_backpack-2.0.0 → python_backpack-2.0.1}/LICENSE +0 -0
  38. {python_backpack-2.0.0/backpack → python_backpack-2.0.1}/__init__.py +0 -0
  39. {python_backpack-2.0.0 → python_backpack-2.0.1}/backpack/cache.py +0 -0
  40. {python_backpack-2.0.0 → python_backpack-2.0.1}/backpack/custom_errors.py +0 -0
  41. {python_backpack-2.0.0 → python_backpack-2.0.1}/backpack/folder_utils.py +0 -0
  42. {python_backpack-2.0.0 → python_backpack-2.0.1}/backpack/json_metadata.py +0 -0
  43. {python_backpack-2.0.0 → python_backpack-2.0.1}/backpack/json_user_settings.py +0 -0
  44. {python_backpack-2.0.0 → python_backpack-2.0.1}/backpack/logger.py +0 -0
  45. {python_backpack-2.0.0 → python_backpack-2.0.1}/backpack/patterns.py +0 -0
  46. {python_backpack-2.0.0 → python_backpack-2.0.1}/backpack/strings.py +0 -0
  47. {python_backpack-2.0.0 → python_backpack-2.0.1}/backpack/test_utils.py +0 -0
@@ -0,0 +1,51 @@
1
+ name: Python Tests & Coverage
2
+
3
+ on:
4
+ - push
5
+ - pull_request
6
+
7
+ jobs:
8
+ build:
9
+ runs-on: ubuntu-latest
10
+ strategy:
11
+ matrix:
12
+ python-version: ['3.11']
13
+
14
+ steps:
15
+ - uses: actions/checkout@v6
16
+
17
+ - name: Set up Python ${{ matrix.python-version }}
18
+ uses: actions/setup-python@v6
19
+ with:
20
+ python-version: ${{ matrix.python-version }}
21
+
22
+ - name: Add path to PYTHONPATH
23
+ run: |
24
+ echo "PYTHONPATH=$GITHUB_WORKSPACE" >> $GITHUB_ENV
25
+
26
+ - name: Install dependencies
27
+ run: |
28
+ python -m pip install --upgrade pip
29
+ pip install uv
30
+ uv sync --dev
31
+
32
+ - name: Run tests with coverage
33
+ run: |
34
+ uv run coverage run --source=backpack -m pytest
35
+ uv run coverage report -m --fail-under=70
36
+ uv run coverage xml
37
+
38
+ - name: Upload coverage.xml
39
+ if: ${{ matrix.python-version == '3.11' }}
40
+ uses: actions/upload-artifact@v7
41
+ with:
42
+ name: upload coverage
43
+ path: ${{ github.workspace }}/coverage.xml
44
+ if-no-files-found: warn
45
+
46
+ # upload coverage.xml to codecov
47
+ - name: Upload coverage.xml to codecov
48
+ if: ${{ matrix.python-version == '3.11' }}
49
+ uses: codecov/codecov-action@v6
50
+ with:
51
+ token: ${{ secrets.CODECOV_TOKEN }}
@@ -0,0 +1,131 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ pip-wheel-metadata/
24
+ share/python-wheels/
25
+ *.egg-info/
26
+ .installed.cfg
27
+ *.egg
28
+ MANIFEST
29
+
30
+ # PyInstaller
31
+ # Usually these files are written by a python script from a template
32
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
33
+ *.manifest
34
+ *.spec
35
+
36
+ # Installer logs
37
+ pip-log.txt
38
+ pip-delete-this-directory.txt
39
+
40
+ # Unit test / coverage reports
41
+ htmlcov/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py,cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+
53
+ # Translations
54
+ *.mo
55
+ *.pot
56
+
57
+ # Django stuff:
58
+ *.log
59
+ local_settings.py
60
+ db.sqlite3
61
+ db.sqlite3-journal
62
+
63
+ # Flask stuff:
64
+ instance/
65
+ .webassets-cache
66
+
67
+ # Scrapy stuff:
68
+ .scrapy
69
+
70
+ # Sphinx documentation
71
+ docs/_build/
72
+
73
+ # PyBuilder
74
+ target/
75
+
76
+ # Jupyter Notebook
77
+ .ipynb_checkpoints
78
+
79
+ # IPython
80
+ profile_default/
81
+ ipython_config.py
82
+
83
+ # pyenv
84
+ .python-version
85
+
86
+ # pipenv
87
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
88
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
89
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
90
+ # install all needed dependencies.
91
+ #Pipfile.lock
92
+
93
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow
94
+ __pypackages__/
95
+
96
+ # Celery stuff
97
+ celerybeat-schedule
98
+ celerybeat.pid
99
+
100
+ # SageMath parsed files
101
+ *.sage.py
102
+
103
+ # Environments
104
+ .env
105
+ .venv
106
+ env/
107
+ venv/
108
+ ENV/
109
+ env.bak/
110
+ venv.bak/
111
+
112
+ # Spyder project settings
113
+ .spyderproject
114
+ .spyproject
115
+
116
+ # Rope project settings
117
+ .ropeproject
118
+
119
+ # mkdocs documentation
120
+ /site
121
+
122
+ # mypy
123
+ .mypy_cache/
124
+ .dmypy.json
125
+ dmypy.json
126
+
127
+ # Pyre type checker
128
+ .pyre/
129
+
130
+ # test generated files
131
+ tests/test_json/MD_test.json
@@ -0,0 +1,7 @@
1
+ Metadata-Version: 2.4
2
+ Name: python-backpack
3
+ Version: 2.0.1
4
+ Summary: A collection of personal scripts for json, File/Folder Operations, String Validation, Custom Errors, Cache and stuff.
5
+ Author: Maximiliano Rocamora
6
+ License-File: LICENSE
7
+ Requires-Python: >=3.11
@@ -0,0 +1,144 @@
1
+ [![PyPI Supported Python Versions](https://img.shields.io/pypi/pyversions/python-backpack.svg?style=flat-square&logo=appveyor)](https://pypi.python.org/pypi/python-backpack/)
2
+ [![PyPI version](https://badge.fury.io/py/python-backpack.svg?style=flat-square&logo=appveyor)](https://badge.fury.io/py/python-backpack)
3
+ [![GitHub version](https://badge.fury.io/gh/MaxRocamora%2Fpython-backpack.svg?style=flat-square&logo=appveyor)](https://badge.fury.io/gh/MaxRocamora%2Fpython-backpack)
4
+ [![codecov](https://codecov.io/gh/MaxRocamora/python-backpack/graph/badge.svg?token=6D1xwYdXW2)](https://codecov.io/gh/MaxRocamora/python-backpack)
5
+
6
+
7
+ # Python-Backpack
8
+
9
+ Python-Backpack is a lightweight utility collection for common scripting tasks:
10
+
11
+ - JSON load/save helpers with validation
12
+ - user settings persistence under the OS user directory
13
+ - metadata export/import to JSON files
14
+ - file and folder operations
15
+ - string normalization and case conversion
16
+ - cache decorator with expiration support
17
+ - custom exceptions, logging helper, singleton pattern, and testing helpers
18
+
19
+ ## Compatibility
20
+
21
+ - Python 3.11+
22
+
23
+ ## Installation
24
+
25
+ ```bash
26
+ pip install python-backpack
27
+ ```
28
+
29
+ ## Package API Reference
30
+
31
+ ### Cache (`backpack.cache`)
32
+
33
+ - `timed_lru_cache(seconds: int, maxsize: int = 128)`
34
+ - `functools.lru_cache` decorator with expiration time.
35
+ - Wrapped calls support `force_clear=True` and `show_log=True`.
36
+
37
+ ### Custom Errors (`backpack.custom_errors`)
38
+
39
+ - `EnvironmentVariableNotFoundError(var_name: str)`
40
+ - Raised when a required environment variable is missing.
41
+ - `ApplicationNotFoundError(app_name: str)`
42
+ - Raised when a required application is not found.
43
+
44
+ ### File Utils (`backpack.file_utils`)
45
+
46
+ - `replace_strings_in_file(ascii_file: str, strings: list, new_string: str) -> None`
47
+ - Replaces multiple string occurrences in a text file.
48
+ - `remove_line_from_file(ascii_file: str, strings: list, verbose: bool = False) -> None`
49
+ - Removes exact matching lines from a text file.
50
+ - `file_is_writeable(filepath: str) -> bool`
51
+ - Checks whether a file can be opened for read/write.
52
+
53
+ ### Folder Utils (`backpack.folder_utils`)
54
+
55
+ - `browse_folder(folder: str) -> bool`
56
+ - Opens a folder in Windows Explorer.
57
+ - `create_folders(folders: list, force_empty: bool = False, verbose: bool = False)`
58
+ - Creates multiple folders.
59
+ - `create_folder(path: str, force_empty: bool = False, verbose: bool = True)`
60
+ - Creates a folder and optionally clears it if it already exists.
61
+ - `remove_files_in_dir(path: str)`
62
+ - Removes all files and subdirectories inside a directory.
63
+ - `recursive_dir_copy(source_path: str, target_path: str)`
64
+ - Recursively copies files and subfolders from source to target.
65
+
66
+ ### JSON Utils (`backpack.json_utils`)
67
+
68
+ - `json_load(json_file: str) -> dict`
69
+ - Loads JSON data from file with validation and error handling.
70
+ - `json_save(data: dict, json_file: str) -> bool`
71
+ - Saves a dictionary to JSON file.
72
+
73
+ ### JSON Metadata (`backpack.json_metadata`)
74
+
75
+ - `JsonMetaFile(name: str, path: str)`
76
+ - Manages a metadata JSON file with package/system/time information.
77
+ - Main public methods:
78
+ - `has_file() -> bool`
79
+ - `load() -> None`
80
+ - `insert(key: str, value: Any) -> None`
81
+ - `remove(key: str) -> None`
82
+ - `save() -> None`
83
+ - `load_as_class() -> type`
84
+ - `insert_class(_class: type) -> None`
85
+
86
+ ### JSON User Settings (`backpack.json_user_settings`)
87
+
88
+ - `JsonUserSettings(folder: str, name: str)`
89
+ - Saves and loads JSON settings in the current user's home directory.
90
+ - Main public methods:
91
+ - `save_settings(data: dict | None = None) -> bool | None`
92
+ - `load_settings() -> dict | bool`
93
+
94
+ ### Logger (`backpack.logger`)
95
+
96
+ - `get_logger(name: str) -> logging.Logger`
97
+ - Returns a configured logger with stream handler.
98
+
99
+ ### Patterns (`backpack.patterns`)
100
+
101
+ - `Singleton`
102
+ - Base class implementing singleton behavior via `__new__`.
103
+
104
+ ### Strings (`backpack.strings`)
105
+
106
+ - `normalize_input_string(input_string: str, under_spaces: bool = True, under_hyphen: bool = True, replacer: str = '_') -> str`
107
+ - Keeps alphanumeric/space/hyphen characters and normalizes separators.
108
+ - `begin_or_end_with_numbers(input_string: str) -> bool`
109
+ - Checks whether first or last character is numeric.
110
+ - `begin_with_number(input_string: str) -> bool`
111
+ - Checks whether the first character is numeric.
112
+ - `has_numbers(input_string: str) -> bool`
113
+ - Checks whether any character is numeric.
114
+ - `camelcase_to_snakecase(input_string: str) -> str`
115
+ - Converts CamelCase to snake_case.
116
+ - Handles acronym prefixes, for example `HTTPServer -> http_server`.
117
+
118
+ ### Test Utils (`backpack.test_utils`)
119
+
120
+ - `random_string(length: int = 10) -> str`
121
+ - Generates a random lowercase string.
122
+ - `time_function_decorator(method: type)`
123
+ - Decorator that logs execution time.
124
+
125
+ ## Quick Example
126
+
127
+ ```python
128
+ from backpack.cache import timed_lru_cache
129
+ from backpack.strings import camelcase_to_snakecase, normalize_input_string
130
+ from backpack.json_utils import json_save, json_load
131
+
132
+
133
+ @timed_lru_cache(seconds=60)
134
+ def expensive_call():
135
+ return {'ok': True}
136
+
137
+
138
+ value = camelcase_to_snakecase('HTTPServer')
139
+ clean = normalize_input_string('Blade Runner-2049')
140
+
141
+ json_save({'value': value, 'clean': clean}, 'data.json')
142
+ data = json_load('data.json')
143
+ ```
144
+
File without changes
@@ -84,3 +84,44 @@ def file_is_writeable(filepath: str) -> bool:
84
84
  log.info(x.strerror)
85
85
 
86
86
  return False
87
+
88
+
89
+ def get_version_from_filename(filename: str) -> str:
90
+ """Extracts version from filename.
91
+
92
+ Args:
93
+ filename: (str) file name to extract version from
94
+ Returns:
95
+ str : version extracted from filename
96
+
97
+ Examples:
98
+ get_version_from_filename('myfile_23.txt') -> '23'
99
+ get_version_from_filename('myfile-9.txt') -> '9'
100
+ get_version_from_filename('myfile.130.txt') -> '130'
101
+ get_version_from_filename('myfile_v1002.txt') -> '1002'
102
+
103
+ """
104
+
105
+ filename_no_ext = filename.rsplit('.', 1)[0]
106
+
107
+ # guess is the separator is an underscore, dash, dot or v, and the version is the last part before the extension
108
+ separators = ['_', '-', '.', 'v']
109
+ if not any(sep in filename_no_ext for sep in separators):
110
+ log.info(f'No separator found in filename: {filename_no_ext}. Using fallback extraction.')
111
+ else:
112
+ for sep in separators:
113
+ if sep in filename_no_ext:
114
+ parts = filename_no_ext.split(sep)
115
+ version_part = parts[-1].split('.')[0] # Get the last part before the extension
116
+ if version_part.replace('.', '').isdigit(): # Check if it's a valid version number
117
+ log.info(
118
+ f'Extracted version: {version_part} from filename: {filename} using separator: {sep}'
119
+ )
120
+ return version_part
121
+
122
+ # Fallback: extract from the entire filename
123
+ version = filename_no_ext.lstrip('v')
124
+ version = version.replace('_', '.').replace('-', '.')
125
+ version = version if version.replace('.', '').isdigit() else '0'
126
+ log.info(f'Extracted version: {version} from filename: {filename}')
127
+ return version
@@ -40,7 +40,7 @@ def json_save(data: dict, json_file: str) -> bool:
40
40
  """
41
41
 
42
42
  if not os.path.exists(os.path.dirname(json_file)):
43
- os.makedirs(os.path.dirname(json_file))
43
+ os.makedirs(os.path.dirname(json_file), exist_ok=True)
44
44
 
45
45
  try:
46
46
  with open(json_file, 'w') as f:
@@ -9,12 +9,12 @@
9
9
  # 1.1.2 07/2025 - Update actions versions, remove unused badge from README
10
10
  # 1.1.3 07/2025 - Improve test class names and docstrings for clarity, ruff formatting
11
11
  # 1.1.4 07/2025 - Moved from pipenv to poetry for dependency management.toml
12
- # 2.0.0 03/2026 - Remove tox workflow and setup.py, standardize Poetry + coverage tooling
12
+ # 2.0.1 05/2026 - Remove tox workflow and setup.py, standardize uv + coverage tooling
13
13
  # ----------------------------------------------------------------------------------------
14
14
 
15
15
  VERSION_MAJOR = 2
16
16
  VERSION_MINOR = 0
17
- VERSION_PATCH = 0
17
+ VERSION_PATCH = 1
18
18
 
19
19
  version = f'{VERSION_MAJOR}.{VERSION_MINOR}.{VERSION_PATCH}'
20
20
 
@@ -0,0 +1,63 @@
1
+ {
2
+ "folders": [
3
+ {
4
+ "path": "."
5
+ }
6
+ ],
7
+ "settings": {
8
+ "python.analysis.completeFunctionParens": false,
9
+ "python.analysis.autoImportCompletions": false,
10
+ "editor.formatOnSave": true,
11
+ "editor.formatOnPaste": false,
12
+ "editor.codeActionsOnSave": {
13
+ "source.organizeImports": "never"
14
+ },
15
+ "editor.rulers": [
16
+ 90
17
+ ],
18
+ "editor.defaultFormatter": "charliermarsh.ruff",
19
+ "editor.wordWrap": "wordWrapColumn",
20
+ "editor.wordWrapColumn": 120,
21
+ "workbench.colorTheme": "Monokai Pro (Filter Spectrum)",
22
+ "workbench.colorCustomizations": {
23
+ "titleBar.activeBackground": "#f36edf",
24
+ "titleBar.activeForeground": "#0c0b0b"
25
+ },
26
+ "python.analysis.extraPaths": [],
27
+ "python.testing.unittestArgs": [
28
+ "-v",
29
+ "-s",
30
+ ".",
31
+ "-p",
32
+ "*test*.py"
33
+ ],
34
+ "python.testing.pytestEnabled": false,
35
+ "python.testing.unittestEnabled": true,
36
+ "terminal.integrated.fontSize": 12,
37
+ "cSpell.words": [
38
+ "autopep",
39
+ "camelcase",
40
+ "charliermarsh",
41
+ "codecov",
42
+ "docstrings",
43
+ "ftime",
44
+ "jsonfile",
45
+ "levelname",
46
+ "Maximiliano",
47
+ "Monokai",
48
+ "numpy",
49
+ "Parens",
50
+ "pipenv",
51
+ "pycodestyle",
52
+ "pydocstyle",
53
+ "pylint",
54
+ "pypi",
55
+ "pytest",
56
+ "PYTHONPATH",
57
+ "Rocamora",
58
+ "runn",
59
+ "snakecase",
60
+ "strerror"
61
+ ],
62
+ }
63
+ }
@@ -0,0 +1,2 @@
1
+ ignore:
2
+ - "*/tests/*"
@@ -0,0 +1,9 @@
1
+ @echo off
2
+ REM Run tests with coverage and generate reports.
3
+ uv run coverage run --source=backpack -m pytest
4
+ if errorlevel 1 exit /b %errorlevel%
5
+ uv run coverage report -m --fail-under=70
6
+ if errorlevel 1 exit /b %errorlevel%
7
+ uv run coverage html
8
+ if errorlevel 1 exit /b %errorlevel%
9
+ uv run coverage xml
@@ -0,0 +1,29 @@
1
+ [project]
2
+ name = "python-backpack"
3
+ version = "2.0.1"
4
+ description = "A collection of personal scripts for json, File/Folder Operations, String Validation, Custom Errors, Cache and stuff."
5
+ authors = [
6
+ { name = "Maximiliano Rocamora" }
7
+ ]
8
+ requires-python = ">=3.11"
9
+ dependencies = []
10
+
11
+ [dependency-groups]
12
+ dev = [
13
+ "coverage>=7.14.0",
14
+ "ruff>=0.15.12",
15
+ "mock>=5.2.0",
16
+ "pytest>=9.0.3",
17
+ "pytest-cov>=7.1.0",
18
+ ]
19
+
20
+ [build-system]
21
+ requires = ["hatchling>=1.27.0"]
22
+ build-backend = "hatchling.build"
23
+
24
+ [tool.hatch.build.targets.wheel]
25
+ packages = ["backpack"]
26
+
27
+ [tool.pytest.ini_options]
28
+ addopts = "-p no:cacheprovider"
29
+ testpaths = ["tests"]
@@ -0,0 +1,26 @@
1
+ line-length = 99
2
+
3
+ # Assume Python 3.11
4
+ target-version = "py311"
5
+
6
+ [lint]
7
+ select = ["D", "F", "N", "I", "W"]
8
+ # ignore E402: module level import not at top of file
9
+ # ignore D100: missing docstring in public module
10
+ # ignore D101: missing docstring in public class
11
+ # ignore D104: missing docstring in public package
12
+ # ignore D202: no blank lines allowed after function docstring
13
+ ignore = ["E402", "D100", "D202", "D101", "D104"]
14
+
15
+ [lint.per-file-ignores]
16
+ # Ignore `E402` (import violations) in all `__init__.py` files.
17
+ "__init__.py" = ["E402"]
18
+
19
+ [lint.pydocstyle]
20
+ convention = "google" # Accepts: "google", "numpy", or "pep257".
21
+
22
+ [format]
23
+ # Enable reformatting of code snippets in docstrings.
24
+ docstring-code-format = true
25
+ docstring-code-line-length = 109
26
+ quote-style = "single"
File without changes
@@ -0,0 +1,20 @@
1
+ # ----------------------------------------------------------------------------------------
2
+ # Python-Backpack - Custom Exceptions UnitTest
3
+ # Maximiliano Rocamora / maxirocamora@gmail.com
4
+ # https://github.com/MaxRocamora/python-backpack
5
+ # ----------------------------------------------------------------------------------------
6
+
7
+ from backpack.cache import timed_lru_cache
8
+
9
+
10
+ @timed_lru_cache(seconds=1)
11
+ def cached_test_function():
12
+ """Some heavy process here to be cached."""
13
+ return True
14
+
15
+
16
+ def test_cache():
17
+ """Testing module."""
18
+ assert cached_test_function()
19
+ assert cached_test_function(force_clear=True) is True
20
+ assert cached_test_function(force_clear=True, show_log=True) is True
@@ -0,0 +1,47 @@
1
+ # ----------------------------------------------------------------------------------------
2
+ # Python-Backpack - Custom Exceptions UnitTest
3
+ # Maximiliano Rocamora / maxirocamora@gmail.com
4
+ # https://github.com/MaxRocamora/python-backpack
5
+ # ----------------------------------------------------------------------------------------
6
+
7
+ import os
8
+ import sys
9
+
10
+ import pytest
11
+
12
+ from backpack.custom_errors import ApplicationNotFoundError, EnvironmentVariableNotFoundError
13
+
14
+ mod_path = os.path.dirname(__file__)
15
+ if mod_path not in sys.path:
16
+ sys.path.append(mod_path)
17
+
18
+
19
+ def get_env_var(name: str):
20
+ """Call for an env var or raise EnvironmentVariableNotFoundError."""
21
+ try:
22
+ value = os.environ[name]
23
+ except KeyError as e:
24
+ raise EnvironmentVariableNotFoundError(name) from e
25
+ return value
26
+
27
+
28
+ def get_app(name: str):
29
+ """Raisers error."""
30
+ raise ApplicationNotFoundError(f'Background executable not found {name}')
31
+
32
+
33
+ def test_env_var_error():
34
+ """Testing module."""
35
+ with pytest.raises(EnvironmentVariableNotFoundError):
36
+ get_env_var('my_env_var')
37
+ error = EnvironmentVariableNotFoundError('my_env_var')
38
+ assert str(error) == error.message
39
+
40
+
41
+ def test_env_app_error():
42
+ """Testing module."""
43
+ with pytest.raises(ApplicationNotFoundError):
44
+ get_app('my_app.exe')
45
+
46
+ error = ApplicationNotFoundError('my_app.exe')
47
+ assert str(error) == error.message
@@ -0,0 +1,71 @@
1
+ # ----------------------------------------------------------------------------------------
2
+ # Python-Backpack - FolderUtils Tests
3
+ # Maximiliano Rocamora / maxirocamora@gmail.com
4
+ # https://github.com/MaxRocamora/python-backpack
5
+ # ----------------------------------------------------------------------------------------
6
+
7
+ import os
8
+ import shutil
9
+ import sys
10
+
11
+ import pytest
12
+
13
+ from backpack.file_utils import (
14
+ file_is_writeable,
15
+ get_version_from_filename,
16
+ remove_line_from_file,
17
+ replace_strings_in_file,
18
+ )
19
+
20
+ mod_path = os.path.dirname(__file__)
21
+ if mod_path not in sys.path:
22
+ sys.path.append(mod_path)
23
+
24
+ # test folders
25
+ BASE_FILE = os.path.join(mod_path, 'test_files', 'origin.txt')
26
+ EDITED_FILE = os.path.join(mod_path, 'test_files', 'edited.txt')
27
+ STRINGS = ['to be replaced!', 'also replaced']
28
+ NEW_STRING = 'REPLACED'
29
+ LOCKED_FILE = os.path.join(mod_path, 'test_files', 'locked_file.txt')
30
+ UNLOCKED_FILE = os.path.join(mod_path, 'test_files', 'unlocked_file.txt')
31
+ NO_FILE = os.path.join(mod_path, 'test_files', 'no_file.txt')
32
+
33
+
34
+ def test_replace_strings_in_file():
35
+ """Testing module."""
36
+ shutil.copy(BASE_FILE, EDITED_FILE)
37
+ replace_strings_in_file(EDITED_FILE, STRINGS, NEW_STRING)
38
+
39
+
40
+ def test_remove_line_from_file():
41
+ """Testing module."""
42
+ source_file = os.path.join(mod_path, 'test_files', 'origin_remove.txt')
43
+ test_file = os.path.join(mod_path, 'test_files', 'edited_remove.txt')
44
+ shutil.copy(source_file, test_file)
45
+ remove_line_from_file(test_file, ['REMOVE_ME', 'to be replaced!'], verbose=True)
46
+
47
+
48
+ def test_locked_file():
49
+ """Testing module."""
50
+ assert file_is_writeable(UNLOCKED_FILE)
51
+ assert not file_is_writeable(NO_FILE)
52
+
53
+
54
+ @pytest.mark.parametrize(
55
+ ('filename', 'expected_version'),
56
+ [
57
+ ('myfile_23.txt', '23'),
58
+ ('myfile-9.txt', '9'),
59
+ ('myfile.130.txt', '130'),
60
+ ('myfile_v1002.txt', '1002'),
61
+ ('name_without_separator.txt', '0'),
62
+ ('v42.txt', '42'),
63
+ ('myfile-alpha.txt', '0'),
64
+ ('myfile.txt', '0'),
65
+ ('mod_asset.1004.ma', '1004'),
66
+ ('script_nuke_s100_v1005.ma', '1005'),
67
+ ],
68
+ )
69
+ def test_get_version_from_filename(filename: str, expected_version: str):
70
+ """Version extraction should handle supported separators and invalid values."""
71
+ assert get_version_from_filename(filename) == expected_version