iker-python-common 1.0.15__tar.gz → 1.0.16__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 (70) hide show
  1. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/PKG-INFO +1 -1
  2. iker_python_common-1.0.16/resources/unittest/csv/data.csv +7 -0
  3. iker_python_common-1.0.16/resources/unittest/csv/data.tsv +7 -0
  4. iker_python_common-1.0.16/src/iker/common/utils/csv.py +99 -0
  5. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/src/iker_python_common.egg-info/PKG-INFO +1 -1
  6. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/src/iker_python_common.egg-info/SOURCES.txt +4 -0
  7. iker_python_common-1.0.16/test/iker_tests/common/utils/csv_test.py +241 -0
  8. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/.editorconfig +0 -0
  9. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/.github/workflows/pr.yml +0 -0
  10. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/.github/workflows/push.yml +0 -0
  11. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/.gitignore +0 -0
  12. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/MANIFEST.in +0 -0
  13. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/README.md +0 -0
  14. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/VERSION +0 -0
  15. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/pyproject.toml +0 -0
  16. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/resources/unittest/shutils/dir.baz/file.bar.baz +0 -0
  17. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/resources/unittest/shutils/dir.baz/file.foo.bar +0 -0
  18. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/resources/unittest/shutils/dir.baz/file.foo.baz +0 -0
  19. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/resources/unittest/shutils/dir.foo/dir.foo.bar/dir.foo.bar.baz/file.foo.bar.baz +0 -0
  20. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/resources/unittest/shutils/dir.foo/dir.foo.bar/file.bar.baz +0 -0
  21. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/resources/unittest/shutils/dir.foo/dir.foo.bar/file.foo.bar +0 -0
  22. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/resources/unittest/shutils/dir.foo/dir.foo.bar/file.foo.baz +0 -0
  23. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/resources/unittest/shutils/dir.foo/file.bar +0 -0
  24. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/resources/unittest/shutils/dir.foo/file.baz +0 -0
  25. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/resources/unittest/shutils/dir.foo/file.foo +0 -0
  26. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/setup.cfg +0 -0
  27. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/setup.py +0 -0
  28. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/src/iker/common/__init__.py +0 -0
  29. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/src/iker/common/utils/__init__.py +0 -0
  30. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/src/iker/common/utils/argutils.py +0 -0
  31. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/src/iker/common/utils/config.py +0 -0
  32. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/src/iker/common/utils/dbutils.py +0 -0
  33. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/src/iker/common/utils/dockerutils.py +0 -0
  34. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/src/iker/common/utils/dtutils.py +0 -0
  35. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/src/iker/common/utils/funcutils.py +0 -0
  36. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/src/iker/common/utils/jsonutils.py +0 -0
  37. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/src/iker/common/utils/logger.py +0 -0
  38. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/src/iker/common/utils/numutils.py +0 -0
  39. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/src/iker/common/utils/randutils.py +0 -0
  40. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/src/iker/common/utils/retry.py +0 -0
  41. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/src/iker/common/utils/s3utils.py +0 -0
  42. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/src/iker/common/utils/sequtils.py +0 -0
  43. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/src/iker/common/utils/shutils.py +0 -0
  44. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/src/iker/common/utils/span.py +0 -0
  45. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/src/iker/common/utils/strutils.py +0 -0
  46. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/src/iker/common/utils/testutils.py +0 -0
  47. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/src/iker_python_common.egg-info/dependency_links.txt +0 -0
  48. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/src/iker_python_common.egg-info/not-zip-safe +0 -0
  49. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/src/iker_python_common.egg-info/requires.txt +0 -0
  50. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/src/iker_python_common.egg-info/top_level.txt +0 -0
  51. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/test/iker_tests/__init__.py +0 -0
  52. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/test/iker_tests/common/utils/argutils_test.py +0 -0
  53. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/test/iker_tests/common/utils/config_test.py +0 -0
  54. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/test/iker_tests/common/utils/dbutils_test.py +0 -0
  55. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/test/iker_tests/common/utils/dockerutils_test.py +0 -0
  56. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/test/iker_tests/common/utils/dtutils_test.py +0 -0
  57. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/test/iker_tests/common/utils/funcutils_test.py +0 -0
  58. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/test/iker_tests/common/utils/jsonutils_test.py +0 -0
  59. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/test/iker_tests/common/utils/logger_test.py +0 -0
  60. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/test/iker_tests/common/utils/numutils_test.py +0 -0
  61. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/test/iker_tests/common/utils/randutils_test.py +0 -0
  62. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/test/iker_tests/common/utils/retry_test.py +0 -0
  63. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/test/iker_tests/common/utils/s3utils_test.py +0 -0
  64. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/test/iker_tests/common/utils/sequtils_test.py +0 -0
  65. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/test/iker_tests/common/utils/shutils_test.py +0 -0
  66. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/test/iker_tests/common/utils/span_test.py +0 -0
  67. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/test/iker_tests/common/utils/strutils_test.py +0 -0
  68. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/test/iker_tests/common/utils/testutils_test.py +0 -0
  69. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/test/iker_tests/docker_fixtures.py +0 -0
  70. {iker_python_common-1.0.15 → iker_python_common-1.0.16}/test/iker_tests/iker_test.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: iker-python-common
