python-backpack 1.0.4__tar.gz → 1.1.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.
- python_backpack-1.1.4/PKG-INFO +12 -0
- python_backpack-1.1.4/backpack/cache.py +68 -0
- {python-backpack-1.0.4 → python_backpack-1.1.4}/backpack/custom_errors.py +9 -8
- {python-backpack-1.0.4 → python_backpack-1.1.4}/backpack/file_utils.py +30 -21
- {python-backpack-1.0.4 → python_backpack-1.1.4}/backpack/folder_utils.py +15 -14
- python_backpack-1.1.4/backpack/json_metadata.py +132 -0
- {python-backpack-1.0.4 → python_backpack-1.1.4}/backpack/json_user_settings.py +30 -27
- {python-backpack-1.0.4 → python_backpack-1.1.4}/backpack/json_utils.py +19 -12
- {python-backpack-1.0.4 → python_backpack-1.1.4}/backpack/logger.py +2 -2
- {python-backpack-1.0.4 → python_backpack-1.1.4}/backpack/patterns.py +5 -3
- {python-backpack-1.0.4 → python_backpack-1.1.4}/backpack/strings.py +15 -16
- {python-backpack-1.0.4 → python_backpack-1.1.4}/backpack/test_utils.py +9 -6
- {python-backpack-1.0.4 → python_backpack-1.1.4}/backpack/version.py +7 -1
- python_backpack-1.1.4/pyproject.toml +23 -0
- python-backpack-1.0.4/MANIFEST.in +0 -1
- python-backpack-1.0.4/PKG-INFO +0 -70
- python-backpack-1.0.4/README.md +0 -50
- python-backpack-1.0.4/backpack/json_metadata.py +0 -155
- python-backpack-1.0.4/python_backpack.egg-info/PKG-INFO +0 -70
- python-backpack-1.0.4/python_backpack.egg-info/SOURCES.txt +0 -30
- python-backpack-1.0.4/python_backpack.egg-info/dependency_links.txt +0 -1
- python-backpack-1.0.4/python_backpack.egg-info/top_level.txt +0 -2
- python-backpack-1.0.4/setup.cfg +0 -4
- python-backpack-1.0.4/setup.py +0 -36
- python-backpack-1.0.4/tests/__init__.py +0 -0
- python-backpack-1.0.4/tests/test_errors.py +0 -50
- python-backpack-1.0.4/tests/test_file_utils.py +0 -49
- python-backpack-1.0.4/tests/test_folder_utils.py +0 -96
- python-backpack-1.0.4/tests/test_json_user_settings.py +0 -76
- python-backpack-1.0.4/tests/test_json_utils.py +0 -46
- python-backpack-1.0.4/tests/test_jsonmd.py +0 -90
- python-backpack-1.0.4/tests/test_misc.py +0 -54
- python-backpack-1.0.4/tests/test_strings.py +0 -94
- python-backpack-1.0.4/tests/test_test_utils.py +0 -45
- {python-backpack-1.0.4 → python_backpack-1.1.4}/LICENSE +0 -0
- {python-backpack-1.0.4 → python_backpack-1.1.4}/backpack/__init__.py +0 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: python-backpack
|
|
3
|
+
Version: 1.1.4
|
|
4
|
+
Summary: A collection of personal scripts for json, File/Folder Operations, String Validation, Custom Errors, Cache and stuff.
|
|
5
|
+
Author: Maximiliano Rocamora
|
|
6
|
+
Requires-Python: >=3.9,<4.0
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
@@ -0,0 +1,68 @@
|
|
|
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
|
+
|
|
9
|
+
from backpack.logger import get_logger
|
|
10
|
+
|
|
11
|
+
log = get_logger('Python Backpack - Cache')
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def timed_lru_cache(seconds: int, maxsize: int = 128):
|
|
15
|
+
"""Lru_cache with expiration time.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
seconds (int): expiration time in seconds
|
|
19
|
+
maxsize (int): maxsize for lru_cache
|
|
20
|
+
Note:
|
|
21
|
+
Wrapped function can be forced to clear cache with: force_clear=True
|
|
22
|
+
Wrapped function can show log on clear with: show_log=True
|
|
23
|
+
Returns:
|
|
24
|
+
function result
|
|
25
|
+
|
|
26
|
+
# * Usage:
|
|
27
|
+
|
|
28
|
+
# * Add the decorator to your function
|
|
29
|
+
|
|
30
|
+
@timed_lru_cache(seconds=60)
|
|
31
|
+
def my_function():
|
|
32
|
+
return 'Hello World'
|
|
33
|
+
|
|
34
|
+
# * to clear the cache, use force_clear=True on the function call
|
|
35
|
+
my_function(force_clear=True)
|
|
36
|
+
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def wrapper_cache(func):
|
|
40
|
+
func = lru_cache(maxsize=maxsize)(func)
|
|
41
|
+
func.lifetime = timedelta(seconds=seconds)
|
|
42
|
+
func.expiration = datetime.now(timezone.utc) + func.lifetime
|
|
43
|
+
|
|
44
|
+
@wraps(func)
|
|
45
|
+
def wrapped_func(*args, force_clear: bool = False, show_log: bool = False, **kwargs):
|
|
46
|
+
"""Wrapper function for lru_cache with expiration time.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
*args: function arguments
|
|
50
|
+
force_clear (bool): forces a clear cache
|
|
51
|
+
show_log (bool): show log on clear
|
|
52
|
+
**kwargs: function keyword arguments
|
|
53
|
+
Returns:
|
|
54
|
+
function result
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
if force_clear or datetime.now(timezone.utc) >= func.expiration:
|
|
58
|
+
if show_log:
|
|
59
|
+
log.debug(f'Cache cleared for {func.__name__}')
|
|
60
|
+
|
|
61
|
+
func.cache_clear()
|
|
62
|
+
func.expiration = datetime.now(timezone.utc) + func.lifetime
|
|
63
|
+
|
|
64
|
+
return func(*args, **kwargs)
|
|
65
|
+
|
|
66
|
+
return wrapped_func
|
|
67
|
+
|
|
68
|
+
return wrapper_cache
|
|
@@ -4,33 +4,34 @@
|
|
|
4
4
|
# https://github.com/MaxRocamora/python-backpack
|
|
5
5
|
# ----------------------------------------------------------------------------------------
|
|
6
6
|
|
|
7
|
-
class EnvironmentVariableNotFound(Exception):
|
|
8
7
|
|
|
8
|
+
class EnvironmentVariableNotFoundError(Exception):
|
|
9
9
|
def __init__(self, var_name: str) -> None:
|
|
10
|
-
|
|
10
|
+
"""Error Raised when a required environment variable is missing from os.
|
|
11
11
|
|
|
12
12
|
Args:
|
|
13
13
|
var_name (str): name of the required variable missing
|
|
14
|
-
|
|
14
|
+
"""
|
|
15
15
|
self.var_name = var_name
|
|
16
|
-
self.message = f'
|
|
16
|
+
self.message = f'Required Environment Variable [{var_name}] not found.'
|
|
17
17
|
super().__init__(self.message)
|
|
18
18
|
|
|
19
19
|
def __str__(self):
|
|
20
|
+
"""Return the error message."""
|
|
20
21
|
return self.message
|
|
21
22
|
|
|
22
23
|
|
|
23
|
-
class
|
|
24
|
-
|
|
24
|
+
class ApplicationNotFoundError(Exception):
|
|
25
25
|
def __init__(self, app_name: str) -> None:
|
|
26
|
-
|
|
26
|
+
"""Error Raised when an Application Name required is not found.
|
|
27
27
|
|
|
28
28
|
Args:
|
|
29
29
|
app_name (str): name of the required application missing
|
|
30
|
-
|
|
30
|
+
"""
|
|
31
31
|
self.app_name = app_name
|
|
32
32
|
self.message = f'Application ({app_name}) not found.'
|
|
33
33
|
super().__init__(self.message)
|
|
34
34
|
|
|
35
35
|
def __str__(self):
|
|
36
|
+
"""Return the error message."""
|
|
36
37
|
return self.message
|
|
@@ -8,23 +8,25 @@ import contextlib
|
|
|
8
8
|
|
|
9
9
|
from backpack.logger import get_logger
|
|
10
10
|
|
|
11
|
-
log = get_logger('FileUtils')
|
|
11
|
+
log = get_logger('Python Backpack - FileUtils')
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
def replace_strings_in_file(ascii_file: str, strings: list, new_string: str):
|
|
15
|
-
|
|
14
|
+
def replace_strings_in_file(ascii_file: str, strings: list, new_string: str) -> None:
|
|
15
|
+
"""Opens ascii file and replaces all occurrences from strings into new_string.
|
|
16
|
+
|
|
16
17
|
In this class we use a full path to avoid use of os.dirname, which
|
|
17
18
|
causes string encode problems.
|
|
19
|
+
|
|
18
20
|
Args:
|
|
19
|
-
ascii_file (fullpath) file to open
|
|
20
|
-
strings (list) strings to replace
|
|
21
|
-
new_string (string) new string or path to set
|
|
22
|
-
|
|
21
|
+
ascii_file: (fullpath string) file to open
|
|
22
|
+
strings: (list) strings to replace
|
|
23
|
+
new_string: (string) new string or path to set
|
|
24
|
+
"""
|
|
23
25
|
|
|
24
26
|
# force forward slashes
|
|
25
|
-
new_string = new_string.replace(
|
|
27
|
+
new_string = new_string.replace('\\', '/')
|
|
26
28
|
|
|
27
|
-
log.info(f
|
|
29
|
+
log.info(f'Replacing Strings, Opening File: {ascii_file}')
|
|
28
30
|
|
|
29
31
|
with open(ascii_file) as f:
|
|
30
32
|
file_data = f.read()
|
|
@@ -37,41 +39,48 @@ def replace_strings_in_file(ascii_file: str, strings: list, new_string: str):
|
|
|
37
39
|
with open(ascii_file, 'w') as f:
|
|
38
40
|
f.write(file_data)
|
|
39
41
|
f.close()
|
|
40
|
-
log.info(f
|
|
42
|
+
log.info(f'Closing File: {ascii_file}')
|
|
43
|
+
|
|
41
44
|
|
|
45
|
+
def remove_line_from_file(ascii_file: str, strings: list, verbose: bool = False) -> None:
|
|
46
|
+
"""Removes given lines from ascii file.
|
|
42
47
|
|
|
43
|
-
def remove_line_from_file(ascii_file: str, strings: str):
|
|
44
|
-
''' removes given lines from ascii file.
|
|
45
48
|
Args:
|
|
46
|
-
ascii_file (path) ASCII file to process
|
|
47
|
-
strings (list) match lines to remove
|
|
48
|
-
|
|
49
|
+
ascii_file: (string path) ASCII file to process
|
|
50
|
+
strings: (list) match lines to remove
|
|
51
|
+
verbose: (bool) if true, prints removed lines
|
|
52
|
+
"""
|
|
49
53
|
|
|
50
54
|
with open(ascii_file) as f:
|
|
51
55
|
file_content = f.read().splitlines()
|
|
52
56
|
|
|
53
57
|
for line in file_content:
|
|
54
|
-
|
|
58
|
+
if verbose:
|
|
59
|
+
log.info(f'Checking line: {line}')
|
|
55
60
|
with contextlib.suppress(ValueError):
|
|
56
61
|
if line in strings:
|
|
62
|
+
if verbose:
|
|
63
|
+
log.info(f'Removing line: {line}')
|
|
57
64
|
file_content.pop(file_content.index(line))
|
|
58
65
|
|
|
59
|
-
with open(ascii_file,
|
|
60
|
-
contents =
|
|
66
|
+
with open(ascii_file, 'w') as f:
|
|
67
|
+
contents = '\n'.join(file_content)
|
|
61
68
|
f.write(contents)
|
|
62
69
|
|
|
63
70
|
|
|
64
71
|
def file_is_writeable(filepath: str) -> bool:
|
|
65
|
-
|
|
72
|
+
"""Checks if a file is locked and can be overwritten.
|
|
73
|
+
|
|
66
74
|
Args:
|
|
67
|
-
filepath (str)
|
|
75
|
+
filepath: (str) file to check
|
|
68
76
|
Returns:
|
|
69
77
|
bool : true if file is writeable
|
|
70
|
-
|
|
78
|
+
"""
|
|
71
79
|
try:
|
|
72
80
|
with open(filepath, 'r+') as _:
|
|
73
81
|
return True
|
|
74
82
|
except OSError as x:
|
|
75
83
|
log.warning(f'{filepath} is locked ')
|
|
76
84
|
log.info(x.strerror)
|
|
85
|
+
|
|
77
86
|
return False
|
|
@@ -10,42 +10,43 @@ import subprocess
|
|
|
10
10
|
|
|
11
11
|
from backpack.logger import get_logger
|
|
12
12
|
|
|
13
|
-
log = get_logger('FolderUtils')
|
|
13
|
+
log = get_logger('Python Backpack - FolderUtils')
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
def browse_folder(folder: str) -> bool:
|
|
17
|
-
|
|
17
|
+
"""Open windows explorer on folder.
|
|
18
|
+
|
|
18
19
|
Args:
|
|
19
|
-
folder (path) folder to open
|
|
20
|
-
|
|
20
|
+
folder: (string path) folder to open
|
|
21
|
+
"""
|
|
21
22
|
if folder and os.path.isdir(folder):
|
|
22
|
-
subprocess.Popen(f
|
|
23
|
+
subprocess.Popen(f'explorer {os.path.abspath(folder)}')
|
|
23
24
|
return True
|
|
24
25
|
|
|
25
|
-
log.warning(f
|
|
26
|
+
log.warning(f'Unable to open folder {folder}')
|
|
26
27
|
return False
|
|
27
28
|
|
|
28
29
|
|
|
29
30
|
def create_folders(folders: list, force_empty: bool = False, verbose: bool = False):
|
|
30
|
-
|
|
31
|
+
"""Creates multiple folders on disc.
|
|
31
32
|
|
|
32
33
|
Args:
|
|
33
34
|
folders (list): folder list
|
|
34
35
|
force_empty (bool, optional): forces clearing folder content. Defaults to False.
|
|
35
36
|
verbose (bool, optional):shows log. Defaults to False.
|
|
36
|
-
|
|
37
|
+
"""
|
|
37
38
|
for folder in folders:
|
|
38
39
|
create_folder(folder, force_empty=force_empty, verbose=verbose)
|
|
39
40
|
|
|
40
41
|
|
|
41
42
|
def create_folder(path: str, force_empty: bool = False, verbose: bool = True):
|
|
42
|
-
|
|
43
|
+
"""Creates a folder.
|
|
43
44
|
|
|
44
45
|
Args:
|
|
45
46
|
path (str): folder path
|
|
46
47
|
force_empty (bool, optional): forces clearing folder content. Defaults to False.
|
|
47
48
|
verbose (bool, optional): show log. Defaults to True.
|
|
48
|
-
|
|
49
|
+
"""
|
|
49
50
|
abspath = os.path.abspath(path)
|
|
50
51
|
if verbose:
|
|
51
52
|
log.info(f'Creating Folder {abspath}')
|
|
@@ -56,14 +57,14 @@ def create_folder(path: str, force_empty: bool = False, verbose: bool = True):
|
|
|
56
57
|
elif force_empty:
|
|
57
58
|
remove_files_in_dir(abspath)
|
|
58
59
|
except OSError as e:
|
|
59
|
-
log.warning(f
|
|
60
|
+
log.warning(f'Unable to create folder: {path}')
|
|
60
61
|
log.error(str(e))
|
|
61
62
|
|
|
62
63
|
return True
|
|
63
64
|
|
|
64
65
|
|
|
65
66
|
def remove_files_in_dir(path: str):
|
|
66
|
-
|
|
67
|
+
"""Clears all content in given directory."""
|
|
67
68
|
for root, dirs, files in os.walk(path):
|
|
68
69
|
for f in files:
|
|
69
70
|
os.unlink(os.path.join(root, f))
|
|
@@ -72,12 +73,12 @@ def remove_files_in_dir(path: str):
|
|
|
72
73
|
|
|
73
74
|
|
|
74
75
|
def recursive_dir_copy(source_path: str, target_path: str):
|
|
75
|
-
|
|
76
|
+
"""Copy all files src dir to dest dir, including sub-directories.
|
|
76
77
|
|
|
77
78
|
Args:
|
|
78
79
|
source_path (str): source path
|
|
79
80
|
target_path (str): destination path
|
|
80
|
-
|
|
81
|
+
"""
|
|
81
82
|
|
|
82
83
|
create_folder(source_path)
|
|
83
84
|
create_folder(target_path)
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# ----------------------------------------------------------------------------------------
|
|
2
|
+
# Python-Backpack - JsonMetadata
|
|
3
|
+
# Maximiliano Rocamora / maxirocamora@gmail.com
|
|
4
|
+
# https://github.com/MaxRocamora/python-backpack
|
|
5
|
+
# ----------------------------------------------------------------------------------------
|
|
6
|
+
import inspect
|
|
7
|
+
import os
|
|
8
|
+
import platform
|
|
9
|
+
import sys
|
|
10
|
+
import time
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from backpack.json_utils import json_load, json_save
|
|
15
|
+
from backpack.version import version
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class JsonMetaFile:
|
|
19
|
+
PREFIX = 'MD_'
|
|
20
|
+
|
|
21
|
+
def __init__(self, name: str, path: str) -> None:
|
|
22
|
+
"""saves/load a class/dict as a json metadata file.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
name (str): name of the file/class
|
|
26
|
+
path (str, optional): filepath. Defaults to None.
|
|
27
|
+
"""
|
|
28
|
+
self._name = name
|
|
29
|
+
self._path = path
|
|
30
|
+
self._data = {'_about': {'package': 'python-backpack', 'version': self.version}}
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def name(self) -> str:
|
|
34
|
+
"""Name of this metadata class."""
|
|
35
|
+
return self._name
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def version(self) -> str:
|
|
39
|
+
"""Version of this metadata class."""
|
|
40
|
+
return version
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def filename(self) -> str:
|
|
44
|
+
"""Returns default filename with prefix and extension."""
|
|
45
|
+
return self.PREFIX + self.name + '.json'
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def filepath(self) -> str:
|
|
49
|
+
"""Full json metadata filepath."""
|
|
50
|
+
return os.path.join(self.path, self.filename)
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def path(self) -> str:
|
|
54
|
+
"""Base path location of metadata json file."""
|
|
55
|
+
return self._path
|
|
56
|
+
|
|
57
|
+
def has_file(self) -> bool:
|
|
58
|
+
"""Returns true if file exists."""
|
|
59
|
+
return os.path.exists(self.filepath)
|
|
60
|
+
|
|
61
|
+
# ------------------------------------------------------------------------------------
|
|
62
|
+
# LOAD/INSERT/REMOVE/SAVE
|
|
63
|
+
# ------------------------------------------------------------------------------------
|
|
64
|
+
|
|
65
|
+
def load(self) -> None:
|
|
66
|
+
"""Loads metadata from disk."""
|
|
67
|
+
self._data = json_load(self.filepath) if self.has_file() else {}
|
|
68
|
+
|
|
69
|
+
def insert(self, key: str, value: Any) -> None:
|
|
70
|
+
"""Inserts value into metadata."""
|
|
71
|
+
self._data[key] = value
|
|
72
|
+
|
|
73
|
+
def remove(self, key: str) -> None:
|
|
74
|
+
"""Remove key from metadata."""
|
|
75
|
+
if key in self._data.keys():
|
|
76
|
+
del self._data[key]
|
|
77
|
+
|
|
78
|
+
def save(self) -> None:
|
|
79
|
+
"""Save current metadata into json file."""
|
|
80
|
+
if not os.path.exists(self.path):
|
|
81
|
+
os.makedirs(self.path)
|
|
82
|
+
|
|
83
|
+
self._data['system'] = self._system_data()
|
|
84
|
+
json_save(self._data, self.filepath)
|
|
85
|
+
|
|
86
|
+
# ------------------------------------------------------------------------------------
|
|
87
|
+
# CLASS MODE METHODS
|
|
88
|
+
# ------------------------------------------------------------------------------------
|
|
89
|
+
|
|
90
|
+
def load_as_class(self) -> type:
|
|
91
|
+
"""Returns the metadata dict as a class obj."""
|
|
92
|
+
metadata_class = type(self.name, (), self._data)
|
|
93
|
+
return metadata_class
|
|
94
|
+
|
|
95
|
+
def insert_class(self, _class: type) -> None:
|
|
96
|
+
"""Load all attributes from a given class into this class metadata."""
|
|
97
|
+
attributes = {}
|
|
98
|
+
for name in dir(_class):
|
|
99
|
+
value = getattr(_class, name)
|
|
100
|
+
if not name.startswith('__') and not inspect.ismethod(value):
|
|
101
|
+
attributes[name] = value
|
|
102
|
+
|
|
103
|
+
self._data = attributes
|
|
104
|
+
|
|
105
|
+
# ------------------------------------------------------------------------------------
|
|
106
|
+
# SYSTEM METADATA OS/USER/TIME
|
|
107
|
+
# ------------------------------------------------------------------------------------
|
|
108
|
+
|
|
109
|
+
def _system_data(self) -> dict:
|
|
110
|
+
"""Add system metadata to the default data before save."""
|
|
111
|
+
return {
|
|
112
|
+
'name': self.name,
|
|
113
|
+
'app': os.path.basename(sys.executable),
|
|
114
|
+
'PC': str(platform.node()),
|
|
115
|
+
'python_version': sys.version,
|
|
116
|
+
'User': str(os.getenv('username')),
|
|
117
|
+
'time': self._current_time_metadata(),
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
def _current_time_metadata(self) -> dict:
|
|
121
|
+
"""Get export time info."""
|
|
122
|
+
ftime = time.strftime('%Y,%b,%d,%j,%H:%M', time.localtime())
|
|
123
|
+
times = ftime.split(',')
|
|
124
|
+
td = {
|
|
125
|
+
'year': times[0],
|
|
126
|
+
'month': times[1],
|
|
127
|
+
'day': times[2],
|
|
128
|
+
'year_day': times[3],
|
|
129
|
+
'time': times[4],
|
|
130
|
+
'save_time': datetime.now().ctime(),
|
|
131
|
+
}
|
|
132
|
+
return td
|
|
@@ -1,68 +1,71 @@
|
|
|
1
|
-
#
|
|
2
|
-
# --------------------------------------------------------------------------------------------
|
|
1
|
+
# ----------------------------------------------------------------------------------------
|
|
3
2
|
# Json Settings Class
|
|
4
3
|
# This class handle load/save json files on win/linux local user folder
|
|
5
|
-
|
|
4
|
+
|
|
5
|
+
"""Usage.
|
|
6
|
+
|
|
6
7
|
us = JsonUserSettings('my_app')
|
|
7
8
|
us.save(someDict)
|
|
8
9
|
data = us.load()
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
#
|
|
11
|
+
"""
|
|
12
|
+
# ----------------------------------------------------------------------------------------
|
|
12
13
|
import os
|
|
13
14
|
|
|
15
|
+
from backpack.json_utils import json_load, json_save
|
|
14
16
|
from backpack.logger import get_logger
|
|
15
|
-
from backpack.json_utils import json_save, json_load
|
|
16
17
|
|
|
17
|
-
log = get_logger('UserSettings')
|
|
18
|
+
log = get_logger('Python Backpack - UserSettings')
|
|
18
19
|
|
|
19
20
|
|
|
20
|
-
class JsonUserSettings
|
|
21
|
-
def __init__(self, folder: str, name: str):
|
|
22
|
-
|
|
21
|
+
class JsonUserSettings:
|
|
22
|
+
def __init__(self, folder: str, name: str) -> None:
|
|
23
|
+
"""Manages saving/loading json file on local user folder.
|
|
23
24
|
|
|
24
25
|
Args:
|
|
25
26
|
folder (str): name of sub folder inside user path. Defaults to 'json_settings'.
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
name (str): name used for the json file. Defaults to 'user_data'.
|
|
28
|
+
"""
|
|
28
29
|
self.name = name
|
|
29
30
|
self.folder = folder
|
|
30
31
|
self._user_data = {}
|
|
31
32
|
self._verify_path()
|
|
32
33
|
|
|
33
34
|
@property
|
|
34
|
-
def filepath(self):
|
|
35
|
-
|
|
36
|
-
path = os.path.join(self.os_user_folder, self.folder, self.name
|
|
35
|
+
def filepath(self) -> str:
|
|
36
|
+
"""Returns user filepath."""
|
|
37
|
+
path = os.path.join(self.os_user_folder, self.folder, f'{self.name}.json')
|
|
37
38
|
return os.path.abspath(path)
|
|
38
39
|
|
|
39
40
|
@property
|
|
40
|
-
def os_user_folder(self):
|
|
41
|
-
|
|
41
|
+
def os_user_folder(self) -> str:
|
|
42
|
+
"""Returns os users home directory."""
|
|
42
43
|
return os.path.expanduser('~')
|
|
43
44
|
|
|
44
45
|
@property
|
|
45
|
-
def user_data(self):
|
|
46
|
-
|
|
46
|
+
def user_data(self) -> dict:
|
|
47
|
+
"""Override this property to modify saving dict."""
|
|
47
48
|
return self._user_data
|
|
48
49
|
|
|
49
50
|
@user_data.setter
|
|
50
|
-
def user_data(self, v):
|
|
51
|
+
def user_data(self, v: dict) -> None:
|
|
51
52
|
self._user_data = v
|
|
52
53
|
|
|
53
|
-
def _verify_path(self):
|
|
54
|
-
|
|
54
|
+
def _verify_path(self) -> bool:
|
|
55
|
+
"""Checks for target directory or make it."""
|
|
55
56
|
path = os.path.dirname(self.filepath)
|
|
56
57
|
if not os.path.exists(path):
|
|
57
58
|
os.makedirs(path)
|
|
59
|
+
|
|
58
60
|
return True
|
|
59
61
|
|
|
60
|
-
def save_settings(self, data=False):
|
|
61
|
-
|
|
62
|
+
def save_settings(self, data=False) -> bool:
|
|
63
|
+
"""Saves a dictionary into a json file (os user path).
|
|
64
|
+
|
|
62
65
|
Args:
|
|
63
66
|
data (dictionary) : info dictionary to save, if set to False,
|
|
64
67
|
saves instead local self.user_data property
|
|
65
|
-
|
|
68
|
+
"""
|
|
66
69
|
if not data:
|
|
67
70
|
data = self.user_data
|
|
68
71
|
|
|
@@ -71,8 +74,8 @@ class JsonUserSettings():
|
|
|
71
74
|
log.info('json settings file saved! [%s]', self.filepath)
|
|
72
75
|
return r
|
|
73
76
|
|
|
74
|
-
def load_settings(self):
|
|
75
|
-
|
|
77
|
+
def load_settings(self) -> dict:
|
|
78
|
+
"""Load json file from path and returns its contents."""
|
|
76
79
|
try:
|
|
77
80
|
return json_load(self.filepath)
|
|
78
81
|
except OSError:
|
|
@@ -1,36 +1,43 @@
|
|
|
1
|
-
import os
|
|
2
1
|
import json
|
|
2
|
+
import os
|
|
3
3
|
|
|
4
4
|
from backpack.logger import get_logger
|
|
5
5
|
|
|
6
|
-
log = get_logger('JsonUtils')
|
|
6
|
+
log = get_logger('Python Backpack - JsonUtils')
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
def json_load(json_file: str) -> dict:
|
|
10
|
-
|
|
10
|
+
"""Reads a json file.
|
|
11
|
+
|
|
11
12
|
Args:
|
|
12
|
-
json_file (filepath) json file to read data.
|
|
13
|
+
json_file: (string filepath) json file to read data.
|
|
14
|
+
|
|
13
15
|
Returns:
|
|
14
16
|
dict (file content)
|
|
15
|
-
|
|
17
|
+
"""
|
|
16
18
|
if not os.path.exists(json_file):
|
|
17
|
-
raise OSError(f
|
|
19
|
+
raise OSError(f'json_load: File not found: {json_file}.')
|
|
18
20
|
|
|
19
21
|
with open(json_file) as json_file_opened:
|
|
20
22
|
try:
|
|
21
23
|
value = json.load(json_file_opened)
|
|
22
24
|
except ValueError as e:
|
|
23
25
|
json_file_opened.close()
|
|
24
|
-
raise OSError(f
|
|
26
|
+
raise OSError(f'{json_file} \n JSON File issue: {str(e)}') from e
|
|
27
|
+
|
|
25
28
|
return value
|
|
26
29
|
|
|
27
30
|
|
|
28
|
-
def json_save(data: dict, json_file: str):
|
|
29
|
-
|
|
31
|
+
def json_save(data: dict, json_file: str) -> bool:
|
|
32
|
+
"""Saves a dictionary into a json file.
|
|
33
|
+
|
|
30
34
|
Args:
|
|
31
|
-
data (dict)
|
|
32
|
-
json_file (filepath) json file to save data.
|
|
33
|
-
|
|
35
|
+
data: (dict) dictionary to save
|
|
36
|
+
json_file: (string filepath) json file to save data.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
bool (True if success)
|
|
40
|
+
"""
|
|
34
41
|
|
|
35
42
|
if not os.path.exists(os.path.dirname(json_file)):
|
|
36
43
|
os.makedirs(os.path.dirname(json_file))
|
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
# ----------------------------------------------------------------------------------------
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
class Singleton
|
|
9
|
-
|
|
8
|
+
class Singleton:
|
|
9
|
+
"""Python Singleton BaseClass.
|
|
10
|
+
|
|
10
11
|
Usage:
|
|
11
12
|
|
|
12
13
|
class MyClass(Singleton, otherClass):
|
|
@@ -14,11 +15,12 @@ class Singleton():
|
|
|
14
15
|
|
|
15
16
|
obj = MyClass() # any new object will be taken from the same instance
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
"""
|
|
18
19
|
|
|
19
20
|
_instance = None
|
|
20
21
|
|
|
21
22
|
def __new__(cls, *args, **kwargs):
|
|
23
|
+
"""Singleton __new__ method."""
|
|
22
24
|
if not isinstance(cls._instance, cls):
|
|
23
25
|
# print(f'Initializing {cls.__name__}')
|
|
24
26
|
cls._instance = object.__new__(cls)
|