snowflake-sqlalchemy 1.8.2__tar.gz → 1.9.0__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.
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/DESCRIPTION.md +17 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/PKG-INFO +190 -3
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/README.md +189 -1
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/ci/build.sh +2 -2
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/ci/test_linux.sh +9 -2
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/pyproject.toml +6 -2
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/src/snowflake/sqlalchemy/__init__.py +4 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/src/snowflake/sqlalchemy/base.py +38 -6
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/src/snowflake/sqlalchemy/compat.py +1 -1
- snowflake_sqlalchemy-1.9.0/src/snowflake/sqlalchemy/custom_types.py +295 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/src/snowflake/sqlalchemy/parser/custom_type_parser.py +21 -0
- snowflake_sqlalchemy-1.9.0/src/snowflake/sqlalchemy/provision.py +37 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/src/snowflake/sqlalchemy/snowdialect.py +334 -121
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/src/snowflake/sqlalchemy/structured_type_info_manager.py +12 -1
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/src/snowflake/sqlalchemy/version.py +1 -1
- snowflake_sqlalchemy-1.9.0/tests/README.rst +99 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tests/conftest.py +11 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tests/custom_tables/__snapshots__/test_reflect_snowflake_table.ambr +3 -3
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tests/custom_tables/test_create_dynamic_table.py +6 -9
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tests/custom_tables/test_reflect_dynamic_table.py +8 -6
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tests/custom_tables/test_reflect_snowflake_table.py +3 -3
- snowflake_sqlalchemy-1.9.0/tests/test_compat.py +39 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tests/test_compiler.py +102 -2
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tests/test_core.py +270 -19
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tests/test_custom_types.py +11 -2
- snowflake_sqlalchemy-1.9.0/tests/test_decfloat.py +366 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tests/test_dialect_connect.py +9 -10
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tests/test_index_reflection.py +2 -1
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tests/test_pandas.py +38 -0
- snowflake_sqlalchemy-1.9.0/tests/test_timestamp.py +298 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tests/test_unit_core.py +50 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tests/test_unit_structured_types.py +31 -1
- snowflake_sqlalchemy-1.9.0/tests/test_vector.py +198 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tests/util.py +18 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tox.ini +21 -13
- snowflake_sqlalchemy-1.8.2/src/snowflake/sqlalchemy/custom_types.py +0 -155
- snowflake_sqlalchemy-1.8.2/src/snowflake/sqlalchemy/provision.py +0 -12
- snowflake_sqlalchemy-1.8.2/tests/README.rst +0 -51
- snowflake_sqlalchemy-1.8.2/tests/test_timestamp.py +0 -83
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/.gitignore +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/.gitmodules +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/.pre-commit-config.yaml +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/LICENSE.txt +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/MANIFEST.in +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/ci/build_docker.sh +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/ci/docker/sqlalchemy_build/Dockerfile +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/ci/docker/sqlalchemy_build/scripts/entrypoint.sh +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/ci/set_base_image.sh +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/ci/test.sh +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/ci/test_docker.sh +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/license_header.txt +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/setup.cfg +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/snyk/requirements.txt +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/snyk/requiremtnts.txt +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/snyk/update_requirements.py +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/src/snowflake/sqlalchemy/_constants.py +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/src/snowflake/sqlalchemy/custom_commands.py +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/src/snowflake/sqlalchemy/exc.py +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/src/snowflake/sqlalchemy/functions.py +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/src/snowflake/sqlalchemy/name_utils.py +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/src/snowflake/sqlalchemy/requirements.py +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/src/snowflake/sqlalchemy/sql/__init__.py +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/src/snowflake/sqlalchemy/sql/custom_schema/__init__.py +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/src/snowflake/sqlalchemy/sql/custom_schema/clustered_table.py +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/src/snowflake/sqlalchemy/sql/custom_schema/custom_table_base.py +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/src/snowflake/sqlalchemy/sql/custom_schema/custom_table_prefix.py +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/src/snowflake/sqlalchemy/sql/custom_schema/dynamic_table.py +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/src/snowflake/sqlalchemy/sql/custom_schema/hybrid_table.py +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/src/snowflake/sqlalchemy/sql/custom_schema/iceberg_table.py +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/src/snowflake/sqlalchemy/sql/custom_schema/options/__init__.py +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/src/snowflake/sqlalchemy/sql/custom_schema/options/as_query_option.py +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/src/snowflake/sqlalchemy/sql/custom_schema/options/cluster_by_option.py +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/src/snowflake/sqlalchemy/sql/custom_schema/options/identifier_option.py +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/src/snowflake/sqlalchemy/sql/custom_schema/options/invalid_table_option.py +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/src/snowflake/sqlalchemy/sql/custom_schema/options/keyword_option.py +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/src/snowflake/sqlalchemy/sql/custom_schema/options/keywords.py +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/src/snowflake/sqlalchemy/sql/custom_schema/options/literal_option.py +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/src/snowflake/sqlalchemy/sql/custom_schema/options/table_option.py +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/src/snowflake/sqlalchemy/sql/custom_schema/options/target_lag_option.py +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/src/snowflake/sqlalchemy/sql/custom_schema/snowflake_table.py +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/src/snowflake/sqlalchemy/sql/custom_schema/table_from_query.py +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/src/snowflake/sqlalchemy/util.py +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tested_requirements/requirements_310.reqs +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tested_requirements/requirements_37.reqs +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tested_requirements/requirements_38.reqs +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tested_requirements/requirements_39.reqs +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tests/__init__.py +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tests/__snapshots__/test_compile_dynamic_table.ambr +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tests/__snapshots__/test_core.ambr +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tests/__snapshots__/test_orm.ambr +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tests/__snapshots__/test_reflect_dynamic_table.ambr +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tests/__snapshots__/test_structured_datatypes.ambr +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tests/__snapshots__/test_unit_structured_types.ambr +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tests/custom_tables/__init__.py +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tests/custom_tables/__snapshots__/test_compile_dynamic_table.ambr +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tests/custom_tables/__snapshots__/test_compile_hybrid_table.ambr +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tests/custom_tables/__snapshots__/test_compile_iceberg_table.ambr +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tests/custom_tables/__snapshots__/test_compile_snowflake_table.ambr +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tests/custom_tables/__snapshots__/test_create_dynamic_table.ambr +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tests/custom_tables/__snapshots__/test_create_hybrid_table.ambr +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tests/custom_tables/__snapshots__/test_create_iceberg_table.ambr +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tests/custom_tables/__snapshots__/test_create_snowflake_table.ambr +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tests/custom_tables/__snapshots__/test_generic_options.ambr +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tests/custom_tables/__snapshots__/test_reflect_hybrid_table.ambr +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tests/custom_tables/test_compile_dynamic_table.py +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tests/custom_tables/test_compile_hybrid_table.py +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tests/custom_tables/test_compile_iceberg_table.py +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tests/custom_tables/test_compile_snowflake_table.py +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tests/custom_tables/test_create_hybrid_table.py +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tests/custom_tables/test_create_iceberg_table.py +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tests/custom_tables/test_create_snowflake_table.py +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tests/custom_tables/test_generic_options.py +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tests/custom_tables/test_reflect_hybrid_table.py +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tests/data/users.txt +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tests/sqlalchemy_test_suite/README.md +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tests/sqlalchemy_test_suite/__init__.py +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tests/sqlalchemy_test_suite/conftest.py +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tests/sqlalchemy_test_suite/test_suite.py +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tests/sqlalchemy_test_suite/test_suite_20.py +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tests/test_copy.py +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tests/test_create.py +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tests/test_custom_functions.py +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tests/test_geography.py +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tests/test_geometry.py +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tests/test_imports.py +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tests/test_multivalues_insert.py +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tests/test_orm.py +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tests/test_qmark.py +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tests/test_quote.py +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tests/test_quote_identifiers.py +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tests/test_semi_structured_datatypes.py +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tests/test_sequence.py +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tests/test_structured_datatypes.py +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tests/test_transactions.py +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tests/test_unit_cte.py +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tests/test_unit_types.py +0 -0
- {snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/tests/test_unit_url.py +0 -0
|
@@ -11,6 +11,23 @@ Source code is also available at:
|
|
|
11
11
|
|
|
12
12
|
# Release Notes
|
|
13
13
|
|
|
14
|
+
- v1.9.0 (March 4, 2026)
|
|
15
|
+
- Add support for `DECFLOAT` and `VECTOR` data types
|
|
16
|
+
- Add server_version_info support
|
|
17
|
+
- Add support for `ILIKE` in queries
|
|
18
|
+
- Fix `SYSDATE()` rendering
|
|
19
|
+
- Fix and improve schema reflection (SNOW-593204, SNOW-2331576, SNOW-2852779)
|
|
20
|
+
- Fix crash when reflecting without specifying a schema, caused by `None` arguments in internal schema resolution ([#623](https://github.com/snowflakedb/snowflake-sqlalchemy/issues/623)).
|
|
21
|
+
- Fix crash when `SHOW TABLES` returns empty string table names, causing `IndexError` during reflection ([#296](https://github.com/snowflakedb/snowflake-sqlalchemy/issues/296)).
|
|
22
|
+
- Fix incomplete identity column reflection metadata, now includes all fields required by SQLAlchemy 2.0+ (`always`, `cycle`, `order`, etc.).
|
|
23
|
+
- Introduce shared helper for fully-qualified schema name resolution, replacing inconsistent ad-hoc patterns across reflection methods.
|
|
24
|
+
- Refactor column reflection internals into dedicated helpers to reduce complexity without changing behavior.
|
|
25
|
+
- Add `pytest-xdist` parallel test support via per-worker schema provisioning hooks.
|
|
26
|
+
- Bump `pandas` lower bound in `sa14` test environment from `<2.1` to `>=2.1.1,<2.2` to ensure pre-built wheels are available for Python 3.12
|
|
27
|
+
- Fix SQLAlchemy version parsing (SNOW-3066571)
|
|
28
|
+
- Document support for session parameters (like [QUERY_TAG](https://docs.snowflake.com/en/sql-reference/parameters#query-tag)), references: [#644](https://github.com/snowflakedb/snowflake-sqlalchemy/issues/495)
|
|
29
|
+
- Support timezone in timestamp and datetime types ([#199](https://github.com/snowflakedb/snowflake-sqlalchemy/issues/199))
|
|
30
|
+
|
|
14
31
|
- v1.8.2 (December 9, 2025)
|
|
15
32
|
- Updated supported max python version to 3.13
|
|
16
33
|
- Version 1.8.1 yanked due to max python version supported by `snowflake-connector-python`
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: snowflake-sqlalchemy
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.9.0
|
|
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
|
|
@@ -39,7 +39,6 @@ Requires-Python: >=3.8
|
|
|
39
39
|
Requires-Dist: snowflake-connector-python<5.0.0
|
|
40
40
|
Requires-Dist: sqlalchemy>=1.4.19
|
|
41
41
|
Provides-Extra: development
|
|
42
|
-
Requires-Dist: mock; extra == 'development'
|
|
43
42
|
Requires-Dist: numpy; extra == 'development'
|
|
44
43
|
Requires-Dist: pre-commit; extra == 'development'
|
|
45
44
|
Requires-Dist: pytest; extra == 'development'
|
|
@@ -64,7 +63,47 @@ Description-Content-Type: text/markdown
|
|
|
64
63
|
|
|
65
64
|
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.
|
|
66
65
|
|
|
67
|
-
|
|
66
|
+
Table of contents:
|
|
67
|
+
<!-- TOC -->
|
|
68
|
+
* [Snowflake SQLAlchemy](#snowflake-sqlalchemy)
|
|
69
|
+
* [Prerequisites](#prerequisites)
|
|
70
|
+
* [Snowflake Connector for Python](#snowflake-connector-for-python)
|
|
71
|
+
* [Data Analytics and Web Application Frameworks (Optional)](#data-analytics-and-web-application-frameworks-optional)
|
|
72
|
+
* [Installing Snowflake SQLAlchemy](#installing-snowflake-sqlalchemy)
|
|
73
|
+
* [Verifying Your Installation](#verifying-your-installation)
|
|
74
|
+
* [Parameters and Behavior](#parameters-and-behavior)
|
|
75
|
+
* [Connection Parameters](#connection-parameters)
|
|
76
|
+
* [Escaping Special Characters such as `%, @` signs in Passwords](#escaping-special-characters-such-as---signs-in-passwords)
|
|
77
|
+
* [Using a proxy server](#using-a-proxy-server)
|
|
78
|
+
* [Using session parameters](#using-session-parameters)
|
|
79
|
+
* [Opening and Closing Connection](#opening-and-closing-connection)
|
|
80
|
+
* [Auto-increment Behavior](#auto-increment-behavior)
|
|
81
|
+
* [Object Name Case Handling](#object-name-case-handling)
|
|
82
|
+
* [Index Support](#index-support)
|
|
83
|
+
* [Single Column Index](#single-column-index)
|
|
84
|
+
* [Multi-Column Index](#multi-column-index)
|
|
85
|
+
* [Numpy Data Type Support](#numpy-data-type-support)
|
|
86
|
+
* [DECFLOAT Data Type Support](#decfloat-data-type-support)
|
|
87
|
+
* [DECFLOAT Precision](#decfloat-precision)
|
|
88
|
+
* [VECTOR Data Type Support](#vector-data-type-support)
|
|
89
|
+
* [Cache Column Metadata](#cache-column-metadata)
|
|
90
|
+
* [VARIANT, ARRAY and OBJECT Support](#variant-array-and-object-support)
|
|
91
|
+
* [Structured Data Types Support](#structured-data-types-support)
|
|
92
|
+
* [MAP](#map)
|
|
93
|
+
* [OBJECT](#object)
|
|
94
|
+
* [ARRAY](#array)
|
|
95
|
+
* [CLUSTER BY Support](#cluster-by-support)
|
|
96
|
+
* [Alembic Support](#alembic-support)
|
|
97
|
+
* [Key Pair Authentication Support](#key-pair-authentication-support)
|
|
98
|
+
* [Merge Command Support](#merge-command-support)
|
|
99
|
+
* [CopyIntoStorage Support](#copyintostorage-support)
|
|
100
|
+
* [Iceberg Table with Snowflake Catalog support](#iceberg-table-with-snowflake-catalog-support)
|
|
101
|
+
* [Hybrid Table support](#hybrid-table-support)
|
|
102
|
+
* [Dynamic Tables support](#dynamic-tables-support)
|
|
103
|
+
* [Notes](#notes)
|
|
104
|
+
* [Verifying Package Signatures](#verifying-package-signatures)
|
|
105
|
+
* [Support](#support)
|
|
106
|
+
<!-- TOC -->
|
|
68
107
|
|
|
69
108
|
## Prerequisites
|
|
70
109
|
|
|
@@ -240,6 +279,44 @@ engine = create_engine(URL(
|
|
|
240
279
|
|
|
241
280
|
Use the supported environment variables, `HTTPS_PROXY`, `HTTP_PROXY` and `NO_PROXY` to configure a proxy server.
|
|
242
281
|
|
|
282
|
+
#### Using session parameters
|
|
283
|
+
|
|
284
|
+
Snowflake [session parameters](https://docs.snowflake.com/en/sql-reference/parameters#session-parameters) (such as [`QUERY_TAG`](https://docs.snowflake.com/en/sql-reference/parameters#query-tag)) cannot be set directly through the `URL` helper.
|
|
285
|
+
Instead, pass them via the `connect_args` parameter of `create_engine`, using the `session_parameters` dict — the same way you would [through the Python connector](https://docs.snowflake.com/en/developer-guide/python-connector/python-connector-connect#setting-session-parameters):
|
|
286
|
+
|
|
287
|
+
```python
|
|
288
|
+
from snowflake.sqlalchemy import URL
|
|
289
|
+
from sqlalchemy import create_engine
|
|
290
|
+
|
|
291
|
+
engine = create_engine(
|
|
292
|
+
URL(
|
|
293
|
+
# CONNECTION_PARAMETERS
|
|
294
|
+
),
|
|
295
|
+
connect_args={
|
|
296
|
+
"session_parameters": {
|
|
297
|
+
"QUERY_TAG": "SOME_QUERY_TAGS",
|
|
298
|
+
}
|
|
299
|
+
},
|
|
300
|
+
)
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
Session parameters set this way apply to all queries executed within the session.
|
|
304
|
+
To change a session parameter for specific queries mid-session, use `ALTER SESSION`:
|
|
305
|
+
|
|
306
|
+
```python
|
|
307
|
+
from sqlalchemy import text
|
|
308
|
+
|
|
309
|
+
with engine.connect() as conn:
|
|
310
|
+
conn.execute(text("ALTER SESSION SET QUERY_TAG = 'batch_job_1'"))
|
|
311
|
+
conn.execute(text("...")) # Uses 'batch_job_1'
|
|
312
|
+
|
|
313
|
+
conn.execute(text("ALTER SESSION SET QUERY_TAG = 'batch_job_2'"))
|
|
314
|
+
conn.execute(text("...")) # Uses 'batch_job_2'
|
|
315
|
+
|
|
316
|
+
conn.execute(text("ALTER SESSION UNSET QUERY_TAG"))
|
|
317
|
+
conn.execute(text("...")) # No tag
|
|
318
|
+
```
|
|
319
|
+
|
|
243
320
|
### Opening and Closing Connection
|
|
244
321
|
|
|
245
322
|
Open a connection by executing `engine.connect()`; avoid using `engine.execute()`. Make certain to close the connection by executing `connection.close()` before
|
|
@@ -359,6 +436,116 @@ The following `NumPy` data types are supported:
|
|
|
359
436
|
- numpy.float64
|
|
360
437
|
- numpy.datatime64
|
|
361
438
|
|
|
439
|
+
### DECFLOAT Data Type Support
|
|
440
|
+
|
|
441
|
+
Snowflake SQLAlchemy supports the `DECFLOAT` data type, which provides decimal floating-point with up to 38 significant digits. For more information, see the [Snowflake DECFLOAT documentation](https://docs.snowflake.com/en/sql-reference/data-types-numeric#decfloat).
|
|
442
|
+
|
|
443
|
+
```python
|
|
444
|
+
from sqlalchemy import Column, Integer, MetaData, Table
|
|
445
|
+
from snowflake.sqlalchemy import DECFLOAT
|
|
446
|
+
|
|
447
|
+
metadata = MetaData()
|
|
448
|
+
t = Table('my_table', metadata,
|
|
449
|
+
Column('id', Integer, primary_key=True),
|
|
450
|
+
Column('value', DECFLOAT()),
|
|
451
|
+
)
|
|
452
|
+
metadata.create_all(engine)
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
#### DECFLOAT Precision
|
|
456
|
+
|
|
457
|
+
The Snowflake Python connector uses Python's `decimal` module context when converting `DECFLOAT` values to Python `Decimal` objects. Python's default decimal context precision is 28 digits, which can truncate `DECFLOAT` values that use up to 38 digits.
|
|
458
|
+
|
|
459
|
+
To preserve full 38-digit precision, add `enable_decfloat=True` to the connection URL:
|
|
460
|
+
|
|
461
|
+
```python
|
|
462
|
+
from sqlalchemy import create_engine
|
|
463
|
+
|
|
464
|
+
engine = create_engine(
|
|
465
|
+
'snowflake://testuser1:0123456@abc123/testdb/public?warehouse=testwh&enable_decfloat=True'
|
|
466
|
+
)
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
Or using the `snowflake.sqlalchemy.URL` helper:
|
|
470
|
+
|
|
471
|
+
```python
|
|
472
|
+
from snowflake.sqlalchemy import URL
|
|
473
|
+
from sqlalchemy import create_engine
|
|
474
|
+
|
|
475
|
+
engine = create_engine(URL(
|
|
476
|
+
account = 'abc123',
|
|
477
|
+
user = 'testuser1',
|
|
478
|
+
password = '0123456',
|
|
479
|
+
database = 'testdb',
|
|
480
|
+
schema = 'public',
|
|
481
|
+
warehouse = 'testwh',
|
|
482
|
+
enable_decfloat = True,
|
|
483
|
+
))
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
**Note**: `DECFLOAT` does not support special values (`inf`, `-inf`, `NaN`) unlike `FLOAT`.
|
|
487
|
+
|
|
488
|
+
**Why is `enable_decfloat` not enabled by default?** Enabling it sets `decimal.getcontext().prec = 38`, which modifies Python's thread-local decimal context and affects all `Decimal` operations in that thread, not just database queries. To avoid unexpected side effects on application code, the dialect emits a warning when `DECFLOAT` values are retrieved without full precision enabled, guiding users to opt-in explicitly.
|
|
489
|
+
|
|
490
|
+
### VECTOR Data Type Support
|
|
491
|
+
|
|
492
|
+
Snowflake SQLAlchemy supports the `VECTOR` data type with varying element type and dimension.
|
|
493
|
+
For more information, see the [Snowflake documentation](https://docs.snowflake.com/en/sql-reference/data-types-vector).
|
|
494
|
+
|
|
495
|
+
```python
|
|
496
|
+
from sqlalchemy import Column, Integer, Float, MetaData, Table
|
|
497
|
+
from snowflake.sqlalchemy import VECTOR
|
|
498
|
+
|
|
499
|
+
metadata = MetaData()
|
|
500
|
+
t = Table('my_table', metadata,
|
|
501
|
+
Column('id', Integer, primary_key=True),
|
|
502
|
+
Column('int_vec', VECTOR(Integer, 20)),
|
|
503
|
+
Column('float_vec', VECTOR(Float, 40)),
|
|
504
|
+
)
|
|
505
|
+
metadata.create_all(engine)
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
### Timestamp and Timezone Support
|
|
509
|
+
|
|
510
|
+
Snowflake SQLAlchemy provides three Snowflake-specific timestamp types that map directly to their Snowflake counterparts:
|
|
511
|
+
|
|
512
|
+
```python
|
|
513
|
+
from sqlalchemy import Column, Integer, MetaData, Table, create_engine
|
|
514
|
+
from snowflake.sqlalchemy import TIMESTAMP_NTZ, TIMESTAMP_TZ, TIMESTAMP_LTZ
|
|
515
|
+
|
|
516
|
+
engine = create_engine(...)
|
|
517
|
+
metadata = MetaData()
|
|
518
|
+
t = Table('events', metadata,
|
|
519
|
+
Column('id', Integer, primary_key=True),
|
|
520
|
+
Column('created_at', TIMESTAMP_NTZ()), # TIMESTAMP WITHOUT TIME ZONE
|
|
521
|
+
Column('scheduled_at', TIMESTAMP_TZ()), # TIMESTAMP WITH TIME ZONE
|
|
522
|
+
Column('logged_at', TIMESTAMP_LTZ()), # TIMESTAMP WITH LOCAL TIME ZONE
|
|
523
|
+
)
|
|
524
|
+
metadata.create_all(engine)
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
SQLAlchemy's generic `DateTime` and `TIMESTAMP` types also support timezone-aware columns via the `timezone` parameter. When `timezone=True` is set, the dialect emits `TIMESTAMP_TZ` instead of the default `TIMESTAMP_NTZ`:
|
|
528
|
+
|
|
529
|
+
```python
|
|
530
|
+
from sqlalchemy import Column, DateTime, Integer, MetaData, Table, create_engine
|
|
531
|
+
from sqlalchemy.types import TIMESTAMP
|
|
532
|
+
|
|
533
|
+
engine = create_engine(...)
|
|
534
|
+
metadata = MetaData()
|
|
535
|
+
t = Table('events', metadata,
|
|
536
|
+
Column('id', Integer, primary_key=True),
|
|
537
|
+
Column('naive_ts', DateTime()), # produces TIMESTAMP_NTZ
|
|
538
|
+
Column('aware_ts', DateTime(timezone=True)), # produces TIMESTAMP_TZ
|
|
539
|
+
Column('naive_ts2', TIMESTAMP()), # produces TIMESTAMP_NTZ
|
|
540
|
+
Column('aware_ts2', TIMESTAMP(timezone=True)), # produces TIMESTAMP_TZ
|
|
541
|
+
)
|
|
542
|
+
metadata.create_all(engine)
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
This also applies when using pandas `to_sql()` with timezone-aware datetime columns, which infers `DateTime(timezone=True)` automatically (see [#199](https://github.com/snowflakedb/snowflake-sqlalchemy/issues/199)).
|
|
546
|
+
|
|
547
|
+
**Note on `Time` and timezones:** SQLAlchemy's `Time` type accepts a `timezone` parameter, but [Snowflake's TIME data type does not support time zones](https://docs.snowflake.com/en/sql-reference/data-types-datetime#time). Using `Time(timezone=True)` will compile to plain `TIME` and the `timezone` flag will have no effect. If you need to store time data with time-zone information, use a timestamp type such as `TIMESTAMP_TZ` or `DateTime(timezone=True)` instead.
|
|
548
|
+
|
|
362
549
|
### Cache Column Metadata
|
|
363
550
|
|
|
364
551
|
SQLAlchemy provides [the runtime inspection API](http://docs.sqlalchemy.org/en/latest/core/inspection.html) to get the runtime information about the various objects. One of the common use case is get all tables and their column metadata in a schema in order to construct a schema catalog. For example, [alembic](http://alembic.zzzcomputing.com/) on top of SQLAlchemy manages database schema migrations. A pseudo code flow is as follows:
|
|
@@ -8,7 +8,47 @@
|
|
|
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
|
-
|
|
11
|
+
Table of contents:
|
|
12
|
+
<!-- TOC -->
|
|
13
|
+
* [Snowflake SQLAlchemy](#snowflake-sqlalchemy)
|
|
14
|
+
* [Prerequisites](#prerequisites)
|
|
15
|
+
* [Snowflake Connector for Python](#snowflake-connector-for-python)
|
|
16
|
+
* [Data Analytics and Web Application Frameworks (Optional)](#data-analytics-and-web-application-frameworks-optional)
|
|
17
|
+
* [Installing Snowflake SQLAlchemy](#installing-snowflake-sqlalchemy)
|
|
18
|
+
* [Verifying Your Installation](#verifying-your-installation)
|
|
19
|
+
* [Parameters and Behavior](#parameters-and-behavior)
|
|
20
|
+
* [Connection Parameters](#connection-parameters)
|
|
21
|
+
* [Escaping Special Characters such as `%, @` signs in Passwords](#escaping-special-characters-such-as---signs-in-passwords)
|
|
22
|
+
* [Using a proxy server](#using-a-proxy-server)
|
|
23
|
+
* [Using session parameters](#using-session-parameters)
|
|
24
|
+
* [Opening and Closing Connection](#opening-and-closing-connection)
|
|
25
|
+
* [Auto-increment Behavior](#auto-increment-behavior)
|
|
26
|
+
* [Object Name Case Handling](#object-name-case-handling)
|
|
27
|
+
* [Index Support](#index-support)
|
|
28
|
+
* [Single Column Index](#single-column-index)
|
|
29
|
+
* [Multi-Column Index](#multi-column-index)
|
|
30
|
+
* [Numpy Data Type Support](#numpy-data-type-support)
|
|
31
|
+
* [DECFLOAT Data Type Support](#decfloat-data-type-support)
|
|
32
|
+
* [DECFLOAT Precision](#decfloat-precision)
|
|
33
|
+
* [VECTOR Data Type Support](#vector-data-type-support)
|
|
34
|
+
* [Cache Column Metadata](#cache-column-metadata)
|
|
35
|
+
* [VARIANT, ARRAY and OBJECT Support](#variant-array-and-object-support)
|
|
36
|
+
* [Structured Data Types Support](#structured-data-types-support)
|
|
37
|
+
* [MAP](#map)
|
|
38
|
+
* [OBJECT](#object)
|
|
39
|
+
* [ARRAY](#array)
|
|
40
|
+
* [CLUSTER BY Support](#cluster-by-support)
|
|
41
|
+
* [Alembic Support](#alembic-support)
|
|
42
|
+
* [Key Pair Authentication Support](#key-pair-authentication-support)
|
|
43
|
+
* [Merge Command Support](#merge-command-support)
|
|
44
|
+
* [CopyIntoStorage Support](#copyintostorage-support)
|
|
45
|
+
* [Iceberg Table with Snowflake Catalog support](#iceberg-table-with-snowflake-catalog-support)
|
|
46
|
+
* [Hybrid Table support](#hybrid-table-support)
|
|
47
|
+
* [Dynamic Tables support](#dynamic-tables-support)
|
|
48
|
+
* [Notes](#notes)
|
|
49
|
+
* [Verifying Package Signatures](#verifying-package-signatures)
|
|
50
|
+
* [Support](#support)
|
|
51
|
+
<!-- TOC -->
|
|
12
52
|
|
|
13
53
|
## Prerequisites
|
|
14
54
|
|
|
@@ -184,6 +224,44 @@ engine = create_engine(URL(
|
|
|
184
224
|
|
|
185
225
|
Use the supported environment variables, `HTTPS_PROXY`, `HTTP_PROXY` and `NO_PROXY` to configure a proxy server.
|
|
186
226
|
|
|
227
|
+
#### Using session parameters
|
|
228
|
+
|
|
229
|
+
Snowflake [session parameters](https://docs.snowflake.com/en/sql-reference/parameters#session-parameters) (such as [`QUERY_TAG`](https://docs.snowflake.com/en/sql-reference/parameters#query-tag)) cannot be set directly through the `URL` helper.
|
|
230
|
+
Instead, pass them via the `connect_args` parameter of `create_engine`, using the `session_parameters` dict — the same way you would [through the Python connector](https://docs.snowflake.com/en/developer-guide/python-connector/python-connector-connect#setting-session-parameters):
|
|
231
|
+
|
|
232
|
+
```python
|
|
233
|
+
from snowflake.sqlalchemy import URL
|
|
234
|
+
from sqlalchemy import create_engine
|
|
235
|
+
|
|
236
|
+
engine = create_engine(
|
|
237
|
+
URL(
|
|
238
|
+
# CONNECTION_PARAMETERS
|
|
239
|
+
),
|
|
240
|
+
connect_args={
|
|
241
|
+
"session_parameters": {
|
|
242
|
+
"QUERY_TAG": "SOME_QUERY_TAGS",
|
|
243
|
+
}
|
|
244
|
+
},
|
|
245
|
+
)
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Session parameters set this way apply to all queries executed within the session.
|
|
249
|
+
To change a session parameter for specific queries mid-session, use `ALTER SESSION`:
|
|
250
|
+
|
|
251
|
+
```python
|
|
252
|
+
from sqlalchemy import text
|
|
253
|
+
|
|
254
|
+
with engine.connect() as conn:
|
|
255
|
+
conn.execute(text("ALTER SESSION SET QUERY_TAG = 'batch_job_1'"))
|
|
256
|
+
conn.execute(text("...")) # Uses 'batch_job_1'
|
|
257
|
+
|
|
258
|
+
conn.execute(text("ALTER SESSION SET QUERY_TAG = 'batch_job_2'"))
|
|
259
|
+
conn.execute(text("...")) # Uses 'batch_job_2'
|
|
260
|
+
|
|
261
|
+
conn.execute(text("ALTER SESSION UNSET QUERY_TAG"))
|
|
262
|
+
conn.execute(text("...")) # No tag
|
|
263
|
+
```
|
|
264
|
+
|
|
187
265
|
### Opening and Closing Connection
|
|
188
266
|
|
|
189
267
|
Open a connection by executing `engine.connect()`; avoid using `engine.execute()`. Make certain to close the connection by executing `connection.close()` before
|
|
@@ -303,6 +381,116 @@ The following `NumPy` data types are supported:
|
|
|
303
381
|
- numpy.float64
|
|
304
382
|
- numpy.datatime64
|
|
305
383
|
|
|
384
|
+
### DECFLOAT Data Type Support
|
|
385
|
+
|
|
386
|
+
Snowflake SQLAlchemy supports the `DECFLOAT` data type, which provides decimal floating-point with up to 38 significant digits. For more information, see the [Snowflake DECFLOAT documentation](https://docs.snowflake.com/en/sql-reference/data-types-numeric#decfloat).
|
|
387
|
+
|
|
388
|
+
```python
|
|
389
|
+
from sqlalchemy import Column, Integer, MetaData, Table
|
|
390
|
+
from snowflake.sqlalchemy import DECFLOAT
|
|
391
|
+
|
|
392
|
+
metadata = MetaData()
|
|
393
|
+
t = Table('my_table', metadata,
|
|
394
|
+
Column('id', Integer, primary_key=True),
|
|
395
|
+
Column('value', DECFLOAT()),
|
|
396
|
+
)
|
|
397
|
+
metadata.create_all(engine)
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
#### DECFLOAT Precision
|
|
401
|
+
|
|
402
|
+
The Snowflake Python connector uses Python's `decimal` module context when converting `DECFLOAT` values to Python `Decimal` objects. Python's default decimal context precision is 28 digits, which can truncate `DECFLOAT` values that use up to 38 digits.
|
|
403
|
+
|
|
404
|
+
To preserve full 38-digit precision, add `enable_decfloat=True` to the connection URL:
|
|
405
|
+
|
|
406
|
+
```python
|
|
407
|
+
from sqlalchemy import create_engine
|
|
408
|
+
|
|
409
|
+
engine = create_engine(
|
|
410
|
+
'snowflake://testuser1:0123456@abc123/testdb/public?warehouse=testwh&enable_decfloat=True'
|
|
411
|
+
)
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
Or using the `snowflake.sqlalchemy.URL` helper:
|
|
415
|
+
|
|
416
|
+
```python
|
|
417
|
+
from snowflake.sqlalchemy import URL
|
|
418
|
+
from sqlalchemy import create_engine
|
|
419
|
+
|
|
420
|
+
engine = create_engine(URL(
|
|
421
|
+
account = 'abc123',
|
|
422
|
+
user = 'testuser1',
|
|
423
|
+
password = '0123456',
|
|
424
|
+
database = 'testdb',
|
|
425
|
+
schema = 'public',
|
|
426
|
+
warehouse = 'testwh',
|
|
427
|
+
enable_decfloat = True,
|
|
428
|
+
))
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
**Note**: `DECFLOAT` does not support special values (`inf`, `-inf`, `NaN`) unlike `FLOAT`.
|
|
432
|
+
|
|
433
|
+
**Why is `enable_decfloat` not enabled by default?** Enabling it sets `decimal.getcontext().prec = 38`, which modifies Python's thread-local decimal context and affects all `Decimal` operations in that thread, not just database queries. To avoid unexpected side effects on application code, the dialect emits a warning when `DECFLOAT` values are retrieved without full precision enabled, guiding users to opt-in explicitly.
|
|
434
|
+
|
|
435
|
+
### VECTOR Data Type Support
|
|
436
|
+
|
|
437
|
+
Snowflake SQLAlchemy supports the `VECTOR` data type with varying element type and dimension.
|
|
438
|
+
For more information, see the [Snowflake documentation](https://docs.snowflake.com/en/sql-reference/data-types-vector).
|
|
439
|
+
|
|
440
|
+
```python
|
|
441
|
+
from sqlalchemy import Column, Integer, Float, MetaData, Table
|
|
442
|
+
from snowflake.sqlalchemy import VECTOR
|
|
443
|
+
|
|
444
|
+
metadata = MetaData()
|
|
445
|
+
t = Table('my_table', metadata,
|
|
446
|
+
Column('id', Integer, primary_key=True),
|
|
447
|
+
Column('int_vec', VECTOR(Integer, 20)),
|
|
448
|
+
Column('float_vec', VECTOR(Float, 40)),
|
|
449
|
+
)
|
|
450
|
+
metadata.create_all(engine)
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
### Timestamp and Timezone Support
|
|
454
|
+
|
|
455
|
+
Snowflake SQLAlchemy provides three Snowflake-specific timestamp types that map directly to their Snowflake counterparts:
|
|
456
|
+
|
|
457
|
+
```python
|
|
458
|
+
from sqlalchemy import Column, Integer, MetaData, Table, create_engine
|
|
459
|
+
from snowflake.sqlalchemy import TIMESTAMP_NTZ, TIMESTAMP_TZ, TIMESTAMP_LTZ
|
|
460
|
+
|
|
461
|
+
engine = create_engine(...)
|
|
462
|
+
metadata = MetaData()
|
|
463
|
+
t = Table('events', metadata,
|
|
464
|
+
Column('id', Integer, primary_key=True),
|
|
465
|
+
Column('created_at', TIMESTAMP_NTZ()), # TIMESTAMP WITHOUT TIME ZONE
|
|
466
|
+
Column('scheduled_at', TIMESTAMP_TZ()), # TIMESTAMP WITH TIME ZONE
|
|
467
|
+
Column('logged_at', TIMESTAMP_LTZ()), # TIMESTAMP WITH LOCAL TIME ZONE
|
|
468
|
+
)
|
|
469
|
+
metadata.create_all(engine)
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
SQLAlchemy's generic `DateTime` and `TIMESTAMP` types also support timezone-aware columns via the `timezone` parameter. When `timezone=True` is set, the dialect emits `TIMESTAMP_TZ` instead of the default `TIMESTAMP_NTZ`:
|
|
473
|
+
|
|
474
|
+
```python
|
|
475
|
+
from sqlalchemy import Column, DateTime, Integer, MetaData, Table, create_engine
|
|
476
|
+
from sqlalchemy.types import TIMESTAMP
|
|
477
|
+
|
|
478
|
+
engine = create_engine(...)
|
|
479
|
+
metadata = MetaData()
|
|
480
|
+
t = Table('events', metadata,
|
|
481
|
+
Column('id', Integer, primary_key=True),
|
|
482
|
+
Column('naive_ts', DateTime()), # produces TIMESTAMP_NTZ
|
|
483
|
+
Column('aware_ts', DateTime(timezone=True)), # produces TIMESTAMP_TZ
|
|
484
|
+
Column('naive_ts2', TIMESTAMP()), # produces TIMESTAMP_NTZ
|
|
485
|
+
Column('aware_ts2', TIMESTAMP(timezone=True)), # produces TIMESTAMP_TZ
|
|
486
|
+
)
|
|
487
|
+
metadata.create_all(engine)
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
This also applies when using pandas `to_sql()` with timezone-aware datetime columns, which infers `DateTime(timezone=True)` automatically (see [#199](https://github.com/snowflakedb/snowflake-sqlalchemy/issues/199)).
|
|
491
|
+
|
|
492
|
+
**Note on `Time` and timezones:** SQLAlchemy's `Time` type accepts a `timezone` parameter, but [Snowflake's TIME data type does not support time zones](https://docs.snowflake.com/en/sql-reference/data-types-datetime#time). Using `Time(timezone=True)` will compile to plain `TIME` and the `timezone` flag will have no effect. If you need to store time data with time-zone information, use a timestamp type such as `TIMESTAMP_TZ` or `DateTime(timezone=True)` instead.
|
|
493
|
+
|
|
306
494
|
### Cache Column Metadata
|
|
307
495
|
|
|
308
496
|
SQLAlchemy provides [the runtime inspection API](http://docs.sqlalchemy.org/en/latest/core/inspection.html) to get the runtime information about the various objects. One of the common use case is get all tables and their column metadata in a schema in order to construct a schema catalog. For example, [alembic](http://alembic.zzzcomputing.com/) on top of SQLAlchemy manages database schema migrations. A pseudo code flow is as follows:
|
|
@@ -22,5 +22,5 @@ echo "[Info] Building snowflake-sqlalchemy with $PYTHON"
|
|
|
22
22
|
# Clean up possible build artifacts
|
|
23
23
|
rm -rf build generated_version.py
|
|
24
24
|
export UV_NO_CACHE=true
|
|
25
|
-
${PYTHON} -m pip install uv
|
|
26
|
-
${PYTHON} -m
|
|
25
|
+
${PYTHON} -m pip install uv
|
|
26
|
+
${PYTHON} -m uv build
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
# - This is the script that test_docker.sh runs inside of the docker container
|
|
8
8
|
|
|
9
9
|
PYTHON_VERSIONS="${1:-3.8 3.9 3.10 3.11 3.12 3.13 3.14}"
|
|
10
|
+
# Python versions where pyarrow (required by pandas extra) is not available
|
|
11
|
+
PANDAS_SKIP_VERSIONS="3.14"
|
|
10
12
|
THIS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
11
13
|
SQLALCHEMY_DIR="$(dirname "${THIS_DIR}")"
|
|
12
14
|
|
|
@@ -19,7 +21,12 @@ for PYTHON_VERSION in ${PYTHON_VERSIONS}; do
|
|
|
19
21
|
echo "[Info] Testing with ${PYTHON_VERSION}"
|
|
20
22
|
SHORT_VERSION=$(python3 -c "print('${PYTHON_VERSION}'.replace('.', ''))")
|
|
21
23
|
SQLALCHEMY_WHL=$(ls $SQLALCHEMY_DIR/dist/snowflake_sqlalchemy-*-py3-none-any.whl | sort -r | head -n 1)
|
|
22
|
-
TEST_ENVLIST=fix_lint,py${SHORT_VERSION}-ci
|
|
24
|
+
TEST_ENVLIST=fix_lint,py${SHORT_VERSION}-ci
|
|
25
|
+
if [[ ! " ${PANDAS_SKIP_VERSIONS} " =~ " ${PYTHON_VERSION} " ]]; then
|
|
26
|
+
TEST_ENVLIST="${TEST_ENVLIST},py${SHORT_VERSION}-pandas-ci"
|
|
27
|
+
else
|
|
28
|
+
echo "[Info] Skipping pandas tests for Python ${PYTHON_VERSION} (pyarrow not available)"
|
|
29
|
+
fi
|
|
23
30
|
echo "[Info] Running tox for ${TEST_ENVLIST}"
|
|
24
|
-
python3 -m tox -e ${TEST_ENVLIST} --installpkg ${SQLALCHEMY_WHL}
|
|
31
|
+
python3 -m tox -p auto --parallel-no-spinner -e ${TEST_ENVLIST} --installpkg ${SQLALCHEMY_WHL}
|
|
25
32
|
done
|
|
@@ -55,7 +55,6 @@ development = [
|
|
|
55
55
|
"pytest-xdist",
|
|
56
56
|
"pytz",
|
|
57
57
|
"numpy",
|
|
58
|
-
"mock",
|
|
59
58
|
"syrupy",
|
|
60
59
|
]
|
|
61
60
|
pandas = ["snowflake-connector-python[pandas]"]
|
|
@@ -85,7 +84,11 @@ installer = "uv"
|
|
|
85
84
|
[tool.hatch.envs.sa14]
|
|
86
85
|
installer = "uv"
|
|
87
86
|
builder = true
|
|
88
|
-
extra-dependencies = [
|
|
87
|
+
extra-dependencies = [
|
|
88
|
+
"SQLAlchemy>=1.4.19,<2.0.0",
|
|
89
|
+
"pandas>=2.1.1,<2.2",
|
|
90
|
+
"numpy<2",
|
|
91
|
+
]
|
|
89
92
|
features = ["development", "pandas"]
|
|
90
93
|
python = "3.12"
|
|
91
94
|
|
|
@@ -142,4 +145,5 @@ markers = [
|
|
|
142
145
|
"external: tests that could but should only run on our external CI",
|
|
143
146
|
"feature_max_lob_size: tests that could but should only run on our external CI",
|
|
144
147
|
"feature_v20: tests that could but should only run on SqlAlchemy v20",
|
|
148
|
+
"mypy: typing tests",
|
|
145
149
|
]
|
{snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/src/snowflake/sqlalchemy/__init__.py
RENAMED
|
@@ -46,6 +46,7 @@ from .custom_types import ( # noqa
|
|
|
46
46
|
BYTEINT,
|
|
47
47
|
CHARACTER,
|
|
48
48
|
DEC,
|
|
49
|
+
DECFLOAT,
|
|
49
50
|
DOUBLE,
|
|
50
51
|
FIXED,
|
|
51
52
|
GEOGRAPHY,
|
|
@@ -61,6 +62,7 @@ from .custom_types import ( # noqa
|
|
|
61
62
|
TINYINT,
|
|
62
63
|
VARBINARY,
|
|
63
64
|
VARIANT,
|
|
65
|
+
VECTOR,
|
|
64
66
|
)
|
|
65
67
|
from .sql.custom_schema import ( # noqa
|
|
66
68
|
DynamicTable,
|
|
@@ -93,6 +95,7 @@ _custom_types = (
|
|
|
93
95
|
"DATE",
|
|
94
96
|
"DATETIME",
|
|
95
97
|
"DECIMAL",
|
|
98
|
+
"DECFLOAT",
|
|
96
99
|
"FLOAT",
|
|
97
100
|
"INT",
|
|
98
101
|
"INTEGER",
|
|
@@ -120,6 +123,7 @@ _custom_types = (
|
|
|
120
123
|
"TINYINT",
|
|
121
124
|
"VARBINARY",
|
|
122
125
|
"VARIANT",
|
|
126
|
+
"VECTOR",
|
|
123
127
|
"MAP",
|
|
124
128
|
)
|
|
125
129
|
|
|
@@ -7,7 +7,7 @@ import operator
|
|
|
7
7
|
import re
|
|
8
8
|
import string
|
|
9
9
|
import warnings
|
|
10
|
-
from typing import List
|
|
10
|
+
from typing import Any, List
|
|
11
11
|
|
|
12
12
|
from sqlalchemy import exc as sa_exc
|
|
13
13
|
from sqlalchemy import inspect, sql
|
|
@@ -16,7 +16,7 @@ from sqlalchemy.engine import default
|
|
|
16
16
|
from sqlalchemy.orm import context
|
|
17
17
|
from sqlalchemy.orm.context import _MapperEntity
|
|
18
18
|
from sqlalchemy.schema import Sequence, Table
|
|
19
|
-
from sqlalchemy.sql import compiler, expression, functions
|
|
19
|
+
from sqlalchemy.sql import compiler, expression, functions, sqltypes
|
|
20
20
|
from sqlalchemy.sql.base import CompileState
|
|
21
21
|
from sqlalchemy.sql.elements import BindParameter, quoted_name
|
|
22
22
|
from sqlalchemy.sql.expression import Executable
|
|
@@ -526,6 +526,9 @@ class SnowflakeCompiler(compiler.SQLCompiler):
|
|
|
526
526
|
def visit_now_func(self, now, **kw):
|
|
527
527
|
return "CURRENT_TIMESTAMP"
|
|
528
528
|
|
|
529
|
+
def visit_sysdate_func(self, sysdate, **kw):
|
|
530
|
+
return "SYSDATE()"
|
|
531
|
+
|
|
529
532
|
def visit_merge_into(self, merge_into, **kw):
|
|
530
533
|
clauses = " ".join(
|
|
531
534
|
clause._compiler_dispatch(self, **kw) for clause in merge_into.clauses
|
|
@@ -779,6 +782,24 @@ class SnowflakeCompiler(compiler.SQLCompiler):
|
|
|
779
782
|
def visit_not_regexp_match_op_binary(self, binary, operator, **kw):
|
|
780
783
|
return f"NOT {self.visit_regexp_match_op_binary(binary, operator, **kw)}"
|
|
781
784
|
|
|
785
|
+
def visit_ilike_op_binary(self, binary, operator, **kw):
|
|
786
|
+
return self._render_ilike(binary, negate=False, **kw)
|
|
787
|
+
|
|
788
|
+
def visit_not_ilike_op_binary(self, binary, operator, **kw):
|
|
789
|
+
return self._render_ilike(binary, negate=True, **kw)
|
|
790
|
+
|
|
791
|
+
def _render_ilike(self, binary, negate=False, **kw):
|
|
792
|
+
left = binary.left._compiler_dispatch(self, **kw)
|
|
793
|
+
right = binary.right._compiler_dispatch(self, **kw)
|
|
794
|
+
escape = binary.modifiers.get("escape")
|
|
795
|
+
escape_clause = (
|
|
796
|
+
" ESCAPE " + self.render_literal_value(escape, sqltypes.STRINGTYPE)
|
|
797
|
+
if escape is not None
|
|
798
|
+
else ""
|
|
799
|
+
)
|
|
800
|
+
operator = "NOT ILIKE" if negate else "ILIKE"
|
|
801
|
+
return f"{left} {operator} {right}{escape_clause}"
|
|
802
|
+
|
|
782
803
|
def visit_join(self, join, asfrom=False, from_linter=None, **kwargs):
|
|
783
804
|
if from_linter:
|
|
784
805
|
from_linter.edges.update(
|
|
@@ -1146,7 +1167,6 @@ class SnowflakeTypeCompiler(compiler.GenericTypeCompiler):
|
|
|
1146
1167
|
else:
|
|
1147
1168
|
contents = []
|
|
1148
1169
|
for key in type_.items_types:
|
|
1149
|
-
|
|
1150
1170
|
row_text = f"{key} {type_.items_types[key][0].compile()}"
|
|
1151
1171
|
# Type and not null is specified
|
|
1152
1172
|
if len(type_.items_types[key]) > 1:
|
|
@@ -1157,10 +1177,14 @@ class SnowflakeTypeCompiler(compiler.GenericTypeCompiler):
|
|
|
1157
1177
|
def visit_BLOB(self, type_, **kw):
|
|
1158
1178
|
return "BINARY"
|
|
1159
1179
|
|
|
1160
|
-
def visit_datetime(self, type_, **kw):
|
|
1180
|
+
def visit_datetime(self, type_: sqltypes.DateTime, **kw: Any) -> str:
|
|
1181
|
+
if type_.timezone:
|
|
1182
|
+
return "TIMESTAMP_TZ"
|
|
1161
1183
|
return "datetime"
|
|
1162
1184
|
|
|
1163
|
-
def visit_DATETIME(self, type_, **kw):
|
|
1185
|
+
def visit_DATETIME(self, type_: sqltypes.DateTime, **kw: Any) -> str:
|
|
1186
|
+
if type_.timezone:
|
|
1187
|
+
return "TIMESTAMP_TZ"
|
|
1164
1188
|
return "DATETIME"
|
|
1165
1189
|
|
|
1166
1190
|
def visit_TIMESTAMP_NTZ(self, type_, **kw):
|
|
@@ -1172,7 +1196,9 @@ class SnowflakeTypeCompiler(compiler.GenericTypeCompiler):
|
|
|
1172
1196
|
def visit_TIMESTAMP_LTZ(self, type_, **kw):
|
|
1173
1197
|
return "TIMESTAMP_LTZ"
|
|
1174
1198
|
|
|
1175
|
-
def visit_TIMESTAMP(self, type_, **kw):
|
|
1199
|
+
def visit_TIMESTAMP(self, type_: sqltypes.TIMESTAMP, **kw: Any) -> str:
|
|
1200
|
+
if type_.timezone:
|
|
1201
|
+
return "TIMESTAMP_TZ"
|
|
1176
1202
|
return "TIMESTAMP"
|
|
1177
1203
|
|
|
1178
1204
|
def visit_GEOGRAPHY(self, type_, **kw):
|
|
@@ -1181,6 +1207,12 @@ class SnowflakeTypeCompiler(compiler.GenericTypeCompiler):
|
|
|
1181
1207
|
def visit_GEOMETRY(self, type_, **kw):
|
|
1182
1208
|
return "GEOMETRY"
|
|
1183
1209
|
|
|
1210
|
+
def visit_DECFLOAT(self, type_, **kw):
|
|
1211
|
+
return "DECFLOAT"
|
|
1212
|
+
|
|
1213
|
+
def visit_VECTOR(self, type_, **kw):
|
|
1214
|
+
return f"VECTOR({type_.element_type}, {type_.dimension})"
|
|
1215
|
+
|
|
1184
1216
|
|
|
1185
1217
|
construct_arguments = [(Table, {"clusterby": None})]
|
|
1186
1218
|
|
{snowflake_sqlalchemy-1.8.2 → snowflake_sqlalchemy-1.9.0}/src/snowflake/sqlalchemy/compat.py
RENAMED
|
@@ -11,7 +11,7 @@ from sqlalchemy import util
|
|
|
11
11
|
string_types = (str,)
|
|
12
12
|
returns_unicode = util.symbol("RETURNS_UNICODE")
|
|
13
13
|
|
|
14
|
-
IS_VERSION_20 = tuple(int(v) for v in SA_VERSION.split(".")) >= (2, 0
|
|
14
|
+
IS_VERSION_20 = tuple(int(v) for v in SA_VERSION.split(".")[:2]) >= (2, 0)
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
def args_reducer(positions_to_drop: tuple):
|