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 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
- .transform(lambda e: transforms.show_primary_keys(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"))
202
205
  .transform(transforms.show_users)
203
206
  .transform(transforms.create_user)
204
207
  )
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
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
- assert (ident := expression.find(exp.Identifier)), f"No identifier in {expression.sql}"
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() in ["OBJECTS", "TABLES"]
694
+ and (show := expression.this.upper())
695
+ and show in {"OBJECTS", "TABLES"}
705
696
  ):
706
- scope_kind = expression.args.get("scope_kind")
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
- return expression
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 show_primary_keys(expression: exp.Expression, current_database: str | None = None) -> exp.Expression:
1003
- """Transform SHOW PRIMARY KEYS to a query against the duckdb_constraints table.
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() == "PRIMARY KEYS"
1026
+ and expression.this.upper() == f"{snowflake_kind} KEYS"
1011
1027
  ):
1012
- statement = f"""
1013
- SELECT
1014
- to_timestamp(0)::timestamptz as created_on,
1015
- database_name as database_name,
1016
- schema_name as schema_name,
1017
- table_name as table_name,
1018
- unnest(constraint_column_names) as column_name,
1019
- 1 as key_sequence,
1020
- LOWER(CONCAT(database_name, '_', schema_name, '_', table_name, '_pkey')) AS constraint_name,
1021
- 'false' as rely,
1022
- null as comment
1023
- FROM duckdb_constraints
1024
- WHERE constraint_type = 'PRIMARY KEY'
1025
- AND database_name = '{current_database}'
1026
- AND table_name NOT LIKE '_fs_%'
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.5
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.1.0
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 ~=7.4 ; extra == 'dev'
223
- Requires-Dist: ruff ~=0.1.6 ; extra == 'dev'
224
- Requires-Dist: twine ~=4.0 ; extra == 'dev'
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
  [![release](https://github.com/tekumara/fakesnow/actions/workflows/release.yml/badge.svg)](https://github.com/tekumara/fakesnow/actions/workflows/release.yml)
235
235
  [![PyPI](https://img.shields.io/pypi/v/fakesnow?color=violet)](https://pypi.org/project/fakesnow/)
236
236
 
237
+ [![ci](../../actions/workflows/ci.yml/badge.svg)](../../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=MhZeKSY602Pdl2XMNoHAgs_ZB5ROUJjEWlZlueNUw6Y,28205
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=9UfkGYdjJHTHSk9rbWqxlFUcLGhrS3CyGwP-gkS3A6Q,6265
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=jYzJ6EeObd-MgNdOKjkFD1usmP8wZGqT6rQd2KrtUMM,38424
13
- fakesnow-0.9.5.dist-info/LICENSE,sha256=kW-7NWIyaRMQiDpryfSmF2DObDZHGR1cJZ39s6B1Svg,11344
14
- fakesnow-0.9.5.dist-info/METADATA,sha256=R5uWClyMFddD_7wrovBLhw2EkgKzCLdMf8jy6TIe0pE,17719
15
- fakesnow-0.9.5.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
16
- fakesnow-0.9.5.dist-info/entry_points.txt,sha256=2riAUgu928ZIHawtO8EsfrMEJhi-EH-z_Vq7Q44xKPM,47
17
- fakesnow-0.9.5.dist-info/top_level.txt,sha256=500evXI1IFX9so82cizGIEMHAb_dJNPaZvd2H9dcKTA,24
18
- fakesnow-0.9.5.dist-info/RECORD,,
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.42.0)
2
+ Generator: bdist_wheel (0.43.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5