fakesnow 0.9.5__tar.gz → 0.9.6__tar.gz

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.
Files changed (33) hide show
  1. {fakesnow-0.9.5/fakesnow.egg-info → fakesnow-0.9.6}/PKG-INFO +8 -6
  2. {fakesnow-0.9.5 → fakesnow-0.9.6}/README.md +2 -0
  3. {fakesnow-0.9.5 → fakesnow-0.9.6}/fakesnow/fakes.py +4 -1
  4. {fakesnow-0.9.5 → fakesnow-0.9.6}/fakesnow/info_schema.py +1 -0
  5. {fakesnow-0.9.5 → fakesnow-0.9.6}/fakesnow/transforms.py +105 -60
  6. {fakesnow-0.9.5 → fakesnow-0.9.6/fakesnow.egg-info}/PKG-INFO +8 -6
  7. {fakesnow-0.9.5 → fakesnow-0.9.6}/fakesnow.egg-info/SOURCES.txt +0 -1
  8. {fakesnow-0.9.5 → fakesnow-0.9.6}/fakesnow.egg-info/requires.txt +5 -5
  9. {fakesnow-0.9.5 → fakesnow-0.9.6}/pyproject.toml +38 -51
  10. {fakesnow-0.9.5 → fakesnow-0.9.6}/tests/test_fakes.py +135 -8
  11. {fakesnow-0.9.5 → fakesnow-0.9.6}/tests/test_sqlalchemy.py +16 -9
  12. fakesnow-0.9.5/MANIFEST.in +0 -1
  13. {fakesnow-0.9.5 → fakesnow-0.9.6}/LICENSE +0 -0
  14. {fakesnow-0.9.5 → fakesnow-0.9.6}/fakesnow/__init__.py +0 -0
  15. {fakesnow-0.9.5 → fakesnow-0.9.6}/fakesnow/__main__.py +0 -0
  16. {fakesnow-0.9.5 → fakesnow-0.9.6}/fakesnow/checks.py +0 -0
  17. {fakesnow-0.9.5 → fakesnow-0.9.6}/fakesnow/cli.py +0 -0
  18. {fakesnow-0.9.5 → fakesnow-0.9.6}/fakesnow/expr.py +0 -0
  19. {fakesnow-0.9.5 → fakesnow-0.9.6}/fakesnow/fixtures.py +0 -0
  20. {fakesnow-0.9.5 → fakesnow-0.9.6}/fakesnow/global_database.py +0 -0
  21. {fakesnow-0.9.5 → fakesnow-0.9.6}/fakesnow/macros.py +0 -0
  22. {fakesnow-0.9.5 → fakesnow-0.9.6}/fakesnow/py.typed +0 -0
  23. {fakesnow-0.9.5 → fakesnow-0.9.6}/fakesnow.egg-info/dependency_links.txt +0 -0
  24. {fakesnow-0.9.5 → fakesnow-0.9.6}/fakesnow.egg-info/entry_points.txt +0 -0
  25. {fakesnow-0.9.5 → fakesnow-0.9.6}/fakesnow.egg-info/top_level.txt +0 -0
  26. {fakesnow-0.9.5 → fakesnow-0.9.6}/setup.cfg +0 -0
  27. {fakesnow-0.9.5 → fakesnow-0.9.6}/tests/test_checks.py +0 -0
  28. {fakesnow-0.9.5 → fakesnow-0.9.6}/tests/test_cli.py +0 -0
  29. {fakesnow-0.9.5 → fakesnow-0.9.6}/tests/test_expr.py +0 -0
  30. {fakesnow-0.9.5 → fakesnow-0.9.6}/tests/test_info_schema.py +0 -0
  31. {fakesnow-0.9.5 → fakesnow-0.9.6}/tests/test_patch.py +0 -0
  32. {fakesnow-0.9.5 → fakesnow-0.9.6}/tests/test_transforms.py +0 -0
  33. {fakesnow-0.9.5 → fakesnow-0.9.6}/tests/test_users.py +0 -0