3
- Version: 1.0.15
3
+ Version: 1.0.16
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Python :: 3.11
6
6
  Classifier: Programming Language :: Python :: 3.12
@@ -0,0 +1,7 @@
1
+ dummy_str,dummy_bool,dummy_int,dummy_float,dummy_datetime,dummy_params
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
4
+ baz,\N,100,inf,2020-01-01T00:00:00,\N
5
+ ,\N,-100,-inf,2020-01-01T00:00:00,
6
+ \N,\N,0,nan,2020-01-01T00:00:00,\N
7
+ \N,\N,\N,\N,\N,\N
@@ -0,0 +1,7 @@
1
+ dummy_str dummy_bool dummy_int dummy_float dummy_datetime dummy_params
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
4
+ baz <null> 100 inf 2020-01-01T00:00:00 <null>
5
+ <null> -100 -inf 2020-01-01T00:00:00
6
+ <null> <null> 0 nan 2020-01-01T00:00:00 <null>
7
+ <null> <null> <null> <null> <null> <null>
@@ -0,0 +1,99 @@
1
+ import os
2
+ from collections.abc import Callable, Generator, Iterable, Mapping, Sequence
3
+ from typing import Any
4
+
5
+ __all__ = [
6
+ "CSVColumn",
7
+ "CSVView",
8
+ "column",
9
+ "view",
10
+ ]
11
+
12
+
13
+ class CSVColumn(object):
14
+ def __init__(
15
+ self,
16
+ name: str,
17
+ loader: Callable[[str], Any] = str,
18
+ dumper: Callable[[Any], str] = str,
19
+ null_str: str = "",
20
+ ):
21
+ self.name = name
22
+ self.loader = loader
23
+ self.dumper = dumper
24
+ self.null_str = null_str
25
+
26
+
27
+ class CSVView(object):
28
+ def __init__(self, schema: Sequence[CSVColumn], *, row_delim: str = "\n", col_delim: str = ","):
29
+ self.schema = schema
30
+ self.row_delim = row_delim
31
+ self.col_delim = col_delim
32
+
33
+ def load_lines(
34
+ self,
35
+ lines: Iterable[str],
36
+ has_header: bool = True,
37
+ ret_dict: bool = False,
38
+ ) -> Generator[list[Any] | dict[str, Any], None, None]:
39
+ rows_iter = iter(lines)
40
+ if has_header:
41
+ header_row = next(rows_iter)
42
+ header_cols = header_row.split(self.col_delim)
43
+ if len(self.schema) != len(header_cols):
44
+ raise ValueError("size of the schema is not identical to size of the columns")
45
+ for c, header_col in zip(self.schema, header_cols):
46
+ if c.name != header_col:
47
+ raise ValueError("name of the schema is not equal to the name of the columns")
48
+ for row in rows_iter:
49
+ cols = row.split(self.col_delim)
50
+ if len(self.schema) != len(cols):
51
+ continue
52
+ if ret_dict:
53
+ yield {c.name: None if col == c.null_str else c.loader(col) for c, col in zip(self.schema, cols)}
54
+ else:
55
+ yield [None if col == c.null_str else c.loader(col) for c, col in zip(self.schema, cols)]
56
+
57
+ def dump_lines(
58
+ self,
59
+ data: Iterable[Sequence[Any] | Mapping[str, Any]],
60
+ has_header: bool = True,
61
+ ) -> Generator[str, None, None]:
62
+ if has_header:
63
+ yield self.col_delim.join(c.name for c in self.schema)
64
+ for cols in data:
65
+ if isinstance(cols, Sequence):
66
+ if len(self.schema) != len(cols):
67
+ raise ValueError("size of the schema is not identical to size of the columns")
68
+ yield self.col_delim.join(c.null_str if col is None else c.dumper(col)
69
+ for c, col in zip(self.schema, cols))
70
+ if isinstance(cols, Mapping):
71
+ yield self.col_delim.join(c.null_str if cols.get(c.name) is None else c.dumper(cols.get(c.name))
72
+ for c in self.schema)
73
+
74
+ def load_file(
75
+ self,
76
+ file_path: os.PathLike | str,
77
+ has_header: bool = True,
78
+ ret_dict: bool = False,
79
+ **kwargs,
80
+ ) -> Generator[list[Any] | dict[str, Any], None, None]:
81
+ with open(file_path, mode="r", **kwargs) as fh:
82
+ lines = fh.read().split(self.row_delim)
83
+ yield from self.load_lines(lines, has_header, ret_dict)
84
+
85
+ def dump_file(
86
+ self,
87
+ data: Iterable[Sequence[Any] | Mapping[str, Any]],
88
+ file_path: os.PathLike | str,
89
+ has_header: bool = True,
90
+ **kwargs,
91
+ ) -> None:
92
+ with open(file_path, mode="w", **kwargs) as fh:
93
+ for line in self.dump_lines(data, has_header):
94
+ fh.write(line)
95
+ fh.write(self.row_delim)
96
+
97
+
98
+ column = CSVColumn
99
+ view = CSVView
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: iker-python-common
3
- Version: 1.0.15
3
+ Version: 1.0.16
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Python :: 3.11
6
6
  Classifier: Programming Language :: Python :: 3.12
