python-backpack 1.0.4__py3-none-any.whl → 1.1.4__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 ADDED
@@ -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
backpack/custom_errors.py CHANGED
@@ -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
- '''Error Raised when a required environment variable is missing from os.
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'System required ({var_name}) Environment Variable not found.'
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 ApplicationNotFound(Exception):
24
-
24
+ class ApplicationNotFoundError(Exception):
25
25
  def __init__(self, app_name: str) -> None:
26
- '''Error Raised when an Application Name required is not found.
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
backpack/file_utils.py CHANGED
@@ -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
- ''' Opens ascii file and replaces all occurrences from strings into new_string.
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"Replacing Strings, Opening File: {ascii_file}")
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"Closing File: {ascii_file}")
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
- print('file line', line)
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, "w") as f:
60
- contents = "\n".join(file_content)
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
- ''' checks if a file is locked and can be overwritten
72
+ """Checks if a file is locked and can be overwritten.
73
+
66
74
  Args:
67
- filepath (str)) file to check
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
backpack/folder_utils.py CHANGED
@@ -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
- ''' Open windows explorer on folder
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"explorer {os.path.abspath(folder)}")
23
+ subprocess.Popen(f'explorer {os.path.abspath(folder)}')
23
24
  return True
24
25
 
25
- log.warning(f"Unable to open folder {folder}")
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
- ''' Creates multiple folders on disc.
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
- '''creates a folder
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"Unable to create folder: {path}")
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
- ''' clears all content in given directory '''
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
- ''' Copy all files src dir to dest dir, including sub-directories.
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)
backpack/json_metadata.py CHANGED
@@ -3,153 +3,130 @@
3
3
  # Maximiliano Rocamora / maxirocamora@gmail.com
4
4
  # https://github.com/MaxRocamora/python-backpack
5
5
  # ----------------------------------------------------------------------------------------
6
- import sys
6
+ import inspect
7
7
  import os
8
+ import platform
9
+ import sys
8
10
  import time
9
- import inspect
10
11
  from datetime import datetime
11
- import platform
12
+ from typing import Any
12
13
 
13
14
  from backpack.json_utils import json_load, json_save
14
15
  from backpack.version import version
15
16
 
16
17
 
17
- class JsonMetaFile():
18
+ class JsonMetaFile:
19
+ PREFIX = 'MD_'
18
20
 
19
- def __init__(self, name: str, path: str):
20
- '''saves/load a class/dict as a json metadata file
21
+ def __init__(self, name: str, path: str) -> None:
22
+ """saves/load a class/dict as a json metadata file.
21
23
 
22
24
  Args:
23
25
  name (str): name of the file/class
24
26
  path (str, optional): filepath. Defaults to None.
