fakesnow 0.9.26__py3-none-any.whl → 0.9.28__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/arrow.py CHANGED
@@ -5,7 +5,7 @@ from typing import cast
5
5
  import pyarrow as pa
6
6
  import pyarrow.compute as pc
7
7
 
8
- from fakesnow.types import ColumnInfo
8
+ from fakesnow.rowtype import ColumnInfo
9
9
 
10
10
 
11
11
  def to_sf_schema(schema: pa.Schema, rowtype: list[ColumnInfo]) -> pa.Schema:
@@ -27,6 +27,10 @@ def to_sf_schema(schema: pa.Schema, rowtype: list[ColumnInfo]) -> pa.Schema:
27
27
  field = field.with_type(pa.struct(fields))
28
28
  elif isinstance(field.type, pa.Time64Type):
29
29
  field = field.with_type(pa.int64())
30
+ elif pa.types.is_uint64(field.type):
31
+ # snowflake-python-connector expects signed ints
32
+ # see https://github.com/snowflakedb/snowflake-connector-python/blob/5d7064c7f3f756792c1f6252bf5c9d807e4307e8/src/snowflake/connector/nanoarrow_cpp/ArrowIterator/CArrowChunkIterator.cpp#L187
33
+ field = field.with_type(pa.int64())
30
34
 
