fakesnow 0.7.0__py3-none-any.whl → 0.8.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fakesnow/__init__.py +1 -4
- fakesnow/fakes.py +19 -20
- fakesnow/fixtures.py +1 -1
- fakesnow/info_schema.py +1 -2
- fakesnow/transforms.py +46 -48
- {fakesnow-0.7.0.dist-info → fakesnow-0.8.0.dist-info}/METADATA +8 -8
- fakesnow-0.8.0.dist-info/RECORD +13 -0
- {fakesnow-0.7.0.dist-info → fakesnow-0.8.0.dist-info}/WHEEL +1 -1
- fakesnow-0.7.0.dist-info/RECORD +0 -13
- {fakesnow-0.7.0.dist-info → fakesnow-0.8.0.dist-info}/LICENSE +0 -0
- {fakesnow-0.7.0.dist-info → fakesnow-0.8.0.dist-info}/top_level.txt +0 -0
fakesnow/__init__.py
CHANGED
@@ -4,11 +4,8 @@ import contextlib
|
|
4
4
|
import importlib
|
5
5
|
import sys
|
6
6
|
import unittest.mock as mock
|
7
|
+
from collections.abc import Iterator, Sequence
|
7
8
|
from contextlib import contextmanager
|
8
|
-
from typing import (
|
9
|
-
Iterator,
|
10
|
-
Sequence,
|
11
|
-
)
|
12
9
|
|
13
10
|
import duckdb
|
14
11
|
import snowflake.connector
|
fakesnow/fakes.py
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
import re
|
4
|
+
from collections.abc import Iterable, Iterator, Sequence
|
4
5
|
from types import TracebackType
|
5
|
-
from typing import TYPE_CHECKING, Any,
|
6
|
+
from typing import TYPE_CHECKING, Any, Literal, Optional, Union, cast
|
6
7
|
|
7
8
|
import duckdb
|
8
9
|
|
@@ -55,9 +56,9 @@ class FakeSnowflakeCursor:
|
|
55
56
|
|
56
57
|
def __exit__(
|
57
58
|
self,
|
58
|
-
exc_type:
|
59
|
-
exc_value:
|
60
|
-
traceback:
|
59
|
+
exc_type: type[BaseException] | None,
|
60
|
+
exc_value: BaseException | None,
|
61
|
+
traceback: TracebackType | None,
|
61
62
|
) -> bool:
|
62
63
|
return False
|
63
64
|
|
@@ -149,6 +150,7 @@ class FakeSnowflakeCursor:
|
|
149
150
|
.transform(transforms.values_columns)
|
150
151
|
.transform(transforms.to_date)
|
151
152
|
.transform(transforms.to_decimal)
|
153
|
+
.transform(transforms.to_timestamp)
|
152
154
|
.transform(transforms.object_construct)
|
153
155
|
.transform(transforms.timestamp_ntz_ns)
|
154
156
|
.transform(transforms.float_to_double)
|
@@ -250,11 +252,11 @@ class FakeSnowflakeCursor:
|
|
250
252
|
reader = self._duck_conn.fetch_record_batch(rows_per_batch=1000)
|
251
253
|
|
252
254
|
batches = []
|
253
|
-
|
254
|
-
|
255
|
+
try:
|
256
|
+
while True:
|
255
257
|
batches.append(FakeResultBatch(self._use_dict_result, reader.read_next_batch()))
|
256
|
-
|
257
|
-
|
258
|
+
except StopIteration:
|
259
|
+
pass
|
258
260
|
|
259
261
|
return batches
|
260
262
|
|
@@ -320,13 +322,10 @@ class FakeSnowflakeCursor:
|
|
320
322
|
return ResultMetadata(
|
321
323
|
name=column_name, type_code=12, display_size=None, internal_size=None, precision=0, scale=9, is_nullable=True # noqa: E501
|
322
324
|
)
|
323
|
-
elif column_type == "JSON[]":
|
324
|
-
return ResultMetadata(
|
325
|
-
name=column_name, type_code=10, display_size=None, internal_size=None, precision=None, scale=None, is_nullable=True # noqa: E501
|
326
|
-
)
|
327
325
|
elif column_type == "JSON":
|
326
|
+
# TODO: correctly map OBJECT and ARRAY see https://github.com/tekumara/fakesnow/issues/26
|
328
327
|
return ResultMetadata(
|
329
|
-
name=column_name, type_code=
|
328
|
+
name=column_name, type_code=5, display_size=None, internal_size=None, precision=None, scale=None, is_nullable=True # noqa: E501
|
330
329
|
)
|
331
330
|
else:
|
332
331
|
# TODO handle more types
|
@@ -365,8 +364,8 @@ class FakeSnowflakeConnection:
|
|
365
364
|
def __init__(
|
366
365
|
self,
|
367
366
|
duck_conn: DuckDBPyConnection,
|
368
|
-
database:
|
369
|
-
schema:
|
367
|
+
database: str | None = None,
|
368
|
+
schema: str | None = None,
|
370
369
|
create_database: bool = True,
|
371
370
|
create_schema: bool = True,
|
372
371
|
*args: Any,
|
@@ -435,16 +434,16 @@ class FakeSnowflakeConnection:
|
|
435
434
|
|
436
435
|
def __exit__(
|
437
436
|
self,
|
438
|
-
exc_type:
|
439
|
-
exc_value:
|
440
|
-
traceback:
|
437
|
+
exc_type: type[BaseException] | None,
|
438
|
+
exc_value: BaseException | None,
|
439
|
+
traceback: TracebackType | None,
|
441
440
|
) -> bool:
|
442
441
|
return False
|
443
442
|
|
444
443
|
def commit(self) -> None:
|
445
444
|
self.cursor().execute("COMMIT")
|
446
445
|
|
447
|
-
def cursor(self, cursor_class:
|
446
|
+
def cursor(self, cursor_class: type[SnowflakeCursor] = SnowflakeCursor) -> FakeSnowflakeCursor:
|
448
447
|
return FakeSnowflakeCursor(conn=self, duck_conn=self._duck_conn, use_dict_result=cursor_class == DictCursor)
|
449
448
|
|
450
449
|
def execute_string(
|
@@ -452,7 +451,7 @@ class FakeSnowflakeConnection:
|
|
452
451
|
sql_text: str,
|
453
452
|
remove_comments: bool = False,
|
454
453
|
return_cursors: bool = True,
|
455
|
-
cursor_class:
|
454
|
+
cursor_class: type[SnowflakeCursor] = SnowflakeCursor,
|
456
455
|
**kwargs: dict[str, Any],
|
457
456
|
) -> Iterable[FakeSnowflakeCursor]:
|
458
457
|
cursors = [
|
fakesnow/fixtures.py
CHANGED
fakesnow/info_schema.py
CHANGED
@@ -40,8 +40,7 @@ case when starts_with(data_type, 'DECIMAL') or data_type='BIGINT' then 'NUMBER'
|
|
40
40
|
when data_type='DOUBLE' then 'FLOAT'
|
41
41
|
when data_type='BLOB' then 'BINARY'
|
42
42
|
when data_type='TIMESTAMP' then 'TIMESTAMP_NTZ'
|
43
|
-
when data_type='JSON
|
44
|
-
when data_type='JSON' then 'OBJECT'
|
43
|
+
when data_type='JSON' then 'VARIANT'
|
45
44
|
else data_type end as data_type,
|
46
45
|
ext_character_maximum_length as character_maximum_length, ext_character_octet_length as character_octet_length,
|
47
46
|
case when data_type='BIGINT' then 38
|
fakesnow/transforms.py
CHANGED
@@ -2,7 +2,6 @@ from __future__ import annotations
|
|
2
2
|
|
3
3
|
from typing import cast
|
4
4
|
|
5
|
-
import snowflake.connector
|
6
5
|
import sqlglot
|
7
6
|
from sqlglot import exp
|
8
7
|
|
@@ -140,7 +139,7 @@ def extract_text_length(expression: exp.Expression) -> exp.Expression:
|
|
140
139
|
for dt in expression.find_all(exp.DataType):
|
141
140
|
if dt.this in (exp.DataType.Type.VARCHAR, exp.DataType.Type.TEXT):
|
142
141
|
col_name = dt.parent and dt.parent.this and dt.parent.this.this
|
143
|
-
if dt_size := dt.find(exp.
|
142
|
+
if dt_size := dt.find(exp.DataTypeParam):
|
144
143
|
size = (
|
145
144
|
isinstance(dt_size.this, exp.Literal)
|
146
145
|
and isinstance(dt_size.this.this, str)
|
@@ -311,14 +310,8 @@ def parse_json(expression: exp.Expression) -> exp.Expression:
|
|
311
310
|
def regex_replace(expression: exp.Expression) -> exp.Expression:
|
312
311
|
"""Transform regex_replace expressions from snowflake to duckdb."""
|
313
312
|
|
314
|
-
if (
|
315
|
-
|
316
|
-
and isinstance(expression.this, str)
|
317
|
-
and expression.this.upper() == "REGEXP_REPLACE"
|
318
|
-
):
|
319
|
-
expressions = expression.expressions
|
320
|
-
|
321
|
-
if len(expressions) > 3:
|
313
|
+
if isinstance(expression, exp.RegexpReplace) and isinstance(expression.expression, exp.Literal):
|
314
|
+
if len(expression.args) > 3:
|
322
315
|
# see https://docs.snowflake.com/en/sql-reference/functions/regexp_replace
|
323
316
|
raise NotImplementedError(
|
324
317
|
"REGEXP_REPLACE with additional parameters (eg: <position>, <occurrence>, <parameters>) not supported"
|
@@ -326,64 +319,56 @@ def regex_replace(expression: exp.Expression) -> exp.Expression:
|
|
326
319
|
|
327
320
|
# pattern: snowflake requires escaping backslashes in single-quoted string constants, but duckdb doesn't
|
328
321
|
# see https://docs.snowflake.com/en/sql-reference/functions-regexp#label-regexp-escape-character-caveats
|
329
|
-
|
322
|
+
expression.args["expression"] = exp.Literal(
|
323
|
+
this=expression.expression.this.replace("\\\\", "\\"), is_string=True
|
324
|
+
)
|
330
325
|
|
331
|
-
if
|
326
|
+
if not expression.args.get("replacement"):
|
332
327
|
# if no replacement string, the snowflake default is ''
|
333
|
-
|
328
|
+
expression.args["replacement"] = exp.Literal(this="", is_string=True)
|
334
329
|
|
335
330
|
# snowflake regex replacements are global
|
336
|
-
|
331
|
+
expression.args["modifiers"] = exp.Literal(this="g", is_string=True)
|
337
332
|
|
338
333
|
return expression
|
339
334
|
|
340
335
|
|
341
336
|
def regex_substr(expression: exp.Expression) -> exp.Expression:
|
342
|
-
"""Transform regex_substr expressions from snowflake to duckdb.
|
343
|
-
|
344
|
-
if (
|
345
|
-
isinstance(expression, exp.Anonymous)
|
346
|
-
and isinstance(expression.this, str)
|
347
|
-
and expression.this.upper() == "REGEXP_SUBSTR"
|
348
|
-
):
|
349
|
-
expressions = expression.expressions
|
337
|
+
"""Transform regex_substr expressions from snowflake to duckdb.
|
350
338
|
|
351
|
-
|
352
|
-
|
353
|
-
msg=f"SQL compilation error:\nnot enough arguments for function [{expression.sql()}], expected 2, got {len(expressions)}", # noqa: E501
|
354
|
-
errno=938,
|
355
|
-
sqlstate="22023",
|
356
|
-
)
|
339
|
+
See https://docs.snowflake.com/en/sql-reference/functions/regexp_substr
|
340
|
+
"""
|
357
341
|
|
358
|
-
|
342
|
+
if isinstance(expression, exp.RegexpExtract):
|
343
|
+
subject = expression.this
|
359
344
|
|
360
345
|
# pattern: snowflake requires escaping backslashes in single-quoted string constants, but duckdb doesn't
|
361
346
|
# see https://docs.snowflake.com/en/sql-reference/functions-regexp#label-regexp-escape-character-caveats
|
362
|
-
pattern =
|
347
|
+
pattern = expression.expression
|
363
348
|
pattern.args["this"] = pattern.this.replace("\\\\", "\\")
|
364
349
|
|
365
350
|
# number of characters from the beginning of the string where the function starts searching for matches
|
366
351
|
try:
|
367
|
-
position =
|
368
|
-
except
|
352
|
+
position = expression.args["position"]
|
353
|
+
except KeyError:
|
369
354
|
position = exp.Literal(this="1", is_string=False)
|
370
355
|
|
371
356
|
# which occurrence of the pattern to match
|
372
357
|
try:
|
373
|
-
occurrence =
|
374
|
-
except
|
358
|
+
occurrence = expression.args["occurrence"]
|
359
|
+
except KeyError:
|
375
360
|
occurrence = exp.Literal(this="1", is_string=False)
|
376
361
|
|
377
362
|
try:
|
378
|
-
regex_parameters_value = str(
|
363
|
+
regex_parameters_value = str(expression.args["parameters"].this)
|
379
364
|
# 'e' parameter doesn't make sense for duckdb
|
380
365
|
regex_parameters = exp.Literal(this=regex_parameters_value.replace("e", ""), is_string=True)
|
381
|
-
except
|
366
|
+
except KeyError:
|
382
367
|
regex_parameters = exp.Literal(is_string=True)
|
383
368
|
|
384
369
|
try:
|
385
|
-
group_num =
|
386
|
-
except
|
370
|
+
group_num = expression.args["group"]
|
371
|
+
except KeyError:
|
387
372
|
if isinstance(regex_parameters.this, str) and "e" in regex_parameters.this:
|
388
373
|
group_num = exp.Literal(this="1", is_string=False)
|
389
374
|
else:
|
@@ -546,6 +531,20 @@ def to_decimal(expression: exp.Expression) -> exp.Expression:
|
|
546
531
|
return expression
|
547
532
|
|
548
533
|
|
534
|
+
def to_timestamp(expression: exp.Expression) -> exp.Expression:
|
535
|
+
"""Convert to_timestamp(seconds) to timestamp without timezone (ie: TIMESTAMP_NTZ).
|
536
|
+
|
537
|
+
See https://docs.snowflake.com/en/sql-reference/functions/to_timestamp
|
538
|
+
"""
|
539
|
+
|
540
|
+
if isinstance(expression, exp.UnixToTime):
|
541
|
+
return exp.Cast(
|
542
|
+
this=expression,
|
543
|
+
to=exp.DataType(this=exp.DataType.Type.TIMESTAMP, nested=False, prefix=False),
|
544
|
+
)
|
545
|
+
return expression
|
546
|
+
|
547
|
+
|
549
548
|
def timestamp_ntz_ns(expression: exp.Expression) -> exp.Expression:
|
550
549
|
"""Convert timestamp_ntz(9) to timestamp_ntz.
|
551
550
|
|
@@ -555,7 +554,7 @@ def timestamp_ntz_ns(expression: exp.Expression) -> exp.Expression:
|
|
555
554
|
if (
|
556
555
|
isinstance(expression, exp.DataType)
|
557
556
|
and expression.this == exp.DataType.Type.TIMESTAMP
|
558
|
-
and exp.
|
557
|
+
and exp.DataTypeParam(this=exp.Literal(this="9", is_string=False)) in expression.expressions
|
559
558
|
):
|
560
559
|
new = expression.copy()
|
561
560
|
del new.args["expressions"]
|
@@ -579,15 +578,14 @@ def semi_structured_types(expression: exp.Expression) -> exp.Expression:
|
|
579
578
|
exp.Expression: The transformed expression.
|
580
579
|
"""
|
581
580
|
|
582
|
-
if isinstance(expression, exp.DataType)
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
return new
|
581
|
+
if isinstance(expression, exp.DataType) and expression.this in [
|
582
|
+
exp.DataType.Type.ARRAY,
|
583
|
+
exp.DataType.Type.OBJECT,
|
584
|
+
exp.DataType.Type.VARIANT,
|
585
|
+
]:
|
586
|
+
new = expression.copy()
|
587
|
+
new.args["this"] = exp.DataType.Type.JSON
|
588
|
+
return new
|
591
589
|
|
592
590
|
return expression
|
593
591
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: fakesnow
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.8.0
|
4
4
|
Summary: Fake Snowflake Connector for Python. Run Snowflake DB locally.
|
5
5
|
License: MIT License
|
6
6
|
|
@@ -29,17 +29,17 @@ Classifier: License :: OSI Approved :: MIT License
|
|
29
29
|
Requires-Python: >=3.9
|
30
30
|
Description-Content-Type: text/markdown
|
31
31
|
License-File: LICENSE
|
32
|
-
Requires-Dist: duckdb ~=0.
|
32
|
+
Requires-Dist: duckdb ~=0.9.2
|
33
33
|
Requires-Dist: pyarrow
|
34
34
|
Requires-Dist: snowflake-connector-python
|
35
|
-
Requires-Dist: sqlglot ~=
|
35
|
+
Requires-Dist: sqlglot ~=19.5.1
|
36
36
|
Provides-Extra: dev
|
37
|
-
Requires-Dist: black ~=23.
|
38
|
-
Requires-Dist: build ~=0
|
37
|
+
Requires-Dist: black ~=23.9 ; extra == 'dev'
|
38
|
+
Requires-Dist: build ~=1.0 ; extra == 'dev'
|
39
39
|
Requires-Dist: snowflake-connector-python[pandas,secure-local-storage] ; extra == 'dev'
|
40
|
-
Requires-Dist: pre-commit ~=3.
|
41
|
-
Requires-Dist: pytest ~=7.
|
42
|
-
Requires-Dist: ruff ~=0.
|
40
|
+
Requires-Dist: pre-commit ~=3.4 ; extra == 'dev'
|
41
|
+
Requires-Dist: pytest ~=7.4 ; extra == 'dev'
|
42
|
+
Requires-Dist: ruff ~=0.1.6 ; extra == 'dev'
|
43
43
|
Requires-Dist: twine ~=4.0 ; extra == 'dev'
|
44
44
|
Provides-Extra: notebook
|
45
45
|
Requires-Dist: duckdb-engine ; extra == 'notebook'
|
@@ -0,0 +1,13 @@
|
|
1
|
+
fakesnow/__init__.py,sha256=JXg0HSEHSAOSjEE1ZeA_ogNQ2iFXGEuoibQWk7Pz-WM,3133
|
2
|
+
fakesnow/checks.py,sha256=1qVLR0ZB3z3UPij3Hm8hqlkcNLH2QJnwe8OqkoFCwv8,2356
|
3
|
+
fakesnow/expr.py,sha256=CAxuYIUkwI339DQIBzvFF0F-m1tcVGKEPA5rDTzmH9A,892
|
4
|
+
fakesnow/fakes.py,sha256=qYYtgVNeRSDOMRZYrDo4lS3YB1vTmpAPiVQaeILUdAs,21352
|
5
|
+
fakesnow/fixtures.py,sha256=FfVGhfuIQea0_GQKW8H4ZH0DoxrU2ZnMVJj2eobVbnI,518
|
6
|
+
fakesnow/info_schema.py,sha256=maLS3k10ed_NUYVDeSaL9aXDuVGgQSX8mCYzTqMiW0U,4503
|
7
|
+
fakesnow/py.typed,sha256=B-DLSjYBi7pkKjwxCSdpVj2J02wgfJr-E7B1wOUyxYU,80
|
8
|
+
fakesnow/transforms.py,sha256=TIZa6uLbqTQ66j-xXzr7T475RTJLLyrTcVUvB5dgL1A,22894
|
9
|
+
fakesnow-0.8.0.dist-info/LICENSE,sha256=BL6v_VTnU7xdsocviIQJMFr3stX_-uRfTyByo3gRu4M,1071
|
10
|
+
fakesnow-0.8.0.dist-info/METADATA,sha256=pqyCHOXBSQ4O_I2QtxoGYEbiVfmesGj7V0nonJXt_LY,5418
|
11
|
+
fakesnow-0.8.0.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
12
|
+
fakesnow-0.8.0.dist-info/top_level.txt,sha256=x8S-sMmvfgNm2_1w0zlIF5YlDs2hR7eNQdVA6TgmPZE,14
|
13
|
+
fakesnow-0.8.0.dist-info/RECORD,,
|
fakesnow-0.7.0.dist-info/RECORD
DELETED
@@ -1,13 +0,0 @@
|
|
1
|
-
fakesnow/__init__.py,sha256=mjtMo58BOxp9LU6pFPyVgl6GYtoF_tnaX5UqmPINVlU,3137
|
2
|
-
fakesnow/checks.py,sha256=1qVLR0ZB3z3UPij3Hm8hqlkcNLH2QJnwe8OqkoFCwv8,2356
|
3
|
-
fakesnow/expr.py,sha256=CAxuYIUkwI339DQIBzvFF0F-m1tcVGKEPA5rDTzmH9A,892
|
4
|
-
fakesnow/fakes.py,sha256=xlhuB0teuW3ftNUra93h1HiiRwyupDnoqQYW8RosGwA,21508
|
5
|
-
fakesnow/fixtures.py,sha256=LANb4LuiUjKbTZRHmgnAi50xC1rs1xF8SHLoBikB88c,509
|
6
|
-
fakesnow/info_schema.py,sha256=3lkpRI_ByXbA1PwZ3hh4PtB9aLAsRbkI4MM_hTaOce8,4544
|
7
|
-
fakesnow/py.typed,sha256=B-DLSjYBi7pkKjwxCSdpVj2J02wgfJr-E7B1wOUyxYU,80
|
8
|
-
fakesnow/transforms.py,sha256=v7-Erd5cyK4dqw5jXkkQKIWp9j7lYeWaGhFeAnjGQsY,22962
|
9
|
-
fakesnow-0.7.0.dist-info/LICENSE,sha256=BL6v_VTnU7xdsocviIQJMFr3stX_-uRfTyByo3gRu4M,1071
|
10
|
-
fakesnow-0.7.0.dist-info/METADATA,sha256=UMNSLxz4REM3ha9JGd-dh_y06fv6C8EczHyI_SFo1bo,5421
|
11
|
-
fakesnow-0.7.0.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
|
12
|
-
fakesnow-0.7.0.dist-info/top_level.txt,sha256=x8S-sMmvfgNm2_1w0zlIF5YlDs2hR7eNQdVA6TgmPZE,14
|
13
|
-
fakesnow-0.7.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|