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 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, Iterable, Iterator, Literal, Optional, Sequence, Type, Union, cast
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: Optional[Type[BaseException]] = ...,
59
- exc_value: Optional[BaseException] = ...,
60
- traceback: Optional[TracebackType] = ...,
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
- while True:
254
- try:
255
+ try:
256
+ while True:
255
257
  batches.append(FakeResultBatch(self._use_dict_result, reader.read_next_batch()))
256
- except StopIteration:
257
- break
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=9, display_size=None, internal_size=None, precision=None, scale=None, is_nullable=True # noqa: E501
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: Optional[str] = None,
369
- schema: Optional[str] = None,
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: Optional[Type[BaseException]] = ...,
439
- exc_value: Optional[BaseException] = ...,
440
- traceback: Optional[TracebackType] = ...,
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: Type[SnowflakeCursor] = SnowflakeCursor) -> FakeSnowflakeCursor:
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: Type[SnowflakeCursor] = SnowflakeCursor,
454
+ cursor_class: type[SnowflakeCursor] = SnowflakeCursor,
456
455
  **kwargs: dict[str, Any],
457
456
  ) -> Iterable[FakeSnowflakeCursor]:
458
457
  cursors = [
fakesnow/fixtures.py CHANGED
@@ -1,4 +1,4 @@
1
- from typing import Iterator
1
+ from collections.abc import Iterator
2
2
 
3
3
  import pytest
4
4
 
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[]' then 'ARRAY'
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.DataTypeSize):
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
- isinstance(expression, exp.Anonymous)
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
- expressions[1].args["this"] = expressions[1].this.replace("\\\\", "\\")
322
+ expression.args["expression"] = exp.Literal(
323
+ this=expression.expression.this.replace("\\\\", "\\"), is_string=True
324
+ )
330
325
 
331
- if len(expressions) == 2:
326
+ if not expression.args.get("replacement"):
332
327
  # if no replacement string, the snowflake default is ''
333
- expressions.append(exp.Literal(this="", is_string=True))
328
+ expression.args["replacement"] = exp.Literal(this="", is_string=True)
334
329
 
335
330
  # snowflake regex replacements are global
336
- expressions.append(exp.Literal(this="g", is_string=True))
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
- if len(expressions) < 2:
352
- raise snowflake.connector.errors.ProgrammingError(
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
- subject = expressions[0]
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 = expressions[1]
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 = expressions[2]
368
- except IndexError:
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 = expressions[3]
374
- except IndexError:
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(expressions[4].this)
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 IndexError:
366
+ except KeyError:
382
367
  regex_parameters = exp.Literal(is_string=True)
383
368
 
384
369
  try:
385
- group_num = expressions[5]
386
- except IndexError:
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.DataTypeSize(this=exp.Literal(this="9", is_string=False)) in expression.expressions
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
- if expression.this in [exp.DataType.Type.OBJECT, exp.DataType.Type.VARIANT]:
584
- new = expression.copy()
585
- new.args["this"] = exp.DataType.Type.JSON
586
- return new
587
- elif expression.this == exp.DataType.Type.ARRAY:
588
- new = expression.copy()
589
- new.set("expressions", [exp.DataType(this=exp.DataType.Type.JSON)])
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.7.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.8.0
32
+ Requires-Dist: duckdb ~=0.9.2
33
33
  Requires-Dist: pyarrow
34
34
  Requires-Dist: snowflake-connector-python
35
- Requires-Dist: sqlglot ~=16.8.1
35
+ Requires-Dist: sqlglot ~=19.5.1
36
36
  Provides-Extra: dev
37
- Requires-Dist: black ~=23.3 ; extra == 'dev'
38
- Requires-Dist: build ~=0.10 ; extra == 'dev'
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.2 ; extra == 'dev'
41
- Requires-Dist: pytest ~=7.3 ; extra == 'dev'
42
- Requires-Dist: ruff ~=0.0.285 ; extra == 'dev'
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.41.2)
2
+ Generator: bdist_wheel (0.42.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -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,,