31
35
  return field.with_metadata(
32
36
  {
fakesnow/conn.py CHANGED
@@ -60,7 +60,7 @@ class FakeSnowflakeConnection:
60
60
  where upper(catalog_name) = '{self.database}'"""
61
61
  ).fetchone()
62
62
  ):
63
- db_file = f"{self.db_path/self.database}.db" if self.db_path else ":memory:"
63
+ db_file = f"{self.db_path / self.database}.db" if self.db_path else ":memory:"
64
64
  duck_conn.execute(f"ATTACH DATABASE '{db_file}' AS {self.database}")
65
65
  duck_conn.execute(info_schema.creation_sql(self.database))
66
66
  duck_conn.execute(macros.creation_sql(self.database))
@@ -114,6 +114,10 @@ class FakeSnowflakeConnection:
114
114
  ) -> None:
115
115
  pass
116
116
 
117
+ def autocommit(self, _mode: bool) -> None:
118
+ # autcommit is always on in duckdb
119
+ pass
120
+
117
121
  def close(self, retry: bool = True) -> None:
118
122
  self._duck_conn.close()
119
123
  self._is_closed = True
fakesnow/cursor.py CHANGED
@@ -3,13 +3,14 @@ from __future__ import annotations
3
3
  import os
4
4
  import re
5
5
  import sys
6
+ import uuid
6
7
  from collections.abc import Iterator, Sequence
7
8
  from string import Template
8
9
  from types import TracebackType
9
10
  from typing import TYPE_CHECKING, Any, cast
10
11
 
11
12
  import duckdb
12
- import pyarrow
13
+ import pyarrow # needed by fetch_arrow_table()
13
14
  import snowflake.connector.converter
14
15
  import snowflake.connector.errors
15
16
  import sqlglot
@@ -23,7 +24,7 @@ import fakesnow.checks as checks
23
24
  import fakesnow.expr as expr
24
25
  import fakesnow.info_schema as info_schema
25
26
  import fakesnow.transforms as transforms
26
- from fakesnow.types import describe_as_result_metadata
27
+ from fakesnow.rowtype import describe_as_result_metadata
27
28
 
28
29
  if TYPE_CHECKING:
29
30
  # don't require pandas at import time
@@ -71,6 +72,7 @@ class FakeSnowflakeCursor:
71
72
  self._arrow_table = None
72
73
  self._arrow_table_fetch_index = None
73
74
  self._rowcount = None
75
+ self._sfqid = None
74
76
  self._converter = snowflake.connector.converter.SnowflakeConverter()
75
77
 
76
78
  def __enter__(self) -> Self:
@@ -222,6 +224,7 @@ class FakeSnowflakeCursor:
222
224
  self._arrow_table = None
223
225
  self._arrow_table_fetch_index = None
224
226
  self._rowcount = None
227
+ self._sfqid = None
225
228
 
226
229
  cmd = expr.key_command(transformed)
227
230
 
@@ -353,6 +356,7 @@ class FakeSnowflakeCursor:
353
356
 
354
357
  self._arrow_table = self._duck_conn.fetch_arrow_table()
355
358
  self._rowcount = affected_count or self._arrow_table.num_rows
359
+ self._sfqid = str(uuid.uuid4())
356
360
 
357
361
  self._last_sql = result_sql or sql
358
362
  self._last_params = params
@@ -423,7 +427,7 @@ class FakeSnowflakeCursor:
423
427
 
424
428
  @property
425
429
  def sfqid(self) -> str | None:
426
- return "fakesnow"
430
+ return self._sfqid
427
431
 
428
432
  @property
429
433
  def sqlstate(self) -> str | None:
fakesnow/info_schema.py CHANGED
@@ -146,7 +146,7 @@ def insert_table_comment_sql(catalog: str, schema: str, table: str, comment: str
146
146
 
147
147
  def insert_text_lengths_sql(catalog: str, schema: str, table: str, text_lengths: list[tuple[str, int]]) -> str:
148
148
  values = ", ".join(
149
- f"('{catalog}', '{schema}', '{table}', '{col_name}', {size}, {min(size*4,16777216)})"
149
+ f"('{catalog}', '{schema}', '{table}', '{col_name}', {size}, {min(size * 4, 16777216)})"
150
150
  for (col_name, size) in text_lengths
151
151
  )
152
152
 
fakesnow/pandas_tools.py CHANGED
@@ -4,13 +4,13 @@ import json
4
4
  from collections.abc import Sequence
5
5
  from typing import TYPE_CHECKING, Any, Literal, Optional
6
6
 
7
- import numpy as np
8
7
  from duckdb import DuckDBPyConnection
9
8
 
10
9
  from fakesnow.conn import FakeSnowflakeConnection
11
10
 
12
11
  if TYPE_CHECKING:
13
- # don't require pandas at import time
12
+ # don't require pandas or numpy at import time
13
+ import numpy as np
14
14
  import pandas as pd
15
15
 
16
16
 
@@ -25,12 +25,14 @@ duckdb_to_sf_type = {
25
25
  "DATE": "date",
26
26
  "DECIMAL": "fixed",
27
27
  "DOUBLE": "real",
28
+ "HUGEINT": "fixed",
28
29
  "INTEGER": "fixed",
29
30
  "JSON": "variant",
30
31
  "TIME": "time",
31
32
  "TIMESTAMP WITH TIME ZONE": "timestamp_tz",
32
33
  "TIMESTAMP_NS": "timestamp_ntz",
33
34
  "TIMESTAMP": "timestamp_ntz",
35
+ "UBIGINT": "fixed",
34
36
  "VARCHAR": "text",
35
37
  }
36
38
 
fakesnow/server.py CHANGED
@@ -17,7 +17,7 @@ from starlette.routing import Route
17
17
  from fakesnow.arrow import to_ipc, to_sf
18
18
  from fakesnow.fakes import FakeSnowflakeConnection
19
19
  from fakesnow.instance import FakeSnow
20
- from fakesnow.types import describe_as_rowtype
20
+ from fakesnow.rowtype import describe_as_rowtype
21
21
 
22
22
  shared_fs = FakeSnow()
23
23
  sessions: dict[str, FakeSnowflakeConnection] = {}
@@ -87,7 +87,8 @@ async def query_request(request: Request) -> JSONResponse:
87
87
  "data": {
88
88
  "rowtype": rowtype,
89
89
  "rowsetBase64": rowset_b64,
90
- "total": 1,
90
+ "total": cur._rowcount, # noqa: SLF001
91
+ "queryId": cur.sfqid,
91
92
  "queryResultFormat": "arrow",
92
93
  },
93
94
  "success": True,
fakesnow/transforms.py CHANGED
@@ -28,6 +28,8 @@ def alias_in_join(expression: exp.Expression) -> exp.Expression:
28
28
  and (col := on.this)
29
29
  and (isinstance(col, exp.Column))
30
30
  and (alias := aliases.get(col.this))
31
+ # don't rewrite col with table identifier
32
+ and not col.table
31
33
  ):
32
34
  col.args["this"] = alias.this
33
35
 
@@ -131,7 +133,7 @@ def create_database(expression: exp.Expression, db_path: Path | None = None) ->
131
133
  ident = expression.find(exp.Identifier)
132
134
  assert ident, f"No identifier in {expression.sql}"
133
135
  db_name = ident.this
134
- db_file = f"{db_path/db_name}.db" if db_path else ":memory:"
136
+ db_file = f"{db_path / db_name}.db" if db_path else ":memory:"
135
137
 
136
138
  if_not_exists = "IF NOT EXISTS " if expression.args.get("exists") else ""
137
139
 
@@ -658,8 +660,7 @@ def integer_precision(expression: exp.Expression) -> exp.Expression:
658
660
  if (
659
661
  isinstance(expression, exp.DataType)
660
662
  and (expression.this == exp.DataType.Type.DECIMAL and not expression.expressions)
661
- or expression.this in (exp.DataType.Type.INT, exp.DataType.Type.SMALLINT, exp.DataType.Type.TINYINT)
662
- ):
663
+ ) or expression.this in (exp.DataType.Type.INT, exp.DataType.Type.SMALLINT, exp.DataType.Type.TINYINT):
663
664
  return exp.DataType(
664
665
  this=exp.DataType.Type.BIGINT,
665
666
  nested=False,
@@ -48,7 +48,7 @@ def _create_merge_candidates(merge_expr: exp.Merge) -> exp.Expression:
48
48
  )
49
49
 
50
50
  # Iterate through the WHEN clauses to build up the CASE WHEN clauses
51
- for w_idx, w in enumerate(merge_expr.expressions):
51
+ for w_idx, w in enumerate(merge_expr.args["whens"]):
52
52
  assert isinstance(w, exp.When), f"Expected When expression, got {w}"
53
53
 
54
54
  predicate = join_expr.copy()
@@ -83,9 +83,9 @@ def _create_merge_candidates(merge_expr: exp.Merge) -> exp.Expression:
83
83
  sql = f"""
84
84
  CREATE OR REPLACE TEMPORARY TABLE merge_candidates AS
85
85
  SELECT
86
- {', '.join(sorted(values))},
86
+ {", ".join(sorted(values))},
87
87
  CASE
88
- {' '.join(case_when_clauses)}
88
+ {" ".join(case_when_clauses)}
89
89
  ELSE NULL
90
90
  END AS MERGE_OP
91
91
  FROM {target_tbl}
@@ -109,7 +109,7 @@ def _mutations(merge_expr: exp.Merge) -> list[exp.Expression]:
109
109
  statements: list[exp.Expression] = []
110
110
 
111
111
  # Iterate through the WHEN clauses to generate delete/update/insert statements
112
- for w_idx, w in enumerate(merge_expr.expressions):
112
+ for w_idx, w in enumerate(merge_expr.args["whens"]):
113
113
  assert isinstance(w, exp.When), f"Expected When expression, got {w}"
114
114
 
115
115
  matched = w.args.get("matched")
@@ -173,7 +173,7 @@ def _counts(merge_expr: exp.Merge) -> exp.Expression:
173
173
  operations = {"inserted": [], "updated": [], "deleted": []}
174
174
 
175
175
  # Iterate through the WHEN clauses to categorize operations
176
- for w_idx, w in enumerate(merge_expr.expressions):
176
+ for w_idx, w in enumerate(merge_expr.args["whens"]):
177
177
  assert isinstance(w, exp.When), f"Expected When expression, got {w}"
178
178
 
179
179
  matched = w.args.get("matched")
@@ -191,12 +191,12 @@ def _counts(merge_expr: exp.Merge) -> exp.Expression:
191
191
  operations["inserted"].append(w_idx)
192
192
 
193
193
  count_statements = [
194
- f"""COUNT_IF(merge_op in ({','.join(map(str, indices))})) as \"number of rows {op}\""""
194
+ f"""COUNT_IF(merge_op in ({",".join(map(str, indices))})) as \"number of rows {op}\""""
195
195
  for op, indices in operations.items()
196
196
  if indices
197
197
  ]
198
198
  sql = f"""
199
- SELECT {', '.join(count_statements)}
199
+ SELECT {", ".join(count_statements)}
200
200
  FROM merge_candidates
201
201
  """
202
202
 
@@ -1,8 +1,8 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: fakesnow
3
- Version: 0.9.26
3
+ Version: 0.9.28
4
4
  Summary: Fake Snowflake Connector for Python. Run, mock and test Snowflake DB locally.
5
- License: Apache License
5
+ License: Apache License
6
6
  Version 2.0, January 2004
7
7
  http://www.apache.org/licenses/
8
8
 
@@ -213,7 +213,7 @@ License-File: LICENSE
213
213
  Requires-Dist: duckdb~=1.1.3
214
214
  Requires-Dist: pyarrow
215
215
  Requires-Dist: snowflake-connector-python
216
- Requires-Dist: sqlglot~=25.24.1
216
+ Requires-Dist: sqlglot~=26.3.9
217
217
  Provides-Extra: dev
218
218
  Requires-Dist: build~=1.0; extra == "dev"
219
219
  Requires-Dist: dirty-equals; extra == "dev"
@@ -223,9 +223,9 @@ Requires-Dist: pre-commit~=4.0; extra == "dev"
223
223
  Requires-Dist: pyarrow-stubs==10.0.1.9; extra == "dev"
224
224
  Requires-Dist: pytest~=8.0; extra == "dev"
225
225
  Requires-Dist: pytest-asyncio; extra == "dev"
226
- Requires-Dist: ruff~=0.7.2; extra == "dev"
227
- Requires-Dist: twine~=5.0; extra == "dev"
228
- Requires-Dist: snowflake-sqlalchemy~=1.6.1; extra == "dev"
226
+ Requires-Dist: ruff~=0.9.4; extra == "dev"
227
+ Requires-Dist: twine~=6.0; extra == "dev"
228
+ Requires-Dist: snowflake-sqlalchemy~=1.7.0; extra == "dev"
229
229
  Provides-Extra: notebook
230
230
  Requires-Dist: duckdb-engine; extra == "notebook"
231
231
  Requires-Dist: ipykernel; extra == "notebook"
@@ -0,0 +1,26 @@
1
+ fakesnow/__init__.py,sha256=qUfgucQYPdELrJaxczalhJgWAWQ6cfTCUAHx6nUqRaI,3528
2
+ fakesnow/__main__.py,sha256=GDrGyNTvBFuqn_UfDjKs7b3LPtU6gDv1KwosVDrukIM,76
3
+ fakesnow/arrow.py,sha256=MwatkdZX5AFADzXvxhBFmcRJVxbW4D39VoqLyhpTbl0,5057
4
+ fakesnow/checks.py,sha256=N8sXldhS3u1gG32qvZ4VFlsKgavRKrQrxLiQU8am1lw,2691
5
+ fakesnow/cli.py,sha256=9qfI-Ssr6mo8UmIlXkUAOz2z2YPBgDsrEVaZv9FjGFs,2201
6
+ fakesnow/conn.py,sha256=GJ7Y2dBW2jcOCIZ0gTYS0F8OmeoD7aW6lTWHpm02hbE,5459
7
+ fakesnow/cursor.py,sha256=uYf3zshauWXnKdUoVEE3YxMbc-SoVLHCUUkqwtMW8ns,20228
8
+ fakesnow/expr.py,sha256=CAxuYIUkwI339DQIBzvFF0F-m1tcVGKEPA5rDTzmH9A,892
9
+ fakesnow/fakes.py,sha256=JQTiUkkwPeQrJ8FDWhPFPK6pGwd_aR2oiOrNzCWznlM,187
10
+ fakesnow/fixtures.py,sha256=G-NkVeruSQAJ7fvSS2fR2oysUn0Yra1pohHlOvacKEk,455
11
+ fakesnow/info_schema.py,sha256=FyDcajHU0BK0Yx6JTIkWFoM0PPFm6f6Pf0B2NHLNR0M,6310
12
+ fakesnow/instance.py,sha256=3cJvPRuFy19dMKXbtBLl6imzO48pEw8uTYhZyFDuwhk,3133
13
+ fakesnow/macros.py,sha256=pX1YJDnQOkFJSHYUjQ6ErEkYIKvFI6Ncz_au0vv1csA,265
14
+ fakesnow/pandas_tools.py,sha256=wI203UQHC8JvDzxE_VjE1NeV4rThek2P-u52oTg2foo,3481
15
+ fakesnow/py.typed,sha256=B-DLSjYBi7pkKjwxCSdpVj2J02wgfJr-E7B1wOUyxYU,80
16
+ fakesnow/rowtype.py,sha256=QUp8EaXD5LT0Xv8BXk5ze4WseEn52xoJ6R05pJjs5mM,2729
17
+ fakesnow/server.py,sha256=lrXk8iXioSl-qWweXLH7l6aPrcV4Bym9rjB6x_C5Fg8,4222
18
+ fakesnow/transforms.py,sha256=pSv3pQlD1Y8tzXQ1rft2g5wYLcHrDRoy5EkFWgqkmec,55453
19
+ fakesnow/transforms_merge.py,sha256=Pg7_rwbAT_vr1U4ocBofUSyqaK8_e3qdIz_2SDm2S3s,8320
20
+ fakesnow/variables.py,sha256=WXyPnkeNwD08gy52yF66CVe2twiYC50tztNfgXV4q1k,3032
21
+ fakesnow-0.9.28.dist-info/LICENSE,sha256=kW-7NWIyaRMQiDpryfSmF2DObDZHGR1cJZ39s6B1Svg,11344
22
+ fakesnow-0.9.28.dist-info/METADATA,sha256=t0B6J7rG5uyS2430rgPWieQgACfyUU9dIL5cUdYllDg,18107
23
+ fakesnow-0.9.28.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
24
+ fakesnow-0.9.28.dist-info/entry_points.txt,sha256=2riAUgu928ZIHawtO8EsfrMEJhi-EH-z_Vq7Q44xKPM,47
25
+ fakesnow-0.9.28.dist-info/top_level.txt,sha256=500evXI1IFX9so82cizGIEMHAb_dJNPaZvd2H9dcKTA,24
26
+ fakesnow-0.9.28.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.45.0)
2
+ Generator: setuptools (75.8.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,26 +0,0 @@
1
- fakesnow/__init__.py,sha256=qUfgucQYPdELrJaxczalhJgWAWQ6cfTCUAHx6nUqRaI,3528
2
- fakesnow/__main__.py,sha256=GDrGyNTvBFuqn_UfDjKs7b3LPtU6gDv1KwosVDrukIM,76
3
- fakesnow/arrow.py,sha256=EGAYeuCnRuvmWBEGqw2YOcgQR4zcCsZBu85kSRl70dQ,4698
4
- fakesnow/checks.py,sha256=N8sXldhS3u1gG32qvZ4VFlsKgavRKrQrxLiQU8am1lw,2691
5
- fakesnow/cli.py,sha256=9qfI-Ssr6mo8UmIlXkUAOz2z2YPBgDsrEVaZv9FjGFs,2201
6
- fakesnow/conn.py,sha256=Gy_Z7BZRm5yMjV3x6hR4iegDQFdG9aJBjqWdc3iWYFU,5353
7
- fakesnow/cursor.py,sha256=8wWtRCxzrM1yiHmH2C-9CT0b98nTzr23ygeaEAkumRE,20086
8
- fakesnow/expr.py,sha256=CAxuYIUkwI339DQIBzvFF0F-m1tcVGKEPA5rDTzmH9A,892
9
- fakesnow/fakes.py,sha256=JQTiUkkwPeQrJ8FDWhPFPK6pGwd_aR2oiOrNzCWznlM,187
10
- fakesnow/fixtures.py,sha256=G-NkVeruSQAJ7fvSS2fR2oysUn0Yra1pohHlOvacKEk,455
11
- fakesnow/info_schema.py,sha256=nsDceFtjiSXrvkksKziVvqrefskaSyOmAspBwMAsaDg,6307
12
- fakesnow/instance.py,sha256=3cJvPRuFy19dMKXbtBLl6imzO48pEw8uTYhZyFDuwhk,3133
13
- fakesnow/macros.py,sha256=pX1YJDnQOkFJSHYUjQ6ErEkYIKvFI6Ncz_au0vv1csA,265
14
- fakesnow/pandas_tools.py,sha256=WjyjTV8QUCQQaCGboaEOvx2uo4BkknpWYjtLwkeCY6U,3468
15
- fakesnow/py.typed,sha256=B-DLSjYBi7pkKjwxCSdpVj2J02wgfJr-E7B1wOUyxYU,80
16
- fakesnow/server.py,sha256=SO5xKZ4rvySsuKDsoSPSCZcFuIX_K7d1XJYhRRJ-7Bk,4150
17
- fakesnow/transforms.py,sha256=VFLA5Fc1i4FuiVdvUuDrK-kA2caqiT8Gw9btMDPJhRA,55367
18
- fakesnow/transforms_merge.py,sha256=7rq-UPjfFNRrFsqR8xx3otwP6-k4eslLVLhfuqSXq1A,8314
19
- fakesnow/types.py,sha256=9Tt83Z7ctc9_v6SYyayXYz4MEI4RZo4zq_uqdj4g3Dk,2681
20
- fakesnow/variables.py,sha256=WXyPnkeNwD08gy52yF66CVe2twiYC50tztNfgXV4q1k,3032
21
- fakesnow-0.9.26.dist-info/LICENSE,sha256=kW-7NWIyaRMQiDpryfSmF2DObDZHGR1cJZ39s6B1Svg,11344
22
- fakesnow-0.9.26.dist-info/METADATA,sha256=92zIwzq7FP-BrfhUcKbdbqYs0eqN9TCKvT_NVdEKZTI,18075
23
- fakesnow-0.9.26.dist-info/WHEEL,sha256=bFJAMchF8aTQGUgMZzHJyDDMPTO3ToJ7x23SLJa1SVo,92
24
- fakesnow-0.9.26.dist-info/entry_points.txt,sha256=2riAUgu928ZIHawtO8EsfrMEJhi-EH-z_Vq7Q44xKPM,47
25
- fakesnow-0.9.26.dist-info/top_level.txt,sha256=500evXI1IFX9so82cizGIEMHAb_dJNPaZvd2H9dcKTA,24
26
- fakesnow-0.9.26.dist-info/RECORD,,