snowflake-sqlalchemy 1.6.1__tar.gz → 1.7.1__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 (126) hide show
  1. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/.pre-commit-config.yaml +1 -0
  2. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/DESCRIPTION.md +17 -2
  3. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/PKG-INFO +155 -7
  4. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/README.md +152 -4
  5. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/pyproject.toml +5 -0
  6. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/src/snowflake/sqlalchemy/__init__.py +52 -6
  7. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/src/snowflake/sqlalchemy/_constants.py +2 -0
  8. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/src/snowflake/sqlalchemy/base.py +78 -15
  9. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/src/snowflake/sqlalchemy/custom_commands.py +7 -2
  10. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/src/snowflake/sqlalchemy/custom_types.py +20 -0
  11. snowflake_sqlalchemy-1.7.1/src/snowflake/sqlalchemy/exc.py +82 -0
  12. snowflake_sqlalchemy-1.7.1/src/snowflake/sqlalchemy/parser/custom_type_parser.py +190 -0
  13. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/src/snowflake/sqlalchemy/snowdialect.py +214 -142
  14. snowflake_sqlalchemy-1.7.1/src/snowflake/sqlalchemy/sql/custom_schema/__init__.py +9 -0
  15. snowflake_sqlalchemy-1.7.1/src/snowflake/sqlalchemy/sql/custom_schema/clustered_table.py +37 -0
  16. snowflake_sqlalchemy-1.7.1/src/snowflake/sqlalchemy/sql/custom_schema/custom_table_base.py +127 -0
  17. snowflake_sqlalchemy-1.7.1/src/snowflake/sqlalchemy/sql/custom_schema/custom_table_prefix.py +13 -0
  18. snowflake_sqlalchemy-1.7.1/src/snowflake/sqlalchemy/sql/custom_schema/dynamic_table.py +117 -0
  19. snowflake_sqlalchemy-1.7.1/src/snowflake/sqlalchemy/sql/custom_schema/hybrid_table.py +62 -0
  20. snowflake_sqlalchemy-1.7.1/src/snowflake/sqlalchemy/sql/custom_schema/iceberg_table.py +102 -0
  21. snowflake_sqlalchemy-1.7.1/src/snowflake/sqlalchemy/sql/custom_schema/options/__init__.py +33 -0
  22. snowflake_sqlalchemy-1.7.1/src/snowflake/sqlalchemy/sql/custom_schema/options/as_query_option.py +63 -0
  23. snowflake_sqlalchemy-1.7.1/src/snowflake/sqlalchemy/sql/custom_schema/options/cluster_by_option.py +58 -0
  24. snowflake_sqlalchemy-1.7.1/src/snowflake/sqlalchemy/sql/custom_schema/options/identifier_option.py +63 -0
  25. snowflake_sqlalchemy-1.7.1/src/snowflake/sqlalchemy/sql/custom_schema/options/invalid_table_option.py +25 -0
  26. snowflake_sqlalchemy-1.7.1/src/snowflake/sqlalchemy/sql/custom_schema/options/keyword_option.py +65 -0
  27. snowflake_sqlalchemy-1.7.1/src/snowflake/sqlalchemy/sql/custom_schema/options/keywords.py +14 -0
  28. snowflake_sqlalchemy-1.7.1/src/snowflake/sqlalchemy/sql/custom_schema/options/literal_option.py +67 -0
  29. snowflake_sqlalchemy-1.7.1/src/snowflake/sqlalchemy/sql/custom_schema/options/table_option.py +84 -0
  30. snowflake_sqlalchemy-1.7.1/src/snowflake/sqlalchemy/sql/custom_schema/options/target_lag_option.py +94 -0
  31. snowflake_sqlalchemy-1.7.1/src/snowflake/sqlalchemy/sql/custom_schema/snowflake_table.py +70 -0
  32. snowflake_sqlalchemy-1.7.1/src/snowflake/sqlalchemy/sql/custom_schema/table_from_query.py +54 -0
  33. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/src/snowflake/sqlalchemy/version.py +1 -1
  34. snowflake_sqlalchemy-1.7.1/tests/__snapshots__/test_compile_dynamic_table.ambr +13 -0
  35. snowflake_sqlalchemy-1.7.1/tests/__snapshots__/test_core.ambr +4 -0
  36. snowflake_sqlalchemy-1.7.1/tests/__snapshots__/test_orm.ambr +4 -0
  37. snowflake_sqlalchemy-1.7.1/tests/__snapshots__/test_reflect_dynamic_table.ambr +4 -0
  38. snowflake_sqlalchemy-1.7.1/tests/__snapshots__/test_structured_datatypes.ambr +90 -0
  39. snowflake_sqlalchemy-1.7.1/tests/__snapshots__/test_unit_structured_types.ambr +4 -0
  40. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/tests/conftest.py +30 -0
  41. snowflake_sqlalchemy-1.7.1/tests/custom_tables/__init__.py +2 -0
  42. snowflake_sqlalchemy-1.7.1/tests/custom_tables/__snapshots__/test_compile_dynamic_table.ambr +40 -0
  43. snowflake_sqlalchemy-1.7.1/tests/custom_tables/__snapshots__/test_compile_hybrid_table.ambr +7 -0
  44. snowflake_sqlalchemy-1.7.1/tests/custom_tables/__snapshots__/test_compile_iceberg_table.ambr +19 -0
  45. snowflake_sqlalchemy-1.7.1/tests/custom_tables/__snapshots__/test_compile_snowflake_table.ambr +35 -0
  46. snowflake_sqlalchemy-1.7.1/tests/custom_tables/__snapshots__/test_create_dynamic_table.ambr +7 -0
  47. snowflake_sqlalchemy-1.7.1/tests/custom_tables/__snapshots__/test_create_hybrid_table.ambr +7 -0
  48. snowflake_sqlalchemy-1.7.1/tests/custom_tables/__snapshots__/test_create_iceberg_table.ambr +14 -0
  49. snowflake_sqlalchemy-1.7.1/tests/custom_tables/__snapshots__/test_create_snowflake_table.ambr +4 -0
  50. snowflake_sqlalchemy-1.7.1/tests/custom_tables/__snapshots__/test_generic_options.ambr +13 -0
  51. snowflake_sqlalchemy-1.7.1/tests/custom_tables/__snapshots__/test_reflect_hybrid_table.ambr +4 -0
  52. snowflake_sqlalchemy-1.7.1/tests/custom_tables/__snapshots__/test_reflect_snowflake_table.ambr +29 -0
  53. snowflake_sqlalchemy-1.7.1/tests/custom_tables/test_compile_dynamic_table.py +271 -0
  54. snowflake_sqlalchemy-1.7.1/tests/custom_tables/test_compile_hybrid_table.py +52 -0
  55. snowflake_sqlalchemy-1.7.1/tests/custom_tables/test_compile_iceberg_table.py +116 -0
  56. snowflake_sqlalchemy-1.7.1/tests/custom_tables/test_compile_snowflake_table.py +180 -0
  57. snowflake_sqlalchemy-1.7.1/tests/custom_tables/test_create_dynamic_table.py +124 -0
  58. snowflake_sqlalchemy-1.7.1/tests/custom_tables/test_create_hybrid_table.py +95 -0
  59. snowflake_sqlalchemy-1.7.1/tests/custom_tables/test_create_iceberg_table.py +43 -0
  60. snowflake_sqlalchemy-1.7.1/tests/custom_tables/test_create_snowflake_table.py +66 -0
  61. snowflake_sqlalchemy-1.7.1/tests/custom_tables/test_generic_options.py +83 -0
  62. snowflake_sqlalchemy-1.7.1/tests/custom_tables/test_reflect_dynamic_table.py +88 -0
  63. snowflake_sqlalchemy-1.7.1/tests/custom_tables/test_reflect_hybrid_table.py +65 -0
  64. snowflake_sqlalchemy-1.7.1/tests/custom_tables/test_reflect_snowflake_table.py +92 -0
  65. snowflake_sqlalchemy-1.7.1/tests/sqlalchemy_test_suite/__init__.py +3 -0
  66. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/tests/test_copy.py +48 -17
  67. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/tests/test_core.py +89 -89
  68. snowflake_sqlalchemy-1.7.1/tests/test_custom_types.py +67 -0
  69. snowflake_sqlalchemy-1.7.1/tests/test_imports.py +64 -0
  70. snowflake_sqlalchemy-1.7.1/tests/test_index_reflection.py +34 -0
  71. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/tests/test_orm.py +178 -9
  72. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/tests/test_pandas.py +1 -1
  73. snowflake_sqlalchemy-1.7.1/tests/test_structured_datatypes.py +271 -0
  74. snowflake_sqlalchemy-1.7.1/tests/test_unit_structured_types.py +73 -0
  75. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/tests/util.py +2 -0
  76. snowflake_sqlalchemy-1.6.1/tests/test_custom_types.py +0 -36
  77. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/.gitignore +0 -0
  78. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/.gitmodules +0 -0
  79. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/LICENSE.txt +0 -0
  80. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/MANIFEST.in +0 -0
  81. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/ci/build.sh +0 -0
  82. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/ci/build_docker.sh +0 -0
  83. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/ci/docker/sqlalchemy_build/Dockerfile +0 -0
  84. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/ci/docker/sqlalchemy_build/scripts/entrypoint.sh +0 -0
  85. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/ci/set_base_image.sh +0 -0
  86. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/ci/test.sh +0 -0
  87. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/ci/test_docker.sh +0 -0
  88. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/ci/test_linux.sh +0 -0
  89. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/license_header.txt +0 -0
  90. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/setup.cfg +0 -0
  91. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/snyk/requirements.txt +0 -0
  92. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/snyk/requiremtnts.txt +0 -0
  93. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/snyk/update_requirements.py +0 -0
  94. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/src/snowflake/sqlalchemy/compat.py +0 -0
  95. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/src/snowflake/sqlalchemy/functions.py +0 -0
  96. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/src/snowflake/sqlalchemy/provision.py +0 -0
  97. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/src/snowflake/sqlalchemy/requirements.py +0 -0
  98. {snowflake_sqlalchemy-1.6.1/tests → snowflake_sqlalchemy-1.7.1/src/snowflake/sqlalchemy/sql}/__init__.py +0 -0
  99. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/src/snowflake/sqlalchemy/util.py +0 -0
  100. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/tested_requirements/requirements_310.reqs +0 -0
  101. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/tested_requirements/requirements_37.reqs +0 -0
  102. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/tested_requirements/requirements_38.reqs +0 -0
  103. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/tested_requirements/requirements_39.reqs +0 -0
  104. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/tests/README.rst +0 -0
  105. {snowflake_sqlalchemy-1.6.1/tests/sqlalchemy_test_suite → snowflake_sqlalchemy-1.7.1/tests}/__init__.py +0 -0
  106. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/tests/data/users.txt +0 -0
  107. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/tests/sqlalchemy_test_suite/README.md +0 -0
  108. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/tests/sqlalchemy_test_suite/conftest.py +0 -0
  109. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/tests/sqlalchemy_test_suite/test_suite.py +0 -0
  110. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/tests/sqlalchemy_test_suite/test_suite_20.py +0 -0
  111. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/tests/test_compiler.py +0 -0
  112. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/tests/test_create.py +0 -0
  113. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/tests/test_custom_functions.py +0 -0
  114. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/tests/test_geography.py +0 -0
  115. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/tests/test_geometry.py +0 -0
  116. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/tests/test_multivalues_insert.py +0 -0
  117. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/tests/test_qmark.py +0 -0
  118. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/tests/test_quote.py +0 -0
  119. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/tests/test_semi_structured_datatypes.py +0 -0
  120. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/tests/test_sequence.py +0 -0
  121. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/tests/test_timestamp.py +0 -0
  122. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/tests/test_unit_core.py +0 -0
  123. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/tests/test_unit_cte.py +0 -0
  124. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/tests/test_unit_types.py +0 -0
  125. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/tests/test_unit_url.py +0 -0
  126. {snowflake_sqlalchemy-1.6.1 → snowflake_sqlalchemy-1.7.1}/tox.ini +0 -0