@@ -7,6 +7,8 @@ pyproject.toml
7
7
  setup.py
8
8
  .github/workflows/pr.yml
9
9
  .github/workflows/push.yml
10
+ resources/unittest/csv/data.csv
11
+ resources/unittest/csv/data.tsv
10
12
  resources/unittest/shutils/dir.baz/file.bar.baz
11
13
  resources/unittest/shutils/dir.baz/file.foo.bar
12
14
  resources/unittest/shutils/dir.baz/file.foo.baz
@@ -21,6 +23,7 @@ src/iker/common/__init__.py
21
23
  src/iker/common/utils/__init__.py
22
24
  src/iker/common/utils/argutils.py
23
25
  src/iker/common/utils/config.py
26
+ src/iker/common/utils/csv.py
24
27
  src/iker/common/utils/dbutils.py
25
28
  src/iker/common/utils/dockerutils.py
26
29
  src/iker/common/utils/dtutils.py
@@ -47,6 +50,7 @@ test/iker_tests/docker_fixtures.py
47
50
  test/iker_tests/iker_test.py
48
51
  test/iker_tests/common/utils/argutils_test.py
49
52
  test/iker_tests/common/utils/config_test.py
53
+ test/iker_tests/common/utils/csv_test.py
50
54
  test/iker_tests/common/utils/dbutils_test.py
51
55
  test/iker_tests/common/utils/dockerutils_test.py
52
56
  test/iker_tests/common/utils/dtutils_test.py
