fakesnow 0.9.32__py3-none-any.whl → 0.9.34__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/converter.py ADDED
@@ -0,0 +1,60 @@
1
+ from __future__ import annotations
2
+
3
+ import binascii
4
+ import datetime
5
+ from datetime import date, time, timezone
6
+
7
+ # convert bindings provided as strings to the server into python types
8
+
9
+
10
+ def from_binding(binding: dict[str, str]) -> int | bytes | bool | date | time | datetime.datetime | str:
11
+ typ = binding["type"]
12
+ value = binding["value"]
13
+ if typ == "FIXED":
14
+ return int(value)
15
+ elif typ == "BINARY":
16
+ return from_binary(value)
17
+ # TODO: not strictly needed
18
+ elif typ == "BOOLEAN":
19
+ return value.lower() == "true"
20
+ elif typ == "DATE":
21
+ return from_date(value)
22
+ elif typ == "TIME":
23
+ return from_time(value)
24
+ elif typ == "TIMESTAMP_NTZ":
25
+ return from_datetime(value)
26
+ else:
27
+ # For other types, return str
28
+ return value
29
+
30
+
31
+ def from_binary(s: str) -> bytes:
32
+ return binascii.unhexlify(s)
33
+
34
+
35
+ def from_boolean(s: str) -> bool:
36
+ return s.lower() == "true"
37
+
38
+
39
+ def from_date(s: str) -> date:
40
+ milliseconds = int(s)
41
+ seconds = milliseconds / 1000
42
+ return datetime.datetime.fromtimestamp(seconds, timezone.utc).date()
43
+
44
+
45
+ def from_time(s: str) -> time:
46
+ nanoseconds = int(s)
47
+ microseconds = nanoseconds / 1000
48
+ return (
49
+ datetime.datetime.fromtimestamp(microseconds / 1_000_000, timezone.utc)
50
+ .replace(microsecond=int(microseconds % 1_000_000))
51
+ .time()
52
+ )
53
+
54
+
55
+ def from_datetime(s: str) -> datetime.datetime:
56
+ nanoseconds = int(s)
57
+ microseconds = nanoseconds / 1000
58
+ return datetime.datetime.fromtimestamp(microseconds / 1_000_000, timezone.utc).replace(
59
+ microsecond=int(microseconds % 1_000_000)
60
+ )
fakesnow/cursor.py CHANGED
@@ -139,7 +139,12 @@ class FakeSnowflakeCursor:
139
139
  print(f"{command};{params=}" if params else f"{command};", file=sys.stderr)
140
140
 
141
141
  command = self._inline_variables(command)
142
- command, params = self._rewrite_with_params(command, params)
142
+ if kwargs.get("binding_params"):
143
+ # params have come via the server
144
+ params = kwargs["binding_params"]
145
+ else:
146
+ command, params = self._rewrite_with_params(command, params)
147
+
143
148
  if self._conn.nop_regexes and any(re.match(p, command, re.IGNORECASE) for p in self._conn.nop_regexes):
144
149
  transformed = transforms.SUCCESS_NOP
145
150
  self._execute(transformed, params)
@@ -384,7 +389,7 @@ class FakeSnowflakeCursor:
384
389
  self._sfqid = str(uuid.uuid4())
385
390
 
386
391
  self._last_sql = result_sql or sql
387
- self._last_params = params
392
+ self._last_params = None if result_sql else params
388
393
 
389
394
  def _log_sql(self, sql: str, params: Sequence[Any] | dict[Any, Any] | None = None) -> None:
390
395
  if (fs_debug := os.environ.get("FAKESNOW_DEBUG")) and fs_debug != "snowflake":
fakesnow/server.py CHANGED
@@ -16,6 +16,7 @@ from starlette.responses import JSONResponse
16
16
  from starlette.routing import Route
17
17
 
18
18
  from fakesnow.arrow import to_ipc, to_sf
19
+ from fakesnow.converter import from_binding
19
20
  from fakesnow.fakes import FakeSnowflakeConnection
20
21
  from fakesnow.instance import FakeSnow
21
22
  from fakesnow.rowtype import describe_as_rowtype
@@ -77,9 +78,16 @@ async def query_request(request: Request) -> JSONResponse:
77
78
 
78
79
  sql_text = body_json["sqlText"]
