ParUtils 1.2.3__py3-none-any.whl → 1.2.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.
parutils/changelog.py CHANGED
@@ -1,7 +1,16 @@
1
- __VERSION__ = '1.2.3'
2
- # close_logger returns the closed logger object
3
- # no more stip('\n') in save_list
4
- # empty_log_every_buffer
1
+ __VERSION__ = '1.2.5'
2
+ # Faster file.load_txt
3
+ # Corrected comment typos
4
+
5
+ # __VERSION__ = '1.2.4'
6
+ # # !dir -> log_dir
7
+ # # const to default kwargs
8
+ # # Logger kwargs comments
9
+
10
+ # __VERSION__ = '1.2.3'
11
+ # # close_logger returns the closed logger object
12
+ # # no more strip('\n') in save_list
13
+ # # empty_log_every_buffer
5
14
 
6
15
  # __VERSION__ = '1.2.2'
7
16
  # # log_every
parutils/csvl.py CHANGED
@@ -8,7 +8,7 @@ E_WRONG_TYPE_LIST = "List elements must be of type list (if you want to save a l
8
8
 
9
9
 
10
10
  def get_csv_fields_dict(in_path):
11
- """Returns a dictionary whose keys are the csv fields of the 'in_path' file
11
+ """Returns a dictionary whose keys are the CSV fields of the 'in_path' file
12
12
  and elements are the columns index.
13
13
  """
14
14
 
@@ -21,10 +21,10 @@ def get_csv_fields_dict(in_path):
21
21
 
22
22
 
23
23
  def load_csv(in_path, quote=False):
24
- """Loads a csv file and returns a list whose elements correspond to the
24
+ """Loads a CSV file and returns a list whose elements correspond to the
25
25
  lines of the 'in_path' file.
26
- Each element is also a list whose elements correspond to the csv fields.
27
- When quote is True, the buitin csv package is used and separators between quote char
26
+ Each element is also a list whose elements correspond to the CSV fields.
27
+ When quote is True, the builtin CSV package is used and separators between quote char
28
28
  are ignored (less performant).
29
29
  """
30
30
 
@@ -43,16 +43,16 @@ def load_csv(in_path, quote=False):
43
43
 
44
44
 
45
45
  def csv_to_list(line_in: str):
46
- """Converts a csv line to a list using SEPARATOR as separator"""
46
+ """Converts a CSV line to a list using SEPARATOR as separator"""
47
47
 
48
48
  return line_in.strip('\n').split(SEPARATOR)
49
49
 
50
50
 
51
51
  def save_csv(array_in, out_path, mode='w', quote=False):
52
- """Saves a list to a csv file
52
+ """Saves a list to a CSV file
53
53
 
54
54
  - mode: mode for the open methode.
55
- - quote: determines whether each field should be wrapped into double quotes or not (default=False)
55
+ - quote: determines whether each field should be wrapped in double quotes or not (default=False)
56
56
  """
57
57
  import os.path as p
58
58
 
@@ -63,9 +63,9 @@ def save_csv(array_in, out_path, mode='w', quote=False):
63
63
 
64
64
 
65
65
  def write_csv_line(row, out_file: TextIOWrapper, quote=False):
66
- """Writes a line to a csv file
66
+ """Writes a line to a CSV file
67
67
 
68
- - row: has to be a list
68
+ - row: must be a list
69
69
  """
70
70
 
71
71
  if not isinstance(row, list):
@@ -78,7 +78,7 @@ def write_csv_line(row, out_file: TextIOWrapper, quote=False):
78
78
 
79
79
 
80
80
  def csv_clean(s: str):
81
- """Cleans a csv field by removing csv separators and new line characters"""
81
+ """Cleans a CSV field by removing CSV separators and new line characters"""
82
82
 
83
83
  out = s.replace('\r', '')
84
84
  out = out.replace('\n', '')
@@ -89,7 +89,7 @@ def csv_clean(s: str):
89
89
  def get_header(in_path, csv=False):
90
90
  """Returns the header of a file
91
91
 
92
- - csv: if True, the returned header is a list containing each csv field
92
+ - csv: if True, the returned header is a list containing each CSV field
93
93
  """
94
94
 
95
95
  with open(in_path, 'r', encoding='utf-8') as in_file:
parutils/file.py CHANGED
@@ -15,7 +15,7 @@ def delete_folder(dir):
15
15
 
16
16
 
17
17
  def mkdirs(dir, delete=False):
18
- """Same as os.makedirs but
18
+ """Same as os.makedirs but:
19
19
  - Input can also be a path
20
20
  - With a 'delete' option which (if True) deletes the folder if it already exists."""
21
21
 
@@ -38,7 +38,7 @@ def list_files(in_dir,
38
38
  ignore_list=[]):
39
39
  """Lists the files of the 'in_dir' directory
40
40
 
41
- - incl_root: if True, the root directory is included in each paths
41
+ - incl_root: if True, the root directory is included in each path
42
42
  - walk: if True, the files of all the subdirectories are listed as well
43
43
  - only_list: list of wanted patterns. e.g. ['*.py'] (only these patterns will be output)