@@ -4,6 +4,7 @@ repos:
4
4
  rev: v4.5.0
5
5
  hooks:
6
6
  - id: trailing-whitespace
7
+ exclude: '\.ambr$'
7
8
  - id: end-of-file-fixer
8
9
  - id: check-yaml
9
10
  exclude: .github/repo_meta.yaml
@@ -9,6 +9,21 @@ Source code is also available at:
9
9
 
10
10
  # Release Notes
11
11
 
12
+ - v1.7.1(December 02, 2024)
13
+ - Add support for partition by to copy into <location>
14
+ - Fix BOOLEAN type not found in snowdialect
15
+
16
+ - v1.7.0(November 21, 2024)
17
+
18
+ - Add support for dynamic tables and required options
19
+ - Add support for hybrid tables
20
+ - Fixed SAWarning when registering functions with existing name in default namespace
21
+ - Update options to be defined in key arguments instead of arguments.
22
+ - Add support for refresh_mode option in DynamicTable
23
+ - Add support for iceberg table with Snowflake Catalog
24
+ - Fix cluster by option to support explicit expressions
25
+ - Add support for MAP datatype
26
+
12
27
  - v1.6.1(July 9, 2024)
13
28
 
14
29
  - Update internal project workflow with pypi publishing
@@ -24,7 +39,7 @@ Source code is also available at:
24
39
 
