ParUtils 1.2.4__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 +9 -5
- parutils/csvl.py +11 -11
- parutils/file.py +8 -14
- parutils/logging/main.py +2 -2
- parutils/msc.py +3 -3
- parutils/strg.py +2 -2
- parutils/tests/conftest.py +20 -0
- parutils/tests/logging/check_log.py +76 -0
- parutils/tests/logging/test_logging.py +177 -0
- parutils/tests/string/check_log.py +10 -0
- parutils/tests/string/test_string.py +74 -0
- parutils/tests/test_csv.py +27 -0
- parutils/tests/test_dq.py +35 -0
- parutils/tests/test_file.py +27 -0
- parutils/tests/test_msc.py +29 -0
- {ParUtils-1.2.4.dist-info → parutils-1.2.5.dist-info}/METADATA +22 -10
- parutils-1.2.5.dist-info/RECORD +32 -0
- {ParUtils-1.2.4.dist-info → parutils-1.2.5.dist-info}/WHEEL +1 -1
- ParUtils-1.2.4.dist-info/RECORD +0 -23
- {ParUtils-1.2.4.dist-info → parutils-1.2.5.dist-info/licenses}/LICENSE +0 -0
- {ParUtils-1.2.4.dist-info → parutils-1.2.5.dist-info}/top_level.txt +0 -0
parutils/changelog.py
CHANGED
@@ -1,11 +1,15 @@
|
|
1
|
-
__VERSION__ = '1.2.
|
2
|
-
#
|
3
|
-
#
|
4
|
-
|
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
|
5
9
|
|
6
10
|
# __VERSION__ = '1.2.3'
|
7
11
|
# # close_logger returns the closed logger object
|
8
|
-
# # no more
|
12
|
+
# # no more strip('\n') in save_list
|
9
13
|
# # empty_log_every_buffer
|
10
14
|
|
11
15
|
# __VERSION__ = '1.2.2'
|
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
|
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
|
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
|
27
|
-
When quote is True, the
|
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
|
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
|
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
|
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
|
66
|
+
"""Writes a line to a CSV file
|
67
67
|
|
68
|
-
- row:
|
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
|
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
|
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
|
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
|
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
|
-
|
76
|
+
return data.split('\n')
|
74
77
|
else:
|
75
|
-
|
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/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
|
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
|
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'
|
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
|
18
|
-
|
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
|
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
|
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,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.
|
1
|
+
Metadata-Version: 2.4
|
2
2
|
Name: ParUtils
|
3
|
-
Version: 1.2.
|
4
|
-
Summary: This package contains a bunch of Python
|
5
|
-
|
6
|
-
|
7
|
-
|
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.
|
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
|
-
|
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,,
|
ParUtils-1.2.4.dist-info/RECORD
DELETED
@@ -1,23 +0,0 @@
|
|
1
|
-
parutils/__init__.py,sha256=EaFtUmU0kYgnjvAFwPmoKt7I5zn9bxY02R7TUtcE6M4,1434
|
2
|
-
parutils/changelog.py,sha256=VKGB9PccjQjzrSdDQqZP6dNZYaHj_5hUGwbOfKmWyfs,1835
|
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=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=arUvgB0YTZpidt4wl2GZsR4bRUj_rrSYMrTnCMKt4gA,2127
|
18
|
-
parutils/logging/sl.py,sha256=3-sj_o33cZmOqeFxlTl5HyHOvSAhn9glYcc-BmTUpZc,1164
|
19
|
-
ParUtils-1.2.4.dist-info/LICENSE,sha256=6kbiFSfobTZ7beWiKnHpN902HgBx-Jzgcme0SvKqhKY,1091
|
20
|
-
ParUtils-1.2.4.dist-info/METADATA,sha256=lKylRNkXvhQfauNzKFWolRssvyxgC1G0IZ7xEALSSlw,5285
|
21
|
-
ParUtils-1.2.4.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
22
|
-
ParUtils-1.2.4.dist-info/top_level.txt,sha256=1MDobUcroeYEvdupZCAFvA5hJjm7LSDUV5A4jHySNis,9
|
23
|
-
ParUtils-1.2.4.dist-info/RECORD,,
|
File without changes
|
File without changes
|