25
- '''
27
+ """
26
28
  self._name = name
27
29
  self._path = path
28
- self._data = {}
29
- self._data["_about"] = {'package': 'python-backpack',
30
- 'version': self.version}
31
-
32
- # ------------------------------------------------------------------------------------
33
- # PROPERTIES
34
- # ------------------------------------------------------------------------------------
30
+ self._data = {'_about': {'package': 'python-backpack', 'version': self.version}}
35
31
 
36
32
  @property
37
- def name(self):
38
- ''' name of this metadata class'''
33
+ def name(self) -> str:
34
+ """Name of this metadata class."""
39
35
  return self._name
40
36
 
41
37
  @property
42
- def version(self):
43
- ''' version of this metadata class'''
38
+ def version(self) -> str:
39
+ """Version of this metadata class."""
44
40
  return version
45
41
 
46
42
  @property
47
- def data(self):
48
- ''' stored metadata dict '''
49
- return self._data
50
-
51
- @data.setter
52
- def data(self, val):
53
- self._data = val
54
-
55
- @property
56
- def prefix(self):
57
- ''' file prefix, is auto-included on the filename '''
58
- return "MD_"
59
-
60
- @property
61
- def filename(self):
62
- ''' Returns default filename with prefix and extension '''
63
- return self.prefix + self.name + '.json'
43
+ def filename(self) -> str:
44
+ """Returns default filename with prefix and extension."""
45
+ return self.PREFIX + self.name + '.json'
64
46
 
65
47
  @property
66
- def filepath(self):
67
- ''' full json metadata filepath '''
48
+ def filepath(self) -> str:
49
+ """Full json metadata filepath."""
68
50
  return os.path.join(self.path, self.filename)
69
51
 
70
52
  @property
71
- def path(self):
72
- ''' base path location of metadata json file '''
53
+ def path(self) -> str:
54
+ """Base path location of metadata json file."""
73
55
  return self._path
74
56
 
75
- def has_file(self):
57
+ def has_file(self) -> bool:
58
+ """Returns true if file exists."""
76
59
  return os.path.exists(self.filepath)
77
60
 
78
61
  # ------------------------------------------------------------------------------------
79
62
  # LOAD/INSERT/REMOVE/SAVE
80
63
  # ------------------------------------------------------------------------------------
81
64
 
82
- def load(self):
83
- ''' loads metadata from disk '''
65
+ def load(self) -> None:
66
+ """Loads metadata from disk."""
84
67
  self._data = json_load(self.filepath) if self.has_file() else {}
85
68
 
86
- def insert(self, key, value):
87
- ''' inserts value into metadata '''
69
+ def insert(self, key: str, value: Any) -> None:
70
+ """Inserts value into metadata."""
88
71
  self._data[key] = value
89
72
 
90
- def remove(self, key):
91
- ''' remove key from metadata '''
73
+ def remove(self, key: str) -> None:
74
+ """Remove key from metadata."""
92
75
  if key in self._data.keys():
93
76
  del self._data[key]
94
77
 
95
- def save(self, path: str = None):
96
- ''' Save current metadata into json file.
97
- Args:
98
- path (str) sets target path for json file. Optional. Defaults to None
99
- '''
78
+ def save(self) -> None:
79
+ """Save current metadata into json file."""
100
80
  if not os.path.exists(self.path):
101
81
  os.makedirs(self.path)
102
82
 
103
- self.data['system'] = self._system_data()
104
- json_save(self.data, self.filepath)
83
+ self._data['system'] = self._system_data()
84
+ json_save(self._data, self.filepath)
105
85
 
106
86
  # ------------------------------------------------------------------------------------
107
87
  # CLASS MODE METHODS
108
88
  # ------------------------------------------------------------------------------------
109
89
 
110
- def load_as_class(self):
111
- ''' returns the metadata dict as a class obj '''
112
- metadataClass = type(self.name, (), self._data)
113
- return metadataClass
114
-
115
- def insert_class(self, _class):
116
- ''' set class dict to data, data is cleared '''
117
- self.data = self._attributes_from_class(_class)
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
118
94
 
119
- def _attributes_from_class(self, _class):
95
+ def insert_class(self, _class: type) -> None:
96
+ """Load all attributes from a given class into this class metadata."""
120
97
  attributes = {}
121
98
  for name in dir(_class):
122
99
  value = getattr(_class, name)
123
100
  if not name.startswith('__') and not inspect.ismethod(value):
124
101
  attributes[name] = value
125
- return attributes
102
+
103
+ self._data = attributes
126
104
 
127
105
  # ------------------------------------------------------------------------------------
128
106
  # SYSTEM METADATA OS/USER/TIME
129
107
  # ------------------------------------------------------------------------------------
130
108
 
131
- def _system_data(self):
132
- ''' add system metadata to the default data before save '''
109
+ def _system_data(self) -> dict:
110
+ """Add system metadata to the default data before save."""
133
111
  return {
134
112
  'name': self.name,
135
113
  'app': os.path.basename(sys.executable),
136
114
  'PC': str(platform.node()),
137
115
  'python_version': sys.version,
138
116
  'User': str(os.getenv('username')),
139
- 'time': self._get_time_metadata,
117
+ 'time': self._current_time_metadata(),
140
118
  }
141
119
 
142
- @property
143
- def _get_time_metadata(self):
144
- ''' Get export time info. '''
145
- ftime = time.strftime("%Y,%b,%d,%j,%H:%M", time.localtime())
146
- times = ftime.split(",")
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(',')
147
124
  td = {
148
- "year": times[0],
149
- "month": times[1],
150
- "day": times[2],
151
- "year_day": times[3],
152
- "time": times[4],
153
- 'save_time': datetime.now().ctime()
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(),
154
131
  }
155
132
  return td
@@ -1,68 +1,71 @@
1
- # -*- coding: utf-8 -*-
2
- # --------------------------------------------------------------------------------------------
1
+ # ----------------------------------------------------------------------------------------
3
2
  # Json Settings Class
4
3
  # This class handle load/save json files on win/linux local user folder
5
- ''' Usage:
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
- '''Manages saving/loading json file on local user folder
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
- filename (str): name used for the json file. Defaults to 'user_data'.
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
- ''' Returns user filepath '''
36
- path = os.path.join(self.os_user_folder, self.folder, self.name + '.json')
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
- ''' returns os users home directory '''
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
- ''' override this property to modify saving dict '''
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
- ''' Checks for target directory or make it '''
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
- ''' Saves a dictionary into a json file (os user path)
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
- ''' Load json file from path and returns its contents '''
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:
backpack/json_utils.py CHANGED
@@ -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
- ''' Reads a json file
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"json_load: File not found: {json_file}.")
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"{json_file} \n JSON File issue: {str(e)}") from e
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
- ''' Saves a dictionary into a json file
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) : dictionary to save
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))