iker-python-common 1.0.66__tar.gz → 1.0.68__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.
- {iker_python_common-1.0.66 → iker_python_common-1.0.68}/PKG-INFO +1 -1
- {iker_python_common-1.0.66/resources/unittest/csv → iker_python_common-1.0.68/resources/unittest/csvutils}/data.csv +1 -1
- {iker_python_common-1.0.66/resources/unittest/csv → iker_python_common-1.0.68/resources/unittest/csvutils}/data.tsv +1 -1
- {iker_python_common-1.0.66 → iker_python_common-1.0.68}/src/iker/common/utils/config.py +2 -2
- iker_python_common-1.0.66/src/iker/common/utils/csv.py → iker_python_common-1.0.68/src/iker/common/utils/csvutils.py +41 -24
- {iker_python_common-1.0.66 → iker_python_common-1.0.68}/src/iker/common/utils/retry.py +2 -2
- {iker_python_common-1.0.66 → iker_python_common-1.0.68}/src/iker/common/utils/span.py +55 -16
- {iker_python_common-1.0.66 → iker_python_common-1.0.68}/src/iker_python_common.egg-info/PKG-INFO +1 -1
- {iker_python_common-1.0.66 → iker_python_common-1.0.68}/src/iker_python_common.egg-info/SOURCES.txt +7 -5
- iker_python_common-1.0.68/test/iker_tests/__init__.py +0 -0
- iker_python_common-1.0.68/test/iker_tests/common/__init__.py +0 -0
- iker_python_common-1.0.68/test/iker_tests/common/utils/__init__.py +0 -0
- {iker_python_common-1.0.66 → iker_python_common-1.0.68}/test/iker_tests/common/utils/config_test.py +1 -1
- iker_python_common-1.0.66/test/iker_tests/common/utils/csv_test.py → iker_python_common-1.0.68/test/iker_tests/common/utils/csvutils_test.py +29 -30
- {iker_python_common-1.0.66 → iker_python_common-1.0.68}/test/iker_tests/common/utils/shutils_test.py +1 -1
- {iker_python_common-1.0.66 → iker_python_common-1.0.68}/test/iker_tests/common/utils/span_test.py +15 -3
- iker_python_common-1.0.66/test/iker_tests/__init__.py → iker_python_common-1.0.68/test/testenv.py +1 -1
- iker_python_common-1.0.66/test/iker_test.py +0 -13
- {iker_python_common-1.0.66 → iker_python_common-1.0.68}/.editorconfig +0 -0
- {iker_python_common-1.0.66 → iker_python_common-1.0.68}/.github/workflows/pr.yml +0 -0
- {iker_python_common-1.0.66 → iker_python_common-1.0.68}/.github/workflows/push.yml +0 -0
- {iker_python_common-1.0.66 → iker_python_common-1.0.68}/.gitignore +0 -0
- {iker_python_common-1.0.66 → iker_python_common-1.0.68}/MANIFEST.in +0 -0
- {iker_python_common-1.0.66 → iker_python_common-1.0.68}/README.md +0 -0
- {iker_python_common-1.0.66 → iker_python_common-1.0.68}/VERSION +0 -0
- {iker_python_common-1.0.66 → iker_python_common-1.0.68}/pyproject.toml +0 -0
- {iker_python_common-1.0.66 → iker_python_common-1.0.68}/resources/unittest/config/config.cfg +0 -0
- {iker_python_common-1.0.66 → iker_python_common-1.0.68}/resources/unittest/shutils/dir.baz/file.bar.baz +0 -0
- {iker_python_common-1.0.66 → iker_python_common-1.0.68}/resources/unittest/shutils/dir.baz/file.foo.bar +0 -0
- {iker_python_common-1.0.66 → iker_python_common-1.0.68}/resources/unittest/shutils/dir.baz/file.foo.baz +0 -0
- {iker_python_common-1.0.66 → iker_python_common-1.0.68}/resources/unittest/shutils/dir.foo/dir.foo.bar/dir.foo.bar.baz/file.foo.bar.baz +0 -0
- {iker_python_common-1.0.66 → iker_python_common-1.0.68}/resources/unittest/shutils/dir.foo/dir.foo.bar/file.bar.baz +0 -0
- {iker_python_common-1.0.66 → iker_python_common-1.0.68}/resources/unittest/shutils/dir.foo/dir.foo.bar/file.foo.bar +0 -0
- {iker_python_common-1.0.66 → iker_python_common-1.0.68}/resources/unittest/shutils/dir.foo/dir.foo.bar/file.foo.baz +0 -0
- {iker_python_common-1.0.66 → iker_python_common-1.0.68}/resources/unittest/shutils/dir.foo/file.bar +0 -0
- {iker_python_common-1.0.66 → iker_python_common-1.0.68}/resources/unittest/shutils/dir.foo/file.baz +0 -0
- {iker_python_common-1.0.66 → iker_python_common-1.0.68}/resources/unittest/shutils/dir.foo/file.foo +0 -0
- {iker_python_common-1.0.66 → iker_python_common-1.0.68}/setup.cfg +0 -0
- {iker_python_common-1.0.66 → iker_python_common-1.0.68}/setup.py +0 -0
- {iker_python_common-1.0.66 → iker_python_common-1.0.68}/src/iker/common/__init__.py +0 -0
- {iker_python_common-1.0.66 → iker_python_common-1.0.68}/src/iker/common/utils/__init__.py +0 -0
- {iker_python_common-1.0.66 → iker_python_common-1.0.68}/src/iker/common/utils/argutils.py +0 -0
- {iker_python_common-1.0.66 → iker_python_common-1.0.68}/src/iker/common/utils/dbutils.py +0 -0
- {iker_python_common-1.0.66 → iker_python_common-1.0.68}/src/iker/common/utils/dtutils.py +0 -0
- {iker_python_common-1.0.66 → iker_python_common-1.0.68}/src/iker/common/utils/funcutils.py +0 -0
- {iker_python_common-1.0.66 → iker_python_common-1.0.68}/src/iker/common/utils/iterutils.py +0 -0
- {iker_python_common-1.0.66 → iker_python_common-1.0.68}/src/iker/common/utils/jsonutils.py +0 -0
- {iker_python_common-1.0.66 → iker_python_common-1.0.68}/src/iker/common/utils/logger.py +0 -0
- {iker_python_common-1.0.66 → iker_python_common-1.0.68}/src/iker/common/utils/numutils.py +0 -0
- {iker_python_common-1.0.66 → iker_python_common-1.0.68}/src/iker/common/utils/randutils.py +0 -0
- {iker_python_common-1.0.66 → iker_python_common-1.0.68}/src/iker/common/utils/shutils.py +0 -0
- {iker_python_common-1.0.66 → iker_python_common-1.0.68}/src/iker/common/utils/strutils.py +0 -0
- {iker_python_common-1.0.66 → iker_python_common-1.0.68}/src/iker/common/utils/testutils.py +0 -0
- {iker_python_common-1.0.66 → iker_python_common-1.0.68}/src/iker/common/utils/typeutils.py +0 -0
- {iker_python_common-1.0.66 → iker_python_common-1.0.68}/src/iker_python_common.egg-info/dependency_links.txt +0 -0
- {iker_python_common-1.0.66 → iker_python_common-1.0.68}/src/iker_python_common.egg-info/not-zip-safe +0 -0
- {iker_python_common-1.0.66 → iker_python_common-1.0.68}/src/iker_python_common.egg-info/requires.txt +0 -0
- {iker_python_common-1.0.66 → iker_python_common-1.0.68}/src/iker_python_common.egg-info/top_level.txt +0 -0
- {iker_python_common-1.0.66 → iker_python_common-1.0.68}/test/iker_tests/common/utils/argutils_test.py +0 -0
- {iker_python_common-1.0.66 → iker_python_common-1.0.68}/test/iker_tests/common/utils/dbutils_test.py +0 -0
- {iker_python_common-1.0.66 → iker_python_common-1.0.68}/test/iker_tests/common/utils/dtutils_test.py +0 -0
- {iker_python_common-1.0.66 → iker_python_common-1.0.68}/test/iker_tests/common/utils/funcutils_test.py +0 -0
- {iker_python_common-1.0.66 → iker_python_common-1.0.68}/test/iker_tests/common/utils/iterutils_test.py +0 -0
- {iker_python_common-1.0.66 → iker_python_common-1.0.68}/test/iker_tests/common/utils/jsonutils_test.py +0 -0
- {iker_python_common-1.0.66 → iker_python_common-1.0.68}/test/iker_tests/common/utils/logger_test.py +0 -0
- {iker_python_common-1.0.66 → iker_python_common-1.0.68}/test/iker_tests/common/utils/numutils_test.py +0 -0
- {iker_python_common-1.0.66 → iker_python_common-1.0.68}/test/iker_tests/common/utils/randutils_test.py +0 -0
- {iker_python_common-1.0.66 → iker_python_common-1.0.68}/test/iker_tests/common/utils/retry_test.py +0 -0
- {iker_python_common-1.0.66 → iker_python_common-1.0.68}/test/iker_tests/common/utils/strutils_test.py +0 -0
- {iker_python_common-1.0.66 → iker_python_common-1.0.68}/test/iker_tests/common/utils/testutils_test.py +0 -0
- {iker_python_common-1.0.66 → iker_python_common-1.0.68}/test/iker_tests/common/utils/typeutils_test.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
dummy_str,dummy_bool,dummy_int,dummy_float,dummy_datetime,dummy_params
|
|
2
2
|
foo,True,1,1.0,2020-01-01T00:00:00,
|
|
3
|
-
bar,False,-1,-1.0,2020-01-01T00:00:00,key_1=value_1
|
|
3
|
+
bar,False,-1,-1.0,2020-01-01T00:00:00,"key_1=value_1,key_2,!key_3"
|
|
4
4
|
baz,\N,100,inf,2020-01-01T00:00:00,\N
|
|
5
5
|
,\N,-100,-inf,2020-01-01T00:00:00,
|
|
6
6
|
\N,\N,0,nan,2020-01-01T00:00:00,\N
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
dummy_str dummy_bool dummy_int dummy_float dummy_datetime dummy_params
|
|
2
2
|
foo True 1 1.0 2020-01-01T00:00:00
|
|
3
|
-
bar False -1 -1.0 2020-01-01T00:00:00 key_1:value_1
|
|
3
|
+
bar False -1 -1.0 2020-01-01T00:00:00 key_1:value_1;key_2;-key_3
|
|
4
4
|
baz <null> 100 inf 2020-01-01T00:00:00 <null>
|
|
5
5
|
<null> -100 -inf 2020-01-01T00:00:00
|
|
6
6
|
<null> <null> 0 nan 2020-01-01T00:00:00 <null>
|
|
@@ -31,7 +31,7 @@ class Config(object):
|
|
|
31
31
|
self.config_parser.read(self.config_path, encoding="utf-8")
|
|
32
32
|
return True
|
|
33
33
|
except IOError as e:
|
|
34
|
-
logger.exception("Failed to restore config from file '%s'", self.config_path)
|
|
34
|
+
logger.exception("Failed to restore config from file <'%s'>", self.config_path)
|
|
35
35
|
return False
|
|
36
36
|
|
|
37
37
|
def persist(self) -> bool:
|
|
@@ -42,7 +42,7 @@ class Config(object):
|
|
|
42
42
|
self.config_parser.write(fh)
|
|
43
43
|
return True
|
|
44
44
|
except IOError as e:
|
|
45
|
-
logger.exception("Failed to persist config to file '%s'", self.config_path)
|
|
45
|
+
logger.exception("Failed to persist config to file <'%s'>", self.config_path)
|
|
46
46
|
return False
|
|
47
47
|
|
|
48
48
|
def has_section(self, section: str) -> bool:
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import csv
|
|
1
2
|
import os
|
|
2
3
|
from collections.abc import Callable, Generator, Iterable, Mapping, Sequence
|
|
3
4
|
from typing import Any
|
|
@@ -41,19 +42,31 @@ class CSVView(object):
|
|
|
41
42
|
files, with options for headers and dictionary or list output.
|
|
42
43
|
|
|
43
44
|
:param schema: The sequence of ``CSVColumn`` objects defining the schema.
|
|
44
|
-
:param
|
|
45
|
-
:param
|
|
45
|
+
:param column_delimiter: The delimiter used to separate columns in the CSV (default: ",").
|
|
46
|
+
:param line_terminator: The string used to terminate lines in the CSV (default: "\n").
|
|
47
|
+
:param quote_char: The character used to quote fields in the CSV (default: '"').
|
|
48
|
+
:param strict: Whether to raise an error on malformed CSV (default: ``True``).
|
|
46
49
|
"""
|
|
47
50
|
|
|
48
|
-
def __init__(
|
|
51
|
+
def __init__(
|
|
52
|
+
self,
|
|
53
|
+
schema: Sequence[CSVColumn],
|
|
54
|
+
*,
|
|
55
|
+
column_delimiter: str = ",",
|
|
56
|
+
line_terminator: str = "\n",
|
|
57
|
+
quote_char: str = '"',
|
|
58
|
+
strict: bool = True,
|
|
59
|
+
):
|
|
49
60
|
self.schema = schema
|
|
50
|
-
self.
|
|
51
|
-
self.
|
|
61
|
+
self.column_delimiter = column_delimiter
|
|
62
|
+
self.line_terminator = line_terminator
|
|
63
|
+
self.quote_char = quote_char
|
|
64
|
+
self.strict = strict
|
|
52
65
|
|
|
53
66
|
@overload
|
|
54
67
|
def load_lines(
|
|
55
68
|
self,
|
|
56
|
-
lines: Iterable[str],
|
|
69
|
+
lines: Iterable[Sequence[str]],
|
|
57
70
|
has_header: bool,
|
|
58
71
|
ret_dict: False = False,
|
|
59
72
|
) -> Generator[list[Any], None, None]:
|
|
@@ -62,7 +75,7 @@ class CSVView(object):
|
|
|
62
75
|
@overload
|
|
63
76
|
def load_lines(
|
|
64
77
|
self,
|
|
65
|
-
lines: Iterable[str],
|
|
78
|
+
lines: Iterable[Sequence[str]],
|
|
66
79
|
ret_dict: False = False,
|
|
67
80
|
) -> Generator[list[Any], None, None]:
|
|
68
81
|
...
|
|
@@ -70,7 +83,7 @@ class CSVView(object):
|
|
|
70
83
|
@overload
|
|
71
84
|
def load_lines(
|
|
72
85
|
self,
|
|
73
|
-
lines: Iterable[str],
|
|
86
|
+
lines: Iterable[Sequence[str]],
|
|
74
87
|
has_header: bool,
|
|
75
88
|
ret_dict: True = True,
|
|
76
89
|
) -> Generator[dict[str, Any], None, None]:
|
|
@@ -79,14 +92,14 @@ class CSVView(object):
|
|
|
79
92
|
@overload
|
|
80
93
|
def load_lines(
|
|
81
94
|
self,
|
|
82
|
-
lines: Iterable[str],
|
|
95
|
+
lines: Iterable[Sequence[str]],
|
|
83
96
|
ret_dict: True = True,
|
|
84
97
|
) -> Generator[dict[str, Any], None, None]:
|
|
85
98
|
...
|
|
86
99
|
|
|
87
100
|
def load_lines(
|
|
88
101
|
self,
|
|
89
|
-
lines: Iterable[str],
|
|
102
|
+
lines: Iterable[Sequence[str]],
|
|
90
103
|
has_header: bool = True,
|
|
91
104
|
ret_dict: bool = False,
|
|
92
105
|
) -> Generator[list[Any] | dict[str, Any], None, None]:
|
|
@@ -101,15 +114,13 @@ class CSVView(object):
|
|
|
101
114
|
"""
|
|
102
115
|
rows_iter = iter(lines)
|
|
103
116
|
if has_header:
|
|
104
|
-
|
|
105
|
-
header_cols = header_row.split(self.col_delim)
|
|
117
|
+
header_cols = next(rows_iter)
|
|
106
118
|
if len(self.schema) != len(header_cols):
|
|
107
119
|
raise ValueError("size of the schema is not identical to size of the columns")
|
|
108
120
|
for c, header_col in zip(self.schema, header_cols):
|
|
109
121
|
if c.name != header_col:
|
|
110
122
|
raise ValueError("name of the schema is not equal to the name of the columns")
|
|
111
|
-
for
|
|
112
|
-
cols = row.split(self.col_delim)
|
|
123
|
+
for cols in rows_iter:
|
|
113
124
|
if len(self.schema) != len(cols):
|
|
114
125
|
continue
|
|
115
126
|
if ret_dict:
|
|
@@ -121,7 +132,7 @@ class CSVView(object):
|
|
|
121
132
|
self,
|
|
122
133
|
data: Iterable[Sequence[Any] | Mapping[str, Any]],
|
|
123
134
|
has_header: bool = True,
|
|
124
|
-
) -> Generator[str, None, None]:
|
|
135
|
+
) -> Generator[list[str], None, None]:
|
|
125
136
|
"""
|
|
126
137
|
Dumps data to CSV lines according to the schema, optionally including a header row.
|
|
127
138
|
|
|
@@ -130,16 +141,14 @@ class CSVView(object):
|
|
|
130
141
|
:return: A generator yielding CSV lines as strings.
|
|
131
142
|
"""
|
|
132
143
|
if has_header:
|
|
133
|
-
yield
|
|
144
|
+
yield list(c.name for c in self.schema)
|
|
134
145
|
for cols in data:
|
|
135
146
|
if isinstance(cols, Sequence):
|
|
136
147
|
if len(self.schema) != len(cols):
|
|
137
148
|
raise ValueError("size of the schema is not identical to size of the columns")
|
|
138
|
-
yield
|
|
139
|
-
for c, col in zip(self.schema, cols))
|
|
149
|
+
yield list(c.null_str if col is None else c.dumper(col) for c, col in zip(self.schema, cols))
|
|
140
150
|
if isinstance(cols, Mapping):
|
|
141
|
-
yield
|
|
142
|
-
for c in self.schema)
|
|
151
|
+
yield list(c.null_str if cols.get(c.name) is None else c.dumper(cols.get(c.name)) for c in self.schema)
|
|
143
152
|
|
|
144
153
|
@overload
|
|
145
154
|
def load_file(
|
|
@@ -196,8 +205,12 @@ class CSVView(object):
|
|
|
196
205
|
:return: A generator yielding each row as a list or dictionary.
|
|
197
206
|
"""
|
|
198
207
|
with open(file_path, mode="r", **kwargs) as fh:
|
|
199
|
-
|
|
200
|
-
|
|
208
|
+
reader = csv.reader(fh,
|
|
209
|
+
delimiter=self.column_delimiter,
|
|
210
|
+
lineterminator=self.line_terminator,
|
|
211
|
+
quotechar=self.quote_char,
|
|
212
|
+
strict=self.strict)
|
|
213
|
+
yield from self.load_lines(reader, has_header, ret_dict)
|
|
201
214
|
|
|
202
215
|
def dump_file(
|
|
203
216
|
self,
|
|
@@ -215,9 +228,13 @@ class CSVView(object):
|
|
|
215
228
|
:param kwargs: Additional keyword arguments for file opening.
|
|
216
229
|
"""
|
|
217
230
|
with open(file_path, mode="w", **kwargs) as fh:
|
|
231
|
+
writer = csv.writer(fh,
|
|
232
|
+
delimiter=self.column_delimiter,
|
|
233
|
+
lineterminator=self.line_terminator,
|
|
234
|
+
quotechar=self.quote_char,
|
|
235
|
+
strict=self.strict)
|
|
218
236
|
for line in self.dump_lines(data, has_header):
|
|
219
|
-
|
|
220
|
-
fh.write(self.row_delim)
|
|
237
|
+
writer.writerow(line)
|
|
221
238
|
|
|
222
239
|
|
|
223
240
|
column = CSVColumn
|
|
@@ -165,11 +165,11 @@ class RetryWrapper(object):
|
|
|
165
165
|
else:
|
|
166
166
|
return self.target(*args, **kwargs)
|
|
167
167
|
except Exception as e:
|
|
168
|
-
logger.exception("Function target '%s' failed on attempt
|
|
168
|
+
logger.exception("Function target <'%s'> failed on attempt <%d>", self.target, attempt_number)
|
|
169
169
|
last_exception = e
|
|
170
170
|
time.sleep(self.next_wait(attempt_number))
|
|
171
171
|
|
|
172
|
-
raise RuntimeError(f"failed to execute function target '{self.target}' after
|
|
172
|
+
raise RuntimeError(f"failed to execute function target '{self.target}' after {attempt_number} attempts")
|
|
173
173
|
|
|
174
174
|
|
|
175
175
|
def retry(wait: int = None, retrials: int = None, timeout: int = None):
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import enum
|
|
2
2
|
import itertools
|
|
3
|
-
from collections.abc import Sequence
|
|
3
|
+
from collections.abc import Iterator, Sequence
|
|
4
|
+
from typing import Protocol, Self
|
|
4
5
|
|
|
5
6
|
__all__ = [
|
|
6
7
|
"SpanRelation",
|
|
8
|
+
"SpanEndpoint",
|
|
9
|
+
"Span",
|
|
10
|
+
"span",
|
|
7
11
|
"span_relation",
|
|
8
12
|
"spans_union",
|
|
9
13
|
"spans_intersect",
|
|
@@ -40,7 +44,39 @@ class SpanRelation(enum.IntEnum):
|
|
|
40
44
|
RightDetach = LeftRightOut | RightRightOut
|
|
41
45
|
|
|
42
46
|
|
|
43
|
-
|
|
47
|
+
class SpanEndpoint(Protocol):
|
|
48
|
+
def __lt__(self, other: Self) -> bool: ...
|
|
49
|
+
|
|
50
|
+
def __sub__(self, other: Self) -> Self: ...
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class Span[T: SpanEndpoint]:
|
|
54
|
+
def __init__(self, lo: T, hi: T):
|
|
55
|
+
self.lo = lo
|
|
56
|
+
self.hi = hi
|
|
57
|
+
|
|
58
|
+
def __eq__(self, other: object) -> bool:
|
|
59
|
+
if not isinstance(other, Span):
|
|
60
|
+
return NotImplemented
|
|
61
|
+
return self.lo == other.lo and self.hi == other.hi
|
|
62
|
+
|
|
63
|
+
def __lt__(self, other: Self) -> bool:
|
|
64
|
+
if not isinstance(other, Span):
|
|
65
|
+
return NotImplemented
|
|
66
|
+
if self.lo == other.lo:
|
|
67
|
+
return self.hi < other.hi
|
|
68
|
+
return self.lo < other.lo
|
|
69
|
+
|
|
70
|
+
def __iter__(self) -> Iterator[T]:
|
|
71
|
+
yield self.lo
|
|
72
|
+
yield self.hi
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def distance(self) -> T:
|
|
76
|
+
return self.hi - self.lo
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def span_relation[T: SpanEndpoint](a: Span[T], b: Span[T]) -> int:
|
|
44
80
|
(a0, a1), (b0, b1) = a, b
|
|
45
81
|
rel = 0
|
|
46
82
|
if a0 < b0:
|
|
@@ -66,7 +102,10 @@ def span_relation(a: tuple[float, float], b: tuple[float, float]) -> int:
|
|
|
66
102
|
return rel
|
|
67
103
|
|
|
68
104
|
|
|
69
|
-
|
|
105
|
+
span = Span
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def spans_union[T: SpanEndpoint](a: Sequence[Span[T]], *bs: Sequence[Span[T]]) -> list[Span[T]]:
|
|
70
109
|
"""
|
|
71
110
|
Computes the union of the given span lists. The spans in each of the lists must be sorted and must not
|
|
72
111
|
mutually overlap.
|
|
@@ -76,11 +115,11 @@ def spans_union[T](a: Sequence[tuple[T, T]], *bs: Sequence[tuple[T, T]]) -> list
|
|
|
76
115
|
:return: The union of the span lists, with spans sorted and merged where possible.
|
|
77
116
|
"""
|
|
78
117
|
|
|
79
|
-
def union(xs: Sequence[
|
|
118
|
+
def union(xs: Sequence[Span[T]], ys: Sequence[Span[T]]) -> list[Span[T]]:
|
|
80
119
|
if not xs or not ys:
|
|
81
120
|
return list(itertools.chain(xs, ys))
|
|
82
121
|
|
|
83
|
-
result: list[
|
|
122
|
+
result: list[Span[T]] = []
|
|
84
123
|
|
|
85
124
|
i, j = 0, 0
|
|
86
125
|
x0_lo, _ = xs[i]
|
|
@@ -106,12 +145,12 @@ def spans_union[T](a: Sequence[tuple[T, T]], *bs: Sequence[tuple[T, T]]) -> list
|
|
|
106
145
|
curr_lo, curr_hi = curr
|
|
107
146
|
|
|
108
147
|
if hi < curr_lo:
|
|
109
|
-
result.append((lo, hi))
|
|
148
|
+
result.append(Span(lo, hi))
|
|
110
149
|
lo, hi = curr
|
|
111
150
|
else:
|
|
112
151
|
hi = max(hi, curr_hi)
|
|
113
152
|
|
|
114
|
-
result.append((lo, hi))
|
|
153
|
+
result.append(Span(lo, hi))
|
|
115
154
|
|
|
116
155
|
return result
|
|
117
156
|
|
|
@@ -120,7 +159,7 @@ def spans_union[T](a: Sequence[tuple[T, T]], *bs: Sequence[tuple[T, T]]) -> list
|
|
|
120
159
|
return a
|
|
121
160
|
|
|
122
161
|
|
|
123
|
-
def spans_intersect[T](a: Sequence[
|
|
162
|
+
def spans_intersect[T: SpanEndpoint](a: Sequence[Span[T]], *bs: Sequence[Span[T]]) -> list[Span[T]]:
|
|
124
163
|
"""
|
|
125
164
|
Computes the intersection of the given span lists. The spans in each of the lists must be sorted and must not
|
|
126
165
|
mutually overlap.
|
|
@@ -130,11 +169,11 @@ def spans_intersect[T](a: Sequence[tuple[T, T]], *bs: Sequence[tuple[T, T]]) ->
|
|
|
130
169
|
:return: The intersection of the span lists, with spans sorted.
|
|
131
170
|
"""
|
|
132
171
|
|
|
133
|
-
def intersect(xs: Sequence[
|
|
172
|
+
def intersect(xs: Sequence[Span[T]], ys: Sequence[Span[T]]) -> list[Span[T]]:
|
|
134
173
|
if not xs or not ys:
|
|
135
174
|
return []
|
|
136
175
|
|
|
137
|
-
result: list[
|
|
176
|
+
result: list[Span[T]] = []
|
|
138
177
|
|
|
139
178
|
i, j = 0, 0
|
|
140
179
|
while i < len(xs) and j < len(ys):
|
|
@@ -144,7 +183,7 @@ def spans_intersect[T](a: Sequence[tuple[T, T]], *bs: Sequence[tuple[T, T]]) ->
|
|
|
144
183
|
hi = min(x_hi, y_hi)
|
|
145
184
|
|
|
146
185
|
if not hi < lo:
|
|
147
|
-
result.append((lo, hi))
|
|
186
|
+
result.append(Span(lo, hi))
|
|
148
187
|
|
|
149
188
|
if x_hi < y_hi:
|
|
150
189
|
i += 1
|
|
@@ -158,7 +197,7 @@ def spans_intersect[T](a: Sequence[tuple[T, T]], *bs: Sequence[tuple[T, T]]) ->
|
|
|
158
197
|
return a
|
|
159
198
|
|
|
160
199
|
|
|
161
|
-
def spans_subtract[T](a: Sequence[
|
|
200
|
+
def spans_subtract[T: SpanEndpoint](a: Sequence[Span[T]], *bs: Sequence[Span[T]]) -> list[Span[T]]:
|
|
162
201
|
"""
|
|
163
202
|
Computes the subtraction on the first span list by the remaining span lists. The spans in each of the lists must be
|
|
164
203
|
sorted and must not mutually overlap.
|
|
@@ -168,11 +207,11 @@ def spans_subtract[T](a: Sequence[tuple[T, T]], *bs: Sequence[tuple[T, T]]) -> l
|
|
|
168
207
|
:return: The subtraction of the first span list by the remaining span lists, with spans sorted.
|
|
169
208
|
"""
|
|
170
209
|
|
|
171
|
-
def subtract(xs: Sequence[
|
|
210
|
+
def subtract(xs: Sequence[Span[T]], ys: Sequence[Span[T]]) -> list[Span[T]]:
|
|
172
211
|
if not xs or not ys:
|
|
173
212
|
return list(xs)
|
|
174
213
|
|
|
175
|
-
result: list[
|
|
214
|
+
result: list[Span[T]] = []
|
|
176
215
|
|
|
177
216
|
i, j = 0, 0
|
|
178
217
|
curr = xs[i]
|
|
@@ -184,9 +223,9 @@ def spans_subtract[T](a: Sequence[tuple[T, T]], *bs: Sequence[tuple[T, T]]) -> l
|
|
|
184
223
|
|
|
185
224
|
if not lo > hi:
|
|
186
225
|
if curr_lo < lo:
|
|
187
|
-
result.append((curr_lo, lo))
|
|
226
|
+
result.append(Span(curr_lo, lo))
|
|
188
227
|
if hi < curr_hi:
|
|
189
|
-
curr = hi, curr_hi
|
|
228
|
+
curr = Span(hi, curr_hi)
|
|
190
229
|
else:
|
|
191
230
|
curr = None
|
|
192
231
|
elif curr_hi < y_lo:
|
{iker_python_common-1.0.66 → iker_python_common-1.0.68}/src/iker_python_common.egg-info/SOURCES.txt
RENAMED
|
@@ -8,8 +8,8 @@ setup.py
|
|
|
8
8
|
.github/workflows/pr.yml
|
|
9
9
|
.github/workflows/push.yml
|
|
10
10
|
resources/unittest/config/config.cfg
|
|
11
|
-
resources/unittest/
|
|
12
|
-
resources/unittest/
|
|
11
|
+
resources/unittest/csvutils/data.csv
|
|
12
|
+
resources/unittest/csvutils/data.tsv
|
|
13
13
|
resources/unittest/shutils/dir.baz/file.bar.baz
|
|
14
14
|
resources/unittest/shutils/dir.baz/file.foo.bar
|
|
15
15
|
resources/unittest/shutils/dir.baz/file.foo.baz
|
|
@@ -24,7 +24,7 @@ src/iker/common/__init__.py
|
|
|
24
24
|
src/iker/common/utils/__init__.py
|
|
25
25
|
src/iker/common/utils/argutils.py
|
|
26
26
|
src/iker/common/utils/config.py
|
|
27
|
-
src/iker/common/utils/
|
|
27
|
+
src/iker/common/utils/csvutils.py
|
|
28
28
|
src/iker/common/utils/dbutils.py
|
|
29
29
|
src/iker/common/utils/dtutils.py
|
|
30
30
|
src/iker/common/utils/funcutils.py
|
|
@@ -45,11 +45,13 @@ src/iker_python_common.egg-info/dependency_links.txt
|
|
|
45
45
|
src/iker_python_common.egg-info/not-zip-safe
|
|
46
46
|
src/iker_python_common.egg-info/requires.txt
|
|
47
47
|
src/iker_python_common.egg-info/top_level.txt
|
|
48
|
-
test/
|
|
48
|
+
test/testenv.py
|
|
49
49
|
test/iker_tests/__init__.py
|
|
50
|
+
test/iker_tests/common/__init__.py
|
|
51
|
+
test/iker_tests/common/utils/__init__.py
|
|
50
52
|
test/iker_tests/common/utils/argutils_test.py
|
|
51
53
|
test/iker_tests/common/utils/config_test.py
|
|
52
|
-
test/iker_tests/common/utils/
|
|
54
|
+
test/iker_tests/common/utils/csvutils_test.py
|
|
53
55
|
test/iker_tests/common/utils/dbutils_test.py
|
|
54
56
|
test/iker_tests/common/utils/dtutils_test.py
|
|
55
57
|
test/iker_tests/common/utils/funcutils_test.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import datetime
|
|
2
1
|
import itertools
|
|
3
2
|
import math
|
|
4
3
|
import os.path
|
|
@@ -6,12 +5,12 @@ import unittest
|
|
|
6
5
|
|
|
7
6
|
import ddt
|
|
8
7
|
|
|
9
|
-
from iker.common.utils import csv
|
|
10
|
-
from iker.common.utils.dtutils import dt_format_iso, dt_parse_iso
|
|
8
|
+
from iker.common.utils import csvutils as csv
|
|
9
|
+
from iker.common.utils.dtutils import dt_format_iso, dt_from_ts, dt_parse_iso, dt_to_ts
|
|
11
10
|
from iker.common.utils.jsonutils import json_compare
|
|
12
11
|
from iker.common.utils.strutils import make_params_string, parse_params_string
|
|
13
12
|
from iker.common.utils.strutils import parse_bool
|
|
14
|
-
from
|
|
13
|
+
from testenv import resources_directory
|
|
15
14
|
|
|
16
15
|
|
|
17
16
|
@ddt.ddt
|
|
@@ -25,8 +24,8 @@ class CSVTest(unittest.TestCase):
|
|
|
25
24
|
csv.column("dummy_int", loader=int, dumper=str, null_str=r"\N"),
|
|
26
25
|
csv.column("dummy_float", loader=float, dumper=str, null_str=r"\N"),
|
|
27
26
|
csv.column("dummy_datetime",
|
|
28
|
-
loader=lambda x: dt_parse_iso(x)
|
|
29
|
-
dumper=lambda x: dt_format_iso(
|
|
27
|
+
loader=lambda x: dt_to_ts(dt_parse_iso(x)),
|
|
28
|
+
dumper=lambda x: dt_format_iso(dt_from_ts(x)),
|
|
30
29
|
null_str=r"\N"),
|
|
31
30
|
csv.column("dummy_params",
|
|
32
31
|
loader=lambda x: parse_params_string(x, delim=";", kv_delim="=", neg_prefix="!"),
|
|
@@ -36,22 +35,22 @@ class CSVTest(unittest.TestCase):
|
|
|
36
35
|
)
|
|
37
36
|
|
|
38
37
|
lines = [
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
r"
|
|
43
|
-
r"
|
|
44
|
-
r"\N
|
|
45
|
-
r"\N
|
|
38
|
+
["dummy_str", "dummy_bool", "dummy_int", "dummy_float", "dummy_datetime", "dummy_params"],
|
|
39
|
+
["foo", "True", "1", "1.0", "2020-01-01T00:00:00", ""],
|
|
40
|
+
["bar", "False", "-1", "-1.0", "2020-01-01T00:00:00", "key_1=value_1;key_2;!key_3"],
|
|
41
|
+
["baz", r"\N", "100", "inf", "2020-01-01T00:00:00", r"\N"],
|
|
42
|
+
["", r"\N", "-100", "-inf", "2020-01-01T00:00:00", ""],
|
|
43
|
+
[r"\N", r"\N", "0", "nan", "2020-01-01T00:00:00", r"\N"],
|
|
44
|
+
[r"\N", r"\N", r"\N", r"\N", r"\N", r"\N"],
|
|
46
45
|
]
|
|
47
46
|
|
|
48
47
|
lines_no_header = [
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
r"
|
|
52
|
-
r"
|
|
53
|
-
r"\N
|
|
54
|
-
r"\N
|
|
48
|
+
["foo", "True", "1", "1.0", "2020-01-01T00:00:00", ""],
|
|
49
|
+
["bar", "False", "-1", "-1.0", "2020-01-01T00:00:00", "key_1=value_1;key_2;!key_3"],
|
|
50
|
+
["baz", r"\N", "100", "inf", "2020-01-01T00:00:00", r"\N"],
|
|
51
|
+
["", r"\N", "-100", "-inf", "2020-01-01T00:00:00", ""],
|
|
52
|
+
[r"\N", r"\N", "0", "nan", "2020-01-01T00:00:00", r"\N"],
|
|
53
|
+
[r"\N", r"\N", r"\N", r"\N", r"\N", r"\N"],
|
|
55
54
|
]
|
|
56
55
|
|
|
57
56
|
time = dt_parse_iso("2020-01-01T00:00:00").timestamp()
|
|
@@ -189,12 +188,12 @@ class CSVTest(unittest.TestCase):
|
|
|
189
188
|
csv.column("dummy_int", loader=int, dumper=str, null_str=r"\N"),
|
|
190
189
|
csv.column("dummy_float", loader=float, dumper=str, null_str=r"\N"),
|
|
191
190
|
csv.column("dummy_datetime",
|
|
192
|
-
loader=lambda x: dt_parse_iso(x)
|
|
193
|
-
dumper=lambda x: dt_format_iso(
|
|
191
|
+
loader=lambda x: dt_to_ts(dt_parse_iso(x)),
|
|
192
|
+
dumper=lambda x: dt_format_iso(dt_from_ts(x)),
|
|
194
193
|
null_str=r"\N"),
|
|
195
194
|
csv.column("dummy_params",
|
|
196
|
-
loader=lambda x: parse_params_string(x, delim="
|
|
197
|
-
dumper=lambda x: make_params_string(x, delim="
|
|
195
|
+
loader=lambda x: parse_params_string(x, delim=",", kv_delim="=", neg_prefix="!"),
|
|
196
|
+
dumper=lambda x: make_params_string(x, delim=",", kv_delim="=", neg_prefix="!"),
|
|
198
197
|
null_str=r"\N"),
|
|
199
198
|
],
|
|
200
199
|
)
|
|
@@ -206,15 +205,15 @@ class CSVTest(unittest.TestCase):
|
|
|
206
205
|
csv.column("dummy_int", loader=int, dumper=str, null_str=r"<null>"),
|
|
207
206
|
csv.column("dummy_float", loader=float, dumper=str, null_str=r"<null>"),
|
|
208
207
|
csv.column("dummy_datetime",
|
|
209
|
-
loader=lambda x: dt_parse_iso(x)
|
|
210
|
-
dumper=lambda x: dt_format_iso(
|
|
208
|
+
loader=lambda x: dt_to_ts(dt_parse_iso(x)),
|
|
209
|
+
dumper=lambda x: dt_format_iso(dt_from_ts(x)),
|
|
211
210
|
null_str=r"<null>"),
|
|
212
211
|
csv.column("dummy_params",
|
|
213
|
-
loader=lambda x: parse_params_string(x, delim="
|
|
214
|
-
dumper=lambda x: make_params_string(x, delim="
|
|
212
|
+
loader=lambda x: parse_params_string(x, delim=";", kv_delim=":", neg_prefix="-"),
|
|
213
|
+
dumper=lambda x: make_params_string(x, delim=";", kv_delim=":", neg_prefix="-"),
|
|
215
214
|
null_str=r"<null>"),
|
|
216
215
|
],
|
|
217
|
-
|
|
216
|
+
column_delimiter="\t",
|
|
218
217
|
)
|
|
219
218
|
|
|
220
219
|
time = dt_parse_iso("2020-01-01T00:00:00").timestamp()
|
|
@@ -228,10 +227,10 @@ class CSVTest(unittest.TestCase):
|
|
|
228
227
|
[None, None, None, None, None, None],
|
|
229
228
|
]
|
|
230
229
|
|
|
231
|
-
for c, t, d in zip(view_csv.load_file(os.path.join(resources_directory, "unittest/
|
|
230
|
+
for c, t, d in zip(view_csv.load_file(os.path.join(resources_directory, "unittest/csvutils/data.csv"),
|
|
232
231
|
has_header=True,
|
|
233
232
|
encoding="utf-8"),
|
|
234
|
-
view_tsv.load_file(os.path.join(resources_directory, "unittest/
|
|
233
|
+
view_tsv.load_file(os.path.join(resources_directory, "unittest/csvutils/data.tsv"),
|
|
235
234
|
has_header=True,
|
|
236
235
|
encoding="utf-8"),
|
|
237
236
|
data):
|
{iker_python_common-1.0.66 → iker_python_common-1.0.68}/test/iker_tests/common/utils/shutils_test.py
RENAMED
|
@@ -10,7 +10,7 @@ from iker.common.utils.shutils import expanded_path, path_depth
|
|
|
10
10
|
from iker.common.utils.shutils import extension, extensions, stem
|
|
11
11
|
from iker.common.utils.shutils import glob_match
|
|
12
12
|
from iker.common.utils.testutils import norm_path
|
|
13
|
-
from
|
|
13
|
+
from testenv import resources_directory
|
|
14
14
|
|
|
15
15
|
work_dir = pathlib.Path.cwd
|
|
16
16
|
home_dir = pathlib.Path.home
|
{iker_python_common-1.0.66 → iker_python_common-1.0.68}/test/iker_tests/common/utils/span_test.py
RENAMED
|
@@ -4,7 +4,7 @@ import unittest
|
|
|
4
4
|
import ddt
|
|
5
5
|
|
|
6
6
|
from iker.common.utils.iterutils import last
|
|
7
|
-
from iker.common.utils.span import SpanRelation
|
|
7
|
+
from iker.common.utils.span import Span, SpanRelation
|
|
8
8
|
from iker.common.utils.span import span_relation, spans_intersect, spans_subtract, spans_union
|
|
9
9
|
|
|
10
10
|
|
|
@@ -42,7 +42,7 @@ class SpanTest(unittest.TestCase):
|
|
|
42
42
|
@ddt.idata(data_span_relation)
|
|
43
43
|
@ddt.unpack
|
|
44
44
|
def test_span_relation(self, b, a, expect):
|
|
45
|
-
self.assertEqual(expect, span_relation(a, b))
|
|
45
|
+
self.assertEqual(expect, span_relation(Span(*a), Span(*b)))
|
|
46
46
|
|
|
47
47
|
data_spans_union = [
|
|
48
48
|
([], [], []),
|
|
@@ -102,6 +102,10 @@ class SpanTest(unittest.TestCase):
|
|
|
102
102
|
@ddt.idata(data_spans_union)
|
|
103
103
|
@ddt.unpack
|
|
104
104
|
def test_spans_union(self, xs, ys, expect):
|
|
105
|
+
xs = list(Span(*x) for x in xs)
|
|
106
|
+
ys = list(Span(*y) for y in ys)
|
|
107
|
+
expect = list(Span(*e) for e in expect)
|
|
108
|
+
|
|
105
109
|
self.assertEqual(expect, spans_union(xs, ys))
|
|
106
110
|
self.assertEqual(expect, spans_union(ys, xs))
|
|
107
111
|
self.assertEqual(expect, spans_union(xs, *[[y] for y in ys]))
|
|
@@ -165,6 +169,10 @@ class SpanTest(unittest.TestCase):
|
|
|
165
169
|
@ddt.idata(data_spans_intersect)
|
|
166
170
|
@ddt.unpack
|
|
167
171
|
def test_spans_intersect(self, xs, ys, expect):
|
|
172
|
+
xs = list(Span(*x) for x in xs)
|
|
173
|
+
ys = list(Span(*y) for y in ys)
|
|
174
|
+
expect = list(Span(*e) for e in expect)
|
|
175
|
+
|
|
168
176
|
self.assertEqual(expect, spans_intersect(xs, ys))
|
|
169
177
|
self.assertEqual(expect, spans_intersect(ys, xs))
|
|
170
178
|
self.assertEqual(expect,
|
|
@@ -280,6 +288,10 @@ class SpanTest(unittest.TestCase):
|
|
|
280
288
|
@ddt.idata(data_spans_subtract)
|
|
281
289
|
@ddt.unpack
|
|
282
290
|
def test_spans_subtract(self, xs, ys, expect):
|
|
291
|
+
xs = list(Span(*x) for x in xs)
|
|
292
|
+
ys = list(Span(*y) for y in ys)
|
|
293
|
+
expect = list(Span(*e) for e in expect)
|
|
294
|
+
|
|
283
295
|
self.assertEqual(expect, spans_subtract(xs, ys))
|
|
284
296
|
self.assertEqual(expect, spans_subtract(xs, *[[y] for y in ys]))
|
|
285
297
|
self.assertEqual(expect, spans_subtract(xs, spans_intersect(xs, ys)))
|
|
@@ -292,7 +304,7 @@ class SpanTest(unittest.TestCase):
|
|
|
292
304
|
vs = [float(random.randrange(lo, hi))]
|
|
293
305
|
for _ in range(size * 2):
|
|
294
306
|
vs.append(last(vs) + float(random.randrange(lo, hi)))
|
|
295
|
-
return [(vs[2 * i], vs[2 * i + 1]) for i in range(size)]
|
|
307
|
+
return [Span(vs[2 * i], vs[2 * i + 1]) for i in range(size)]
|
|
296
308
|
|
|
297
309
|
xs = make_spans(200, 2, 10)
|
|
298
310
|
ys = make_spans(200, 2, 10)
|
iker_python_common-1.0.66/test/iker_tests/__init__.py → iker_python_common-1.0.68/test/testenv.py
RENAMED
|
@@ -8,7 +8,7 @@ __all__ = [
|
|
|
8
8
|
"temporary_directory",
|
|
9
9
|
]
|
|
10
10
|
|
|
11
|
-
module_directory: str = os.path.abspath(os.path.dirname(os.path.dirname(
|
|
11
|
+
module_directory: str = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
|
|
12
12
|
source_directory: str = os.path.abspath(os.path.join(module_directory, "src"))
|
|
13
13
|
test_directory: str = os.path.abspath(os.path.join(module_directory, "test"))
|
|
14
14
|
resources_directory: str = os.path.abspath(os.path.join(module_directory, "resources"))
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import unittest
|
|
2
|
-
|
|
3
|
-
from iker_tests import *
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class Test(unittest.TestCase):
|
|
7
|
-
|
|
8
|
-
def test(self):
|
|
9
|
-
self.assertIsNotNone(module_directory)
|
|
10
|
-
self.assertIsNotNone(source_directory)
|
|
11
|
-
self.assertIsNotNone(test_directory)
|
|
12
|
-
self.assertIsNotNone(resources_directory)
|
|
13
|
-
self.assertIsNotNone(temporary_directory)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{iker_python_common-1.0.66 → iker_python_common-1.0.68}/resources/unittest/config/config.cfg
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{iker_python_common-1.0.66 → iker_python_common-1.0.68}/resources/unittest/shutils/dir.foo/file.bar
RENAMED
|
File without changes
|
{iker_python_common-1.0.66 → iker_python_common-1.0.68}/resources/unittest/shutils/dir.foo/file.baz
RENAMED
|
File without changes
|
{iker_python_common-1.0.66 → iker_python_common-1.0.68}/resources/unittest/shutils/dir.foo/file.foo
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{iker_python_common-1.0.66 → iker_python_common-1.0.68}/src/iker_python_common.egg-info/not-zip-safe
RENAMED
|
File without changes
|
{iker_python_common-1.0.66 → iker_python_common-1.0.68}/src/iker_python_common.egg-info/requires.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{iker_python_common-1.0.66 → iker_python_common-1.0.68}/test/iker_tests/common/utils/dbutils_test.py
RENAMED
|
File without changes
|
{iker_python_common-1.0.66 → iker_python_common-1.0.68}/test/iker_tests/common/utils/dtutils_test.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{iker_python_common-1.0.66 → iker_python_common-1.0.68}/test/iker_tests/common/utils/logger_test.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{iker_python_common-1.0.66 → iker_python_common-1.0.68}/test/iker_tests/common/utils/retry_test.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|