fakesnow 0.9.5__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/fakes.py +4 -1
- fakesnow/info_schema.py +1 -0
- fakesnow/transforms.py +105 -60
- {fakesnow-0.9.5.dist-info → fakesnow-0.9.6.dist-info}/METADATA +8 -6
- {fakesnow-0.9.5.dist-info → fakesnow-0.9.6.dist-info}/RECORD +9 -9
- {fakesnow-0.9.5.dist-info → fakesnow-0.9.6.dist-info}/WHEEL +1 -1
- {fakesnow-0.9.5.dist-info → fakesnow-0.9.6.dist-info}/LICENSE +0 -0
- {fakesnow-0.9.5.dist-info → fakesnow-0.9.6.dist-info}/entry_points.txt +0 -0
- {fakesnow-0.9.5.dist-info → fakesnow-0.9.6.dist-info}/top_level.txt +0 -0
fakesnow/fakes.py
CHANGED
@@ -198,7 +198,10 @@ class FakeSnowflakeCursor:
|
|
198
198
|
.transform(transforms.identifier)
|
199
199
|
.transform(lambda e: transforms.show_schemas(e, self._conn.database))
|
200
200
|
.transform(lambda e: transforms.show_objects_tables(e, self._conn.database))
|
201
|
-
|
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"))
|
202
205
|
.transform(transforms.show_users)
|
203
206
|
.transform(transforms.create_user)
|
204
207
|
)
|
fakesnow/info_schema.py
CHANGED
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
|
|
@@ -681,55 +682,61 @@ def set_schema(expression: exp.Expression, current_database: str | None) -> exp.
|
|
681
682
|
return expression
|
682
683
|
|
683
684
|
|
684
|
-
SQL_SHOW_OBJECTS = """
|
685
|
-
select
|
686
|
-
to_timestamp(0)::timestamptz as 'created_on',
|
687
|
-
table_name as 'name',
|
688
|
-
case when table_type='BASE TABLE' then 'TABLE' else table_type end as 'kind',
|
689
|
-
table_catalog as 'database_name',
|
690
|
-
table_schema as 'schema_name'
|
691
|
-
from information_schema.tables
|
692
|
-
"""
|
693
|
-
|
694
|
-
|
695
685
|
def show_objects_tables(expression: exp.Expression, current_database: str | None = None) -> exp.Expression:
|
696
686
|
"""Transform SHOW OBJECTS/TABLES to a query against the information_schema.tables table.
|
697
687
|
|
698
688
|
See https://docs.snowflake.com/en/sql-reference/sql/show-objects
|
699
689
|
https://docs.snowflake.com/en/sql-reference/sql/show-tables
|
700
690
|
"""
|
701
|
-
if (
|
691
|
+
if not (
|
702
692
|
isinstance(expression, exp.Show)
|
703
693
|
and isinstance(expression.this, str)
|
704
|
-
and expression.this.upper()
|
694
|
+
and (show := expression.this.upper())
|
695
|
+
and show in {"OBJECTS", "TABLES"}
|
705
696
|
):
|
706
|
-
|
707
|
-
table = expression.find(exp.Table)
|
708
|
-
|
709
|
-
if scope_kind == "DATABASE":
|
710
|
-
catalog = (table and table.name) or current_database
|
711
|
-
schema = None
|
712
|
-
elif scope_kind == "SCHEMA" and table:
|
713
|
-
catalog = table.db or current_database
|
714
|
-
schema = table.name
|
715
|
-
else:
|
716
|
-
# all objects / tables
|
717
|
-
catalog = None
|
718
|
-
schema = None
|
719
|
-
|
720
|
-
tables_only = "table_type = 'BASE TABLE' and " if expression.this.upper() == "TABLES" else ""
|
721
|
-
exclude_fakesnow_tables = "not (table_schema == 'information_schema' and table_name like '_fs_%%')"
|
722
|
-
# without a database will show everything in the "account"
|
723
|
-
table_catalog = f" and table_catalog = '{catalog}'" if catalog else ""
|
724
|
-
schema = f" and table_schema = '{schema}'" if schema else ""
|
725
|
-
limit = limit.sql() if (limit := expression.args.get("limit")) and isinstance(limit, exp.Expression) else ""
|
726
|
-
|
727
|
-
return sqlglot.parse_one(
|
728
|
-
f"{SQL_SHOW_OBJECTS} where {tables_only}{exclude_fakesnow_tables}{table_catalog}{schema}{limit}",
|
729
|
-
read="duckdb",
|
730
|
-
)
|
697
|
+
return expression
|
731
698
|
|
732
|
-
|
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")
|
733
740
|
|
734
741
|
|
735
742
|
SQL_SHOW_SCHEMAS = """
|
@@ -999,32 +1006,70 @@ def create_user(expression: exp.Expression) -> exp.Expression:
|
|
999
1006
|
return expression
|
1000
1007
|
|
1001
1008
|
|
1002
|
-
def
|
1003
|
-
|
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.
|
1004
1016
|
|
1005
1017
|
https://docs.snowflake.com/en/sql-reference/sql/show-primary-keys
|
1006
1018
|
"""
|
1019
|
+
snowflake_kind = kind
|
1020
|
+
if kind == "FOREIGN":
|
1021
|
+
snowflake_kind = "IMPORTED"
|
1022
|
+
|
1007
1023
|
if (
|
1008
1024
|
isinstance(expression, exp.Show)
|
1009
1025
|
and isinstance(expression.this, str)
|
1010
|
-
and expression.this.upper() == "
|
1026
|
+
and expression.this.upper() == f"{snowflake_kind} KEYS"
|
1011
1027
|
):
|
1012
|
-
|
1013
|
-
|
1014
|
-
|
1015
|
-
|
1016
|
-
|
1017
|
-
|
1018
|
-
|
1019
|
-
|
1020
|
-
|
1021
|
-
|
1022
|
-
|
1023
|
-
|
1024
|
-
|
1025
|
-
|
1026
|
-
|
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
|
+
"""
|
1028
1073
|
|
1029
1074
|
scope_kind = expression.args.get("scope_kind")
|
1030
1075
|
if scope_kind:
|
@@ -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,15 +213,15 @@ 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
225
|
Requires-Dist: snowflake-sqlalchemy ~=1.5.0 ; extra == 'dev'
|
226
226
|
Provides-Extra: notebook
|
227
227
|
Requires-Dist: duckdb-engine ; extra == 'notebook'
|
@@ -234,6 +234,8 @@ Requires-Dist: jupysql ; 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
|
@@ -3,16 +3,16 @@ fakesnow/__main__.py,sha256=GDrGyNTvBFuqn_UfDjKs7b3LPtU6gDv1KwosVDrukIM,76
|
|
3
3
|
fakesnow/checks.py,sha256=-QMvdcrRbhN60rnzxLBJ0IkUBWyLR8gGGKKmCS0w9mA,2383
|
4
4
|
fakesnow/cli.py,sha256=9qfI-Ssr6mo8UmIlXkUAOz2z2YPBgDsrEVaZv9FjGFs,2201
|
5
5
|
fakesnow/expr.py,sha256=CAxuYIUkwI339DQIBzvFF0F-m1tcVGKEPA5rDTzmH9A,892
|
6
|
-
fakesnow/fakes.py,sha256=
|
6
|
+
fakesnow/fakes.py,sha256=5Fq_Qk-Iqxxzl-1XkcMyGDw2NY5hzE_50e5xii-jxhA,28463
|
7
7
|
fakesnow/fixtures.py,sha256=G-NkVeruSQAJ7fvSS2fR2oysUn0Yra1pohHlOvacKEk,455
|
8
8
|
fakesnow/global_database.py,sha256=WTVIP1VhNvdCeX7TQncX1TRpGQU5rBf5Pbxim40zeSU,1399
|
9
|
-
fakesnow/info_schema.py,sha256=
|
9
|
+
fakesnow/info_schema.py,sha256=CdIcGXHEQ_kmEAzdQKvA-PX41LA6wlK-4p1J45qgKYA,6266
|
10
10
|
fakesnow/macros.py,sha256=pX1YJDnQOkFJSHYUjQ6ErEkYIKvFI6Ncz_au0vv1csA,265
|
11
11
|
fakesnow/py.typed,sha256=B-DLSjYBi7pkKjwxCSdpVj2J02wgfJr-E7B1wOUyxYU,80
|
12
|
-
fakesnow/transforms.py,sha256=
|
13
|
-
fakesnow-0.9.
|
14
|
-
fakesnow-0.9.
|
15
|
-
fakesnow-0.9.
|
16
|
-
fakesnow-0.9.
|
17
|
-
fakesnow-0.9.
|
18
|
-
fakesnow-0.9.
|
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,,
|
File without changes
|
File without changes
|
File without changes
|