25
40
  - v1.5.3(April 16, 2024)
26
41
 
27
- - Limit SQLAlchemy to < 2.0.0 before releasing version compatible with 2.0
42
+ - Limit SQLAlchemy to < 2.0.0 before releasing version compatible with 2.0
28
43
 
29
44
  - v1.5.2(April 11, 2024)
30
45
 
@@ -33,7 +48,7 @@ Source code is also available at:
33
48
 
34
49
  - v1.5.1(November 03, 2023)
35
50
 
36
- - Fixed a compatibility issue with Snowflake Behavioral Change 1057 on outer lateral join, for more details check https://docs.snowflake.com/en/release-notes/bcr-bundles/2023_04/bcr-1057.
51
+ - Fixed a compatibility issue with Snowflake Behavioral Change 1057 on outer lateral join, for more details check <https://docs.snowflake.com/en/release-notes/bcr-bundles/2023_04/bcr-1057>.
37
52
  - Fixed credentials with `externalbrowser` authentication not caching due to incorrect parsing of boolean query parameters.
38
53
  - This fixes other boolean parameter passing to driver as well.
39
54
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: snowflake-sqlalchemy
3
- Version: 1.6.1
3
+ Version: 1.7.1
4
4
  Summary: Snowflake SQLAlchemy Dialect
5
5
  Project-URL: Changelog, https://github.com/snowflakedb/snowflake-sqlalchemy/blob/main/DESCRIPTION.md
6
6
  Project-URL: Documentation, https://docs.snowflake.com/en/user-guide/sqlalchemy.html
@@ -8,8 +8,7 @@ Project-URL: Homepage, https://www.snowflake.com/
8
8
  Project-URL: Issues, https://github.com/snowflakedb/snowflake-sqlalchemy/issues
9
9
  Project-URL: Source, https://github.com/snowflakedb/snowflake-sqlalchemy
