fakesnow 0.9.22__py3-none-any.whl → 0.9.23__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.
@@ -0,0 +1,77 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Sequence
4
+ from typing import TYPE_CHECKING, Any, Literal, Optional
5
+
6
+ import numpy as np
7
+
8
+ if TYPE_CHECKING:
9
+ import pandas as pd
10
+
11
+ from fakesnow.conn import FakeSnowflakeConnection
12
+
13
+ CopyResult = tuple[
14
+ str,
15
+ str,
16
+ int,
17
+ int,
18
+ int,
19
+ int,
20
+ Optional[str],
21
+ Optional[int],
22
+ Optional[int],
23
+ Optional[str],
24
+ ]
25
+
26
+ WritePandasResult = tuple[
27
+ bool,
28
+ int,
29
+ int,
30
+ Sequence[CopyResult],
31
+ ]
32
+
33
+
34
+ def sql_type(dtype: np.dtype) -> str:
35
+ if str(dtype) == "int64":
36
+ return "NUMBER"
37
+ elif str(dtype) == "object":
38
+ return "VARCHAR"
39
+ else:
40
+ raise NotImplementedError(f"sql_type {dtype=}")
41
+
42
+
43
+ def write_pandas(
44
+ conn: FakeSnowflakeConnection,
45
+ df: pd.DataFrame,
46
+ table_name: str,
47
+ database: str | None = None,
48
+ schema: str | None = None,
49
+ chunk_size: int | None = None,
50
+ compression: str = "gzip",
51
+ on_error: str = "abort_statement",
52
+ parallel: int = 4,
53
+ quote_identifiers: bool = True,
54
+ auto_create_table: bool = False,
55
+ create_temp_table: bool = False,
56
+ overwrite: bool = False,
57
+ table_type: Literal["", "temp", "temporary", "transient"] = "",
58
+ **kwargs: Any,
59
+ ) -> WritePandasResult:
60
+ name = table_name
61
+ if schema:
62
+ name = f"{schema}.{name}"
63
+ if database:
64
+ name = f"{database}.{name}"
65
+
66
+ if auto_create_table:
67
+ cols = [f"{c} {sql_type(t)}" for c, t in df.dtypes.to_dict().items()]
68
+
69
+ conn.cursor().execute(f"CREATE TABLE IF NOT EXISTS {name} ({','.join(cols)})")
70
+
71
+ count = conn._insert_df(df, name) # noqa: SLF001
72
+
73
+ # mocks https://docs.snowflake.com/en/sql-reference/sql/copy-into-table.html#output
74
+ mock_copy_results = [("fakesnow/file0.txt", "LOADED", count, count, 1, 0, None, None, None, None)]
75
+
76
+ # return success
77
+ return (True, len(mock_copy_results), count, mock_copy_results)
fakesnow/server.py CHANGED
@@ -12,7 +12,7 @@ from starlette.requests import Request
12
12
  from starlette.responses import JSONResponse
13
13
  from starlette.routing import Route
14
14
 
15
- from fakesnow.arrow import to_ipc
15
+ from fakesnow.arrow import to_ipc, to_rowtype, with_sf_metadata
16
16
  from fakesnow.fakes import FakeSnowflakeConnection
17
17
  from fakesnow.instance import FakeSnow
18
18
 
@@ -52,19 +52,13 @@ async def query_request(request: Request) -> JSONResponse:
52
52
  batch_bytes = to_ipc(cur._arrow_table) # noqa: SLF001
53
53
  rowset_b64 = b64encode(batch_bytes).decode("utf-8")
54
54
 
55
+ # TODO: avoid calling with_sf_metadata twice
56
+ rowtype = to_rowtype(with_sf_metadata(cur._arrow_table.schema)) # noqa: SLF001
57
+
55
58
  return JSONResponse(
56
59
  {
57
60
  "data": {
58
- "rowtype": [
59
- {
60
- "name": "'HELLO WORLD'",
61
- "nullable": False,
62
- "type": "text",
63
- "length": 11,
64
- "scale": None,
65
- "precision": None,
66
- }
67
- ],
61
+ "rowtype": rowtype,
68
62
  "rowsetBase64": rowset_b64,
69
63
  "total": 1,
70
64
  "queryResultFormat": "arrow",
fakesnow/types.py ADDED
@@ -0,0 +1,89 @@
1
+ import re
2
+ from typing import Optional, TypedDict
3
+
4
+ from snowflake.connector.cursor import ResultMetadata
5
+
6
+
7
+ class ColumnInfo(TypedDict):
8
+ name: str
9
+ database: str
10
+ schema: str
11
+ table: str
12
+ nullable: bool
13
+ type: str
14
+ byteLength: Optional[int]
15
+ length: Optional[int]
16
+ scale: Optional[int]
17
+ precision: Optional[int]
18
+ collation: Optional[str]
19
+
20
+
21
+ duckdb_to_sf_type = {
22
+ "BIGINT": "fixed",
23
+ "BLOB": "binary",
24
+ "BOOLEAN": "boolean",
25
+ "DATE": "date",
26
+ "DECIMAL": "fixed",
27
+ "DOUBLE": "real",
28
+ "INTEGER": "fixed",
29
+ "JSON": "variant",
30
+ "TIME": "time",
31
+ "TIMESTAMP WITH TIME ZONE": "timestamp_tz",
32
+ "TIMESTAMP_NS": "timestamp_ntz",
33
+ "TIMESTAMP": "timestamp_ntz",
34
+ "VARCHAR": "text",
35
+ }
36
+
37
+
38
+ def describe_as_rowtype(describe_results: list) -> list[ColumnInfo]:
39
+ """Convert duckdb column type to snowflake rowtype returned by the API."""
40
+
41
+ def as_column_info(column_name: str, column_type: str) -> ColumnInfo:
42
+ if not (sf_type := duckdb_to_sf_type.get("DECIMAL" if column_type.startswith("DECIMAL") else column_type)):
43
+ raise NotImplementedError(f"for column type {column_type}")
44
+
45
+ info: ColumnInfo = {
46
+ "name": column_name,
47
+ # TODO
48
+ "database": "",
49
+ "schema": "",
50
+ "table": "",
51
+ # TODO
52
+ "nullable": True,
53
+ "type": sf_type,
54
+ "byteLength": None,
55
+ "length": None,
56
+ "scale": None,
57
+ "precision": None,
58
+ "collation": None,
59
+ }
60
+
61
+ if column_type.startswith("DECIMAL"):
62
+ match = re.search(r"\((\d+),(\d+)\)", column_type)
63
+ info["precision"] = int(match[1]) if match else 38
64
+ info["scale"] = int(match[2]) if match else 0
65
+ elif sf_type == "fixed":
66
+ info["precision"] = 38
67
+ info["scale"] = 0
68
+ elif sf_type == "text":
69
+ # TODO: fetch actual varchar size
70
+ info["byteLength"] = 16777216
71
+ info["length"] = 16777216
72
+ elif sf_type.startswith("time"):
73
+ info["precision"] = 0
74
+ info["scale"] = 9
75
+ elif sf_type == "binary":
76
+ info["byteLength"] = 8388608
77
+ info["length"] = 8388608
78
+
79
+ return info
80
+
81
+ column_infos = [
82
+ as_column_info(column_name, column_type)
83
+ for (column_name, column_type, _null, _key, _default, _extra) in describe_results
84
+ ]
85
+ return column_infos
86
+
87
+
88
+ def describe_as_result_metadata(describe_results: list) -> list[ResultMetadata]:
89
+ return [ResultMetadata.from_column(c) for c in describe_as_rowtype(describe_results)] # pyright: ignore[reportArgumentType]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fakesnow
3
- Version: 0.9.22
3
+ Version: 0.9.23
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
@@ -216,6 +216,7 @@ Requires-Dist: snowflake-connector-python
216
216
  Requires-Dist: sqlglot~=25.9.0
217
217
  Provides-Extra: dev
218
218
  Requires-Dist: build~=1.0; extra == "dev"
219
+ Requires-Dist: dirty-equals; extra == "dev"
219
220
  Requires-Dist: pandas-stubs; extra == "dev"
220
221
  Requires-Dist: snowflake-connector-python[pandas,secure-local-storage]; extra == "dev"
221
222
  Requires-Dist: pre-commit~=3.4; extra == "dev"
@@ -1,21 +1,25 @@
1
1
  fakesnow/__init__.py,sha256=9tFJJKvowKNW3vfnlmza6hOLN1I52DwChgNc5Ew6CcA,3499
2
2
  fakesnow/__main__.py,sha256=GDrGyNTvBFuqn_UfDjKs7b3LPtU6gDv1KwosVDrukIM,76
3
- fakesnow/arrow.py,sha256=1ypCsf-r2Ven6CuSm-bTLoeq1G31kBD6JnaLvDxpwhU,1218
3
+ fakesnow/arrow.py,sha256=WLkr1nEiNxUcPdzadKSM33sRAiQJsN6LvuzTVIsi3D0,2766
4
4
  fakesnow/checks.py,sha256=-QMvdcrRbhN60rnzxLBJ0IkUBWyLR8gGGKKmCS0w9mA,2383
5
5
  fakesnow/cli.py,sha256=9qfI-Ssr6mo8UmIlXkUAOz2z2YPBgDsrEVaZv9FjGFs,2201
6
+ fakesnow/conn.py,sha256=yR9SMGSKkLvdPfi5fDwj9PQggOrPcKkdBBnQy2y_Bak,6921
7
+ fakesnow/cursor.py,sha256=lITuMMy_hA9_riC121Sv9bFDFbU9BP9NlOLdHlP0ahY,19371
6
8
  fakesnow/expr.py,sha256=CAxuYIUkwI339DQIBzvFF0F-m1tcVGKEPA5rDTzmH9A,892
7
- fakesnow/fakes.py,sha256=8roPAjUiVxSZDhxnpsP85sueSa3abZhyoDwM8awZZBY,31376
9
+ fakesnow/fakes.py,sha256=JQTiUkkwPeQrJ8FDWhPFPK6pGwd_aR2oiOrNzCWznlM,187
8
10
  fakesnow/fixtures.py,sha256=G-NkVeruSQAJ7fvSS2fR2oysUn0Yra1pohHlOvacKEk,455
9
11
  fakesnow/info_schema.py,sha256=DObVOrhzppAFHsdtj4YI9oRISn9SkJUG6ONjVleQQ_Y,6303
10
12
  fakesnow/instance.py,sha256=3cJvPRuFy19dMKXbtBLl6imzO48pEw8uTYhZyFDuwhk,3133
11
13
  fakesnow/macros.py,sha256=pX1YJDnQOkFJSHYUjQ6ErEkYIKvFI6Ncz_au0vv1csA,265
14
+ fakesnow/pandas_tools.py,sha256=ecL0kxIVU5o2--P3bRLWWVhxXqq6Km4trFr36txukMg,1897
12
15
  fakesnow/py.typed,sha256=B-DLSjYBi7pkKjwxCSdpVj2J02wgfJr-E7B1wOUyxYU,80
13
- fakesnow/server.py,sha256=cTuMzbYL3etm61wZJ7bcnWpcSNoCSTk31gAnl0Kxi20,3183
16
+ fakesnow/server.py,sha256=8dzaLUUXPzCMm6-ESn0CBws6XSwwOpnUuHQAZJ-4SwU,3011
14
17
  fakesnow/transforms.py,sha256=ellcY5OBc7mqgL9ChNolrqcCLWXF9RH21Jt88FcFl-I,54419
18
+ fakesnow/types.py,sha256=9Tt83Z7ctc9_v6SYyayXYz4MEI4RZo4zq_uqdj4g3Dk,2681
15
19
  fakesnow/variables.py,sha256=WXyPnkeNwD08gy52yF66CVe2twiYC50tztNfgXV4q1k,3032
16
- fakesnow-0.9.22.dist-info/LICENSE,sha256=kW-7NWIyaRMQiDpryfSmF2DObDZHGR1cJZ39s6B1Svg,11344
17
- fakesnow-0.9.22.dist-info/METADATA,sha256=Uu-JhX3mgGrgAP3jgLCP3b8YjRhWTh4qXwx-izVDZPM,18020
18
- fakesnow-0.9.22.dist-info/WHEEL,sha256=eOLhNAGa2EW3wWl_TU484h7q1UNgy0JXjjoqKoxAAQc,92
19
- fakesnow-0.9.22.dist-info/entry_points.txt,sha256=2riAUgu928ZIHawtO8EsfrMEJhi-EH-z_Vq7Q44xKPM,47
20
- fakesnow-0.9.22.dist-info/top_level.txt,sha256=500evXI1IFX9so82cizGIEMHAb_dJNPaZvd2H9dcKTA,24
21
- fakesnow-0.9.22.dist-info/RECORD,,
20
+ fakesnow-0.9.23.dist-info/LICENSE,sha256=kW-7NWIyaRMQiDpryfSmF2DObDZHGR1cJZ39s6B1Svg,11344
21
+ fakesnow-0.9.23.dist-info/METADATA,sha256=90vwAf40lg9ipw2urbu9nMq7cEARbtENE0aKd7qSpjo,18064
22
+ fakesnow-0.9.23.dist-info/WHEEL,sha256=eOLhNAGa2EW3wWl_TU484h7q1UNgy0JXjjoqKoxAAQc,92
23
+ fakesnow-0.9.23.dist-info/entry_points.txt,sha256=2riAUgu928ZIHawtO8EsfrMEJhi-EH-z_Vq7Q44xKPM,47
24
+ fakesnow-0.9.23.dist-info/top_level.txt,sha256=500evXI1IFX9so82cizGIEMHAb_dJNPaZvd2H9dcKTA,24
25
+ fakesnow-0.9.23.dist-info/RECORD,,