snowflake-sqlalchemy 1.7.0__tar.gz → 1.7.2__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 (130) hide show
  1. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/DESCRIPTION.md +13 -2
  2. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/PKG-INFO +229 -6
  3. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/README.md +224 -3
  4. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/pyproject.toml +3 -0
  5. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/src/snowflake/sqlalchemy/base.py +40 -9
  6. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/src/snowflake/sqlalchemy/custom_commands.py +7 -2
  7. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/src/snowflake/sqlalchemy/custom_types.py +33 -3
  8. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/src/snowflake/sqlalchemy/parser/custom_type_parser.py +78 -23
  9. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/src/snowflake/sqlalchemy/snowdialect.py +136 -105
  10. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/src/snowflake/sqlalchemy/version.py +1 -1
  11. snowflake_sqlalchemy-1.7.2/tests/__snapshots__/test_structured_datatypes.ambr +246 -0
  12. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/tests/conftest.py +27 -0
  13. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/tests/test_compiler.py +16 -1
  14. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/tests/test_copy.py +48 -17
  15. snowflake_sqlalchemy-1.7.2/tests/test_imports.py +64 -0
  16. snowflake_sqlalchemy-1.7.2/tests/test_index_reflection.py +68 -0
  17. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/tests/test_quote.py +23 -0
  18. snowflake_sqlalchemy-1.7.2/tests/test_quote_identifiers.py +43 -0
  19. snowflake_sqlalchemy-1.7.2/tests/test_structured_datatypes.py +582 -0
  20. snowflake_sqlalchemy-1.7.2/tests/test_transactions.py +157 -0
  21. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/tests/test_unit_structured_types.py +10 -2
  22. snowflake_sqlalchemy-1.7.0/tests/__snapshots__/test_structured_datatypes.ambr +0 -90
  23. snowflake_sqlalchemy-1.7.0/tests/test_index_reflection.py +0 -34
  24. snowflake_sqlalchemy-1.7.0/tests/test_structured_datatypes.py +0 -271
  25. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/.gitignore +0 -0
  26. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/.gitmodules +0 -0
  27. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/.pre-commit-config.yaml +0 -0
  28. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/LICENSE.txt +0 -0
  29. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/MANIFEST.in +0 -0
  30. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/ci/build.sh +0 -0
  31. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/ci/build_docker.sh +0 -0
  32. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/ci/docker/sqlalchemy_build/Dockerfile +0 -0
  33. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/ci/docker/sqlalchemy_build/scripts/entrypoint.sh +0 -0
  34. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/ci/set_base_image.sh +0 -0
  35. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/ci/test.sh +0 -0
  36. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/ci/test_docker.sh +0 -0
  37. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/ci/test_linux.sh +0 -0
  38. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/license_header.txt +0 -0
  39. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/setup.cfg +0 -0
  40. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/snyk/requirements.txt +0 -0
  41. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/snyk/requiremtnts.txt +0 -0
  42. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/snyk/update_requirements.py +0 -0
  43. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/src/snowflake/sqlalchemy/__init__.py +0 -0
  44. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/src/snowflake/sqlalchemy/_constants.py +0 -0
  45. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/src/snowflake/sqlalchemy/compat.py +0 -0
  46. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/src/snowflake/sqlalchemy/exc.py +0 -0
  47. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/src/snowflake/sqlalchemy/functions.py +0 -0
  48. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/src/snowflake/sqlalchemy/provision.py +0 -0
  49. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/src/snowflake/sqlalchemy/requirements.py +0 -0
  50. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/src/snowflake/sqlalchemy/sql/__init__.py +0 -0
  51. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/src/snowflake/sqlalchemy/sql/custom_schema/__init__.py +0 -0
  52. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/src/snowflake/sqlalchemy/sql/custom_schema/clustered_table.py +0 -0
  53. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/src/snowflake/sqlalchemy/sql/custom_schema/custom_table_base.py +0 -0
  54. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/src/snowflake/sqlalchemy/sql/custom_schema/custom_table_prefix.py +0 -0
  55. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/src/snowflake/sqlalchemy/sql/custom_schema/dynamic_table.py +0 -0
  56. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/src/snowflake/sqlalchemy/sql/custom_schema/hybrid_table.py +0 -0
  57. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/src/snowflake/sqlalchemy/sql/custom_schema/iceberg_table.py +0 -0
  58. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/src/snowflake/sqlalchemy/sql/custom_schema/options/__init__.py +0 -0
  59. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/src/snowflake/sqlalchemy/sql/custom_schema/options/as_query_option.py +0 -0
  60. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/src/snowflake/sqlalchemy/sql/custom_schema/options/cluster_by_option.py +0 -0
  61. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/src/snowflake/sqlalchemy/sql/custom_schema/options/identifier_option.py +0 -0
  62. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/src/snowflake/sqlalchemy/sql/custom_schema/options/invalid_table_option.py +0 -0
  63. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/src/snowflake/sqlalchemy/sql/custom_schema/options/keyword_option.py +0 -0
  64. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/src/snowflake/sqlalchemy/sql/custom_schema/options/keywords.py +0 -0
  65. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/src/snowflake/sqlalchemy/sql/custom_schema/options/literal_option.py +0 -0
  66. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/src/snowflake/sqlalchemy/sql/custom_schema/options/table_option.py +0 -0
  67. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/src/snowflake/sqlalchemy/sql/custom_schema/options/target_lag_option.py +0 -0
  68. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/src/snowflake/sqlalchemy/sql/custom_schema/snowflake_table.py +0 -0
  69. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/src/snowflake/sqlalchemy/sql/custom_schema/table_from_query.py +0 -0
  70. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/src/snowflake/sqlalchemy/util.py +0 -0
  71. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/tested_requirements/requirements_310.reqs +0 -0
  72. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/tested_requirements/requirements_37.reqs +0 -0
  73. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/tested_requirements/requirements_38.reqs +0 -0
  74. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/tested_requirements/requirements_39.reqs +0 -0
  75. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/tests/README.rst +0 -0
  76. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/tests/__init__.py +0 -0
  77. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/tests/__snapshots__/test_compile_dynamic_table.ambr +0 -0
  78. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/tests/__snapshots__/test_core.ambr +0 -0
  79. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/tests/__snapshots__/test_orm.ambr +0 -0
  80. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/tests/__snapshots__/test_reflect_dynamic_table.ambr +0 -0
  81. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/tests/__snapshots__/test_unit_structured_types.ambr +0 -0
  82. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/tests/custom_tables/__init__.py +0 -0
  83. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/tests/custom_tables/__snapshots__/test_compile_dynamic_table.ambr +0 -0
  84. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/tests/custom_tables/__snapshots__/test_compile_hybrid_table.ambr +0 -0
  85. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/tests/custom_tables/__snapshots__/test_compile_iceberg_table.ambr +0 -0
  86. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/tests/custom_tables/__snapshots__/test_compile_snowflake_table.ambr +0 -0
  87. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/tests/custom_tables/__snapshots__/test_create_dynamic_table.ambr +0 -0
  88. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/tests/custom_tables/__snapshots__/test_create_hybrid_table.ambr +0 -0
  89. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/tests/custom_tables/__snapshots__/test_create_iceberg_table.ambr +0 -0
  90. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/tests/custom_tables/__snapshots__/test_create_snowflake_table.ambr +0 -0
  91. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/tests/custom_tables/__snapshots__/test_generic_options.ambr +0 -0
  92. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/tests/custom_tables/__snapshots__/test_reflect_hybrid_table.ambr +0 -0
  93. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/tests/custom_tables/__snapshots__/test_reflect_snowflake_table.ambr +0 -0
  94. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/tests/custom_tables/test_compile_dynamic_table.py +0 -0
  95. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/tests/custom_tables/test_compile_hybrid_table.py +0 -0
  96. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/tests/custom_tables/test_compile_iceberg_table.py +0 -0
  97. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/tests/custom_tables/test_compile_snowflake_table.py +0 -0
  98. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/tests/custom_tables/test_create_dynamic_table.py +0 -0
  99. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/tests/custom_tables/test_create_hybrid_table.py +0 -0
  100. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/tests/custom_tables/test_create_iceberg_table.py +0 -0
  101. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/tests/custom_tables/test_create_snowflake_table.py +0 -0
  102. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/tests/custom_tables/test_generic_options.py +0 -0
  103. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/tests/custom_tables/test_reflect_dynamic_table.py +0 -0
  104. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/tests/custom_tables/test_reflect_hybrid_table.py +0 -0
  105. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/tests/custom_tables/test_reflect_snowflake_table.py +0 -0
  106. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/tests/data/users.txt +0 -0
  107. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/tests/sqlalchemy_test_suite/README.md +0 -0
  108. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/tests/sqlalchemy_test_suite/__init__.py +0 -0
  109. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/tests/sqlalchemy_test_suite/conftest.py +0 -0
  110. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/tests/sqlalchemy_test_suite/test_suite.py +0 -0
  111. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/tests/sqlalchemy_test_suite/test_suite_20.py +0 -0
  112. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/tests/test_core.py +0 -0
  113. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/tests/test_create.py +0 -0
  114. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/tests/test_custom_functions.py +0 -0
  115. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/tests/test_custom_types.py +0 -0
  116. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/tests/test_geography.py +0 -0
  117. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/tests/test_geometry.py +0 -0
  118. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/tests/test_multivalues_insert.py +0 -0
  119. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/tests/test_orm.py +0 -0
  120. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/tests/test_pandas.py +0 -0
  121. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/tests/test_qmark.py +0 -0
  122. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/tests/test_semi_structured_datatypes.py +0 -0
  123. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/tests/test_sequence.py +0 -0
  124. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/tests/test_timestamp.py +0 -0
  125. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/tests/test_unit_core.py +0 -0
  126. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/tests/test_unit_cte.py +0 -0
  127. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/tests/test_unit_types.py +0 -0
  128. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/tests/test_unit_url.py +0 -0
  129. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/tests/util.py +0 -0
  130. {snowflake_sqlalchemy-1.7.0 → snowflake_sqlalchemy-1.7.2}/tox.ini +0 -0