79
80
 
81
+ params = None
82
+
83
+ if bindings := body_json.get("bindings"):
84
+ # Convert parameters like {'1': {'type': 'FIXED', 'value': '1'}, ...} to tuple (1, ...)
85
+ params = tuple(from_binding(bindings[str(pos)]) for pos in range(1, len(bindings) + 1))
86
+ logger.debug(f"Bindings: {params}")
87
+
80
88
  try:
81
89
  # only a single sql statement is sent at a time by the python snowflake connector
82
- cur = await run_in_threadpool(conn.cursor().execute, sql_text)
90
+ cur = await run_in_threadpool(conn.cursor().execute, sql_text, binding_params=params)
83
91
  rowtype = describe_as_rowtype(cur._describe_last_sql()) # noqa: SLF001
84
92
 
85
93
  except snowflake.connector.errors.ProgrammingError as e:
@@ -59,9 +59,13 @@ def alter_table_strip_cluster_by(expression: exp.Expression) -> exp.Expression:
59
59
 
60
60
  def array_size(expression: exp.Expression) -> exp.Expression:
61
61
  if isinstance(expression, exp.ArraySize):
62
- # case is used to convert 0 to null, because null is returned by duckdb when no case matches
62
+ # return null if not json array
63
63
  jal = exp.Anonymous(this="json_array_length", expressions=[expression.this])
64
- return exp.Case(ifs=[exp.If(this=jal, true=jal)])
64
+ is_json_array = exp.EQ(
65
+ this=exp.Anonymous(this="json_type", expressions=[expression.this]),
66
+ expression=exp.Literal(this="ARRAY", is_string=True),
67
+ )
68
+ return exp.Case(ifs=[exp.If(this=is_json_array, true=jal)])
65
69
 
66
70
  return expression
67
71
 
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: fakesnow
3
- Version: 0.9.32
3
+ Version: 0.9.34
4
4
  Summary: Fake Snowflake Connector for Python. Run, mock and test Snowflake DB locally.
5
5
  License: Apache License
6
6
  Version 2.0, January 2004
@@ -213,7 +213,7 @@ License-File: LICENSE
213
213
  Requires-Dist: duckdb~=1.2.0
214
214
  Requires-Dist: pyarrow
215
215
  Requires-Dist: snowflake-connector-python
216
- Requires-Dist: sqlglot~=26.10.1
216
+ Requires-Dist: sqlglot~=26.12.1
217
217
  Provides-Extra: dev
218
218
  Requires-Dist: build~=1.0; extra == "dev"
219
219
  Requires-Dist: dirty-equals; extra == "dev"
@@ -233,6 +233,7 @@ Requires-Dist: jupysql; extra == "notebook"
233
233
  Provides-Extra: server
234
234
  Requires-Dist: starlette; extra == "server"
235
235
  Requires-Dist: uvicorn; extra == "server"
236
+ Dynamic: license-file
236
237
 
237
238
  # fakesnow ❄️
238
239
 
@@ -4,7 +4,8 @@ fakesnow/arrow.py,sha256=XjTpFyLrD9jULWOtPgpr0RyNMmO6a5yi82y6ivi2CCI,4884
4
4
  fakesnow/checks.py,sha256=be-xo0oMoAUVhlMDCu1_Rkoh_L8p_p8qo9P6reJSHIQ,2874
5
5
  fakesnow/cli.py,sha256=9qfI-Ssr6mo8UmIlXkUAOz2z2YPBgDsrEVaZv9FjGFs,2201
6
6
  fakesnow/conn.py,sha256=2WClMmUgfQkQA2hFQjfMP3R-85TbTbZh_8Y1tCdcerA,6053
7
- fakesnow/cursor.py,sha256=Nvr8TQmmTFs6i0sJwfgCocrEF9td0D0SdDG41quIudI,21621
7
+ fakesnow/converter.py,sha256=7YlASaMomzchMZoorTH3KtVmgBakaHrF5fAl5VP747I,1635
8
+ fakesnow/cursor.py,sha256=mK4nC1iucON1MohicTugJqUOfRsx5c8ToUJgnCfUSbs,21813
8
9
  fakesnow/expr.py,sha256=CAxuYIUkwI339DQIBzvFF0F-m1tcVGKEPA5rDTzmH9A,892