44
44
  - ignore_list: list of unwanted patterns. e.g. ['*.pyc'] (these patterns won't be output)
@@ -66,22 +66,16 @@ def list_files(in_dir,
66
66
  def load_txt(in_path, list_out=True):
67
67
  """Loads a text file
68
68
 
69
- - list_out: if True, a list es output, each element representing a line a the file. If False, a string is output.
69
+ - list_out: if True, a list is output, each element representing a line of the file. If False, a string is output.
70
70
  """
71
71
 
72
+ with open(in_path, 'r', encoding='utf-8') as f:
73
+ data = f.read()
74
+
72
75
  if list_out:
73
- out = []
76
+ return data.split('\n')
74
77
  else:
75
- out = ''
76
-
77
- with open(in_path, 'r', encoding='utf-8') as in_file:
78
- for line in in_file:
79
- if list_out:
80
- out.append(line.strip('\n'))
81
- else:
82
- out += line
83
-
84
- return out
78
+ return data
85
79
 
86
80
 
87
81
  def save_list(in_list, out_path, mode='w'):
parutils/logging/const.py CHANGED
@@ -1,5 +1,2 @@
1
- DEFAULT_LEVEL = 0
2
1
  DEFAULT_DIR = 'log'
3
- DEFAULT_LOG_FORMAT = '%H:%M:%S - '
4
- DEFAULT_FILE_FORMAT = '%Y%m%d_%H%M%S'
5
2
  MAX_ERR_COUNT = 10
@@ -16,12 +16,12 @@ class Logger:
16
16
  self,
17
17
  file_label='',
18
18
  force_new_logger=False,
19
- level=None,
20
- log_format=None,
21
- file_write=True,
22
- dir=None,
23
- file_format=None,
24
- log_every=1,
19
+ level=0,
20
+ log_format='%H:%M:%S - ',
21
+ file_write=True, # Logs are written in a file or not
22
+ log_dir=None, # Directory where the log file is saved (default is 'log')
23
+ file_format='%Y%m%d_%H%M%S',
24
+ log_every=1, # If equals to n, the log file will be written only every n logs. That can significantly improve perfs when writing logs to high latency places (eg. network drives)
25
25
  ) -> None:
26
26
  from . import g
27
27
 
@@ -32,8 +32,8 @@ class Logger:
32
32
  self.logs = []
33
33
  self.buffer = ''
34
34
  self.err_count = 0
35
- self.level = level if level else const.DEFAULT_LEVEL
36
- self.log_format = log_format if log_format else const.DEFAULT_LOG_FORMAT
35
+ self.level = level
36
+ self.log_format = log_format
37
37
  self.file_write = file_write
38
38
  self.start_time = time()
39
39
 
@@ -44,8 +44,8 @@ class Logger:
44
44
  if not file_write:
45
45
  return
46
46
  self.file_label = file_label
47
- self.dir = dir if dir else const.DEFAULT_DIR
48
- self.file_format = file_format if file_format else const.DEFAULT_FILE_FORMAT
47
+ self.dir = log_dir if log_dir else const.DEFAULT_DIR
48
+ self.file_format = file_format
49
49
  file_base_name = datetime.now().strftime(self.file_format)
50
50
  if self.file_label:
51
51
  file_base_name += '_' + self.file_label
parutils/logging/main.py CHANGED
@@ -6,7 +6,7 @@ def log(*args, level=0, c_out=True):
6
6
  """Logs 'str_in' in the current log file (log_path)
7
7
 
8
8
  - level: log level. Current log level is the attribute level of the current logger.
9
- You can get the current loger by using the get_logger function. Nothing is logged if logger level < level
9
+ You can get the current logger by using the get_logger function. Nothing is logged if logger level < level
10
10
  - c_out: specifies if something should be printed in the console or not
11
11
  """
12
12
 
@@ -16,7 +16,7 @@ def log_print(*args, level=0, c_out=True, nb_tab=0, dashes=0, tab_char=' ', s
16
16
  """Prints something in the current log file (log_path)
17
17
 
18
18
  - level: log level. Current log level is the attribute level of the current logger.
19
- You can get the current loger by using the get_logger function. Nothing is logged if logger level < level
19
+ You can get the current logger by using the get_logger function. Nothing is logged if logger level < level
20
20
  - c_out: specifies if something should be printed in the console or not
21
21
  - nb_tab: number of tab indentations
22
22
  - dashes: total length of the input string extended with dashes ('-')
parutils/msc.py CHANGED
@@ -2,7 +2,7 @@ from typing import List
2
2
 
3
3
 
4
4
  def list_to_dict(list_in: List[str], separator='='):
5
- """Transforms 'list_in' to a dictionary using the 'separator'"""
5
+ """Transforms 'list_in' into a dictionary using the 'separator'"""
6
6
 
7
7
  out = {}
8
8
  for elt in list_in:
@@ -14,8 +14,8 @@ def list_to_dict(list_in: List[str], separator='='):
14
14
 
15
15
 
16
16
  def replace_from_dict(str_in: str, dict_in, var_del='@@'):
17
- """Replaces the variables (delimited by '@@') in 'str_in' with the values
18
- of 'dict_in'.
17
+ """Replaces the variables (delimited by '@@') in 'str_in' with values
18
+ from 'dict_in'.
19
19
 
20
20
  Example:
21
21
  - replace_from_dict('Hello @@VAR@@', {'VAR': 'world'}) returns 'Hello world'
parutils/strg.py CHANGED
@@ -74,7 +74,7 @@ def like_dict(in_str, like_dict, case_sensitive=True, skey='', exact=False):
74
74
 
75
75
 
76
76
  def hash512(in_str: str, length=10):
77
- """Contrary to hash, this hash function is not randomised, meaning it
77
+ """Contrary to hash, this hash function is not randomized, meaning it
78
78
  always outputs the same string for the same input string"""
79
79
  import hashlib
80
80
 
@@ -147,7 +147,7 @@ def get_duration_string(start_time, return_dms=False, end_time=None):
147
147
 
148
148
 
149
149
  def big_number(int_in):
150
- """Converts a potentially big number into a lisible string.
150
+ """Converts a potentially big number into a readable string.
151
151
 
152
152
  Example:
153
153
  - big_number(10000000) returns '10 000 000'.
@@ -0,0 +1,20 @@
1
+ TESTS_LOG_DIR = 'log\\tests'
2
+ TESTS_OUT_DIR = 'out\\tests'
3
+
4
+
5
+ def init():
6
+ import parutils as u
7
+ import parutils.logging.const as const
8
+
9
+ const.DEFAULT_DIR = TESTS_LOG_DIR
10
+ u.dq.OUT_DIR = TESTS_OUT_DIR
11
+ u.mkdirs(TESTS_LOG_DIR, True)
12
+ u.mkdirs(TESTS_OUT_DIR, True)
13
+
14
+
15
+ def pytest_sessionstart(session):
16
+ """
17
+ Called after the Session object has been created and
18
+ before performing collection and entering the run test loop.
19
+ """
20
+ init()
@@ -0,0 +1,76 @@
1
+ LOG_FILE = [
2
+ "Log file initialised (*)",
3
+ "Python interpreter path: ",
4
+ "Python version: *",
5
+ "ParUtils version: *",
6
+ "This will be logged in a file",
7
+ ]
8
+ LOG_FILE_NOT = [
9
+ "This won't be logged in a file",
10
+ ]
11
+
12
+ N_W = len(LOG_FILE) + len(LOG_FILE_NOT)
13
+ WARNINGS = [
14
+ "Expression 'This won't be logged in a file' couldn't be found in log file",
15
+ "Expression 'Log file initialised (*)' was found in log file",
16
+ "Expression 'Python interpreter path: ' was found in log file",
17
+ "Expression 'Python version: *' was found in log file",
18
+ "Expression 'ParUtils version: *' was found in log file",
19
+ "Expression 'This will be logged in a file' was found in log file",
20
+ ]
21
+ WARN = WARNINGS + [f"check_log LOG_FILE ended with {N_W} warnings"]
22
+ LEVEL_WARN_ERR = WARNINGS + [
23
+ f"check_log LOG_FILE nok, too many warnings ({N_W} warnings)",
24
+ f"[ttry] Exception caught match expected ('check_log LOG_FILE nok, too many warnings ({N_W} warnings)')",
25
+ ]
26
+
27
+ LOG_INPUT = [
28
+ "Test log input",
29
+ "user command",
30
+ ]
31
+
32
+ STEP_LOG = [
33
+ "5 elements appended in * ms. 20 elements appended in total.",
34
+ "Examples of duplicates (limited to 5)",
35
+ "out_list: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]",
36
+ ]
37
+
38
+ LOG_DICT = [
39
+ "key1: value1",
40
+ "key2: value2",
41
+ "key3:",
42
+ " skey1: value1",
43
+ " skey2:",
44
+ " sskey1: value1",
45
+ " sskey2: value2",
46
+ " skey1: value1",
47
+ " skey2:",
48
+ " sskey1: value1",
49
+ " sskey2: value2",
50
+ " skey1: value1",
51
+ " skey2:",
52
+ " sskey1: value1",
53
+ " sskey2: value2",
54
+ ]
55
+
56
+ ERR_HANDLING = [
57
+ "Warning: the following message couldn't be logged because of [Errno 22] Invalid argument: ':.:.': * - test error handling normal 1",
58
+ "Warning: the following message couldn't be logged because of [Errno 22] Invalid argument: ':.:.': * - test error handling normal 2",
59
+ "* - test error handling normal 3",
60
+ ]
61
+ ERR_HANDLING_NOT = [
62
+ "test error handling max limit",
63
+ ]
64
+
65
+ END_1 = [
66
+ "check_log LOG_FILE ok",
67
+ "check_log WARN ok",
68
+ "check_log LOG_INPUT ok",
69
+ "check_log UPDATE_LOGS ok",
70
+ ]
71
+ END_2 = [
72
+ "check_log LEVEL_WARN_ERR ok",
73
+ "check_log STEP_LOG ok",
74
+ "check_log LOG_DICT ok",
75
+ "check_log ERR_HANDLING ok",
76
+ ]
@@ -0,0 +1,177 @@
1
+ import pytest
2
+ import parutils as u
3
+ from parutils.logging import const
4
+ from parutils.tests.logging import check_log as cl
5
+
6
+
7
+ def t_log_every():
8
+ u.g.logs = []
9
+ u.close_logger()
10
+ u.Logger('TEST_LOG_EVERY', log_every=4)
11
+ u.log("log_elt_1")
12
+ assert u.g.logs == []
13
+ u.check_log(in_list_not=["log_elt_1"], name='LOG_EVERY_1')
14
+
15
+ u.log("log_elt_2")
16
+ u.log("log_elt_3")
17
+ u.log("log_elt_4")
18
+ u.check_log(["log_elt_1", "log_elt_2", "log_elt_3", "log_elt_4", 'check_log LOG_EVERY_1 ok'])
19
+ assert len(u.g.logs) == 2
20
+
21
+ u.log("log_elt_5")
22
+ logs_txt = u.load_txt(u.get_logger().log_path, False)
23
+ assert "log_elt_5" not in logs_txt
24
+ assert len(u.g.logs) == 2
25
+
26
+ log_path = u.get_logger().log_path
27
+ logs = u.load_txt(log_path)
28
+ assert len(logs) == 14
29
+ logs0 = u.close_logger().logs
30
+ assert "log_elt_5" in logs0[2]
31
+ logs = u.load_txt(log_path)
32
+ assert len(logs) == 17
33
+ logs_txt = u.load_txt(log_path, False)
34
+ assert "log_elt_5" in logs_txt
35
+ assert len(u.g.logs) == 3
36
+ u.log_print()
37
+
38
+
39
+ def t_log_file():
40
+ u.g.logs = []
41
+ u.close_logger()
42
+ u.log("This won't be logged in a file\n")
43
+ e_ref = "No log file has been initialised"
44
+ u.ttry(u.check_log, e_ref, cl.LOG_FILE)
45
+ u.log_print()
46
+
47
+ u.Logger('TEST_LOGGING_1')
48
+ u.Logger()
49
+ u.set_logger(u.get_logger())
50
+ u.log("This will be logged", "in a file\n")
51
+ u.get_logger().empty_log_every_buffer()
52
+ u.check_log(cl.LOG_FILE, cl.LOG_FILE_NOT, log_matches=True, name='LOG_FILE')
53
+ u.check_log(["Expression matched: Log file initialised (*)"], name='LOG_MTACHES')
54
+ u.log_print()
55
+
56
+
57
+ def t_warn():
58
+ with pytest.warns(UserWarning): # disables the warnings
59
+ u.check_log(cl.LOG_FILE_NOT, cl.LOG_FILE, name='LOG_FILE', max_warn=10)
60
+ u.check_log(cl.WARN, name='WARN')
61
+ u.log_print()
62
+
63
+
64
+ def t_input(monkeypatch):
65
+ monkeypatch.setattr('builtins.input', mock_input)
66
+ assert u.log_input("Test log input") == "user command"
67
+ u.check_log(cl.LOG_INPUT, name='LOG_INPUT')
68
+ u.log_print()
69
+
70
+
71
+ def t_update_logs():
72
+ s = "test update_logs"
73
+ logs = u.get_logs()
74
+ logs.append(s)
75
+ u.update_logs(logs)
76
+ new_logs = u.get_logs()
77
+ assert logs == new_logs
78
+ u.check_log([s], name='UPDATE_LOGS')
79
+ u.log_print()
80
+
81
+
82
+ def t_level_warn_err():
83
+ u.Logger('TEST_LOGGING_2')
84
+ u.log("This will be logged", "in a file\n")
85
+ u.log("This won't be logged in a file\n", level=1)
86
+ u.log_print("This won't be logged in a file\n", level=1)
87
+ with pytest.warns(UserWarning):
88
+ e_ref = f"check_log LOG_FILE nok, too many warnings ({cl.N_W} warnings)"
89
+ u.ttry(u.check_log, e_ref, cl.LOG_FILE_NOT, cl.LOG_FILE, name='LOG_FILE')
90
+ u.check_log(cl.LEVEL_WARN_ERR, name='LEVEL_WARN_ERR')
91
+ u.log_print()
92
+
93
+
94
+ def t_step_log():
95
+ out_list = []
96
+ u.init_sl_timer()
97
+ for i in range(1, 21):
98
+ # time.sleep(0.05) # simulates io / calculation
99
+ out_list.append(i)
100
+ u.step_log(i, 5, "elements appended")
101
+
102
+ u.log_print('\nout_list:', out_list)
103
+ u.log_example(out_list)
104
+ u.log_example([])
105
+ u.check_log(cl.STEP_LOG, name='STEP_LOG')
106
+ u.log_print()
107
+
108
+
109
+ def t_log_dict():
110
+ ssd = {'sskey1': 'value1', 'sskey2': 'value2'}
111
+ sd = {'skey1': 'value1', 'skey2': ssd}
112
+ d = {'key1': 'value1', 'key2': 'value2', 'key3': sd}
113
+ u.log_dict(d, depth=2, tab_char=' ')
114
+ u.log_dict(d, depth=2, tab_char=' ')
115
+ u.log_dict(d, depth=2, tab_char='\t')
116
+ u.check_log(cl.LOG_DICT, name='LOG_DICT')
117
+ u.log_print()
118
+
119
+
120
+ def t_err_handling():
121
+ # Error handling - normal case
122
+ logger = u.get_logger()
123
+ back = logger.log_path
124
+ logger.log_path = ':.:.'
125
+ u.log('test error handling normal 1')
126
+ u.log('test error handling normal 2')
127
+ logger.log_path = back
128
+ u.log('test error handling normal 3')
129
+
130
+ # Error handling - max limit reatched case
131
+ const.MAX_ERR_COUNT = 2
132
+ logger.log_path = ':.:.'
133
+ u.log('test error handling max limit 1')
134
+ u.log('test error handling max limit 2')
135
+ u.log('test error handling max limit 3')
136
+ u.log('test error handling max limit 4')
137
+ logger.log_path = back
138
+ logger.file_write = True
139
+ logger.buffer = ''
140
+ assert "test error handling max limit 4" in logger.logs[-1]
141
+ assert "The number of logging errors in a row reached" in logger.logs[-2]
142
+ u.check_log(cl.ERR_HANDLING, cl.ERR_HANDLING_NOT, name='ERR_HANDLING')
143
+ u.log_print()
144
+
145
+
146
+ def test_logging(monkeypatch):
147
+ t_log_every()
148
+ t_log_file()
149
+ t_warn()
150
+ t_input(monkeypatch)
151
+ t_update_logs()
152
+ u.check_log(cl.END_1, name='END_1')
153
+ u.close_logger()
154
+ u.log_print()
155
+
156
+ t_level_warn_err()
157
+ t_step_log()
158
+ t_log_dict()
159
+ t_err_handling()
160
+ assert "This won't be logged in a file" in u.g.logs[0]
161
+ u.check_log(cl.END_2, name='END_2')
162
+ u.close_logger()
163
+ u.log_print()
164
+
165
+
166
+ def mock_input(txt):
167
+ out = "user command"
168
+ print(txt + out)
169
+ return out
170
+
171
+
172
+ if __name__ == '__main__': # pragma: no cover
173
+ from parutils.tests.conftest import init
174
+ from _pytest.monkeypatch import MonkeyPatch
175
+
176
+ init()
177
+ test_logging(MonkeyPatch())
@@ -0,0 +1,10 @@
1
+ CL = [
2
+ "Log file initialised",
3
+ "Python version:",
4
+ "Test of like functions",
5
+ "like simple ok",
6
+ "like m ok",
7
+ "like_list ok",
8
+ "like_dict ok",
9
+ "Test get_duration_string",
10
+ ]
@@ -0,0 +1,74 @@
1
+ import parutils as u
2
+ from parutils.tests.string.check_log import CL
3
+
4
+
5
+ def get_duration():
6
+ u.log_print("Test get_duration_string", dashes=100)
7
+
8
+ dstr = u.get_duration_string(0, end_time=0.35)
9
+ u.log(dstr)
10
+ assert dstr == "350 ms"
11
+
12
+ (dms, dstr) = u.get_duration_string(0, end_time=5.369, return_dms=True)
13
+ u.log(dstr, dms)
14
+ assert (dstr, dms) == ("5.3 s", 5369)
15
+
16
+ dstr = u.get_duration_string(0, end_time=150)
17
+ u.log(dstr)
18
+ assert dstr == "2 minutes and 30 seconds"
19
+
20
+ u.log_print()
21
+
22
+
23
+ def like():
24
+ u.log_print("Test of like functions", dashes=100)
25
+
26
+ s = '2 test ok?'
27
+ assert u.like(s, 'test')
28
+ assert not u.like(s, 'test', exact=True)
29
+ assert u.like(s, '*test*', exact=True)
30
+ assert u.like(s, 'TEST', case_sensitive=False)
31
+ assert u.like(s, 'TEST') is False
32
+ u.log("like simple ok")
33
+
34
+ m = u.like(s, '2 * ok?')
35
+ assert m.group(1) == 'test'
36
+ u.log("like m ok")
37
+
38
+ lst = ['1', 'test']
39
+ e_ref = u.strg.E_WRONG_TYPE_LIST
40
+ u.ttry(u.like_list, e_ref, s, 'test')
41
+ assert u.like_list(s, lst)
42
+ assert u.like_list(s, lst, exact=True) is False
43
+ assert u.like_list('TEST', lst) is False
44
+ u.log("like_list ok")
45
+
46
+ dct = {'1': ['a', 'b'], '2': 'test'}
47
+ e_ref = u.strg.E_WRONG_TYPE_DICT
48
+ u.ttry(u.like_dict, e_ref, s, 'test')
49
+ assert u.like_dict(s, dct) == '2'
50
+ assert u.like_dict(s, dct, exact=True) is False
51
+ assert u.like_dict('b', dct) == '1'
52
+ assert u.like_dict('TEST', dct) is False
53
+ u.log("like_dict ok")
54
+
55
+ assert u.hash512('TEST', 4) == '7bfa'
56
+ assert len(u.gen_random_string(10)) == 10
57
+ u.log_print()
58
+
59
+
60
+ def test_string():
61
+
62
+ u.Logger('TEST_STRING', True)
63
+ assert u.big_number(1000) == '1 000'
64
+ assert u.truncate('test_test', 4) == 't...'
65
+ get_duration()
66
+ like()
67
+ u.check_log(CL)
68
+ u.close_logger()
69
+
70
+
71
+ if __name__ == '__main__': # pragma: no cover
72
+ from parutils.tests.conftest import init
73
+ init()
74
+ test_string()
@@ -0,0 +1,27 @@
1
+ import parutils as u
2
+
3
+ IN1_PATH = 'parutils\\tests\\files\\in1.csv'
4
+ IN3_PATH = 'parutils\\tests\\files\\in3.csv'
5
+ OUT_PATH = 'parutils\\tests\\files\\out.csv'
6
+
7
+
8
+ def test_csv():
9
+
10
+ ar1 = u.load_csv(IN3_PATH, True)
11
+ assert ar1[1][1] == 'comment1;comment2'
12
+ u.save_csv(ar1, IN3_PATH, quote=True)
13
+ ar2 = u.load_csv(IN3_PATH, True)
14
+ assert ar1 == ar2
15
+
16
+ d = u.get_csv_fields_dict(IN1_PATH)
17
+ assert d == {'ID': 0, 'NAME': 1}
18
+
19
+ s = u.csv_clean('FIELD1;\n')
20
+ assert s == 'FIELD1'
21
+
22
+ e_ref = u.csvl.E_WRONG_TYPE_LIST
23
+ u.ttry(u.save_csv, e_ref, ['1'], OUT_PATH)
24
+
25
+
26
+ if __name__ == '__main__': # pragma: no cover
27
+ test_csv()
@@ -0,0 +1,35 @@
1
+ import parutils.tests.conftest as conftest
2
+ import parutils as u
3
+
4
+ FILES_DIR = 'parutils\\tests\\files'
5
+ OUT_DIR = conftest.TESTS_OUT_DIR
6
+ DUP_IN = FILES_DIR + '\\dup_in.csv'
7
+ DUP_OUT = OUT_DIR + '\\out_dup.csv'
8
+ DUP_OUT_REF = FILES_DIR + '\\dup_out_ref.csv'
9
+ IN_1 = FILES_DIR + '\\in1.csv'
10
+ IN_2 = FILES_DIR + '\\in2.csv'
11
+
12
+
13
+ def test_dq():
14
+
15
+ u.Logger('TEST_DQ', True)
16
+ u.log_print("Test toolDup - find_dup_list", dashes=100)
17
+ list_in = u.load_csv(DUP_IN)
18
+ dup_list = u.find_dup_list(list_in)
19
+ u.log_example(dup_list)
20
+ u.save_csv(dup_list, DUP_OUT)
21
+ u.file_match(DUP_OUT, DUP_OUT_REF, del_dup=True)
22
+ u.diff_list(['1'], ['2'])
23
+
24
+ e_ref = "Files don't match"
25
+ u.ttry(u.file_match, e_ref, IN_1, IN_2)
26
+
27
+ assert u.find_dup_list([]) == []
28
+ assert u.del_dup_list([]) == []
29
+ u.close_logger()
30
+
31
+
32
+ if __name__ == '__main__': # pragma: no cover
33
+ from parutils.tests.conftest import init
34
+ init()
35
+ test_dq()
@@ -0,0 +1,27 @@
1
+ import parutils as u
2
+
3
+
4
+ def test_file():
5
+ u.mkdirs('')
6
+
7
+ assert u.list_files('not exist') == []
8
+ assert len(u.list_files('parutils\\tests')) > 1
9
+
10
+ out = u.list_files('parutils\\tests', only_list=['test'], ignore_list=['file', 'msc', '0'])
11
+ assert out == ['parutils\\tests\\conftest.py', 'parutils\\tests\\test_csv.py', 'parutils\\tests\\test_dq.py']
12
+
13
+ out = u.list_files('parutils\\tests', only_list=['in*.csv'], ignore_list=['file', 'msc', 'init'], incl_root=False, walk=True)
14
+ assert out == ['in1.csv', 'in2.csv', 'in3.csv']
15
+
16
+ out = u.list_files('parutils\\tests', only_list=['test'], ignore_list=['file', 'msc', 'init', '0'], abspath=True)
17
+ lst = ['c:\\*\\tests\\conftest.py', 'c:\\*\\tests\\test_csv.py', 'c:\\*\\tests\\test_dq.py']
18
+ for elt in out:
19
+ assert u.like_list(elt, lst, case_sensitive=False)
20
+
21
+ path = 'out\\tests\\out.txt'
22
+ u.save_list(out, path)
23
+ assert u.count_lines(path) == 3
24
+
25
+
26
+ if __name__ == '__main__': # pragma: no cover
27
+ test_file()
@@ -0,0 +1,29 @@
1
+ import parutils as u
2
+
3
+
4
+ def test_msc():
5
+ lst = ['key1=value1', 'key2=value2']
6
+ out = u.list_to_dict(lst)
7
+
8
+ d = {'key1': 'value1', 'key2': 'value2'}
9
+ assert out == d
10
+
11
+ out = u.replace_from_dict('Hello @@VAR@@', {'VAR': 'world'})
12
+ assert out == 'Hello world'
13
+
14
+ u.ttry(nok_func, 'test_error')
15
+ err = "[ttry] Exception caught ('test_error') don't match expected ('test_error_1')"
16
+ u.ttry(u.ttry, err, nok_func, 'test_error_1')
17
+ u.ttry(u.ttry, "[ttry] No exception was caught", ok_func, 'test_error')
18
+
19
+
20
+ def nok_func():
21
+ raise Exception('test_error')
22
+
23
+
24
+ def ok_func():
25
+ pass
26
+
27
+
28
+ if __name__ == '__main__': # pragma: no cover
29
+ test_msc()
@@ -1,17 +1,16 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: ParUtils
3
- Version: 1.2.3
4
- Summary: This package contains a bunch of Python utils developed for Support, Test and Automation IT Engineers
5
- Home-page: https://github.com/paularnaud2/ParUtils
6
- Author: Paul ARNAUD
7
- Author-email: paularnaud2@gmail.com
8
- Project-URL: Bug Tracker, https://github.com/paularnaud2/ParUtils/issues
3
+ Version: 1.2.5
4
+ Summary: This package contains a bunch of Python utilities developed for Support, Test and Automation IT Engineers
5
+ Author-email: Paul ARNAUD <paularnaud2@gmail.com>
6
+ Project-URL: Homepage, https://github.com/paularnaud2/ParUtils
7
+ Project-URL: Issues, https://github.com/paularnaud2/ParUtils/issues
9
8
  Classifier: Programming Language :: Python :: 3
10
- Classifier: License :: OSI Approved :: MIT License
11
9
  Classifier: Operating System :: OS Independent
12
- Requires-Python: >=3.6
10
+ Requires-Python: >=3.8
13
11
  Description-Content-Type: text/markdown
14
12
  License-File: LICENSE
13
+ Dynamic: license-file
15
14
 
16
15
  # ParUtils
17
16
 
@@ -76,8 +75,9 @@ Data quality features:
76
75
  - `del_dup_list`: removes duplicates from a list
77
76
 
78
77
 
79
- ### Logging with parutils
78
+ ## Logging with parutils
80
79
 
80
+ ### Basic usage guide
81
81
  The `log` function and the `Logger` class are directly available from the parutils package. So you can do:
82
82
 
83
83
  import parutils as u
@@ -98,6 +98,7 @@ Note that the default constants for the logging sub package are stored in __paru
98
98
 
99
99
  u.logging.const.DEFAULT_DIR = '<my_custom_dir>'
100
100
 
101
+ ### About the step_log function
101
102
  The `step_log` function allows you to log some information only when the input ``counter`` is a multiple of the input ``step``. Thus, `step_log` is to be used in loops to __track the progress of long processes__ such as reading or writing millions of lines in a file. The ``what`` input expects a description of what is being counted. It's default value is ``'lines written'``.
102
103
  In order to correctly measure the elapsed time for the first log line, the ``step_log`` function has to be initialised by running ``init_sl_timer()``.
103
104
  So for example, if you input ``step=500`` and don't input any ``what`` value, you should get something like this:
@@ -108,3 +109,14 @@ So for example, if you input ``step=500`` and don't input any ``what`` value, yo
108
109
 
109
110
  Checkout the __test_logging.py__ file in __tests/logging__ for simple examples of use.
110
111
 
112
+ ### About the retry mechanism
113
+ If the logger is initialized, the log information is written in a file each time you call the log function. This file writing can fail, especially if your log file is located on a network drive.
114
+
115
+ Parutils has a built-in retry mechanism related to log file writing failures. Here is how it works:
116
+ Whenever writing some log fails, a warning is printed, looking like this
117
+
118
+ Warning: the following message couldn't be logged because of <error>: <logged message>
119
+ This warning is stored in a buffer and the logger will try to log this buffer to the log file next time it has something to log. If the next try also fails, another warning is printed and added to the buffer, etc, up to a certain limit. When the size of the buffer exceeds 10, the logger stops trying to log into a file, and just prints the logs in the currently available console.
120
+
121
+ ### About the log_every argument
122
+ Because each logged message implies opening a file, writing to this file, and closing the file, logging can severely affect performance, especially when logging to some network locations, or more generally to a slow drive. The `log_every` argument of the Logger constructor allows to mitigate this by logging to the file only every <log_every> log messages, using a buffer mechanism. By default, log_every is set to one, so the log file is writing for every log messages. For example, if you set log_every to 10, the log file is only written every 10 log entries, while every log entry will still be printed live on the console.
@@ -0,0 +1,32 @@
1
+ parutils/__init__.py,sha256=EaFtUmU0kYgnjvAFwPmoKt7I5zn9bxY02R7TUtcE6M4,1434
2
+ parutils/changelog.py,sha256=-zSqn-8tI_VdDjCEenmTdpBN9br5-meNPj-lY8l0AW4,1920
3
+ parutils/csvl.py,sha256=JgJPnN8IPrdMD8OOlPtDSEhJULMkPkqnDnB-D4z9F-E,2871
4
+ parutils/dq.py,sha256=kdvzOo5FLCumXagY05kGSseYlbR-zvjjmsMtGVliVc8,2302
5
+ parutils/file.py,sha256=prENNneiQmKKRS5BxGH7tN23x5e6XSfyrhpO0lOLs0g,2739
6
+ parutils/g.py,sha256=n99gQbLs2rmszBYw_wy43P4iQo3lqpBHz3-Q7sm9n_U,11
7
+ parutils/msc.py,sha256=6Yx-VJss0xRqQAiuVURu0L4oDHkcsF7T5EK6eXrm9TQ,745
8
+ parutils/strg.py,sha256=g4FjVWlUBsQO3xFoMe-ZYskCjVEQVyUCdDGliS_kbz4,5564
9
+ parutils/testing.py,sha256=kXrMz8Kze4mZejaePmV_OIca4iuNcG_dhGVHCgVuF-k,610
10
+ parutils/wrap.py,sha256=PqMyKodgEjWDXoIVZZXRaBSdkGFL5OWmVJttl2Crrg8,421
11
+ parutils/logging/__init__.py,sha256=rSNpgjuYer-Hhn6zKzKwKSn_KfcajEXlk6cJnPC9eJU,461
12
+ parutils/logging/cl.py,sha256=NCLuxLHvqRTypZlAgwnegkByLTnTirKntnwguJHBelA,2086
13
+ parutils/logging/const.py,sha256=9GrSonZu9YXz0U1dVHIiAaAjGcVQs_cnKc__6-C_CIc,41
14
+ parutils/logging/core.py,sha256=POLKsQaaKYZOR7IhWtAaF5a8noZL7hKdssmrOtle4S4,850
15
+ parutils/logging/g.py,sha256=ZSrgZw63kwxIW66A7-9_iYeDt4AstNZ_XXQgK8MglyQ,47
16
+ parutils/logging/logger.py,sha256=4jBb7fjZBQKKNwat46BjTrcSV20uKX3-hcyBMTFk34M,5011
17
+ parutils/logging/main.py,sha256=va2jaQXN6TO1ycajswyJzRXzlMB2BVQ_CervUnnQE70,2129
18
+ parutils/logging/sl.py,sha256=3-sj_o33cZmOqeFxlTl5HyHOvSAhn9glYcc-BmTUpZc,1164
19
+ parutils/tests/conftest.py,sha256=iIyQvgVV8GzUwJ0tlN2KUJHX5ZO7lp8bKFK3VmRx4DU,485
20
+ parutils/tests/test_csv.py,sha256=vHs_CT1oLWAGkRvKdF-Ilz62MczEDKqQfmmQe-nt-Wg,671
21
+ parutils/tests/test_dq.py,sha256=Qb5lldyg-UAOvNbViNLnchkG93v_-DQ96XkIPgOIXWo,967
22
+ parutils/tests/test_file.py,sha256=iYaSm0YU7qQIq9p7Sea4KPdoKRpqWcbgbkZG4u0DR_s,1031
23
+ parutils/tests/test_msc.py,sha256=C2Q_88i2sIbz52KhvdkY0-3Aa9Yk2FSHFxVYUqoQ_Fs,696
24
+ parutils/tests/logging/check_log.py,sha256=Se-EuWuwxlcksjIkLZU4d77-o3eyFdppJCxGJ-GcorY,2332
25
+ parutils/tests/logging/test_logging.py,sha256=bceIEO7W7rhLm_YrqRcXSA0NaQIAU1x6XZMWU3uNeFk,5260
26
+ parutils/tests/string/check_log.py,sha256=m2RRJIorA6jg7uV_8nxWfKHUFZ98YPDn-DqzcTw3cQ0,211
27
+ parutils/tests/string/test_string.py,sha256=jhfGkrHENUERIG2UAVIlGYDpeecQS-_-JtmMUR7qDMA,2023
28
+ parutils-1.2.5.dist-info/licenses/LICENSE,sha256=6kbiFSfobTZ7beWiKnHpN902HgBx-Jzgcme0SvKqhKY,1091
29
+ parutils-1.2.5.dist-info/METADATA,sha256=EMs4o4amP6SV9U0Uhj15UpTte2ETmkAVke9hYCpzwgo,6868
30
+ parutils-1.2.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
31
+ parutils-1.2.5.dist-info/top_level.txt,sha256=1MDobUcroeYEvdupZCAFvA5hJjm7LSDUV5A4jHySNis,9
32
+ parutils-1.2.5.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,23 +0,0 @@
1
- parutils/__init__.py,sha256=EaFtUmU0kYgnjvAFwPmoKt7I5zn9bxY02R7TUtcE6M4,1434
2
- parutils/changelog.py,sha256=DC3AEcRVeDeJl62Ri4eM5_0TIxG5znRhBpvcZAvAjZY,1730
3
- parutils/csvl.py,sha256=UBqw0lyhTNqFLSNIE-JM7TAQYCRgHftYQmHi21ru8-A,2874
4
- parutils/dq.py,sha256=kdvzOo5FLCumXagY05kGSseYlbR-zvjjmsMtGVliVc8,2302
5
- parutils/file.py,sha256=dedO84jZVXrE0x_31Wy_QhM6wTPOAshhp4SuT92rrPQ,2869
6
- parutils/g.py,sha256=n99gQbLs2rmszBYw_wy43P4iQo3lqpBHz3-Q7sm9n_U,11
7
- parutils/msc.py,sha256=0hf-WrYhDquaDtfHXINNs9tROwJ7iYV2J6y0umkaGRA,745
8
- parutils/strg.py,sha256=_8NQJ1iY58Z_xACTDVcyonvRHDRuy9Ai3Kv3CvVXRh4,5563
9
- parutils/testing.py,sha256=kXrMz8Kze4mZejaePmV_OIca4iuNcG_dhGVHCgVuF-k,610
10
- parutils/wrap.py,sha256=PqMyKodgEjWDXoIVZZXRaBSdkGFL5OWmVJttl2Crrg8,421
11
- parutils/logging/__init__.py,sha256=rSNpgjuYer-Hhn6zKzKwKSn_KfcajEXlk6cJnPC9eJU,461
12
- parutils/logging/cl.py,sha256=NCLuxLHvqRTypZlAgwnegkByLTnTirKntnwguJHBelA,2086
13
- parutils/logging/const.py,sha256=L2OppBDAZYKUTIjsLPt6hTUh8UWH_1mX9YaGWlwzpj4,135
14
- parutils/logging/core.py,sha256=POLKsQaaKYZOR7IhWtAaF5a8noZL7hKdssmrOtle4S4,850
15
- parutils/logging/g.py,sha256=ZSrgZw63kwxIW66A7-9_iYeDt4AstNZ_XXQgK8MglyQ,47
16
- parutils/logging/logger.py,sha256=4x_F98soVd-PO5y2uQlDDyHI50E58m4VTfpjLB0OjJ0,4843
17
- parutils/logging/main.py,sha256=arUvgB0YTZpidt4wl2GZsR4bRUj_rrSYMrTnCMKt4gA,2127
18
- parutils/logging/sl.py,sha256=3-sj_o33cZmOqeFxlTl5HyHOvSAhn9glYcc-BmTUpZc,1164
19
- ParUtils-1.2.3.dist-info/LICENSE,sha256=6kbiFSfobTZ7beWiKnHpN902HgBx-Jzgcme0SvKqhKY,1091
20
- ParUtils-1.2.3.dist-info/METADATA,sha256=RY72J9pjWc6KdNdC_EzYqvNhM0_-5bmzE5wORFlf2wQ,5285
21
- ParUtils-1.2.3.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
22
- ParUtils-1.2.3.dist-info/top_level.txt,sha256=1MDobUcroeYEvdupZCAFvA5hJjm7LSDUV5A4jHySNis,9
23
- ParUtils-1.2.3.dist-info/RECORD,,