@@ -9,8 +9,19 @@ Source code is also available at:
9
9
 
10
10
  # Release Notes
11
11
 
12
- - v1.7.0(November 22, 2024)
13
-
12
+ - v1.7.2(December 18, 2024)
13
+ - Fix quoting of `_` as column name
14
+ - Fix index columns was not being reflected
15
+ - Fix index reflection cache not working
16
+ - Add support for structured OBJECT datatype
17
+ - Add support for structured ARRAY datatype
18
+
19
+ - v1.7.1(December 02, 2024)
20
+ - Add support for partition by to copy into <location>
21
+ - Fix BOOLEAN type not found in snowdialect
22
+ - Add support for autocommit Isolation Level
23
+
24
+ - v1.7.0(November 21, 2024)
14
25
  - Add support for dynamic tables and required options
15
26
  - Add support for hybrid tables
16
27
  - Fixed SAWarning when registering functions with existing name in default namespace
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: snowflake-sqlalchemy
3
- Version: 1.7.0
3
+ Version: 1.7.2
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,7 +8,8 @@ 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: Apache-2.0
11
+ License-Expression: Apache-2.0
12
+ License-File: LICENSE.txt
12
13
  Keywords: Snowflake,analytics,cloud,database,db,warehouse
13
14
  Classifier: Development Status :: 5 - Production/Stable