9
10
  fakesnow/fakes.py,sha256=JQTiUkkwPeQrJ8FDWhPFPK6pGwd_aR2oiOrNzCWznlM,187
10
11
  fakesnow/fixtures.py,sha256=G-NkVeruSQAJ7fvSS2fR2oysUn0Yra1pohHlOvacKEk,455
@@ -14,15 +15,15 @@ fakesnow/macros.py,sha256=pX1YJDnQOkFJSHYUjQ6ErEkYIKvFI6Ncz_au0vv1csA,265
14
15
  fakesnow/pandas_tools.py,sha256=wI203UQHC8JvDzxE_VjE1NeV4rThek2P-u52oTg2foo,3481
15
16
  fakesnow/py.typed,sha256=B-DLSjYBi7pkKjwxCSdpVj2J02wgfJr-E7B1wOUyxYU,80
16
17
  fakesnow/rowtype.py,sha256=QUp8EaXD5LT0Xv8BXk5ze4WseEn52xoJ6R05pJjs5mM,2729
17
- fakesnow/server.py,sha256=oLnWJgcxwhPw4sNebJF3B9uxk28A-a-AI8Pyl_lz2_E,5986
18
+ fakesnow/server.py,sha256=4DgZUTd-G_usjSqy6NdUqd2fWUw2a-wHSSeJt3cdneA,6375
18
19
  fakesnow/variables.py,sha256=WXyPnkeNwD08gy52yF66CVe2twiYC50tztNfgXV4q1k,3032
19
- fakesnow/transforms/__init__.py,sha256=gD8wPo9QprwHkTOEMQ0-IsXSNfUruU0kBJPjO0po-J4,49377
20
+ fakesnow/transforms/__init__.py,sha256=xFrpw28DaHvMt6LGaRMsPqTo8PWogg10JgEu3oa6jdA,49515
20
21
  fakesnow/transforms/merge.py,sha256=Pg7_rwbAT_vr1U4ocBofUSyqaK8_e3qdIz_2SDm2S3s,8320
21
22
  fakesnow/transforms/show.py,sha256=2qfK3Fi0RLylqTnkwSVgv5JIorXYb1y0fnf5oErRZ2o,16839
23
+ fakesnow-0.9.34.dist-info/licenses/LICENSE,sha256=kW-7NWIyaRMQiDpryfSmF2DObDZHGR1cJZ39s6B1Svg,11344
22
24
  tools/decode.py,sha256=kC5kUvLQxdCkMRsnH6BqCajlKxKeN77w6rwCKsY6gqU,1781
23
- fakesnow-0.9.32.dist-info/LICENSE,sha256=kW-7NWIyaRMQiDpryfSmF2DObDZHGR1cJZ39s6B1Svg,11344
24
- fakesnow-0.9.32.dist-info/METADATA,sha256=HTGvul9rrgbrcSK79gmq4KndGAga0be6WzaKSUc_3c4,18106
25
- fakesnow-0.9.32.dist-info/WHEEL,sha256=beeZ86-EfXScwlR_HKu4SllMC9wUEj_8Z_4FJ3egI2w,91
26
- fakesnow-0.9.32.dist-info/entry_points.txt,sha256=2riAUgu928ZIHawtO8EsfrMEJhi-EH-z_Vq7Q44xKPM,47
27
- fakesnow-0.9.32.dist-info/top_level.txt,sha256=Yos7YveA3f03xVYuURqnBsfMV2DePXfu_yGcsj3pPzI,30
28
- fakesnow-0.9.32.dist-info/RECORD,,
25
+ fakesnow-0.9.34.dist-info/METADATA,sha256=Hqkb8CT1-QTNzQqRMVhEHWGwx3gJORv9YHy7wGSoBgQ,18128
26
+ fakesnow-0.9.34.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
27
+ fakesnow-0.9.34.dist-info/entry_points.txt,sha256=2riAUgu928ZIHawtO8EsfrMEJhi-EH-z_Vq7Q44xKPM,47
28
+ fakesnow-0.9.34.dist-info/top_level.txt,sha256=Yos7YveA3f03xVYuURqnBsfMV2DePXfu_yGcsj3pPzI,30
29
+ fakesnow-0.9.34.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (76.1.0)
2
+ Generator: setuptools (78.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5