@@ -0,0 +1,241 @@
1
+ import datetime
2
+ import itertools
3
+ import math
4
+ import os.path
5
+ import unittest
6
+
7
+ import ddt
8
+
9
+ from iker.common.utils import csv
10
+ from iker.common.utils.dtutils import dt_format_iso, dt_parse_iso
11
+ from iker.common.utils.jsonutils import json_compare
12
+ from iker.common.utils.strutils import make_params_string, parse_params_string
13
+ from iker.common.utils.strutils import parse_bool
14
+
15
+ from iker_tests import resources_directory
16
+
17
+
18
+ @ddt.ddt
19
+ class CSVTest(unittest.TestCase):
20
+
21
+ def test_lines(self):
22
+ view = csv.view(
23
+ [
24
+ csv.column("dummy_str", loader=str, dumper=str, null_str=r"\N"),
25
+ csv.column("dummy_bool", loader=parse_bool, dumper=str, null_str=r"\N"),
26
+ csv.column("dummy_int", loader=int, dumper=str, null_str=r"\N"),
27
+ csv.column("dummy_float", loader=float, dumper=str, null_str=r"\N"),
28
+ csv.column("dummy_datetime",
29
+ loader=lambda x: dt_parse_iso(x).timestamp(),
30
+ dumper=lambda x: dt_format_iso(datetime.datetime.fromtimestamp(x, tz=datetime.timezone.utc)),
31
+ null_str=r"\N"),
32
+ csv.column("dummy_params",
33
+ loader=lambda x: parse_params_string(x, delim=";", kv_delim="=", neg_prefix="!"),
34
+ dumper=lambda x: make_params_string(x, delim=";", kv_delim="=", neg_prefix="!"),
35
+ null_str=r"\N"),
36
+ ],
37
+ )
38
+
39
+ lines = [
40
+ r"dummy_str,dummy_bool,dummy_int,dummy_float,dummy_datetime,dummy_params",
41
+ r"foo,True,1,1.0,2020-01-01T00:00:00,",
42
+ r"bar,False,-1,-1.0,2020-01-01T00:00:00,key_1=value_1;key_2;!key_3",
43
+ r"baz,\N,100,inf,2020-01-01T00:00:00,\N",
44
+ r",\N,-100,-inf,2020-01-01T00:00:00,",
45
+ r"\N,\N,0,nan,2020-01-01T00:00:00,\N",
46
+ r"\N,\N,\N,\N,\N,\N",
47
+ ]
48
+
49
+ lines_no_header = [
50
+ r"foo,True,1,1.0,2020-01-01T00:00:00,",
51
+ r"bar,False,-1,-1.0,2020-01-01T00:00:00,key_1=value_1;key_2;!key_3",
52
+ r"baz,\N,100,inf,2020-01-01T00:00:00,\N",
53
+ r",\N,-100,-inf,2020-01-01T00:00:00,",
54
+ r"\N,\N,0,nan,2020-01-01T00:00:00,\N",
55
+ r"\N,\N,\N,\N,\N,\N",
56
+ ]
57
+
58
+ time = dt_parse_iso("2020-01-01T00:00:00").timestamp()
59
+
60
+ list_data = [
61
+ ["foo", True, 1, 1.0, time, {}],
62
+ ["bar", False, -1, -1.0, time, {"key_1": "value_1", "key_2": True, "key_3": False}],
63
+ ["baz", None, 100, math.inf, time, None],
64
+ ["", None, -100, -math.inf, time, {}],
65
+ [None, None, 0, math.nan, time, None],
66
+ [None, None, None, None, None, None],
67
+ ]
68
+
69
+ dict_data = [
70
+ {
71
+ "dummy_str": "foo",
72
+ "dummy_bool": True,
73
+ "dummy_int": 1,
74
+ "dummy_float": 1.0,
75
+ "dummy_datetime": time,
76
+ "dummy_params": {},
77
+ },
78
+ {
79
+ "dummy_str": "bar",
80
+ "dummy_bool": False,
81
+ "dummy_int": -1,
82
+ "dummy_float": -1.0,
83
+ "dummy_datetime": time,
84
+ "dummy_params": {"key_1": "value_1", "key_2": True, "key_3": False},
85
+ },
86
+ {
87
+ "dummy_str": "baz",
88
+ "dummy_bool": None,
89
+ "dummy_int": 100,
90
+ "dummy_float": math.inf,
91
+ "dummy_datetime": time,
92
+ "dummy_params": None,
93
+ },
94
+ {
95
+ "dummy_str": "",
96
+ "dummy_bool": None,
97
+ "dummy_int": -100,
98
+ "dummy_float": -math.inf,
99
+ "dummy_datetime": time,
100
+ "dummy_params": {},
101
+ },
102
+ {
103
+ "dummy_str": None,
104
+ "dummy_bool": None,
105
+ "dummy_int": 0,
106
+ "dummy_float": math.nan,
107
+ "dummy_datetime": time,
108
+ "dummy_params": None,
109
+ },
110
+ {
111
+ "dummy_str": None,
112
+ "dummy_bool": None,
113
+ "dummy_int": None,
114
+ "dummy_float": None,
115
+ "dummy_datetime": None,
116
+ "dummy_params": None,
117
+ },
118
+ ]
119
+
120
+ for a, e in zip(view.load_lines(lines, has_header=True), list_data):
121
+ self.assertTrue(json_compare(a, e))
122
+
123
+ for a, e in zip(view.load_lines(lines, has_header=True, ret_dict=True), dict_data):
124
+ self.assertTrue(json_compare(a, e))
125
+
126
+ for a, e in zip(view.dump_lines(list_data, has_header=True), lines):
127
+ self.assertTrue(json_compare(a, e))
128
+
129
+ for a, e in zip(view.dump_lines(dict_data, has_header=True), lines):
130
+ self.assertTrue(json_compare(a, e))
131
+
132
+ for a, e in zip(view.load_lines(lines_no_header, has_header=False), list_data):
133
+ self.assertTrue(json_compare(a, e))
134
+
135
+ for a, e in zip(view.load_lines(lines_no_header, has_header=False, ret_dict=True), dict_data):
136
+ self.assertTrue(json_compare(a, e))
137
+
138
+ for a, e in zip(view.dump_lines(list_data, has_header=False), lines_no_header):
139
+ self.assertTrue(json_compare(a, e))
140
+
141
+ for a, e in zip(view.dump_lines(dict_data, has_header=False), lines_no_header):
142
+ self.assertTrue(json_compare(a, e))
143
+
144
+ for a, e in zip(view.dump_lines(view.load_lines(lines, has_header=True, ret_dict=True), has_header=True),
145
+ lines):
146
+ self.assertTrue(json_compare(a, e))
147
+
148
+ for a, e in zip(view.load_lines(view.dump_lines(list_data, has_header=True), has_header=True, ret_dict=True),
149
+ dict_data):
150
+ self.assertTrue(json_compare(a, e))
151
+
152
+ for a, e in zip(view.dump_lines(view.load_lines(lines_no_header, has_header=False, ret_dict=True),
153
+ has_header=False),
154
+ lines_no_header):
155
+ self.assertTrue(json_compare(a, e))
156
+
157
+ for a, e in zip(view.load_lines(view.dump_lines(list_data, has_header=False), has_header=False, ret_dict=True),
158
+ dict_data):
159
+ self.assertTrue(json_compare(a, e))
160
+
161
+ for a, e in zip(itertools.chain(view.load_lines(lines, has_header=True),
162
+ view.load_lines(lines, has_header=True, ret_dict=True)),
163
+ list_data + dict_data):
164
+ self.assertTrue(json_compare(a, e))
165
+
166
+ for a, e in zip(itertools.chain(view.dump_lines(list_data, has_header=True),
167
+ view.dump_lines(dict_data, has_header=True)),
168
+ lines + lines):
169
+ self.assertTrue(json_compare(a, e))
170
+
171
+ for a, e in zip(itertools.chain(view.load_lines(lines, has_header=True),
172
+ view.load_lines(lines, has_header=True, ret_dict=True),
173
+ view.load_lines(lines_no_header, has_header=False),
174
+ view.load_lines(lines_no_header, has_header=False, ret_dict=True)),
175
+ list_data + dict_data + list_data + dict_data):
176
+ self.assertTrue(json_compare(a, e))
177
+
178
+ for a, e in zip(itertools.chain(view.dump_lines(list_data, has_header=True),
179
+ view.dump_lines(dict_data, has_header=True),
180
+ view.dump_lines(list_data, has_header=False),
181
+ view.dump_lines(dict_data, has_header=False)),
182
+ lines + lines + lines_no_header + lines_no_header):
183
+ self.assertTrue(json_compare(a, e))
184
+
185
+ def test_file(self):
186
+ view_csv = csv.view(
187
+ [
188
+ csv.column("dummy_str", loader=str, dumper=str, null_str=r"\N"),
189
+ csv.column("dummy_bool", loader=parse_bool, dumper=str, null_str=r"\N"),
190
+ csv.column("dummy_int", loader=int, dumper=str, null_str=r"\N"),
191
+ csv.column("dummy_float", loader=float, dumper=str, null_str=r"\N"),
192
+ csv.column("dummy_datetime",
193
+ loader=lambda x: dt_parse_iso(x).timestamp(),
194
+ dumper=lambda x: dt_format_iso(datetime.datetime.fromtimestamp(x, tz=datetime.timezone.utc)),
195
+ null_str=r"\N"),
196
+ csv.column("dummy_params",
197
+ loader=lambda x: parse_params_string(x, delim=";", kv_delim="=", neg_prefix="!"),
198
+ dumper=lambda x: make_params_string(x, delim=";", kv_delim="=", neg_prefix="!"),
199
+ null_str=r"\N"),
200
+ ],
201
+ )
202
+
203
+ view_tsv = csv.view(
204
+ [
205
+ csv.column("dummy_str", loader=str, dumper=str, null_str=r"<null>"),
206
+ csv.column("dummy_bool", loader=parse_bool, dumper=str, null_str=r"<null>"),
207
+ csv.column("dummy_int", loader=int, dumper=str, null_str=r"<null>"),
208
+ csv.column("dummy_float", loader=float, dumper=str, null_str=r"<null>"),
209
+ csv.column("dummy_datetime",
210
+ loader=lambda x: dt_parse_iso(x).timestamp(),
211
+ dumper=lambda x: dt_format_iso(datetime.datetime.fromtimestamp(x, tz=datetime.timezone.utc)),
212
+ null_str=r"<null>"),
213
+ csv.column("dummy_params",
214
+ loader=lambda x: parse_params_string(x, delim="#", kv_delim=":", neg_prefix="-"),
215
+ dumper=lambda x: make_params_string(x, delim="#", kv_delim=":", neg_prefix="-"),
216
+ null_str=r"<null>"),
217
+ ],
218
+ col_delim="\t",
219
+ )
220
+
221
+ time = dt_parse_iso("2020-01-01T00:00:00").timestamp()
222
+
223
+ data = [
224
+ ["foo", True, 1, 1.0, time, {}],
225
+ ["bar", False, -1, -1.0, time, {"key_1": "value_1", "key_2": True, "key_3": False}],
226
+ ["baz", None, 100, math.inf, time, None],
227
+ ["", None, -100, -math.inf, time, {}],
228
+ [None, None, 0, math.nan, time, None],
229
+ [None, None, None, None, None, None],
230
+ ]
231
+
232
+ for c, t, d in zip(view_csv.load_file(os.path.join(resources_directory, "unittest/csv/data.csv"),
233
+ has_header=True,
234
+ encoding="utf-8"),
235
+ view_tsv.load_file(os.path.join(resources_directory, "unittest/csv/data.tsv"),
236
+ has_header=True,
237
+ encoding="utf-8"),
238
+ data):
239
+ self.assertTrue(json_compare(c, d))
240
+ self.assertTrue(json_compare(t, d))
241
+ self.assertTrue(json_compare(c, t))