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.
Files changed (71) hide show
  1. {iker_python_common-1.0.66 → iker_python_common-1.0.68}/PKG-INFO +1 -1
  2. {iker_python_common-1.0.66/resources/unittest/csv → iker_python_common-1.0.68/resources/unittest/csvutils}/data.csv +1 -1
  3. {iker_python_common-1.0.66/resources/unittest/csv → iker_python_common-1.0.68/resources/unittest/csvutils}/data.tsv +1 -1
  4. {iker_python_common-1.0.66 → iker_python_common-1.0.68}/src/iker/common/utils/config.py +2 -2
  5. 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
  6. {iker_python_common-1.0.66 → iker_python_common-1.0.68}/src/iker/common/utils/retry.py +2 -2
  7. {iker_python_common-1.0.66 → iker_python_common-1.0.68}/src/iker/common/utils/span.py +55 -16
  8. {iker_python_common-1.0.66 → iker_python_common-1.0.68}/src/iker_python_common.egg-info/PKG-INFO +1 -1
  9. {iker_python_common-1.0.66 → iker_python_common-1.0.68}/src/iker_python_common.egg-info/SOURCES.txt +7 -5
  10. iker_python_common-1.0.68/test/iker_tests/__init__.py +0 -0
  11. iker_python_common-1.0.68/test/iker_tests/common/__init__.py +0 -0
  12. iker_python_common-1.0.68/test/iker_tests/common/utils/__init__.py +0 -0
  13. {iker_python_common-1.0.66 → iker_python_common-1.0.68}/test/iker_tests/common/utils/config_test.py +1 -1
  14. 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
  15. {iker_python_common-1.0.66 → iker_python_common-1.0.68}/test/iker_tests/common/utils/shutils_test.py +1 -1
  16. {iker_python_common-1.0.66 → iker_python_common-1.0.68}/test/iker_tests/common/utils/span_test.py +15 -3
  17. iker_python_common-1.0.66/test/iker_tests/__init__.py → iker_python_common-1.0.68/test/testenv.py +1 -1
  18. iker_python_common-1.0.66/test/iker_test.py +0 -13
  19. {iker_python_common-1.0.66 → iker_python_common-1.0.68}/.editorconfig +0 -0
  20. {iker_python_common-1.0.66 → iker_python_common-1.0.68}/.github/workflows/pr.yml +0 -0
  21. {iker_python_common-1.0.66 → iker_python_common-1.0.68}/.github/workflows/push.yml +0 -0
  22. {iker_python_common-1.0.66 → iker_python_common-1.0.68}/.gitignore +0 -0
  23. {iker_python_common-1.0.66 → iker_python_common-1.0.68}/MANIFEST.in +0 -0
  24. {iker_python_common-1.0.66 → iker_python_common-1.0.68}/README.md +0 -0
  25. {iker_python_common-1.0.66 → iker_python_common-1.0.68}/VERSION +0 -0
  26. {iker_python_common-1.0.66 → iker_python_common-1.0.68}/pyproject.toml +0 -0
  27. {iker_python_common-1.0.66 → iker_python_common-1.0.68}/resources/unittest/config/config.cfg +0 -0
  28. {iker_python_common-1.0.66 → iker_python_common-1.0.68}/resources/unittest/shutils/dir.baz/file.bar.baz +0 -0
  29. {iker_python_common-1.0.66 → iker_python_common-1.0.68}/resources/unittest/shutils/dir.baz/file.foo.bar +0 -0
  30. {iker_python_common-1.0.66 → iker_python_common-1.0.68}/resources/unittest/shutils/dir.baz/file.foo.baz +0 -0
  31. {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
  32. {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
  33. {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
  34. {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
  35. {iker_python_common-1.0.66 → iker_python_common-1.0.68}/resources/unittest/shutils/dir.foo/file.bar +0 -0
  36. {iker_python_common-1.0.66 → iker_python_common-1.0.68}/resources/unittest/shutils/dir.foo/file.baz +0 -0
  37. {iker_python_common-1.0.66 → iker_python_common-1.0.68}/resources/unittest/shutils/dir.foo/file.foo +0 -0
  38. {iker_python_common-1.0.66 → iker_python_common-1.0.68}/setup.cfg +0 -0
  39. {iker_python_common-1.0.66 → iker_python_common-1.0.68}/setup.py +0 -0
  40. {iker_python_common-1.0.66 → iker_python_common-1.0.68}/src/iker/common/__init__.py +0 -0
  41. {iker_python_common-1.0.66 → iker_python_common-1.0.68}/src/iker/common/utils/__init__.py +0 -0
  42. {iker_python_common-1.0.66 → iker_python_common-1.0.68}/src/iker/common/utils/argutils.py +0 -0
  43. {iker_python_common-1.0.66 → iker_python_common-1.0.68}/src/iker/common/utils/dbutils.py +0 -0
  44. {iker_python_common-1.0.66 → iker_python_common-1.0.68}/src/iker/common/utils/dtutils.py +0 -0
  45. {iker_python_common-1.0.66 → iker_python_common-1.0.68}/src/iker/common/utils/funcutils.py +0 -0
  46. {iker_python_common-1.0.66 → iker_python_common-1.0.68}/src/iker/common/utils/iterutils.py +0 -0
  47. {iker_python_common-1.0.66 → iker_python_common-1.0.68}/src/iker/common/utils/jsonutils.py +0 -0
  48. {iker_python_common-1.0.66 → iker_python_common-1.0.68}/src/iker/common/utils/logger.py +0 -0
  49. {iker_python_common-1.0.66 → iker_python_common-1.0.68}/src/iker/common/utils/numutils.py +0 -0
  50. {iker_python_common-1.0.66 → iker_python_common-1.0.68}/src/iker/common/utils/randutils.py +0 -0
  51. {iker_python_common-1.0.66 → iker_python_common-1.0.68}/src/iker/common/utils/shutils.py +0 -0
  52. {iker_python_common-1.0.66 → iker_python_common-1.0.68}/src/iker/common/utils/strutils.py +0 -0
  53. {iker_python_common-1.0.66 → iker_python_common-1.0.68}/src/iker/common/utils/testutils.py +0 -0
  54. {iker_python_common-1.0.66 → iker_python_common-1.0.68}/src/iker/common/utils/typeutils.py +0 -0
  55. {iker_python_common-1.0.66 → iker_python_common-1.0.68}/src/iker_python_common.egg-info/dependency_links.txt +0 -0
  56. {iker_python_common-1.0.66 → iker_python_common-1.0.68}/src/iker_python_common.egg-info/not-zip-safe +0 -0
  57. {iker_python_common-1.0.66 → iker_python_common-1.0.68}/src/iker_python_common.egg-info/requires.txt +0 -0
  58. {iker_python_common-1.0.66 → iker_python_common-1.0.68}/src/iker_python_common.egg-info/top_level.txt +0 -0
  59. {iker_python_common-1.0.66 → iker_python_common-1.0.68}/test/iker_tests/common/utils/argutils_test.py +0 -0
  60. {iker_python_common-1.0.66 → iker_python_common-1.0.68}/test/iker_tests/common/utils/dbutils_test.py +0 -0
  61. {iker_python_common-1.0.66 → iker_python_common-1.0.68}/test/iker_tests/common/utils/dtutils_test.py +0 -0
  62. {iker_python_common-1.0.66 → iker_python_common-1.0.68}/test/iker_tests/common/utils/funcutils_test.py +0 -0
  63. {iker_python_common-1.0.66 → iker_python_common-1.0.68}/test/iker_tests/common/utils/iterutils_test.py +0 -0
  64. {iker_python_common-1.0.66 → iker_python_common-1.0.68}/test/iker_tests/common/utils/jsonutils_test.py +0 -0
  65. {iker_python_common-1.0.66 → iker_python_common-1.0.68}/test/iker_tests/common/utils/logger_test.py +0 -0
  66. {iker_python_common-1.0.66 → iker_python_common-1.0.68}/test/iker_tests/common/utils/numutils_test.py +0 -0
  67. {iker_python_common-1.0.66 → iker_python_common-1.0.68}/test/iker_tests/common/utils/randutils_test.py +0 -0
  68. {iker_python_common-1.0.66 → iker_python_common-1.0.68}/test/iker_tests/common/utils/retry_test.py +0 -0
  69. {iker_python_common-1.0.66 → iker_python_common-1.0.68}/test/iker_tests/common/utils/strutils_test.py +0 -0
  70. {iker_python_common-1.0.66 → iker_python_common-1.0.68}/test/iker_tests/common/utils/testutils_test.py +0 -0
  71. {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
  Metadata-Version: 2.4
2
2
  Name: iker-python-common
3
- Version: 1.0.66
3
+ Version: 1.0.68
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Python :: 3.12
6
6
  Classifier: Programming Language :: Python :: 3.13
@@ -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;key_2;!key_3
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#key_2#-key_3
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 row_delim: The delimiter for rows (default is ``'\n'``).
45
- :param col_delim: The delimiter for columns (default is ``','``).
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__(self, schema: Sequence[CSVColumn], *, row_delim: str = "\n", col_delim: str = ","):
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.row_delim = row_delim
51
- self.col_delim = col_delim
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
- header_row = next(rows_iter)
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 row in rows_iter:
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 self.col_delim.join(c.name for c in self.schema)
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 self.col_delim.join(c.null_str if col is None else c.dumper(col)
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 self.col_delim.join(c.null_str if cols.get(c.name) is None else c.dumper(cols.get(c.name))
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
- lines = fh.read().split(self.row_delim)
200
- yield from self.load_lines(lines, has_header, ret_dict)
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
- fh.write(line)
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 '%d'", self.target, attempt_number)
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 '{attempt_number}' attempts")
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
- def span_relation(a: tuple[float, float], b: tuple[float, float]) -> int:
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
- def spans_union[T](a: Sequence[tuple[T, T]], *bs: Sequence[tuple[T, T]]) -> list[tuple[T, T]]:
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[tuple[T, T]], ys: Sequence[tuple[T, T]]) -> list[tuple[T, T]]:
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[tuple[T, T]] = []
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[tuple[T, T]], *bs: Sequence[tuple[T, T]]) -> list[tuple[T, T]]:
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[tuple[T, T]], ys: Sequence[tuple[T, T]]) -> list[tuple[T, T]]:
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[tuple[T, T]] = []
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[tuple[T, T]], *bs: Sequence[tuple[T, T]]) -> list[tuple[T, T]]:
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[tuple[T, T]], ys: Sequence[tuple[T, T]]) -> list[tuple[T, T]]:
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[tuple[T, T]] = []
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:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iker-python-common
3
- Version: 1.0.66
3
+ Version: 1.0.68
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Python :: 3.12
6
6
  Classifier: Programming Language :: Python :: 3.13
@@ -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/csv/data.csv
12
- resources/unittest/csv/data.tsv
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/csv.py
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/iker_test.py
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/csv_test.py
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
@@ -4,7 +4,7 @@ import unittest
4
4
  import ddt
5
5
 
6
6
  from iker.common.utils.config import Config, ConfigVisitor
7
- from iker_tests import resources_directory
7
+ from testenv import resources_directory
8
8
 
9
9
 
10
10
  @ddt.ddt
@@ -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 iker_tests import resources_directory
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).timestamp(),
29
- dumper=lambda x: dt_format_iso(datetime.datetime.fromtimestamp(x, tz=datetime.timezone.utc)),
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
- r"dummy_str,dummy_bool,dummy_int,dummy_float,dummy_datetime,dummy_params",
40
- r"foo,True,1,1.0,2020-01-01T00:00:00,",
41
- r"bar,False,-1,-1.0,2020-01-01T00:00:00,key_1=value_1;key_2;!key_3",
42
- r"baz,\N,100,inf,2020-01-01T00:00:00,\N",
43
- r",\N,-100,-inf,2020-01-01T00:00:00,",
44
- r"\N,\N,0,nan,2020-01-01T00:00:00,\N",
45
- r"\N,\N,\N,\N,\N,\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
- r"foo,True,1,1.0,2020-01-01T00:00:00,",
50
- r"bar,False,-1,-1.0,2020-01-01T00:00:00,key_1=value_1;key_2;!key_3",
51
- r"baz,\N,100,inf,2020-01-01T00:00:00,\N",
52
- r",\N,-100,-inf,2020-01-01T00:00:00,",
53
- r"\N,\N,0,nan,2020-01-01T00:00:00,\N",
54
- r"\N,\N,\N,\N,\N,\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).timestamp(),
193
- dumper=lambda x: dt_format_iso(datetime.datetime.fromtimestamp(x, tz=datetime.timezone.utc)),
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=";", kv_delim="=", neg_prefix="!"),
197
- dumper=lambda x: make_params_string(x, delim=";", kv_delim="=", neg_prefix="!"),
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).timestamp(),
210
- dumper=lambda x: dt_format_iso(datetime.datetime.fromtimestamp(x, tz=datetime.timezone.utc)),
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="#", kv_delim=":", neg_prefix="-"),
214
- dumper=lambda x: make_params_string(x, delim="#", kv_delim=":", neg_prefix="-"),
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
- col_delim="\t",
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/csv/data.csv"),
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/csv/data.tsv"),
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):
@@ -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 iker_tests import resources_directory
13
+ from testenv import resources_directory
14
14
 
15
15
  work_dir = pathlib.Path.cwd
16
16
  home_dir = pathlib.Path.home
@@ -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)
@@ -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(os.path.dirname(__file__))))
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)