14
15
  Classifier: Environment :: Console
@@ -45,6 +46,7 @@ Requires-Dist: pytest-cov; extra == 'development'
45
46
  Requires-Dist: pytest-rerunfailures; extra == 'development'
46
47
  Requires-Dist: pytest-timeout; extra == 'development'
47
48
  Requires-Dist: pytz; extra == 'development'
49
+ Requires-Dist: setuptools; extra == 'development'
48
50
  Requires-Dist: syrupy==4.6.1; extra == 'development'
49
51
  Provides-Extra: pandas
50
52
  Requires-Dist: snowflake-connector-python[pandas]; extra == 'pandas'
@@ -60,6 +62,11 @@ Description-Content-Type: text/markdown
60
62
 
61
63
  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
64
 
65
+
66
+ | :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). |
67
+ |---------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
68
+
69
+
63
70
  ## Prerequisites
64
71
 
65
72
  ### Snowflake Connector for Python
@@ -256,7 +263,7 @@ finally:
256
263
 
257
264
  # Best
258
265
  try:
259
- with engine.connext() as connection:
266
+ with engine.connect() as connection:
260
267
  connection.execute(text(<SQL>))
261
268
  # or
262
269
  connection.exec_driver_sql(<SQL>)
@@ -277,11 +284,43 @@ t = Table('mytable', metadata,
277
284
 
278
285
  ### Object Name Case Handling
279
286
 
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.
287
+ 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
288
 
282
289
  ### Index Support
283
290
 
284
- Snowflake does not utilize indexes, so neither does Snowflake SQLAlchemy.
291
+ 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:
292
+
293
+ #### Single Column Index
294
+
295
+ You can create a single column index by setting the `index=True` parameter on the column or by explicitly defining an `Index` object.
296
+
297
+ ```python
298
+ hybrid_test_table_1 = HybridTable(
299
+ "table_name",
300
+ metadata,
301
+ Column("column1", Integer, primary_key=True),
302
+ Column("column2", String, index=True),
303
+ Index("index_1", "column1", "column2")
304
+ )
305
+
306
+ metadata.create_all(engine_testaccount)
307
+ ```
308
+
309
+ #### Multi-Column Index
310
+
311
+ For multi-column indexes, you define the `Index` object specifying the columns that should be indexed.
312
+
313
+ ```python
314
+ hybrid_test_table_1 = HybridTable(
315
+ "table_name",
316
+ metadata,
317
+ Column("column1", Integer, primary_key=True),
318
+ Column("column2", String),
319
+ Index("index_1", "column1", "column2")
320
+ )
321
+
322
+ metadata.create_all(engine_testaccount)
323
+ ```
285
324
 
286
325
  ### Numpy Data Type Support
287
326
 
@@ -382,6 +421,79 @@ data_object = json.loads(row[1])
382
421
  data_array = json.loads(row[2])
383
422
  ```
384
423
 
424
+ ### Structured Data Types Support
425
+
426
+ This module defines custom SQLAlchemy types for Snowflake structured data, specifically for **Iceberg tables**.
427
+ The types —**MAP**, **OBJECT**, and **ARRAY**— allow you to store complex data structures in your SQLAlchemy models.
428
+ For detailed information, refer to the Snowflake [Structured data types](https://docs.snowflake.com/en/sql-reference/data-types-structured) documentation.
429
+
430
+ ---
431
+
432
+ #### MAP
433
+
434
+ The `MAP` type represents a collection of key-value pairs, where each key and value can have different types.
435
+
436
+ - **Key Type**: The type of the keys (e.g., `TEXT`, `NUMBER`).
437
+ - **Value Type**: The type of the values (e.g., `TEXT`, `NUMBER`).
438
+ - **Not Null**: Whether `NULL` values are allowed (default is `False`).
439
+
440
+ *Example Usage*
441
+
442
+ ```python
443
+ IcebergTable(
444
+ table_name,
445
+ metadata,
446
+ Column("id", Integer, primary_key=True),
447
+ Column("map_col", MAP(NUMBER(10, 0), TEXT(16777216))),
448
+ external_volume="external_volume",
449
+ base_location="base_location",
450
+ )
451
+ ```
452
+
453
+ #### OBJECT
454
+
455
+ The `OBJECT` type represents a semi-structured object with named fields. Each field can have a specific type, and you can also specify whether each field is nullable.
456
+
457
+ - **Items Types**: A dictionary of field names and their types. The type can optionally include a nullable flag (`True` for not nullable, `False` for nullable, default is `False`).
458
+
459
+ *Example Usage*
460
+
461
+ ```python
462
+ IcebergTable(
463
+ table_name,
464
+ metadata,
465
+ Column("id", Integer, primary_key=True),
466
+ Column(
467
+ "object_col",
468
+ OBJECT(key1=(TEXT(16777216), False), key2=(NUMBER(10, 0), False)),
469
+ OBJECT(key1=TEXT(16777216), key2=NUMBER(10, 0)), # Without nullable flag
470
+ ),
471
+ external_volume="external_volume",
472
+ base_location="base_location",
473
+ )
474
+ ```
475
+
476
+ #### ARRAY
477
+
478
+ The `ARRAY` type represents an ordered list of values, where each element has the same type. The type of the elements is defined when creating the array.
479
+
480
+ - **Value Type**: The type of the elements in the array (e.g., `TEXT`, `NUMBER`).
481
+ - **Not Null**: Whether `NULL` values are allowed (default is `False`).
482
+
483
+ *Example Usage*
484
+
485
+ ```python
486
+ IcebergTable(
487
+ table_name,
488
+ metadata,
489
+ Column("id", Integer, primary_key=True),
490
+ Column("array_col", ARRAY(TEXT(16777216))),
491
+ external_volume="external_volume",
492
+ base_location="base_location",
493
+ )
494
+ ```
495
+
496
+
385
497
  ### CLUSTER BY Support
386
498
 
387
499
  Snowflake SQLAchemy supports the `CLUSTER BY` parameter for tables. For information about the parameter, see :doc:`/sql-reference/sql/create-table`.
@@ -508,6 +620,117 @@ copy_into = CopyIntoStorage(from_=users,
508
620
  connection.execute(copy_into)
509
621
  ```
510
622
 
623
+ ### Iceberg Table with Snowflake Catalog support
624
+
625
+ 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.
626
+
627
+ To create an Iceberg Table using Snowflake SQLAlchemy, you can define the table using the SQLAlchemy Core syntax as follows:
628
+
629
+ ```python
630
+ table = IcebergTable(
631
+ "myuser",
632
+ metadata,
633
+ Column("id", Integer, primary_key=True),
634
+ Column("name", String),
635
+ external_volume=external_volume_name,
636
+ base_location="my_iceberg_table",
637
+ as_query="SELECT * FROM table"
638
+ )
639
+ ```
640
+
641
+ Alternatively, you can define the table using a declarative approach:
642
+
643
+ ```python
644
+ class MyUser(Base):
645
+ __tablename__ = "myuser"
646
+
647
+ @classmethod
648
+ def __table_cls__(cls, name, metadata, *arg, **kw):
649
+ return IcebergTable(name, metadata, *arg, **kw)
650
+
651
+ __table_args__ = {
652
+ "external_volume": "my_external_volume",
653
+ "base_location": "my_iceberg_table",
654
+ "as_query": "SELECT * FROM table",
655
+ }
656
+
657
+ id = Column(Integer, primary_key=True)
658
+ name = Column(String)
659
+ ```
660
+
661
+ ### Hybrid Table support
662
+
663
+ 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.
664
+
665
+ To create a Hybrid Table and add an index, you can use the SQLAlchemy Core syntax as follows:
666
+
667
+ ```python
668
+ table = HybridTable(
669
+ "myuser",
670
+ metadata,
671
+ Column("id", Integer, primary_key=True),
672
+ Column("name", String),
673
+ Index("idx_name", "name")
674
+ )
675
+ ```
676
+
677
+ Alternatively, you can define the table using the declarative approach:
678
+
679
+ ```python
680
+ class MyUser(Base):
681
+ __tablename__ = "myuser"
682
+
683
+ @classmethod
684
+ def __table_cls__(cls, name, metadata, *arg, **kw):
685
+ return HybridTable(name, metadata, *arg, **kw)
686
+
687
+ __table_args__ = (
688
+ Index("idx_name", "name"),
689
+ )
690
+
691
+ id = Column(Integer, primary_key=True)
692
+ name = Column(String)
693
+ ```
694
+
695
+ ### Dynamic Tables support
696
+
697
+ 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.
698
+
699
+ To create a Dynamic Table, you can use the SQLAlchemy Core syntax as follows:
700
+
701
+ ```python
702
+ dynamic_test_table_1 = DynamicTable(
703
+ "dynamic_MyUser",
704
+ metadata,
705
+ Column("id", Integer),
706
+ Column("name", String),
707
+ target_lag=(1, TimeUnit.HOURS), # Additionally, you can use SnowflakeKeyword.DOWNSTREAM
708
+ warehouse='test_wh',
709
+ refresh_mode=SnowflakeKeyword.FULL,
710
+ as_query="SELECT id, name from MyUser;"
711
+ )
712
+ ```
713
+
714
+ Alternatively, you can define a table without columns using the SQLAlchemy `select()` construct:
715
+
716
+ ```python
717
+ dynamic_test_table_1 = DynamicTable(
718
+ "dynamic_MyUser",
719
+ metadata,
720
+ target_lag=(1, TimeUnit.HOURS),
721
+ warehouse='test_wh',
722
+ refresh_mode=SnowflakeKeyword.FULL,
723
+ as_query=select(MyUser.id, MyUser.name)
724
+ )
725
+ ```
726
+
727
+ ### Notes
728
+
729
+ - Defining a primary key in a Dynamic Table is not supported, meaning declarative tables don’t support Dynamic Tables.
730
+ - 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.
731
+ - Direct data insertion into Dynamic Tables is not supported.
732
+
733
+
511
734
  ## Support
512
735
 
513
736
  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
 
@@ -330,6 +367,79 @@ data_object = json.loads(row[1])
330
367
  data_array = json.loads(row[2])
331
368
  ```
332
369
 
370
+ ### Structured Data Types Support
371
+
372
+ This module defines custom SQLAlchemy types for Snowflake structured data, specifically for **Iceberg tables**.
373
+ The types —**MAP**, **OBJECT**, and **ARRAY**— allow you to store complex data structures in your SQLAlchemy models.
374
+ For detailed information, refer to the Snowflake [Structured data types](https://docs.snowflake.com/en/sql-reference/data-types-structured) documentation.
375
+
376
+ ---
377
+
378
+ #### MAP
379
+
380
+ The `MAP` type represents a collection of key-value pairs, where each key and value can have different types.
381
+
382
+ - **Key Type**: The type of the keys (e.g., `TEXT`, `NUMBER`).
383
+ - **Value Type**: The type of the values (e.g., `TEXT`, `NUMBER`).
384
+ - **Not Null**: Whether `NULL` values are allowed (default is `False`).
385
+
386
+ *Example Usage*
387
+
388
+ ```python
389
+ IcebergTable(
390
+ table_name,
391
+ metadata,
392
+ Column("id", Integer, primary_key=True),
393
+ Column("map_col", MAP(NUMBER(10, 0), TEXT(16777216))),
394
+ external_volume="external_volume",
395
+ base_location="base_location",
396
+ )
397
+ ```
398
+
399
+ #### OBJECT
400
+
401
+ The `OBJECT` type represents a semi-structured object with named fields. Each field can have a specific type, and you can also specify whether each field is nullable.
402
+
403
+ - **Items Types**: A dictionary of field names and their types. The type can optionally include a nullable flag (`True` for not nullable, `False` for nullable, default is `False`).
404
+
405
+ *Example Usage*
406
+
407
+ ```python
408
+ IcebergTable(
409
+ table_name,
410
+ metadata,
411
+ Column("id", Integer, primary_key=True),
412
+ Column(
413
+ "object_col",
414
+ OBJECT(key1=(TEXT(16777216), False), key2=(NUMBER(10, 0), False)),
415
+ OBJECT(key1=TEXT(16777216), key2=NUMBER(10, 0)), # Without nullable flag
416
+ ),
417
+ external_volume="external_volume",
418
+ base_location="base_location",
419
+ )
420
+ ```
421
+
422
+ #### ARRAY
423
+
424
+ The `ARRAY` type represents an ordered list of values, where each element has the same type. The type of the elements is defined when creating the array.
425
+
426
+ - **Value Type**: The type of the elements in the array (e.g., `TEXT`, `NUMBER`).
427
+ - **Not Null**: Whether `NULL` values are allowed (default is `False`).
428
+
429
+ *Example Usage*
430
+
431
+ ```python
432
+ IcebergTable(
433
+ table_name,
434
+ metadata,
435
+ Column("id", Integer, primary_key=True),
436
+ Column("array_col", ARRAY(TEXT(16777216))),
437
+ external_volume="external_volume",
438
+ base_location="base_location",
439
+ )
440
+ ```
441
+
442
+
333
443
  ### CLUSTER BY Support
334
444
 
335
445
  Snowflake SQLAchemy supports the `CLUSTER BY` parameter for tables. For information about the parameter, see :doc:`/sql-reference/sql/create-table`.
@@ -456,6 +566,117 @@ copy_into = CopyIntoStorage(from_=users,
456
566
  connection.execute(copy_into)
457
567
  ```
458
568
 
569
+ ### Iceberg Table with Snowflake Catalog support
570
+
571
+ 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.
572
+
573
+ To create an Iceberg Table using Snowflake SQLAlchemy, you can define the table using the SQLAlchemy Core syntax as follows:
574
+
575
+ ```python
576
+ table = IcebergTable(
577
+ "myuser",
578
+ metadata,
579
+ Column("id", Integer, primary_key=True),
580
+ Column("name", String),
581
+ external_volume=external_volume_name,
582
+ base_location="my_iceberg_table",
583
+ as_query="SELECT * FROM table"
584
+ )
585
+ ```
586
+
587
+ Alternatively, you can define the table using a declarative approach:
588
+
589
+ ```python
590
+ class MyUser(Base):
591
+ __tablename__ = "myuser"
592
+
593
+ @classmethod
594
+ def __table_cls__(cls, name, metadata, *arg, **kw):
595
+ return IcebergTable(name, metadata, *arg, **kw)
596
+
597
+ __table_args__ = {
598
+ "external_volume": "my_external_volume",
599
+ "base_location": "my_iceberg_table",
600
+ "as_query": "SELECT * FROM table",
601
+ }
602
+
603
+ id = Column(Integer, primary_key=True)
604
+ name = Column(String)
605
+ ```
606
+
607
+ ### Hybrid Table support
608
+
609
+ 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.
610
+
611
+ To create a Hybrid Table and add an index, you can use the SQLAlchemy Core syntax as follows:
612
+
613
+ ```python
614
+ table = HybridTable(
615
+ "myuser",
616
+ metadata,
617
+ Column("id", Integer, primary_key=True),
618
+ Column("name", String),
619
+ Index("idx_name", "name")
620
+ )
621
+ ```
622
+
623
+ Alternatively, you can define the table using the declarative approach:
624
+
625
+ ```python
626
+ class MyUser(Base):
627
+ __tablename__ = "myuser"
628
+
629
+ @classmethod
630
+ def __table_cls__(cls, name, metadata, *arg, **kw):
631
+ return HybridTable(name, metadata, *arg, **kw)
632
+
633
+ __table_args__ = (
634
+ Index("idx_name", "name"),
635
+ )
636
+
637
+ id = Column(Integer, primary_key=True)
638
+ name = Column(String)
639
+ ```
640
+
641
+ ### Dynamic Tables support
642
+
643
+ 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.
644
+
645
+ To create a Dynamic Table, you can use the SQLAlchemy Core syntax as follows:
646
+
647
+ ```python
648
+ dynamic_test_table_1 = DynamicTable(
649
+ "dynamic_MyUser",
650
+ metadata,
651
+ Column("id", Integer),
652
+ Column("name", String),
653
+ target_lag=(1, TimeUnit.HOURS), # Additionally, you can use SnowflakeKeyword.DOWNSTREAM
654
+ warehouse='test_wh',
655
+ refresh_mode=SnowflakeKeyword.FULL,
656
+ as_query="SELECT id, name from MyUser;"
657
+ )
658
+ ```
659
+
660
+ Alternatively, you can define a table without columns using the SQLAlchemy `select()` construct:
661
+
662
+ ```python
663
+ dynamic_test_table_1 = DynamicTable(
664
+ "dynamic_MyUser",
665
+ metadata,
666
+ target_lag=(1, TimeUnit.HOURS),
667
+ warehouse='test_wh',
668
+ refresh_mode=SnowflakeKeyword.FULL,
669
+ as_query=select(MyUser.id, MyUser.name)
670
+ )
671
+ ```
672
+
673
+ ### Notes
674
+
675
+ - Defining a primary key in a Dynamic Table is not supported, meaning declarative tables don’t support Dynamic Tables.
676
+ - 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.
677
+ - Direct data insertion into Dynamic Tables is not supported.
678
+
679
+
459
680
  ## Support
460
681
 
461
682
  Feel free to file an issue or submit a PR here for general cases. For official support, contact Snowflake support at:
@@ -47,6 +47,7 @@ path = "src/snowflake/sqlalchemy/version.py"
47
47
  development = [
48
48
  "pre-commit",
49
49
  "pytest",
50
+ "setuptools",
50
51
  "pytest-cov",
51
52
  "pytest-timeout",
52
53
  "pytest-rerunfailures",
@@ -74,6 +75,8 @@ exclude = ["/.github"]
74
75
  packages = ["src/snowflake"]
75
76
 
76
77
  [tool.hatch.envs.default]
78
+ path = ".venv"
79
+ type = "virtual"
77
80
  extra-dependencies = ["SQLAlchemy>=1.4.19,<2.1.0"]
78
81
  features = ["development", "pandas"]
79
82
  python = "3.8"