10
10
  Author-email: "Snowflake Inc." <triage-snowpark-python-api-dl@snowflake.com>
11
- License-Expression: Apache-2.0
12
- License-File: LICENSE.txt
11
+ License: Apache-2.0
13
12
  Keywords: Snowflake,analytics,cloud,database,db,warehouse
14
13
  Classifier: Development Status :: 5 - Production/Stable
15
14
  Classifier: Environment :: Console
@@ -46,6 +45,7 @@ Requires-Dist: pytest-cov; extra == 'development'
46
45
  Requires-Dist: pytest-rerunfailures; extra == 'development'
47
46
  Requires-Dist: pytest-timeout; extra == 'development'
48
47
  Requires-Dist: pytz; extra == 'development'
48
+ Requires-Dist: syrupy==4.6.1; extra == 'development'
49
49
  Provides-Extra: pandas
50
50
  Requires-Dist: snowflake-connector-python[pandas]; extra == 'pandas'
51
51
  Description-Content-Type: text/markdown
@@ -60,6 +60,11 @@ Description-Content-Type: text/markdown
60
60
 
61
61
  Snowflake SQLAlchemy runs on the top of the Snowflake Connector for Python as a [dialect](http://docs.sqlalchemy.org/en/latest/dialects/) to bridge a Snowflake database and SQLAlchemy applications.
62
62
 
63
+
64
+ | :exclamation: | For production-affecting or urgent issues related to the connector, please [create a case with Snowflake Support](https://community.snowflake.com/s/article/How-To-Submit-a-Support-Case-in-Snowflake-Lodge). |
65
+ |---------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
66
+
67
+
63
68
  ## Prerequisites
64
69
 
65
70
  ### Snowflake Connector for Python
@@ -256,7 +261,7 @@ finally:
256
261
 
257
262
  # Best
258
263
  try:
259
- with engine.connext() as connection:
264
+ with engine.connect() as connection:
260
265
  connection.execute(text(<SQL>))
261
266
  # or
262
267
  connection.exec_driver_sql(<SQL>)
@@ -277,11 +282,43 @@ t = Table('mytable', metadata,
277
282
 
278
283
  ### Object Name Case Handling
279
284
 
280
- Snowflake stores all case-insensitive object names in uppercase text. In contrast, SQLAlchemy considers all lowercase object names to be case-insensitive. Snowflake SQLAlchemy converts the object name case during schema-level communication, i.e. during table and index reflection. If you use uppercase object names, SQLAlchemy assumes they are case-sensitive and encloses the names with quotes. This behavior will cause mismatches agaisnt data dictionary data received from Snowflake, so unless identifier names have been truly created as case sensitive using quotes, e.g., `"TestDb"`, all lowercase names should be used on the SQLAlchemy side.
285
+ Snowflake stores all case-insensitive object names in uppercase text. In contrast, SQLAlchemy considers all lowercase object names to be case-insensitive. Snowflake SQLAlchemy converts the object name case during schema-level communication, i.e. during table and index reflection. If you use uppercase object names, SQLAlchemy assumes they are case-sensitive and encloses the names with quotes. This behavior will cause mismatches against data dictionary data received from Snowflake, so unless identifier names have been truly created as case sensitive using quotes, e.g., `"TestDb"`, all lowercase names should be used on the SQLAlchemy side.
281
286
 
282
287
  ### Index Support
283
288
 
284
- Snowflake does not utilize indexes, so neither does Snowflake SQLAlchemy.
289
+ Indexes are supported only for Hybrid Tables in Snowflake SQLAlchemy. For more details on limitations and use cases, refer to the [Create Index documentation](https://docs.snowflake.com/en/sql-reference/constraints-indexes.html). You can create an index using the following methods:
290
+
291
+ #### Single Column Index
292
+
293
+ You can create a single column index by setting the `index=True` parameter on the column or by explicitly defining an `Index` object.
294
+
295
+ ```python
296
+ hybrid_test_table_1 = HybridTable(
297
+ "table_name",
298
+ metadata,
299
+ Column("column1", Integer, primary_key=True),
300
+ Column("column2", String, index=True),
301
+ Index("index_1", "column1", "column2")
302
+ )
303
+
304
+ metadata.create_all(engine_testaccount)
305
+ ```
306
+
307
+ #### Multi-Column Index
308
+
309
+ For multi-column indexes, you define the `Index` object specifying the columns that should be indexed.
310
+
311
+ ```python
312
+ hybrid_test_table_1 = HybridTable(
313
+ "table_name",
314
+ metadata,
315
+ Column("column1", Integer, primary_key=True),
316
+ Column("column2", String),
317
+ Index("index_1", "column1", "column2")
318
+ )
319
+
320
+ metadata.create_all(engine_testaccount)
321
+ ```
285
322
 
286
323
  ### Numpy Data Type Support
287
324
 
@@ -392,7 +429,7 @@ This example shows how to create a table with two columns, `id` and `name`, as t
392
429
  t = Table('myuser', metadata,
393
430
  Column('id', Integer, primary_key=True),
394
431
  Column('name', String),
395
- snowflake_clusterby=['id', 'name'], ...
432
+ snowflake_clusterby=['id', 'name', text('id > 5')], ...
396
433
  )
397
434
  metadata.create_all(engine)
398
435
  ```
@@ -508,6 +545,117 @@ copy_into = CopyIntoStorage(from_=users,
508
545
  connection.execute(copy_into)
509
546
  ```
510
547
 
548
+ ### Iceberg Table with Snowflake Catalog support
549
+
550
+ Snowflake SQLAlchemy supports Iceberg Tables with the Snowflake Catalog, along with various related parameters. For detailed information about Iceberg Tables, refer to the Snowflake [CREATE ICEBERG](https://docs.snowflake.com/en/sql-reference/sql/create-iceberg-table-snowflake) documentation.
551
+
552
+ To create an Iceberg Table using Snowflake SQLAlchemy, you can define the table using the SQLAlchemy Core syntax as follows:
553
+
554
+ ```python
555
+ table = IcebergTable(
556
+ "myuser",
557
+ metadata,
558
+ Column("id", Integer, primary_key=True),
559
+ Column("name", String),
560
+ external_volume=external_volume_name,
561
+ base_location="my_iceberg_table",
562
+ as_query="SELECT * FROM table"
563
+ )
564
+ ```
565
+
566
+ Alternatively, you can define the table using a declarative approach:
567
+
568
+ ```python
569
+ class MyUser(Base):
570
+ __tablename__ = "myuser"
571
+
572
+ @classmethod
573
+ def __table_cls__(cls, name, metadata, *arg, **kw):
574
+ return IcebergTable(name, metadata, *arg, **kw)
575
+
576
+ __table_args__ = {
577
+ "external_volume": "my_external_volume",
578
+ "base_location": "my_iceberg_table",
579
+ "as_query": "SELECT * FROM table",
580
+ }
581
+
582
+ id = Column(Integer, primary_key=True)
583
+ name = Column(String)
584
+ ```
585
+
586
+ ### Hybrid Table support
587
+
588
+ Snowflake SQLAlchemy supports Hybrid Tables with indexes. For detailed information, refer to the Snowflake [CREATE HYBRID TABLE](https://docs.snowflake.com/en/sql-reference/sql/create-hybrid-table) documentation.
589
+
590
+ To create a Hybrid Table and add an index, you can use the SQLAlchemy Core syntax as follows:
591
+
592
+ ```python
593
+ table = HybridTable(
594
+ "myuser",
595
+ metadata,
596
+ Column("id", Integer, primary_key=True),
597
+ Column("name", String),
598
+ Index("idx_name", "name")
599
+ )
600
+ ```
601
+
602
+ Alternatively, you can define the table using the declarative approach:
603
+
604
+ ```python
605
+ class MyUser(Base):
606
+ __tablename__ = "myuser"
607
+
608
+ @classmethod
609
+ def __table_cls__(cls, name, metadata, *arg, **kw):
610
+ return HybridTable(name, metadata, *arg, **kw)
611
+
612
+ __table_args__ = (
613
+ Index("idx_name", "name"),
614
+ )
615
+
616
+ id = Column(Integer, primary_key=True)
617
+ name = Column(String)
618
+ ```
619
+
620
+ ### Dynamic Tables support
621
+
622
+ Snowflake SQLAlchemy supports Dynamic Tables. For detailed information, refer to the Snowflake [CREATE DYNAMIC TABLE](https://docs.snowflake.com/en/sql-reference/sql/create-dynamic-table) documentation.
623
+
624
+ To create a Dynamic Table, you can use the SQLAlchemy Core syntax as follows:
625
+
626
+ ```python
627
+ dynamic_test_table_1 = DynamicTable(
628
+ "dynamic_MyUser",
629
+ metadata,
630
+ Column("id", Integer),
631
+ Column("name", String),
632
+ target_lag=(1, TimeUnit.HOURS), # Additionally, you can use SnowflakeKeyword.DOWNSTREAM
633
+ warehouse='test_wh',
634
+ refresh_mode=SnowflakeKeyword.FULL,
635
+ as_query="SELECT id, name from MyUser;"
636
+ )
637
+ ```
638
+
639
+ Alternatively, you can define a table without columns using the SQLAlchemy `select()` construct:
640
+
641
+ ```python
642
+ dynamic_test_table_1 = DynamicTable(
643
+ "dynamic_MyUser",
644
+ metadata,
645
+ target_lag=(1, TimeUnit.HOURS),
646
+ warehouse='test_wh',
647
+ refresh_mode=SnowflakeKeyword.FULL,
648
+ as_query=select(MyUser.id, MyUser.name)
649
+ )
650
+ ```
651
+
652
+ ### Notes
653
+
654
+ - Defining a primary key in a Dynamic Table is not supported, meaning declarative tables don’t support Dynamic Tables.
655
+ - When using the `as_query` parameter with a string, you must explicitly define the columns. However, if you use the SQLAlchemy `select()` construct, you don’t need to explicitly define the columns.
656
+ - Direct data insertion into Dynamic Tables is not supported.
657
+
658
+
511
659
  ## Support
512
660
 
513
661
  Feel free to file an issue or submit a PR here for general cases. For official support, contact Snowflake support at:
@@ -8,6 +8,11 @@
8
8
 
9
9
  Snowflake SQLAlchemy runs on the top of the Snowflake Connector for Python as a [dialect](http://docs.sqlalchemy.org/en/latest/dialects/) to bridge a Snowflake database and SQLAlchemy applications.
10
10
 
11
+
12
+ | :exclamation: | For production-affecting or urgent issues related to the connector, please [create a case with Snowflake Support](https://community.snowflake.com/s/article/How-To-Submit-a-Support-Case-in-Snowflake-Lodge). |
13
+ |---------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
14
+
15
+
11
16
  ## Prerequisites
12
17
 
13
18
  ### Snowflake Connector for Python
@@ -204,7 +209,7 @@ finally:
204
209
 
205
210
  # Best
206
211
  try:
207
- with engine.connext() as connection:
212
+ with engine.connect() as connection:
208
213
  connection.execute(text(<SQL>))
209
214
  # or
210
215
  connection.exec_driver_sql(<SQL>)
@@ -225,11 +230,43 @@ t = Table('mytable', metadata,
225
230
 
226
231
  ### Object Name Case Handling
227
232
 
228
- Snowflake stores all case-insensitive object names in uppercase text. In contrast, SQLAlchemy considers all lowercase object names to be case-insensitive. Snowflake SQLAlchemy converts the object name case during schema-level communication, i.e. during table and index reflection. If you use uppercase object names, SQLAlchemy assumes they are case-sensitive and encloses the names with quotes. This behavior will cause mismatches agaisnt data dictionary data received from Snowflake, so unless identifier names have been truly created as case sensitive using quotes, e.g., `"TestDb"`, all lowercase names should be used on the SQLAlchemy side.
233
+ Snowflake stores all case-insensitive object names in uppercase text. In contrast, SQLAlchemy considers all lowercase object names to be case-insensitive. Snowflake SQLAlchemy converts the object name case during schema-level communication, i.e. during table and index reflection. If you use uppercase object names, SQLAlchemy assumes they are case-sensitive and encloses the names with quotes. This behavior will cause mismatches against data dictionary data received from Snowflake, so unless identifier names have been truly created as case sensitive using quotes, e.g., `"TestDb"`, all lowercase names should be used on the SQLAlchemy side.
229
234
 
230
235
  ### Index Support
231
236
 
232
- Snowflake does not utilize indexes, so neither does Snowflake SQLAlchemy.
237
+ Indexes are supported only for Hybrid Tables in Snowflake SQLAlchemy. For more details on limitations and use cases, refer to the [Create Index documentation](https://docs.snowflake.com/en/sql-reference/constraints-indexes.html). You can create an index using the following methods:
238
+
239
+ #### Single Column Index
240
+
241
+ You can create a single column index by setting the `index=True` parameter on the column or by explicitly defining an `Index` object.
242
+
243
+ ```python
244
+ hybrid_test_table_1 = HybridTable(
245
+ "table_name",
246
+ metadata,
247
+ Column("column1", Integer, primary_key=True),
248
+ Column("column2", String, index=True),
249
+ Index("index_1", "column1", "column2")
250
+ )
251
+
252
+ metadata.create_all(engine_testaccount)
253
+ ```
254
+
255
+ #### Multi-Column Index
256
+
257
+ For multi-column indexes, you define the `Index` object specifying the columns that should be indexed.
258
+
259
+ ```python
260
+ hybrid_test_table_1 = HybridTable(
261
+ "table_name",
262
+ metadata,
263
+ Column("column1", Integer, primary_key=True),
264
+ Column("column2", String),
265
+ Index("index_1", "column1", "column2")
266
+ )
267
+
268
+ metadata.create_all(engine_testaccount)
269
+ ```
233
270
 
234
271
  ### Numpy Data Type Support
235
272
 
@@ -340,7 +377,7 @@ This example shows how to create a table with two columns, `id` and `name`, as t
340
377
  t = Table('myuser', metadata,
341
378
  Column('id', Integer, primary_key=True),
342
379
  Column('name', String),
343
- snowflake_clusterby=['id', 'name'], ...
380
+ snowflake_clusterby=['id', 'name', text('id > 5')], ...
344
381
  )
345
382
  metadata.create_all(engine)
346
383
  ```
@@ -456,6 +493,117 @@ copy_into = CopyIntoStorage(from_=users,
456
493
  connection.execute(copy_into)
457
494
  ```
458
495
 
496
+ ### Iceberg Table with Snowflake Catalog support
497
+
498
+ Snowflake SQLAlchemy supports Iceberg Tables with the Snowflake Catalog, along with various related parameters. For detailed information about Iceberg Tables, refer to the Snowflake [CREATE ICEBERG](https://docs.snowflake.com/en/sql-reference/sql/create-iceberg-table-snowflake) documentation.
499
+
500
+ To create an Iceberg Table using Snowflake SQLAlchemy, you can define the table using the SQLAlchemy Core syntax as follows:
501
+
502
+ ```python
503
+ table = IcebergTable(
504
+ "myuser",
505
+ metadata,
506
+ Column("id", Integer, primary_key=True),
507
+ Column("name", String),
508
+ external_volume=external_volume_name,
509
+ base_location="my_iceberg_table",
510
+ as_query="SELECT * FROM table"
511
+ )
512
+ ```
513
+
514
+ Alternatively, you can define the table using a declarative approach:
515
+
516
+ ```python
517
+ class MyUser(Base):
518
+ __tablename__ = "myuser"
519
+
520
+ @classmethod
521
+ def __table_cls__(cls, name, metadata, *arg, **kw):
522
+ return IcebergTable(name, metadata, *arg, **kw)
523
+
524
+ __table_args__ = {
525
+ "external_volume": "my_external_volume",
526
+ "base_location": "my_iceberg_table",
527
+ "as_query": "SELECT * FROM table",
528
+ }
529
+
530
+ id = Column(Integer, primary_key=True)
531
+ name = Column(String)
532
+ ```
533
+
534
+ ### Hybrid Table support
535
+
536
+ Snowflake SQLAlchemy supports Hybrid Tables with indexes. For detailed information, refer to the Snowflake [CREATE HYBRID TABLE](https://docs.snowflake.com/en/sql-reference/sql/create-hybrid-table) documentation.
537
+
538
+ To create a Hybrid Table and add an index, you can use the SQLAlchemy Core syntax as follows:
539
+
540
+ ```python
541
+ table = HybridTable(
542
+ "myuser",
543
+ metadata,
544
+ Column("id", Integer, primary_key=True),
545
+ Column("name", String),
546
+ Index("idx_name", "name")
547
+ )
548
+ ```
549
+
550
+ Alternatively, you can define the table using the declarative approach:
551
+
552
+ ```python
553
+ class MyUser(Base):
554
+ __tablename__ = "myuser"
555
+
556
+ @classmethod
557
+ def __table_cls__(cls, name, metadata, *arg, **kw):
558
+ return HybridTable(name, metadata, *arg, **kw)
559
+
560
+ __table_args__ = (
561
+ Index("idx_name", "name"),
562
+ )
563
+
564
+ id = Column(Integer, primary_key=True)
565
+ name = Column(String)
566
+ ```
567
+
568
+ ### Dynamic Tables support
569
+
570
+ Snowflake SQLAlchemy supports Dynamic Tables. For detailed information, refer to the Snowflake [CREATE DYNAMIC TABLE](https://docs.snowflake.com/en/sql-reference/sql/create-dynamic-table) documentation.
571
+
572
+ To create a Dynamic Table, you can use the SQLAlchemy Core syntax as follows:
573
+
574
+ ```python
575
+ dynamic_test_table_1 = DynamicTable(
576
+ "dynamic_MyUser",
577
+ metadata,
578
+ Column("id", Integer),
579
+ Column("name", String),
580
+ target_lag=(1, TimeUnit.HOURS), # Additionally, you can use SnowflakeKeyword.DOWNSTREAM
581
+ warehouse='test_wh',
582
+ refresh_mode=SnowflakeKeyword.FULL,
583
+ as_query="SELECT id, name from MyUser;"
584
+ )
585
+ ```
586
+
587
+ Alternatively, you can define a table without columns using the SQLAlchemy `select()` construct:
588
+
589
+ ```python
590
+ dynamic_test_table_1 = DynamicTable(
591
+ "dynamic_MyUser",
592
+ metadata,
593
+ target_lag=(1, TimeUnit.HOURS),
594
+ warehouse='test_wh',
595
+ refresh_mode=SnowflakeKeyword.FULL,
596
+ as_query=select(MyUser.id, MyUser.name)
597
+ )
598
+ ```
599
+
600
+ ### Notes
601
+
602
+ - Defining a primary key in a Dynamic Table is not supported, meaning declarative tables don’t support Dynamic Tables.
603
+ - When using the `as_query` parameter with a string, you must explicitly define the columns. However, if you use the SQLAlchemy `select()` construct, you don’t need to explicitly define the columns.
604
+ - Direct data insertion into Dynamic Tables is not supported.
605
+
606
+
459
607
  ## Support
460
608
 
461
609
  Feel free to file an issue or submit a PR here for general cases. For official support, contact Snowflake support at:
@@ -53,6 +53,7 @@ development = [
53
53
  "pytz",
54
54
  "numpy",
55
55
  "mock",
56
+ "syrupy==4.6.1",
56
57
  ]
57
58
  pandas = ["snowflake-connector-python[pandas]"]
58
59
 
@@ -91,6 +92,7 @@ SQLACHEMY_WARN_20 = "1"
91
92
  check = "pre-commit run --all-files"
92
93
  test-dialect = "pytest -ra -vvv --tb=short --cov snowflake.sqlalchemy --cov-append --junitxml ./junit.xml --ignore=tests/sqlalchemy_test_suite tests/"
93
94
  test-dialect-compatibility = "pytest -ra -vvv --tb=short --cov snowflake.sqlalchemy --cov-append --junitxml ./junit.xml tests/sqlalchemy_test_suite"
95
+ test-dialect-aws = "pytest -m \"aws\" -ra -vvv --tb=short --cov snowflake.sqlalchemy --cov-append --junitxml ./junit.xml --ignore=tests/sqlalchemy_test_suite tests/"
94
96
  gh-cache-sum = "python -VV | sha256sum | cut -d' ' -f1"
95
97
  check-import = "python -c 'import snowflake.sqlalchemy; print(snowflake.sqlalchemy.__version__)'"
96
98
 
@@ -109,6 +111,7 @@ line-length = 88
109
111
  line-length = 88
110
112
 
111
113
  [tool.pytest.ini_options]
114
+ addopts = "-m 'not feature_max_lob_size and not aws and not requires_external_volume'"
112
115
  markers = [
113
116
  # Optional dependency groups markers
114
117
  "lambda: AWS lambda tests",
@@ -125,5 +128,7 @@ markers = [
125
128
  # Other markers
126
129
  "timeout: tests that need a timeout time",
127
130
  "internal: tests that could but should only run on our internal CI",
131
+ "requires_external_volume: tests that needs a external volume to be executed",
128
132
  "external: tests that could but should only run on our external CI",
133
+ "feature_max_lob_size: tests that could but should only run on our external CI",
129
134
  ]
@@ -9,7 +9,7 @@ if sys.version_info < (3, 8):
9
9
  else:
10
10
  import importlib.metadata as importlib_metadata
11
11
 
12
- from sqlalchemy.types import (
12
+ from sqlalchemy.types import ( # noqa
13
13
  BIGINT,
14
14
  BINARY,
15
15
  BOOLEAN,
@@ -27,8 +27,8 @@ from sqlalchemy.types import (
27
27
  VARCHAR,
28
28
  )
29
29
 
30
- from . import base, snowdialect
31
- from .custom_commands import (
30
+ from . import base, snowdialect # noqa
31
+ from .custom_commands import ( # noqa
32
32
  AWSBucket,
33
33
  AzureContainer,
34
34
  CopyFormatter,
@@ -41,7 +41,7 @@ from .custom_commands import (
41
41
  MergeInto,
42
42
  PARQUETFormatter,
43
43
  )
44
- from .custom_types import (
44
+ from .custom_types import ( # noqa
45
45
  ARRAY,
46
46
  BYTEINT,
47
47
  CHARACTER,
@@ -50,6 +50,7 @@ from .custom_types import (
50
50
  FIXED,
51
51
  GEOGRAPHY,
52
52
  GEOMETRY,
53
+ MAP,
53
54
  NUMBER,
54
55
  OBJECT,
55
56
  STRING,
@@ -61,13 +62,30 @@ from .custom_types import (
61
62
  VARBINARY,
62
63
  VARIANT,
63
64
  )
64
- from .util import _url as URL
65
+ from .sql.custom_schema import ( # noqa
66
+ DynamicTable,
67
+ HybridTable,
68
+ IcebergTable,
69
+ SnowflakeTable,
70
+ )
71
+ from .sql.custom_schema.options import ( # noqa
72
+ AsQueryOption,
73
+ ClusterByOption,
74
+ IdentifierOption,
75
+ KeywordOption,
76
+ LiteralOption,
77
+ SnowflakeKeyword,
78
+ TableOptionKey,
79
+ TargetLagOption,
80
+ TimeUnit,
81
+ )
82
+ from .util import _url as URL # noqa
65
83
 
66
84
  base.dialect = dialect = snowdialect.dialect
67
85
 
68
86
  __version__ = importlib_metadata.version("snowflake-sqlalchemy")
69
87
 
70
- __all__ = (
88
+ _custom_types = (
71
89
  "BIGINT",
72
90
  "BINARY",
73
91
  "BOOLEAN",
@@ -102,6 +120,10 @@ __all__ = (
102
120
  "TINYINT",
103
121
  "VARBINARY",
104
122
  "VARIANT",
123
+ "MAP",
124
+ )
125
+
126
+ _custom_commands = (
105
127
  "MergeInto",
106
128
  "CSVFormatter",
107
129
  "JSONFormatter",
@@ -114,3 +136,27 @@ __all__ = (
114
136
  "CreateStage",
115
137
  "CreateFileFormat",
116
138
  )
139
+
140
+ _custom_tables = ("HybridTable", "DynamicTable", "IcebergTable", "SnowflakeTable")
141
+
142
+ _custom_table_options = (
143
+ "AsQueryOption",
144
+ "TargetLagOption",
145
+ "LiteralOption",
146
+ "IdentifierOption",
147
+ "KeywordOption",
148
+ "ClusterByOption",
149
+ )
150
+
151
+ _enums = (
152
+ "TimeUnit",
153
+ "TableOptionKey",
154
+ "SnowflakeKeyword",
155
+ )
156
+ __all__ = (
157
+ *_custom_types,
158
+ *_custom_commands,
159
+ *_custom_tables,
160
+ *_custom_table_options,
161
+ *_enums,
162
+ )
@@ -10,3 +10,5 @@ PARAM_INTERNAL_APPLICATION_VERSION = "internal_application_version"
10
10
 
11
11
  APPLICATION_NAME = "SnowflakeSQLAlchemy"
12
12
  SNOWFLAKE_SQLALCHEMY_VERSION = VERSION
13
+ DIALECT_NAME = "snowflake"
14
+ NOT_NULL = "NOT NULL"