fakesnow 0.9.4__py3-none-any.whl → 0.9.6__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 +4 -3
- fakesnow/fakes.py +27 -15
- fakesnow/info_schema.py +38 -20
- fakesnow/transforms.py +141 -41
- {fakesnow-0.9.4.dist-info → fakesnow-0.9.6.dist-info}/METADATA +10 -8
- fakesnow-0.9.6.dist-info/RECORD +18 -0
- {fakesnow-0.9.4.dist-info → fakesnow-0.9.6.dist-info}/WHEEL +1 -1
- fakesnow-0.9.4.dist-info/RECORD +0 -18
- {fakesnow-0.9.4.dist-info → fakesnow-0.9.6.dist-info}/LICENSE +0 -0
- {fakesnow-0.9.4.dist-info → fakesnow-0.9.6.dist-info}/entry_points.txt +0 -0
- {fakesnow-0.9.4.dist-info → fakesnow-0.9.6.dist-info}/top_level.txt +0 -0
fakesnow/__init__.py
CHANGED
fakesnow/fakes.py
CHANGED
@@ -37,6 +37,7 @@ SQL_SUCCESS = "SELECT 'Statement executed successfully.' as 'status'"
|
|
37
37
|
SQL_CREATED_DATABASE = Template("SELECT 'Database ${name} successfully created.' as 'status'")
|
38
38
|
SQL_CREATED_SCHEMA = Template("SELECT 'Schema ${name} successfully created.' as 'status'")
|
39
39
|
SQL_CREATED_TABLE = Template("SELECT 'Table ${name} successfully created.' as 'status'")
|
40
|
+
SQL_CREATED_VIEW = Template("SELECT 'View ${name} successfully created.' as 'status'")
|
40
41
|
SQL_DROPPED = Template("SELECT '${name} successfully dropped.' as 'status'")
|
41
42
|
SQL_INSERTED_ROWS = Template("SELECT ${count} as 'number of rows inserted'")
|
42
43
|
SQL_UPDATED_ROWS = Template("SELECT ${count} as 'number of rows updated', 0 as 'number of multi-joined rows updated'")
|
@@ -177,6 +178,7 @@ class FakeSnowflakeCursor:
|
|
177
178
|
.transform(transforms.indices_to_json_extract)
|
178
179
|
.transform(transforms.json_extract_cast_as_varchar)
|
179
180
|
.transform(transforms.json_extract_cased_as_varchar)
|
181
|
+
.transform(transforms.json_extract_precedence)
|
180
182
|
.transform(transforms.flatten)
|
181
183
|
.transform(transforms.regex_replace)
|
182
184
|
.transform(transforms.regex_substr)
|
@@ -196,6 +198,10 @@ class FakeSnowflakeCursor:
|
|
196
198
|
.transform(transforms.identifier)
|
197
199
|
.transform(lambda e: transforms.show_schemas(e, self._conn.database))
|
198
200
|
.transform(lambda e: transforms.show_objects_tables(e, self._conn.database))
|
201
|
+
# TODO collapse into a single show_keys function
|
202
|
+
.transform(lambda e: transforms.show_keys(e, self._conn.database, kind="PRIMARY"))
|
203
|
+
.transform(lambda e: transforms.show_keys(e, self._conn.database, kind="UNIQUE"))
|
204
|
+
.transform(lambda e: transforms.show_keys(e, self._conn.database, kind="FOREIGN"))
|
199
205
|
.transform(transforms.show_users)
|
200
206
|
.transform(transforms.create_user)
|
201
207
|
)
|
@@ -230,12 +236,18 @@ class FakeSnowflakeCursor:
|
|
230
236
|
raise snowflake.connector.errors.DatabaseError(msg=e.args[0], errno=250002, sqlstate="08003") from None
|
231
237
|
|
232
238
|
affected_count = None
|
233
|
-
|
234
|
-
|
239
|
+
|
240
|
+
if (maybe_ident := expression.find(exp.Identifier, bfs=False)) and isinstance(maybe_ident.this, str):
|
241
|
+
ident = maybe_ident.this if maybe_ident.quoted else maybe_ident.this.upper()
|
242
|
+
else:
|
243
|
+
ident = None
|
244
|
+
|
245
|
+
if cmd == "USE DATABASE" and ident:
|
246
|
+
self._conn.database = ident
|
235
247
|
self._conn.database_set = True
|
236
248
|
|
237
|
-
elif cmd == "USE SCHEMA" and
|
238
|
-
self._conn.schema = ident
|
249
|
+
elif cmd == "USE SCHEMA" and ident:
|
250
|
+
self._conn.schema = ident
|
239
251
|
self._conn.schema_set = True
|
240
252
|
|
241
253
|
elif create_db_name := transformed.args.get("create_db_name"):
|
@@ -243,24 +255,24 @@ class FakeSnowflakeCursor:
|
|
243
255
|
self._duck_conn.execute(info_schema.creation_sql(create_db_name))
|
244
256
|
result_sql = SQL_CREATED_DATABASE.substitute(name=create_db_name)
|
245
257
|
|
246
|
-
elif cmd == "CREATE SCHEMA" and
|
247
|
-
|
248
|
-
|
258
|
+
elif cmd == "CREATE SCHEMA" and ident:
|
259
|
+
result_sql = SQL_CREATED_SCHEMA.substitute(name=ident)
|
260
|
+
|
261
|
+
elif cmd == "CREATE TABLE" and ident:
|
262
|
+
result_sql = SQL_CREATED_TABLE.substitute(name=ident)
|
249
263
|
|
250
|
-
elif cmd == "CREATE
|
251
|
-
|
252
|
-
result_sql = SQL_CREATED_TABLE.substitute(name=name)
|
264
|
+
elif cmd == "CREATE VIEW" and ident:
|
265
|
+
result_sql = SQL_CREATED_VIEW.substitute(name=ident)
|
253
266
|
|
254
|
-
elif cmd.startswith("DROP") and
|
255
|
-
|
256
|
-
result_sql = SQL_DROPPED.substitute(name=name)
|
267
|
+
elif cmd.startswith("DROP") and ident:
|
268
|
+
result_sql = SQL_DROPPED.substitute(name=ident)
|
257
269
|
|
258
270
|
# if dropping the current database/schema then reset conn metadata
|
259
|
-
if cmd == "DROP DATABASE" and
|
271
|
+
if cmd == "DROP DATABASE" and ident == self._conn.database:
|
260
272
|
self._conn.database = None
|
261
273
|
self._conn.schema = None
|
262
274
|
|
263
|
-
elif cmd == "DROP SCHEMA" and
|
275
|
+
elif cmd == "DROP SCHEMA" and ident == self._conn.schema:
|
264
276
|
self._conn.schema = None
|
265
277
|
|
266
278
|
elif cmd == "INSERT":
|
fakesnow/info_schema.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
"""Info schema extension tables/views used for storing snowflake metadata not captured by duckdb."""
|
2
|
+
|
2
3
|
from __future__ import annotations
|
3
4
|
|
4
5
|
from string import Template
|
@@ -35,28 +36,45 @@ create table if not exists ${catalog}.information_schema._fs_columns_ext (
|
|
35
36
|
SQL_CREATE_INFORMATION_SCHEMA_COLUMNS_VIEW = Template(
|
36
37
|
"""
|
37
38
|
create view if not exists ${catalog}.information_schema._fs_columns_snowflake AS
|
38
|
-
select
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
39
|
+
select
|
40
|
+
columns.table_catalog AS table_catalog,
|
41
|
+
columns.table_schema AS table_schema,
|
42
|
+
columns.table_name AS table_name,
|
43
|
+
columns.column_name AS column_name,
|
44
|
+
columns.ordinal_position AS ordinal_position,
|
45
|
+
columns.column_default AS column_default,
|
46
|
+
columns.is_nullable AS is_nullable,
|
47
|
+
case when starts_with(columns.data_type, 'DECIMAL') or columns.data_type='BIGINT' then 'NUMBER'
|
48
|
+
when columns.data_type='VARCHAR' then 'TEXT'
|
49
|
+
when columns.data_type='DOUBLE' then 'FLOAT'
|
50
|
+
when columns.data_type='BLOB' then 'BINARY'
|
51
|
+
when columns.data_type='TIMESTAMP' then 'TIMESTAMP_NTZ'
|
52
|
+
when columns.data_type='TIMESTAMP WITH TIME ZONE' then 'TIMESTAMP_TZ'
|
53
|
+
when columns.data_type='JSON' then 'VARIANT'
|
54
|
+
else columns.data_type end as data_type,
|
47
55
|
ext_character_maximum_length as character_maximum_length, ext_character_octet_length as character_octet_length,
|
48
|
-
case when data_type='BIGINT' then 38
|
49
|
-
when data_type='DOUBLE' then NULL
|
50
|
-
else numeric_precision end as numeric_precision,
|
51
|
-
case when data_type='BIGINT' then 10
|
52
|
-
when data_type='DOUBLE' then NULL
|
53
|
-
else numeric_precision_radix end as numeric_precision_radix,
|
54
|
-
case when data_type='DOUBLE' then NULL else numeric_scale end as numeric_scale,
|
55
|
-
collation_name, is_identity, identity_generation, identity_cycle
|
56
|
-
|
56
|
+
case when columns.data_type='BIGINT' then 38
|
57
|
+
when columns.data_type='DOUBLE' then NULL
|
58
|
+
else columns.numeric_precision end as numeric_precision,
|
59
|
+
case when columns.data_type='BIGINT' then 10
|
60
|
+
when columns.data_type='DOUBLE' then NULL
|
61
|
+
else columns.numeric_precision_radix end as numeric_precision_radix,
|
62
|
+
case when columns.data_type='DOUBLE' then NULL else columns.numeric_scale end as numeric_scale,
|
63
|
+
collation_name, is_identity, identity_generation, identity_cycle,
|
64
|
+
ddb_columns.comment as comment,
|
65
|
+
null as identity_start,
|
66
|
+
null as identity_increment,
|
67
|
+
from ${catalog}.information_schema.columns columns
|
57
68
|
left join ${catalog}.information_schema._fs_columns_ext ext
|
58
|
-
on ext_table_catalog = table_catalog
|
59
|
-
|
69
|
+
on ext_table_catalog = columns.table_catalog
|
70
|
+
AND ext_table_schema = columns.table_schema
|
71
|
+
AND ext_table_name = columns.table_name
|
72
|
+
AND ext_column_name = columns.column_name
|
73
|
+
LEFT JOIN duckdb_columns ddb_columns
|
74
|
+
ON ddb_columns.database_name = columns.table_catalog
|
75
|
+
AND ddb_columns.schema_name = columns.table_schema
|
76
|
+
AND ddb_columns.table_name = columns.table_name
|
77
|
+
AND ddb_columns.column_name = columns.column_name
|
60
78
|
"""
|
61
79
|
)
|
62
80
|
|
fakesnow/transforms.py
CHANGED
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|
2
2
|
|
3
3
|
from pathlib import Path
|
4
4
|
from string import Template
|
5
|
-
from typing import cast
|
5
|
+
from typing import Literal, cast
|
6
6
|
|
7
7
|
import sqlglot
|
8
8
|
from sqlglot import exp
|
@@ -38,7 +38,8 @@ def create_database(expression: exp.Expression, db_path: Path | None = None) ->
|
|
38
38
|
"""
|
39
39
|
|
40
40
|
if isinstance(expression, exp.Create) and str(expression.args.get("kind")).upper() == "DATABASE":
|
41
|
-
|
41
|
+
ident = expression.find(exp.Identifier)
|
42
|
+
assert ident, f"No identifier in {expression.sql}"
|
42
43
|
db_name = ident.this
|
43
44
|
db_file = f"{db_path/db_name}.db" if db_path else ":memory:"
|
44
45
|
|
@@ -449,6 +450,16 @@ def json_extract_cast_as_varchar(expression: exp.Expression) -> exp.Expression:
|
|
449
450
|
return expression
|
450
451
|
|
451
452
|
|
453
|
+
def json_extract_precedence(expression: exp.Expression) -> exp.Expression:
|
454
|
+
"""Associate json extract operands to avoid duckdb operators of higher precedence transforming the expression.
|
455
|
+
|
456
|
+
See https://github.com/tekumara/fakesnow/issues/53
|
457
|
+
"""
|
458
|
+
if isinstance(expression, exp.JSONExtract):
|
459
|
+
return exp.Paren(this=expression)
|
460
|
+
return expression
|
461
|
+
|
462
|
+
|
452
463
|
def random(expression: exp.Expression) -> exp.Expression:
|
453
464
|
"""Convert random() and random(seed).
|
454
465
|
|
@@ -671,55 +682,61 @@ def set_schema(expression: exp.Expression, current_database: str | None) -> exp.
|
|
671
682
|
return expression
|
672
683
|
|
673
684
|
|
674
|
-
SQL_SHOW_OBJECTS = """
|
675
|
-
select
|
676
|
-
to_timestamp(0)::timestamptz as 'created_on',
|
677
|
-
table_name as 'name',
|
678
|
-
case when table_type='BASE TABLE' then 'TABLE' else table_type end as 'kind',
|
679
|
-
table_catalog as 'database_name',
|
680
|
-
table_schema as 'schema_name'
|
681
|
-
from information_schema.tables
|
682
|
-
"""
|
683
|
-
|
684
|
-
|
685
685
|
def show_objects_tables(expression: exp.Expression, current_database: str | None = None) -> exp.Expression:
|
686
686
|
"""Transform SHOW OBJECTS/TABLES to a query against the information_schema.tables table.
|
687
687
|
|
688
688
|
See https://docs.snowflake.com/en/sql-reference/sql/show-objects
|
689
689
|
https://docs.snowflake.com/en/sql-reference/sql/show-tables
|
690
690
|
"""
|
691
|
-
if (
|
691
|
+
if not (
|
692
692
|
isinstance(expression, exp.Show)
|
693
693
|
and isinstance(expression.this, str)
|
694
|
-
and expression.this.upper()
|
694
|
+
and (show := expression.this.upper())
|
695
|
+
and show in {"OBJECTS", "TABLES"}
|
695
696
|
):
|
696
|
-
|
697
|
-
table = expression.find(exp.Table)
|
698
|
-
|
699
|
-
if scope_kind == "DATABASE":
|
700
|
-
catalog = (table and table.name) or current_database
|
701
|
-
schema = None
|
702
|
-
elif scope_kind == "SCHEMA" and table:
|
703
|
-
catalog = table.db or current_database
|
704
|
-
schema = table.name
|
705
|
-
else:
|
706
|
-
# all objects / tables
|
707
|
-
catalog = None
|
708
|
-
schema = None
|
709
|
-
|
710
|
-
tables_only = "table_type = 'BASE TABLE' and " if expression.this.upper() == "TABLES" else ""
|
711
|
-
exclude_fakesnow_tables = "not (table_schema == 'information_schema' and table_name like '_fs_%%')"
|
712
|
-
# without a database will show everything in the "account"
|
713
|
-
table_catalog = f" and table_catalog = '{catalog}'" if catalog else ""
|
714
|
-
schema = f" and table_schema = '{schema}'" if schema else ""
|
715
|
-
limit = limit.sql() if (limit := expression.args.get("limit")) and isinstance(limit, exp.Expression) else ""
|
716
|
-
|
717
|
-
return sqlglot.parse_one(
|
718
|
-
f"{SQL_SHOW_OBJECTS} where {tables_only}{exclude_fakesnow_tables}{table_catalog}{schema}{limit}",
|
719
|
-
read="duckdb",
|
720
|
-
)
|
697
|
+
return expression
|
721
698
|
|
722
|
-
|
699
|
+
scope_kind = expression.args.get("scope_kind")
|
700
|
+
table = expression.find(exp.Table)
|
701
|
+
|
702
|
+
if scope_kind == "DATABASE":
|
703
|
+
catalog = (table and table.name) or current_database
|
704
|
+
schema = None
|
705
|
+
elif scope_kind == "SCHEMA" and table:
|
706
|
+
catalog = table.db or current_database
|
707
|
+
schema = table.name
|
708
|
+
else:
|
709
|
+
# all objects / tables
|
710
|
+
catalog = None
|
711
|
+
schema = None
|
712
|
+
|
713
|
+
tables_only = "table_type = 'BASE TABLE' and " if show == "TABLES" else ""
|
714
|
+
exclude_fakesnow_tables = "not (table_schema == 'information_schema' and table_name like '_fs_%%')"
|
715
|
+
# without a database will show everything in the "account"
|
716
|
+
table_catalog = f" and table_catalog = '{catalog}'" if catalog else ""
|
717
|
+
schema = f" and table_schema = '{schema}'" if schema else ""
|
718
|
+
limit = limit.sql() if (limit := expression.args.get("limit")) and isinstance(limit, exp.Expression) else ""
|
719
|
+
|
720
|
+
columns = [
|
721
|
+
"to_timestamp(0)::timestamptz as 'created_on'",
|
722
|
+
"table_name as 'name'",
|
723
|
+
"case when table_type='BASE TABLE' then 'TABLE' else table_type end as 'kind'",
|
724
|
+
"table_catalog as 'database_name'",
|
725
|
+
"table_schema as 'schema_name'",
|
726
|
+
]
|
727
|
+
|
728
|
+
terse = expression.args["terse"]
|
729
|
+
if not terse:
|
730
|
+
columns.append('null as "comment"')
|
731
|
+
|
732
|
+
columns_str = ", ".join(columns)
|
733
|
+
|
734
|
+
query = (
|
735
|
+
f"SELECT {columns_str} from information_schema.tables "
|
736
|
+
f"where {tables_only}{exclude_fakesnow_tables}{table_catalog}{schema}{limit}"
|
737
|
+
)
|
738
|
+
|
739
|
+
return sqlglot.parse_one(query, read="duckdb")
|
723
740
|
|
724
741
|
|
725
742
|
SQL_SHOW_SCHEMAS = """
|
@@ -987,3 +1004,86 @@ def create_user(expression: exp.Expression) -> exp.Expression:
|
|
987
1004
|
return sqlglot.parse_one(f"INSERT INTO {USERS_TABLE_FQ_NAME} (name) VALUES ('{name}')", read="duckdb")
|
988
1005
|
|
989
1006
|
return expression
|
1007
|
+
|
1008
|
+
|
1009
|
+
def show_keys(
|
1010
|
+
expression: exp.Expression,
|
1011
|
+
current_database: str | None = None,
|
1012
|
+
*,
|
1013
|
+
kind: Literal["PRIMARY", "UNIQUE", "FOREIGN"],
|
1014
|
+
) -> exp.Expression:
|
1015
|
+
"""Transform SHOW <kind> KEYS to a query against the duckdb_constraints meta-table.
|
1016
|
+
|
1017
|
+
https://docs.snowflake.com/en/sql-reference/sql/show-primary-keys
|
1018
|
+
"""
|
1019
|
+
snowflake_kind = kind
|
1020
|
+
if kind == "FOREIGN":
|
1021
|
+
snowflake_kind = "IMPORTED"
|
1022
|
+
|
1023
|
+
if (
|
1024
|
+
isinstance(expression, exp.Show)
|
1025
|
+
and isinstance(expression.this, str)
|
1026
|
+
and expression.this.upper() == f"{snowflake_kind} KEYS"
|
1027
|
+
):
|
1028
|
+
if kind == "FOREIGN":
|
1029
|
+
statement = f"""
|
1030
|
+
SELECT
|
1031
|
+
to_timestamp(0)::timestamptz as created_on,
|
1032
|
+
|
1033
|
+
'' as pk_database_name,
|
1034
|
+
'' as pk_schema_name,
|
1035
|
+
'' as pk_table_name,
|
1036
|
+
'' as pk_column_name,
|
1037
|
+
unnest(constraint_column_names) as pk_column_name,
|
1038
|
+
|
1039
|
+
database_name as fk_database_name,
|
1040
|
+
schema_name as fk_schema_name,
|
1041
|
+
table_name as fk_table_name,
|
1042
|
+
unnest(constraint_column_names) as fk_column_name,
|
1043
|
+
1 as key_sequence,
|
1044
|
+
'NO ACTION' as update_rule,
|
1045
|
+
'NO ACTION' as delete_rule,
|
1046
|
+
LOWER(CONCAT(database_name, '_', schema_name, '_', table_name, '_pkey')) AS fk_name,
|
1047
|
+
LOWER(CONCAT(database_name, '_', schema_name, '_', table_name, '_pkey')) AS pk_name,
|
1048
|
+
'NOT DEFERRABLE' as deferrability,
|
1049
|
+
'false' as rely,
|
1050
|
+
null as "comment"
|
1051
|
+
FROM duckdb_constraints
|
1052
|
+
WHERE constraint_type = 'PRIMARY KEY'
|
1053
|
+
AND database_name = '{current_database}'
|
1054
|
+
AND table_name NOT LIKE '_fs_%'
|
1055
|
+
"""
|
1056
|
+
else:
|
1057
|
+
statement = f"""
|
1058
|
+
SELECT
|
1059
|
+
to_timestamp(0)::timestamptz as created_on,
|
1060
|
+
database_name as database_name,
|
1061
|
+
schema_name as schema_name,
|
1062
|
+
table_name as table_name,
|
1063
|
+
unnest(constraint_column_names) as column_name,
|
1064
|
+
1 as key_sequence,
|
1065
|
+
LOWER(CONCAT(database_name, '_', schema_name, '_', table_name, '_pkey')) AS constraint_name,
|
1066
|
+
'false' as rely,
|
1067
|
+
null as "comment"
|
1068
|
+
FROM duckdb_constraints
|
1069
|
+
WHERE constraint_type = '{kind} KEY'
|
1070
|
+
AND database_name = '{current_database}'
|
1071
|
+
AND table_name NOT LIKE '_fs_%'
|
1072
|
+
"""
|
1073
|
+
|
1074
|
+
scope_kind = expression.args.get("scope_kind")
|
1075
|
+
if scope_kind:
|
1076
|
+
table = expression.args["scope"]
|
1077
|
+
|
1078
|
+
if scope_kind == "SCHEMA":
|
1079
|
+
db = table and table.db
|
1080
|
+
schema = table and table.name
|
1081
|
+
if db:
|
1082
|
+
statement += f"AND database_name = '{db}' "
|
1083
|
+
|
1084
|
+
if schema:
|
1085
|
+
statement += f"AND schema_name = '{schema}' "
|
1086
|
+
else:
|
1087
|
+
raise NotImplementedError(f"SHOW PRIMARY KEYS with {scope_kind} not yet supported")
|
1088
|
+
return sqlglot.parse_one(statement)
|
1089
|
+
return expression
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: fakesnow
|
3
|
-
Version: 0.9.
|
3
|
+
Version: 0.9.6
|
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,20 +213,20 @@ License-File: LICENSE
|
|
213
213
|
Requires-Dist: duckdb ~=0.10.0
|
214
214
|
Requires-Dist: pyarrow
|
215
215
|
Requires-Dist: snowflake-connector-python
|
216
|
-
Requires-Dist: sqlglot ~=21.
|
216
|
+
Requires-Dist: sqlglot ~=21.2.0
|
217
217
|
Provides-Extra: dev
|
218
|
-
Requires-Dist: black ~=23.9 ; extra == 'dev'
|
219
218
|
Requires-Dist: build ~=1.0 ; extra == 'dev'
|
219
|
+
Requires-Dist: pandas-stubs ; extra == 'dev'
|
220
220
|
Requires-Dist: snowflake-connector-python[pandas,secure-local-storage] ; extra == 'dev'
|
221
221
|
Requires-Dist: pre-commit ~=3.4 ; extra == 'dev'
|
222
|
-
Requires-Dist: pytest ~=
|
223
|
-
Requires-Dist: ruff ~=0.
|
224
|
-
Requires-Dist: twine ~=
|
222
|
+
Requires-Dist: pytest ~=8.0 ; extra == 'dev'
|
223
|
+
Requires-Dist: ruff ~=0.3.2 ; extra == 'dev'
|
224
|
+
Requires-Dist: twine ~=5.0 ; extra == 'dev'
|
225
|
+
Requires-Dist: snowflake-sqlalchemy ~=1.5.0 ; extra == 'dev'
|
225
226
|
Provides-Extra: notebook
|
226
227
|
Requires-Dist: duckdb-engine ; extra == 'notebook'
|
227
228
|
Requires-Dist: ipykernel ; extra == 'notebook'
|
228
229
|
Requires-Dist: jupysql ; extra == 'notebook'
|
229
|
-
Requires-Dist: snowflake-sqlalchemy ; extra == 'notebook'
|
230
230
|
|
231
231
|
# fakesnow ❄️
|
232
232
|
|
@@ -234,6 +234,8 @@ Requires-Dist: snowflake-sqlalchemy ; extra == 'notebook'
|
|
234
234
|
[](https://github.com/tekumara/fakesnow/actions/workflows/release.yml)
|
235
235
|
[](https://pypi.org/project/fakesnow/)
|
236
236
|
|
237
|
+
[](../../actions/workflows/ci.yml)
|
238
|
+
|
237
239
|
Fake [Snowflake Connector for Python](https://docs.snowflake.com/en/user-guide/python-connector). Run and mock Snowflake DB locally.
|
238
240
|
|
239
241
|
## Install
|
@@ -356,7 +358,7 @@ For more detail see [tests/test_fakes.py](tests/test_fakes.py)
|
|
356
358
|
## Caveats
|
357
359
|
|
358
360
|
- The order of rows is non deterministic and may not match Snowflake unless ORDER BY is fully specified.
|
359
|
-
-
|
361
|
+
- A more liberal Snowflake SQL dialect than a real Snowflake instance is supported, ie: some queries might pass using fakesnow that a real Snowflake instance would reject.
|
360
362
|
|
361
363
|
## Contributing
|
362
364
|
|
@@ -0,0 +1,18 @@
|
|
1
|
+
fakesnow/__init__.py,sha256=KWTeAxTcWXClN4bNo4kWHbC0ukOum7A4vtuL9zC1pT0,3472
|
2
|
+
fakesnow/__main__.py,sha256=GDrGyNTvBFuqn_UfDjKs7b3LPtU6gDv1KwosVDrukIM,76
|
3
|
+
fakesnow/checks.py,sha256=-QMvdcrRbhN60rnzxLBJ0IkUBWyLR8gGGKKmCS0w9mA,2383
|
4
|
+
fakesnow/cli.py,sha256=9qfI-Ssr6mo8UmIlXkUAOz2z2YPBgDsrEVaZv9FjGFs,2201
|
5
|
+
fakesnow/expr.py,sha256=CAxuYIUkwI339DQIBzvFF0F-m1tcVGKEPA5rDTzmH9A,892
|
6
|
+
fakesnow/fakes.py,sha256=5Fq_Qk-Iqxxzl-1XkcMyGDw2NY5hzE_50e5xii-jxhA,28463
|
7
|
+
fakesnow/fixtures.py,sha256=G-NkVeruSQAJ7fvSS2fR2oysUn0Yra1pohHlOvacKEk,455
|
8
|
+
fakesnow/global_database.py,sha256=WTVIP1VhNvdCeX7TQncX1TRpGQU5rBf5Pbxim40zeSU,1399
|
9
|
+
fakesnow/info_schema.py,sha256=CdIcGXHEQ_kmEAzdQKvA-PX41LA6wlK-4p1J45qgKYA,6266
|
10
|
+
fakesnow/macros.py,sha256=pX1YJDnQOkFJSHYUjQ6ErEkYIKvFI6Ncz_au0vv1csA,265
|
11
|
+
fakesnow/py.typed,sha256=B-DLSjYBi7pkKjwxCSdpVj2J02wgfJr-E7B1wOUyxYU,80
|
12
|
+
fakesnow/transforms.py,sha256=4cGfNbcd-X1l0UEGFudjoRejTjxSwWo-E0NXGpdVlaE,40109
|
13
|
+
fakesnow-0.9.6.dist-info/LICENSE,sha256=kW-7NWIyaRMQiDpryfSmF2DObDZHGR1cJZ39s6B1Svg,11344
|
14
|
+
fakesnow-0.9.6.dist-info/METADATA,sha256=PBc3zlOgUpHFpxAJoTHnZaKcgZz8J6LqfVnP_yMytRw,17802
|
15
|
+
fakesnow-0.9.6.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
16
|
+
fakesnow-0.9.6.dist-info/entry_points.txt,sha256=2riAUgu928ZIHawtO8EsfrMEJhi-EH-z_Vq7Q44xKPM,47
|
17
|
+
fakesnow-0.9.6.dist-info/top_level.txt,sha256=500evXI1IFX9so82cizGIEMHAb_dJNPaZvd2H9dcKTA,24
|
18
|
+
fakesnow-0.9.6.dist-info/RECORD,,
|
fakesnow-0.9.4.dist-info/RECORD
DELETED
@@ -1,18 +0,0 @@
|
|
1
|
-
fakesnow/__init__.py,sha256=UGfEpHKJWFEZfx_myvXuMyxMiHrFjfJmKnJcRQMyxL8,3443
|
2
|
-
fakesnow/__main__.py,sha256=GDrGyNTvBFuqn_UfDjKs7b3LPtU6gDv1KwosVDrukIM,76
|
3
|
-
fakesnow/checks.py,sha256=-QMvdcrRbhN60rnzxLBJ0IkUBWyLR8gGGKKmCS0w9mA,2383
|
4
|
-
fakesnow/cli.py,sha256=9qfI-Ssr6mo8UmIlXkUAOz2z2YPBgDsrEVaZv9FjGFs,2201
|
5
|
-
fakesnow/expr.py,sha256=CAxuYIUkwI339DQIBzvFF0F-m1tcVGKEPA5rDTzmH9A,892
|
6
|
-
fakesnow/fakes.py,sha256=gNw5tRu00pcC8m3CdLfv06wrHxBm_In8kJQ-IGNXp-Y,28197
|
7
|
-
fakesnow/fixtures.py,sha256=G-NkVeruSQAJ7fvSS2fR2oysUn0Yra1pohHlOvacKEk,455
|
8
|
-
fakesnow/global_database.py,sha256=WTVIP1VhNvdCeX7TQncX1TRpGQU5rBf5Pbxim40zeSU,1399
|
9
|
-
fakesnow/info_schema.py,sha256=5Rbq5HWb1YG-QY4Fj5zUC8KWRNt42aHw2J7tfHtbCug,5544
|
10
|
-
fakesnow/macros.py,sha256=pX1YJDnQOkFJSHYUjQ6ErEkYIKvFI6Ncz_au0vv1csA,265
|
11
|
-
fakesnow/py.typed,sha256=B-DLSjYBi7pkKjwxCSdpVj2J02wgfJr-E7B1wOUyxYU,80
|
12
|
-
fakesnow/transforms.py,sha256=z9tZDOtvQDAoYmQDvDegUOwxgBFvjnSfKCo7kTc-ryA,36300
|
13
|
-
fakesnow-0.9.4.dist-info/LICENSE,sha256=kW-7NWIyaRMQiDpryfSmF2DObDZHGR1cJZ39s6B1Svg,11344
|
14
|
-
fakesnow-0.9.4.dist-info/METADATA,sha256=4-uOp2P5nWelvwky5v7htSJPcLlZaJ0DtURryGxAdp4,17724
|
15
|
-
fakesnow-0.9.4.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
16
|
-
fakesnow-0.9.4.dist-info/entry_points.txt,sha256=2riAUgu928ZIHawtO8EsfrMEJhi-EH-z_Vq7Q44xKPM,47
|
17
|
-
fakesnow-0.9.4.dist-info/top_level.txt,sha256=500evXI1IFX9so82cizGIEMHAb_dJNPaZvd2H9dcKTA,24
|
18
|
-
fakesnow-0.9.4.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|