@@ -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
@@ -4,6 +4,8 @@
4
4
  [![release](https://github.com/tekumara/fakesnow/actions/workflows/release.yml/badge.svg)](https://github.com/tekumara/fakesnow/actions/workflows/release.yml)
5
5
  [![PyPI](https://img.shields.io/pypi/v/fakesnow?color=violet)](https://pypi.org/project/fakesnow/)
6
6
 
7
+ [![ci](../../actions/workflows/ci.yml/badge.svg)](../../actions/workflows/ci.yml)
8
+
7
9
  Fake [Snowflake Connector for Python](https://docs.snowflake.com/en/user-guide/python-connector). Run and mock Snowflake DB locally.
8
10
 
9
11
  ## Install
@@ -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
  )
@@ -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
@@ -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
@@ -1,5 +1,4 @@
1
1
  LICENSE
2
- MANIFEST.in
3
2
  README.md
4
3
  pyproject.toml
5
4
  fakesnow/__init__.py
@@ -1,16 +1,16 @@
1
1
  duckdb~=0.10.0
2
2
  pyarrow
3
3
  snowflake-connector-python
4
- sqlglot~=21.1.0
4
+ sqlglot~=21.2.0
5
5
 
6
6
  [dev]
7
- black~=23.9
8
7
  build~=1.0
8
+ pandas-stubs
9
9
  snowflake-connector-python[pandas,secure-local-storage]
10
10
  pre-commit~=3.4
11
- pytest~=7.4
12
- ruff~=0.1.6
13
- twine~=4.0
11
+ pytest~=8.0
12
+ ruff~=0.3.2
13
+ twine~=5.0
14
14
  snowflake-sqlalchemy~=1.5.0
15
15
 
16
16
  [notebook]
@@ -1,7 +1,7 @@
1
1
  [project]
2
2
  name = "fakesnow"
3
3
  description = "Fake Snowflake Connector for Python. Run, mock and test Snowflake DB locally."
4
- version = "0.9.5"
4
+ version = "0.9.6"
5
5
  readme = "README.md"
6
6
  license = { file = "LICENSE" }
7
7
  classifiers = ["License :: OSI Approved :: MIT License"]
@@ -11,7 +11,7 @@ dependencies = [
11
11
  "duckdb~=0.10.0",
12
12
  "pyarrow",
13
13
  "snowflake-connector-python",
14
- "sqlglot~=21.1.0",
14
+ "sqlglot~=21.2.0",
15
15
  ]
16
16
 
17
17
  [project.urls]
@@ -22,29 +22,26 @@ fakesnow = "fakesnow.cli:main"
22
22
 
23
23
  [project.optional-dependencies]
24
24
  dev = [
25
- "black~=23.9",
26
25
  "build~=1.0",
26
+ # to fix https://github.com/pandas-dev/pandas/issues/56995
27
+ "pandas-stubs",
27
28
  # include compatible version of pandas, and secure-local-storage for token caching
28
29
  "snowflake-connector-python[pandas, secure-local-storage]",
29
30
  "pre-commit~=3.4",
30
- "pytest~=7.4",
31
- "ruff~=0.1.6",
32
- "twine~=4.0",
31
+ "pytest~=8.0",
32
+ "ruff~=0.3.2",
33
+ "twine~=5.0",
33
34
  "snowflake-sqlalchemy~=1.5.0",
34
35
  ]
35
36
  # for debugging, see https://duckdb.org/docs/guides/python/jupyter.html
36
37
  notebook = ["duckdb-engine", "ipykernel", "jupysql"]
37
38
 
38
- [tool.setuptools.packages.find]
39
- where = ["."]
40
- exclude = ["tests*"]
41
-
42
39
  [build-system]
43
- requires = ["setuptools~=68.2", "wheel~=0.40"]
40
+ requires = ["setuptools~=69.1", "wheel~=0.42"]
44
41
 
45
- [tool.black]
46
- # use PyCharm default line length of 120
47
- line-length = 120
42
+ [tool.setuptools.packages.find]
43
+ where = ["."]
44
+ exclude = ["tests*", "node_modules*", "build*"]
48
45
 
49
46
  [tool.pyright]
50
47
  venvPath = "."
@@ -54,63 +51,53 @@ strictListInference = true
54
51
  strictDictionaryInference = true
55
52
  strictParameterNoneValue = true
56
53
  reportTypedDictNotRequiredAccess = false
54
+ reportIncompatibleMethodOverride = true
55
+ reportUnnecessaryTypeIgnoreComment = true
57
56
 
58
57
  [tool.ruff]
59
- # Compatibility between Ruff and Black
60
- # https://beta.ruff.rs/docs/faq/#is-ruff-compatible-with-black
61
58
  line-length = 120
59
+ # first-party imports for sorting
60
+ src = ["."]
61
+ fix = true
62
+ show-fixes = true
62
63
 
64
+ [tool.ruff.lint]
63
65
  # rules to enable/ignore
64
66
  select = [
65
- # pyflakes
66
- "F",
67
- # pycodestyle
68
- "E",
69
- "W",
70
- # type annotations
71
- "ANN",
72
- # pep8-naming
73
- "N",
74
- # bugbear
75
- "B",
76
- # isort
77
- "I",
78
- # flake8-unused-arguments - disabled because our fakes don't use all arguments
79
- # "ARG",
80
- # flake8-self
81
- "SLF",
82
- # pyupgrade
83
- "UP",
84
- # perflint
85
- "PERF",
86
- # ruff-specific
87
- "RUF",
88
- # flake8-simplify
89
- "SIM",
90
- # flake8-builtins
91
- "A"
67
+ "F", # pyflakes
68
+ "E", # pycodestyle
69
+ "W", # pycodestyle
70
+ "ANN", # type annotations
71
+ "N", # pep8-naming
72
+ "B", # bugbear
73
+ "I", # isort
74
+ # "ARG", # flake8-unused-arguments - disabled because our fakes don't use all arguments
75
+ "SLF", # flake8-self
76
+ "UP", # pyupgrade
77
+ "PERF", # perflint
78
+ "RUF", # ruff-specific
79
+ "SIM", # flake8-simplify
80
+ "S113", # request-without-timeout
81
+ "A", # flake8-builtins
92
82
  ]
93
83
  ignore = [
94
- # allow untyped self and cls args, and no return type from dunder methods
84
+ # allow untyped self and cls args
95
85
  "ANN101",
96
86
  "ANN102",
87
+ # allow no return type from dunder methods
97
88
  "ANN204",
98
89
  # allow == True because pandas dataframes overload equality
99
90
  "E712",
100
91
  ]
101
- # first-party imports for sorting
102
- src = ["."]
103
- fix = true
104
- show-fixes = true
105
92
 
106
- [tool.ruff.isort]
93
+ [tool.ruff.lint.isort]
107
94
  combine-as-imports = true
108
95
  force-wrap-aliases = true
109
96
 
110
- [tool.ruff.per-file-ignores]
97
+ [tool.ruff.lint.per-file-ignores]
111
98
  # test functions don't need return types
112
99
  "tests/*" = ["ANN201", "ANN202"]
113
100
 
114
- [tool.ruff.flake8-annotations]
101
+ [tool.ruff.lint.flake8-annotations]
115
102
  # allow *args: Any, **kwargs: Any
116
103
  allow-star-arg-any = true
@@ -115,17 +115,21 @@ def test_connect_different_sessions_use_database(_fakesnow_no_auto_create: None)
115
115
 
116
116
  def test_connect_reuse_db():
117
117
  with tempfile.TemporaryDirectory(prefix="fakesnow-test") as db_path:
118
- with fakesnow.patch(db_path=db_path), snowflake.connector.connect(
119
- database="db1", schema="schema1"
120
- ) as conn, conn.cursor() as cur:
118
+ with (
119
+ fakesnow.patch(db_path=db_path),
120
+ snowflake.connector.connect(database="db1", schema="schema1") as conn,
121
+ conn.cursor() as cur,
122
+ ):
121
123
  # creates db1.schema1.example
122
124
  cur.execute("create table example (x int)")
123
125
  cur.execute("insert into example values (420)")
124
126
 
125
127
  # reconnect
126
- with fakesnow.patch(db_path=db_path), snowflake.connector.connect(
127
- database="db1", schema="schema1"
128
- ) as conn, conn.cursor() as cur:
128
+ with (
129
+ fakesnow.patch(db_path=db_path),
130
+ snowflake.connector.connect(database="db1", schema="schema1") as conn,
131
+ conn.cursor() as cur,
132
+ ):
129
133
  assert cur.execute("select * from example").fetchall() == [(420,)]
130
134
 
131
135
 
@@ -864,6 +868,76 @@ def test_semi_structured_types(cur: snowflake.connector.cursor.SnowflakeCursor):
864
868
  ]
865
869
 
866
870
 
871
+ @pytest.mark.xfail(
872
+ reason="only partial supports exists to support sqlalchemy, see test_reflect",
873
+ )
874
+ def test_show_keys(dcur: snowflake.connector.cursor.SnowflakeCursor):
875
+ dcur.execute("CREATE TABLE test_table (id INT PRIMARY KEY, name TEXT UNIQUE)")
876
+ dcur.execute("CREATE TABLE test_table2 (id INT, other_id INT, FOREIGN KEY (other_id) REFERENCES test_table(id))")
877
+
878
+ dcur.execute("SHOW PRIMARY KEYS")
879
+ primary_keys = dcur.fetchall()
880
+ assert primary_keys == [
881
+ {
882
+ "created_on": datetime.datetime(1970, 1, 1, 0, 0, tzinfo=pytz.utc),
883
+ "database_name": "DB1",
884
+ "schema_name": "SCHEMA1",
885
+ "table_name": "TEST_TABLE",
886
+ "column_name": "ID",
887
+ "key_sequence": 1,
888
+ "constraint_name": "SYS_CONSTRAINT_DB1_SCHEMA1_TEST_TABLE_ID_pk",
889
+ "rely": "false",
890
+ "comment": None,
891
+ }
892
+ ]
893
+
894
+ dcur.execute("SHOW UNIQUE KEYS")
895
+ unique_keys = dcur.fetchall()
896
+ assert unique_keys == [
897
+ {
898
+ "created_on": datetime.datetime(1970, 1, 1, 0, 0, tzinfo=pytz.utc),
899
+ "database_name": "DB1",
900
+ "schema_name": "SCHEMA1",
901
+ "table_name": "TEST_TABLE",
902
+ "column_name": "NAME",
903
+ "key_sequence": 1,
904
+ "constraint_name": "SYS_CONSTRAINT_DB1_SCHEMA1_TEST_TABLE_NAME_uk",
905
+ "rely": "false",
906
+ "comment": None,
907
+ }
908
+ ]
909
+
910
+ dcur.execute("SHOW IMPORTED KEYS")
911
+ foreign_keys = dcur.fetchall()
912
+ assert foreign_keys == [
913
+ {
914
+ "created_on": datetime.datetime(1970, 1, 1, 0, 0, tzinfo=pytz.utc),
915
+ "pk_database_name": "DB1",
916
+ "pk_schema_name": "SCHEMA1",
917
+ "pk_table_name": "TEST_TABLE",
918
+ "pk_column_name": "ID",
919
+ "fk_database_name": "DB1",
920
+ "fk_schema_name": "SCHEMA1",
921
+ "fk_table_name": "TEST_TABLE2",
922
+ "fk_column_name": "OTHER_ID",
923
+ "key_sequence": 1,
924
+ "update_rule": "NO ACTION",
925
+ "delete_rule": "NO ACTION",
926
+ "fk_name": "SYS_CONSTRAINT_DB1_SCHEMA1_TEST_TABLE2_OTHER_ID_fk",
927
+ "pk_name": "SYS_CONSTRAINT_DB1_SCHEMA1_TEST_TABLE_ID_pk",
928
+ "deferrability": "NOT DEFERRABLE",
929
+ "rely": "false",
930
+ "comment": None,
931
+ }
932
+ ]
933
+
934
+ dcur.execute("SHOW PRIMARY KEYS IN SCHEMA")
935
+ assert dcur.fetchall() == primary_keys
936
+
937
+ dcur.execute("SHOW PRIMARY KEYS IN DATABASE")
938
+ assert dcur.fetchall() == primary_keys
939
+
940
+
867
941
  def test_show_objects(dcur: snowflake.connector.cursor.SnowflakeCursor):
868
942
  dcur.execute("create table example(x int)")
869
943
  dcur.execute("create view view1 as select * from example")
@@ -885,6 +959,7 @@ def test_show_objects(dcur: snowflake.connector.cursor.SnowflakeCursor):
885
959
  },
886
960
  ]
887
961
  assert dcur.fetchall() == objects
962
+
888
963
  dcur.execute("show terse objects in database")
889
964
  assert dcur.fetchall() == [
890
965
  *objects,
@@ -905,6 +980,24 @@ def test_show_objects(dcur: snowflake.connector.cursor.SnowflakeCursor):
905
980
  ]
906
981
  assert [r.name for r in dcur.description] == ["created_on", "name", "kind", "database_name", "schema_name"]
907
982
 
983
+ dcur.execute("show objects").fetchall()
984
+ assert [r.name for r in dcur.description] == [
985
+ "created_on",
986
+ "name",
987
+ "kind",
988
+ "database_name",
989
+ "schema_name",
990
+ "comment",
991
+ # TODO: include these columns
992
+ # "cluster_by",
993
+ # "rows",
994
+ # "bytes",
995
+ # "owner",
996
+ # "retention_time",
997
+ # "owner_role_type",
998
+ # "budget"
999
+ ]
1000
+
908
1001
 
909
1002
  def test_show_schemas(dcur: snowflake.connector.cursor.SnowflakeCursor):
910
1003
  dcur.execute("show terse schemas in database db1 limit 100")
@@ -943,7 +1036,41 @@ def test_show_tables(dcur: snowflake.connector.cursor.SnowflakeCursor):
943
1036
  # assert dcur.fetchall() == objects
944
1037
  dcur.execute("show terse tables in db1.schema1")
945
1038
  assert dcur.fetchall() == objects
946
- assert [r.name for r in dcur.description] == ["created_on", "name", "kind", "database_name", "schema_name"]
1039
+ assert [r.name for r in dcur.description] == [
1040
+ "created_on",
1041
+ "name",
1042
+ "kind",
1043
+ "database_name",
1044
+ "schema_name",
1045
+ ]
1046
+
1047
+ dcur.execute("show tables in db1.schema1")
1048
+ assert [r.name for r in dcur.description] == [
1049
+ "created_on",
1050
+ "name",
1051
+ "kind",
1052
+ "database_name",
1053
+ "schema_name",
1054
+ "comment",
1055
+ # TODO: include these columns
1056
+ # "cluster_by",
1057
+ # "rows",
1058
+ # "bytes",
1059
+ # "owner",
1060
+ # "retention_time",
1061
+ # "automatic_clustering",
1062
+ # "change_tracking",
1063
+ # "search_optimization",
1064
+ # "search_optimization_progress",
1065
+ # "search_optimization_bytes",
1066
+ # "is_external",
1067
+ # "enable_schema_evolution",
1068
+ # "owner_role_type",
1069
+ # "is_event",
1070
+ # "budget",
1071
+ # "is_hybrid",
1072
+ # "is_iceberg",
1073
+ ]
947
1074
 
948
1075
 
949
1076
  def test_show_primary_keys(dcur: snowflake.connector.cursor.SnowflakeCursor):
@@ -981,7 +1108,7 @@ def test_show_primary_keys(dcur: snowflake.connector.cursor.SnowflakeCursor):
981
1108
  result2 = dcur.fetchall()
982
1109
  assert result == result2
983
1110
 
984
- # Assertion to sanity check that the above "in schema" filter isnt wrong, and in fact filters
1111
+ # Assertion to sanity check that the above "in schema" filter isn't wrong, and in fact filters
985
1112
  dcur.execute("show primary keys in schema db1.information_schema")
986
1113
  result3 = dcur.fetchall()
987
1114
  assert result3 == []
@@ -1,5 +1,5 @@
1
- import duckdb
2
- import pytest
1
+ from typing import cast
2
+
3
3
  from sqlalchemy import Column, MetaData, Table, types
4
4
  from sqlalchemy.engine import Engine
5
5
  from sqlalchemy.sql.expression import TextClause
@@ -18,7 +18,7 @@ def test_engine(snowflake_engine: Engine):
18
18
  def test_metadata_create_all(snowflake_engine: Engine):
19
19
  metadata = MetaData()
20
20
 
21
- table = Table("foo", metadata, Column(types.Integer, name="id"), Column(types.String, name="name"))
21
+ table = cast(Table, Table("foo", metadata, Column(types.Integer, name="id"), Column(types.String, name="name")))
22
22
  metadata.create_all(bind=snowflake_engine)
23
23
 
24
24
  with snowflake_engine.connect() as conn:
@@ -27,13 +27,20 @@ def test_metadata_create_all(snowflake_engine: Engine):
27
27
  assert result.fetchall() == []
28
28
 
29
29
 
30
- @pytest.mark.xfail(
31
- reason="sqlglot currently has unsupported SHOW variants",
32
- strict=True,
33
- raises=duckdb.ParserException,
34
- )
35
30
  def test_reflect(snowflake_engine: Engine):
36
- snowflake_engine.execute(TextClause("CREATE TABLE foo (id INTEGER, name VARCHAR)"))
31
+ with snowflake_engine.connect() as conn:
32
+ conn.execute(TextClause("CREATE TABLE foo (id INTEGER, name VARCHAR)"))
37
33
 
38
34
  metadata = MetaData()
39
35
  metadata.reflect(bind=snowflake_engine, only=["foo"])
36
+
37
+ assert metadata.tables
38
+ foo_table: Table = metadata.tables["foo"]
39
+
40
+ with snowflake_engine.connect() as conn:
41
+ result = conn.execute(foo_table.insert().values(id=1, name="one"))
42
+
43
+ result = conn.execute(foo_table.select())
44
+
45
+ assert result
46
+ assert result.fetchall() == [(1, "one")]
@@ -1 +0,0 @@
1
- recursive-include fakesnow py.typed
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes