Skip to content

Commit 5ce0087

Browse files
committed
keep type checker happy
1 parent dff5a71 commit 5ce0087

3 files changed

Lines changed: 35 additions & 43 deletions

File tree

countess/plugins/csv.py

Lines changed: 27 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,19 @@ class ColumnsMultiParam(MultiParam):
5454
CSV_DELIMITER_CHOICES = {",": ",", ";": ";", "|": "|", "TAB": "\t", "SPACE": " "}
5555

5656

57+
def _openfile(filename, mode):
58+
if filename.endswith(".gz"):
59+
return gzip.open(filename, mode)
60+
elif filename.endswith(".bz2"):
61+
return bz2.open(filename, mode)
62+
elif filename.endswith(".xz"):
63+
return lzma.open(filename, mode)
64+
elif "b" in mode:
65+
return open(filename, mode) # pylint: disable=unspecified-encoding
66+
else:
67+
return open(filename, mode, encoding="utf-8")
68+
69+
5770
class LoadCsvPlugin(LoadFileDeGlobMixin, LoadFileWithFilenameMixin, DuckdbLoadFilePlugin):
5871
"""Load CSV files"""
5972

@@ -70,29 +83,19 @@ class LoadCsvPlugin(LoadFileDeGlobMixin, LoadFileWithFilenameMixin, DuckdbLoadFi
7083
def load_file(
7184
self, cursor: duckdb.DuckDBPyConnection, filename: str, file_param: BaseParam, row_limit: Optional[int] = None
7285
) -> duckdb.DuckDBPyRelation:
73-
options = {
74-
"sep": CSV_DELIMITER_CHOICES[self.delimiter.value],
75-
"null_padding": True,
76-
"strict_mode": False,
77-
}
78-
79-
if len(self.columns):
80-
options["all_varchar"] = True
81-
options["skiprows"] = 1 if self.header else 0
82-
options["header"] = False
83-
else:
84-
options["header"] = self.header.value
85-
86-
if filename.endswith(".xz"):
87-
logger.debug("Reading file %s with LZMA", filename)
88-
with lzma.open(filename) as fh:
89-
rel = duckdb_source_to_table(cursor, cursor.read_csv(fh, **options))
90-
if filename.endswith(".bz2"):
91-
logger.debug("Reading file %s with BZ2", filename)
92-
with bz2.open(filename) as fh:
93-
rel = duckdb_source_to_table(cursor, cursor.read_csv(fh, **options))
94-
else:
95-
rel = duckdb_source_to_view(cursor, cursor.read_csv(filename, **options))
86+
with _openfile(filename, "rb") as fh:
87+
rel = duckdb_source_to_table(
88+
cursor,
89+
cursor.read_csv(
90+
fh,
91+
sep=CSV_DELIMITER_CHOICES[self.delimiter.value],
92+
null_padding=True,
93+
strict_mode=False,
94+
all_varchar=len(self.columns) > 0,
95+
skiprows=1 if self.header and len(self.columns) > 0 else 0,
96+
header=self.header.value and len(self.columns) == 0,
97+
),
98+
)
9699

97100
if row_limit is not None:
98101
rel = rel.limit(row_limit)
@@ -119,7 +122,6 @@ def load_file(
119122
if cp is None or cp.type.is_not_none()
120123
)
121124

122-
123125
logger.debug("LoadCsvPlugin.load_file proj %s", proj)
124126
rel = rel.project(proj)
125127

@@ -174,7 +176,6 @@ class SaveCsvPlugin(DuckdbSaveFilePlugin):
174176
def execute(
175177
self, ddbc: DuckDBPyConnection, source: Optional[DuckDBPyRelation], row_limit: Optional[int] = None
176178
) -> None:
177-
178179
if source is None or row_limit is not None or not self.filename:
179180
return
180181

@@ -185,7 +186,6 @@ def execute(
185186
logger.debug("SaveCsvPlugin.execute order_by %s", order_by)
186187
source = source.order(order_by)
187188

188-
189189
options = {
190190
"index": False,
191191
"sep": self.SEPARATORS[self.delimiter.value],
@@ -195,20 +195,12 @@ def execute(
195195
filename = self.filename.get_file_path()
196196
Path(filename).parent.mkdir(parents=True, exist_ok=True)
197197

198-
def _openfile(filename):
199-
if filename.endswith(".gz"):
200-
return gzip.open(filename, "wb")
201-
elif filename.endswith(".bz2"):
202-
return bz2.open(filename, "wb")
203-
else:
204-
return open(filename, "wb")
205-
206198
# the type check supression is because the first parameter to
207199
# to_csv is called path_or_buf and takes either a filename path
208200
# or a file-like buffer, but is declared as str|None.
209201

210202
logger.debug("writing file %s", filename)
211-
with _openfile(filename) as fh:
203+
with _openfile(filename, "wb") as fh:
212204
chunk = source.fetch_df_chunk()
213205
chunk.to_csv(fh, header=True, **options) # type: ignore
214206
while len(chunk) > 0:

countess/utils/expression_to_sql.py

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ class AddExpr(SqlTemplatingList):
194194
class CompOp(SqlTemplatingSymbol):
195195
regex = re.compile(r"<=?|>=?|[!=]=")
196196

197+
197198
class CompExpr(pypeg2.List):
198199
grammar = AddExpr, pypeg2.maybe_some((CompOp, [NullLiteral, AddExpr]))
199200

@@ -208,10 +209,7 @@ def _subcomp(left, comp, right):
208209
return left.sql() + comp_op + right.sql()
209210

210211
if len(self) >= 3:
211-
return " AND ".join(
212-
_subcomp(self[n], self[n+1], self[n+2])
213-
for n in range(0, len(self)-1, 2)
214-
)
212+
return " AND ".join(_subcomp(self[n], self[n + 1], self[n + 2]) for n in range(0, len(self) - 1, 2))
215213
else:
216214
return self[0].sql()
217215

@@ -256,10 +254,7 @@ def names(self):
256254
return [s.name for s in self[0:-1]]
257255

258256
def sql(self):
259-
return ','.join(
260-
self[-1].sql() + " AS " + s.sql()
261-
for s in self[0:-1]
262-
)
257+
return ",".join(self[-1].sql() + " AS " + s.sql() for s in self[0:-1])
263258

264259

265260
class Filter(pypeg2.Concat):

tests/test_expression.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,27 +26,32 @@
2626
def test_expressions(expr, sql):
2727
assert Block.from_string(expr)[0].sql() == sql
2828

29+
2930
assign_tests = [
3031
("a = 1", '1 AS "a"'),
3132
("a = b = c = 2", '2 AS "a",2 AS "b",2 AS "c"'),
3233
("d = None", 'NULL AS "d"'),
3334
]
3435

36+
3537
@pytest.mark.parametrize("expr,sql", assign_tests)
3638
def test_assignments(expr, sql):
3739
assert Block.from_string(expr).sql_selects()[0] == sql
3840

41+
3942
filter_tests = [
4043
("a < b", '"a"<"b"'),
4144
("b <= c", '"b"<="c"'),
4245
("a < b <= c", '"a"<"b" AND "b"<="c"'),
4346
("__filter = a < b", '"a"<"b"'),
4447
]
4548

49+
4650
@pytest.mark.parametrize("expr,sql", filter_tests)
4751
def test_filters(expr, sql):
4852
assert Block.from_string(expr).sql_wheres()[0] == sql
4953

54+
5055
def test_project_and_filter():
5156
rel = duckdb.sql("select * from (values (1,2), (3,4), (5,6)) t(a,b)")
5257
blk = Block.from_string("c = a+b\nb<5\nd = a + c")

0 commit comments

Comments
 (0)