snowflake-sqlalchemy 1.7.7__tar.gz → 1.8.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.7.7 → snowflake_sqlalchemy-1.8.0}/DESCRIPTION.md +9 -1
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/PKG-INFO +3 -5
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/README.md +0 -3
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/ci/docker/sqlalchemy_build/Dockerfile +10 -3
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/ci/test_docker.sh +19 -19
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/ci/test_linux.sh +2 -2
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/pyproject.toml +8 -7
- snowflake_sqlalchemy-1.8.0/snyk/requirements.txt +2 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/snowdialect.py +59 -13
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/version.py +1 -1
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/conftest.py +22 -33
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/test_core.py +3 -3
- snowflake_sqlalchemy-1.8.0/tests/test_dialect_connect.py +124 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/test_pandas.py +3 -1
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/test_qmark.py +3 -1
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tox.ini +10 -4
- snowflake_sqlalchemy-1.7.7/snyk/requirements.txt +0 -2
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/.gitignore +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/.gitmodules +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/.pre-commit-config.yaml +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/LICENSE.txt +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/MANIFEST.in +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/ci/build.sh +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/ci/build_docker.sh +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/ci/docker/sqlalchemy_build/scripts/entrypoint.sh +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/ci/set_base_image.sh +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/ci/test.sh +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/license_header.txt +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/setup.cfg +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/snyk/requiremtnts.txt +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/snyk/update_requirements.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/__init__.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/_constants.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/base.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/compat.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/custom_commands.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/custom_types.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/exc.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/functions.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/name_utils.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/parser/custom_type_parser.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/provision.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/requirements.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/sql/__init__.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/sql/custom_schema/__init__.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/sql/custom_schema/clustered_table.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/sql/custom_schema/custom_table_base.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/sql/custom_schema/custom_table_prefix.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/sql/custom_schema/dynamic_table.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/sql/custom_schema/hybrid_table.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/sql/custom_schema/iceberg_table.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/sql/custom_schema/options/__init__.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/sql/custom_schema/options/as_query_option.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/sql/custom_schema/options/cluster_by_option.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/sql/custom_schema/options/identifier_option.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/sql/custom_schema/options/invalid_table_option.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/sql/custom_schema/options/keyword_option.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/sql/custom_schema/options/keywords.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/sql/custom_schema/options/literal_option.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/sql/custom_schema/options/table_option.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/sql/custom_schema/options/target_lag_option.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/sql/custom_schema/snowflake_table.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/sql/custom_schema/table_from_query.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/structured_type_info_manager.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/util.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tested_requirements/requirements_310.reqs +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tested_requirements/requirements_37.reqs +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tested_requirements/requirements_38.reqs +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tested_requirements/requirements_39.reqs +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/README.rst +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/__init__.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/__snapshots__/test_compile_dynamic_table.ambr +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/__snapshots__/test_core.ambr +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/__snapshots__/test_orm.ambr +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/__snapshots__/test_reflect_dynamic_table.ambr +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/__snapshots__/test_structured_datatypes.ambr +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/__snapshots__/test_unit_structured_types.ambr +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/custom_tables/__init__.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/custom_tables/__snapshots__/test_compile_dynamic_table.ambr +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/custom_tables/__snapshots__/test_compile_hybrid_table.ambr +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/custom_tables/__snapshots__/test_compile_iceberg_table.ambr +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/custom_tables/__snapshots__/test_compile_snowflake_table.ambr +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/custom_tables/__snapshots__/test_create_dynamic_table.ambr +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/custom_tables/__snapshots__/test_create_hybrid_table.ambr +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/custom_tables/__snapshots__/test_create_iceberg_table.ambr +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/custom_tables/__snapshots__/test_create_snowflake_table.ambr +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/custom_tables/__snapshots__/test_generic_options.ambr +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/custom_tables/__snapshots__/test_reflect_hybrid_table.ambr +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/custom_tables/__snapshots__/test_reflect_snowflake_table.ambr +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/custom_tables/test_compile_dynamic_table.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/custom_tables/test_compile_hybrid_table.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/custom_tables/test_compile_iceberg_table.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/custom_tables/test_compile_snowflake_table.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/custom_tables/test_create_dynamic_table.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/custom_tables/test_create_hybrid_table.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/custom_tables/test_create_iceberg_table.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/custom_tables/test_create_snowflake_table.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/custom_tables/test_generic_options.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/custom_tables/test_reflect_dynamic_table.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/custom_tables/test_reflect_hybrid_table.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/custom_tables/test_reflect_snowflake_table.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/data/users.txt +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/sqlalchemy_test_suite/README.md +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/sqlalchemy_test_suite/__init__.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/sqlalchemy_test_suite/conftest.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/sqlalchemy_test_suite/test_suite.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/sqlalchemy_test_suite/test_suite_20.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/test_compiler.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/test_copy.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/test_create.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/test_custom_functions.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/test_custom_types.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/test_geography.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/test_geometry.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/test_imports.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/test_index_reflection.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/test_multivalues_insert.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/test_orm.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/test_quote.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/test_quote_identifiers.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/test_semi_structured_datatypes.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/test_sequence.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/test_structured_datatypes.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/test_timestamp.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/test_transactions.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/test_unit_core.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/test_unit_cte.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/test_unit_structured_types.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/test_unit_types.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/test_unit_url.py +0 -0
- {snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/util.py +0 -0
|
@@ -6,11 +6,19 @@ Snowflake Documentation is available at:
|
|
|
6
6
|
|
|
7
7
|
Source code is also available at:
|
|
8
8
|
<https://github.com/snowflakedb/snowflake-sqlalchemy>
|
|
9
|
+
|
|
9
10
|
# Unreleased Notes
|
|
10
11
|
|
|
11
12
|
# Release Notes
|
|
13
|
+
|
|
14
|
+
- v1.8.0(December 5, 2025)
|
|
15
|
+
- Add logging of SQLAlchemy version
|
|
16
|
+
- Bump `snowflake-connector-python<5.0.0`
|
|
17
|
+
- Add python up to 3.14
|
|
18
|
+
- Add logging of SQLAlchemy version and pandas (if used)
|
|
19
|
+
|
|
12
20
|
- v1.7.7(September 3, 2025)
|
|
13
|
-
- Fix exception for structured type columns dropped while collecting
|
|
21
|
+
- Fix exception for structured type columns dropped while collecting metadata
|
|
14
22
|
|
|
15
23
|
- v1.7.6(July 10, 2025)
|
|
16
24
|
- Fix get_multi_indexes issue, wrong assign of returned indexes when processing multiple indexes in a table
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: snowflake-sqlalchemy
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.8.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
|
|
@@ -27,6 +27,7 @@ Classifier: Programming Language :: Python :: 3.9
|
|
|
27
27
|
Classifier: Programming Language :: Python :: 3.10
|
|
28
28
|
Classifier: Programming Language :: Python :: 3.11
|
|
29
29
|
Classifier: Programming Language :: Python :: 3.12
|
|
30
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
30
31
|
Classifier: Programming Language :: SQL
|
|
31
32
|
Classifier: Topic :: Database
|
|
32
33
|
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
@@ -35,7 +36,7 @@ Classifier: Topic :: Software Development :: Libraries
|
|
|
35
36
|
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
36
37
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
37
38
|
Requires-Python: >=3.8
|
|
38
|
-
Requires-Dist: snowflake-connector-python<
|
|
39
|
+
Requires-Dist: snowflake-connector-python<5.0.0
|
|
39
40
|
Requires-Dist: sqlalchemy>=1.4.19
|
|
40
41
|
Provides-Extra: development
|
|
41
42
|
Requires-Dist: mock; extra == 'development'
|
|
@@ -63,9 +64,6 @@ Description-Content-Type: text/markdown
|
|
|
63
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.
|
|
64
65
|
|
|
65
66
|
|
|
66
|
-
| :exclamation: | Effective May 8th, 2025, Snowflake SQLAlchemy will transition to maintenance mode and will cease active development. Support will be limited to addressing critical bugs and security vulnerabilities. To report such issues, please [create a case with Snowflake Support](https://community.snowflake.com/s/article/How-To-Submit-a-Support-Case-in-Snowflake-Lodge). for individual evaluation. Please note that pull requests from external contributors may not receive action from Snowflake. |
|
|
67
|
-
|---------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
68
|
-
|
|
69
67
|
|
|
70
68
|
## Prerequisites
|
|
71
69
|
|
|
@@ -9,9 +9,6 @@
|
|
|
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
|
|
|
12
|
-
| :exclamation: | Effective May 8th, 2025, Snowflake SQLAlchemy will transition to maintenance mode and will cease active development. Support will be limited to addressing critical bugs and security vulnerabilities. To report such issues, please [create a case with Snowflake Support](https://community.snowflake.com/s/article/How-To-Submit-a-Support-Case-in-Snowflake-Lodge). for individual evaluation. Please note that pull requests from external contributors may not receive action from Snowflake. |
|
|
13
|
-
|---------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
14
|
-
|
|
15
12
|
|
|
16
13
|
## Prerequisites
|
|
17
14
|
|
{snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/ci/docker/sqlalchemy_build/Dockerfile
RENAMED
|
@@ -4,9 +4,9 @@ FROM $BASE_IMAGE
|
|
|
4
4
|
|
|
5
5
|
# This is to solve permission issue, read https://denibertovic.com/posts/handling-permissions-with-docker-volumes/
|
|
6
6
|
ARG GOSU_URL=https://github.com/tianon/gosu/releases/download/1.14/gosu-amd64
|
|
7
|
-
ENV GOSU_PATH
|
|
7
|
+
ENV GOSU_PATH=$GOSU_URL
|
|
8
8
|
RUN curl -o /usr/local/bin/gosu -SL $GOSU_PATH \
|
|
9
|
-
|
|
9
|
+
&& chmod +x /usr/local/bin/gosu
|
|
10
10
|
|
|
11
11
|
COPY scripts/entrypoint.sh /usr/local/bin/entrypoint.sh
|
|
12
12
|
RUN chmod +x /usr/local/bin/entrypoint.sh
|
|
@@ -14,6 +14,13 @@ RUN chmod +x /usr/local/bin/entrypoint.sh
|
|
|
14
14
|
WORKDIR /home/user
|
|
15
15
|
RUN chmod 777 /home/user
|
|
16
16
|
|
|
17
|
-
ENV PATH="${PATH}:/opt/python/cp37-
|
|
17
|
+
ENV PATH="${PATH}:/opt/python/cp37-cp37/bin"
|
|
18
|
+
ENV PATH="${PATH}:/opt/python/cp38-cp38/bin"
|
|
19
|
+
ENV PATH="${PATH}:/opt/python/cp39-cp39/bin"
|
|
20
|
+
ENV PATH="${PATH}:/opt/python/cp310-cp310/bin"
|
|
21
|
+
ENV PATH="${PATH}:/opt/python/cp311-cp311/bin"
|
|
22
|
+
ENV PATH="${PATH}:/opt/python/cp312-cp312/bin"
|
|
23
|
+
ENV PATH="${PATH}:/opt/python/cp313-cp313/bin"
|
|
24
|
+
ENV PATH="${PATH}:/opt/python/cp314-cp314/bin"
|
|
18
25
|
|
|
19
26
|
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
|
|
@@ -7,11 +7,11 @@
|
|
|
7
7
|
set -o pipefail
|
|
8
8
|
|
|
9
9
|
# In case this is ran from dev-vm
|
|
10
|
-
PYTHON_ENV=${1:-3.
|
|
10
|
+
PYTHON_ENV=${1:-3.8}
|
|
11
11
|
|
|
12
12
|
# Set constants
|
|
13
|
-
THIS_DIR="$(
|
|
14
|
-
SQLALCHEMY_DIR="$(
|
|
13
|
+
THIS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
14
|
+
SQLALCHEMY_DIR="$(dirname "${THIS_DIR}")"
|
|
15
15
|
WORKSPACE=${WORKSPACE:-$SQLALCHEMY_DIR}
|
|
16
16
|
source $THIS_DIR/set_base_image.sh
|
|
17
17
|
|
|
@@ -35,19 +35,19 @@ echo "[Info] Start building docker image and testing"
|
|
|
35
35
|
|
|
36
36
|
user_id=$(id -u ${USER})
|
|
37
37
|
docker run \
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
38
|
+
--rm \
|
|
39
|
+
--network=host \
|
|
40
|
+
-e TERM=vt102 \
|
|
41
|
+
-e PIP_DISABLE_PIP_VERSION_CHECK=1 \
|
|
42
|
+
-e OPENSSL_FIPS=1 \
|
|
43
|
+
-e LOCAL_USER_ID=${user_id} \
|
|
44
|
+
-e AWS_ACCESS_KEY_ID \
|
|
45
|
+
-e AWS_SECRET_ACCESS_KEY \
|
|
46
|
+
-e SF_REGRESS_LOGS \
|
|
47
|
+
-e SF_PROJECT_ROOT \
|
|
48
|
+
-e cloud_provider \
|
|
49
|
+
-e JENKINS_HOME \
|
|
50
|
+
-e GITHUB_ACTIONS \
|
|
51
|
+
--mount type=bind,source="${SQLALCHEMY_DIR}",target=/home/user/snowflake-sqlalchemy \
|
|
52
|
+
$(docker build --pull --build-arg BASE_IMAGE=$BASE_IMAGE --build-arg GOSU_URL="$GOSU_URL" -q .) \
|
|
53
|
+
/home/user/snowflake-sqlalchemy/ci/test_linux.sh ${PYTHON_ENV}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
# - This script assumes that ../dist/repaired_wheels has the wheel(s) built for all versions to be tested
|
|
7
7
|
# - This is the script that test_docker.sh runs inside of the docker container
|
|
8
8
|
|
|
9
|
-
PYTHON_VERSIONS="${1:-3.8 3.9 3.10 3.11}"
|
|
9
|
+
PYTHON_VERSIONS="${1:-3.8 3.9 3.10 3.11 3.12 3.13 3.14}"
|
|
10
10
|
THIS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
11
11
|
SQLALCHEMY_DIR="$(dirname "${THIS_DIR}")"
|
|
12
12
|
|
|
@@ -19,7 +19,7 @@ for PYTHON_VERSION in ${PYTHON_VERSIONS}; do
|
|
|
19
19
|
echo "[Info] Testing with ${PYTHON_VERSION}"
|
|
20
20
|
SHORT_VERSION=$(python3 -c "print('${PYTHON_VERSION}'.replace('.', ''))")
|
|
21
21
|
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,py${SHORT_VERSION}-coverage
|
|
22
|
+
TEST_ENVLIST=fix_lint,py${SHORT_VERSION}-ci,py${SHORT_VERSION}-coverage,py${SHORT_VERSION}-pandas-ci,py${SHORT_VERSION}-pandas-coverage
|
|
23
23
|
echo "[Info] Running tox for ${TEST_ENVLIST}"
|
|
24
24
|
python3 -m tox -e ${TEST_ENVLIST} --installpkg ${SQLALCHEMY_WHL}
|
|
25
25
|
done
|
|
@@ -30,6 +30,7 @@ classifiers = [
|
|
|
30
30
|
"Programming Language :: Python :: 3.10",
|
|
31
31
|
"Programming Language :: Python :: 3.11",
|
|
32
32
|
"Programming Language :: Python :: 3.12",
|
|
33
|
+
"Programming Language :: Python :: 3.13",
|
|
33
34
|
"Programming Language :: SQL",
|
|
34
35
|
"Topic :: Database",
|
|
35
36
|
"Topic :: Scientific/Engineering :: Information Analysis",
|
|
@@ -38,7 +39,7 @@ classifiers = [
|
|
|
38
39
|
"Topic :: Software Development :: Libraries :: Application Frameworks",
|
|
39
40
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
40
41
|
]
|
|
41
|
-
dependencies = ["SQLAlchemy>=1.4.19", "snowflake-connector-python<
|
|
42
|
+
dependencies = ["SQLAlchemy>=1.4.19", "snowflake-connector-python<5.0.0"]
|
|
42
43
|
|
|
43
44
|
[tool.hatch.version]
|
|
44
45
|
path = "src/snowflake/sqlalchemy/version.py"
|
|
@@ -75,17 +76,17 @@ exclude = ["/.github"]
|
|
|
75
76
|
packages = ["src/snowflake"]
|
|
76
77
|
|
|
77
78
|
[tool.hatch.envs.default]
|
|
78
|
-
path = ".venv"
|
|
79
|
-
type = "virtual"
|
|
80
79
|
extra-dependencies = ["SQLAlchemy>=1.4.19,<2.1.0"]
|
|
81
80
|
features = ["development", "pandas"]
|
|
82
|
-
python = "3.
|
|
81
|
+
python = "3.12"
|
|
83
82
|
installer = "uv"
|
|
84
83
|
|
|
85
84
|
[tool.hatch.envs.sa14]
|
|
86
|
-
|
|
85
|
+
installer = "uv"
|
|
86
|
+
builder = true
|
|
87
|
+
extra-dependencies = ["SQLAlchemy>=1.4.19,<2.0.0", "pandas<2.1", "numpy<2"]
|
|
87
88
|
features = ["development", "pandas"]
|
|
88
|
-
python = "3.
|
|
89
|
+
python = "3.12"
|
|
89
90
|
|
|
90
91
|
[tool.hatch.envs.sa14.scripts]
|
|
91
92
|
test-dialect = "pytest --ignore_v20_test -ra -vvv --tb=short --cov snowflake.sqlalchemy --cov-append --junitxml ./junit.xml --ignore=tests/sqlalchemy_test_suite tests/"
|
|
@@ -105,7 +106,7 @@ gh-cache-sum = "python -VV | sha256sum | cut -d' ' -f1"
|
|
|
105
106
|
check-import = "python -c 'import snowflake.sqlalchemy; print(snowflake.sqlalchemy.__version__)'"
|
|
106
107
|
|
|
107
108
|
[[tool.hatch.envs.release.matrix]]
|
|
108
|
-
python = ["3.8", "3.9", "3.10", "3.11", "3.12"]
|
|
109
|
+
python = ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
|
|
109
110
|
features = ["development", "pandas"]
|
|
110
111
|
|
|
111
112
|
[tool.hatch.envs.release.scripts]
|
{snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/snowdialect.py
RENAMED
|
@@ -5,10 +5,13 @@ import operator
|
|
|
5
5
|
from collections import defaultdict
|
|
6
6
|
from enum import Enum
|
|
7
7
|
from functools import reduce
|
|
8
|
-
from
|
|
8
|
+
from logging import getLogger
|
|
9
|
+
from time import time as time_in_seconds
|
|
10
|
+
from typing import Any, Collection, Optional, cast
|
|
9
11
|
from urllib.parse import unquote_plus
|
|
10
12
|
|
|
11
13
|
import sqlalchemy.sql.sqltypes as sqltypes
|
|
14
|
+
from sqlalchemy import __version__ as SQLALCHEMY_VERSION
|
|
12
15
|
from sqlalchemy import event as sa_vnt
|
|
13
16
|
from sqlalchemy import exc as sa_exc
|
|
14
17
|
from sqlalchemy import util as sa_util
|
|
@@ -19,8 +22,10 @@ from sqlalchemy.sql.sqltypes import NullType
|
|
|
19
22
|
from sqlalchemy.types import FLOAT, Date, DateTime, Float, Time
|
|
20
23
|
|
|
21
24
|
from snowflake.connector import errors as sf_errors
|
|
22
|
-
from snowflake.connector.connection import DEFAULT_CONFIGURATION
|
|
25
|
+
from snowflake.connector.connection import DEFAULT_CONFIGURATION, SnowflakeConnection
|
|
23
26
|
from snowflake.connector.constants import UTF8
|
|
27
|
+
from snowflake.connector.network import SnowflakeRestful
|
|
28
|
+
from snowflake.connector.telemetry import TelemetryClient, TelemetryData, TelemetryField
|
|
24
29
|
from snowflake.sqlalchemy.compat import returns_unicode
|
|
25
30
|
from snowflake.sqlalchemy.name_utils import _NameUtils
|
|
26
31
|
from snowflake.sqlalchemy.structured_type_info_manager import _StructuredTypeInfoManager
|
|
@@ -59,6 +64,12 @@ colspecs = {
|
|
|
59
64
|
|
|
60
65
|
_ENABLE_SQLALCHEMY_AS_APPLICATION_NAME = True
|
|
61
66
|
|
|
67
|
+
logger = getLogger(__name__)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class TelemetryEvents(Enum):
|
|
71
|
+
NEW_CONNECTION = "sqlalchemy_new_connection"
|
|
72
|
+
|
|
62
73
|
|
|
63
74
|
class SnowflakeIsolationLevel(Enum):
|
|
64
75
|
READ_COMMITTED = "READ COMMITTED"
|
|
@@ -896,18 +907,53 @@ class SnowflakeDialect(default.DefaultDialect):
|
|
|
896
907
|
return self._value_or_default(data, table_name, schema)
|
|
897
908
|
|
|
898
909
|
def connect(self, *cargs, **cparams):
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
910
|
+
if _ENABLE_SQLALCHEMY_AS_APPLICATION_NAME:
|
|
911
|
+
cparams = _update_connection_application_name(**cparams)
|
|
912
|
+
|
|
913
|
+
connection = super().connect(*cargs, **cparams)
|
|
914
|
+
self._log_new_connection_event(connection)
|
|
915
|
+
|
|
916
|
+
return connection
|
|
917
|
+
|
|
918
|
+
def _log_new_connection_event(self, connection):
|
|
919
|
+
try:
|
|
920
|
+
snowflake_connection = cast(SnowflakeConnection, cast(object, connection))
|
|
921
|
+
snowflake_rest_client = SnowflakeRestful(
|
|
922
|
+
host=snowflake_connection.host,
|
|
923
|
+
port=snowflake_connection.port,
|
|
924
|
+
protocol="https",
|
|
925
|
+
connection=snowflake_connection,
|
|
926
|
+
)
|
|
927
|
+
snowflake_telemetry_client = TelemetryClient(rest=snowflake_rest_client)
|
|
928
|
+
|
|
929
|
+
telemetry_value = {
|
|
930
|
+
"SQLAlchemy": SQLALCHEMY_VERSION,
|
|
931
|
+
}
|
|
932
|
+
try:
|
|
933
|
+
from pandas import __version__ as PANDAS_VERSION
|
|
934
|
+
|
|
935
|
+
telemetry_value["pandas"] = PANDAS_VERSION
|
|
936
|
+
except ImportError:
|
|
937
|
+
pass
|
|
938
|
+
|
|
939
|
+
snowflake_telemetry_client.add_log_to_batch(
|
|
940
|
+
TelemetryData.from_telemetry_data_dict(
|
|
941
|
+
from_dict={
|
|
942
|
+
TelemetryField.KEY_TYPE.value: TelemetryEvents.NEW_CONNECTION.value,
|
|
943
|
+
TelemetryField.KEY_VALUE.value: str(telemetry_value),
|
|
944
|
+
},
|
|
945
|
+
timestamp=int(time_in_seconds() * 1000),
|
|
946
|
+
connection=snowflake_connection,
|
|
947
|
+
)
|
|
948
|
+
)
|
|
949
|
+
snowflake_telemetry_client.send_batch()
|
|
950
|
+
except Exception as e:
|
|
951
|
+
logger.debug(
|
|
952
|
+
"Failed to send telemetry data for %s event: %s: %s",
|
|
953
|
+
TelemetryEvents.NEW_CONNECTION.value,
|
|
954
|
+
type(e).__name__,
|
|
955
|
+
str(e),
|
|
907
956
|
)
|
|
908
|
-
if _ENABLE_SQLALCHEMY_AS_APPLICATION_NAME
|
|
909
|
-
else super().connect(*cargs, **cparams)
|
|
910
|
-
)
|
|
911
957
|
|
|
912
958
|
|
|
913
959
|
@sa_vnt.listens_for(Table, "before_create")
|
|
@@ -9,6 +9,7 @@ import sys
|
|
|
9
9
|
import time
|
|
10
10
|
import uuid
|
|
11
11
|
from logging import getLogger
|
|
12
|
+
from typing import Literal
|
|
12
13
|
|
|
13
14
|
import pytest
|
|
14
15
|
from sqlalchemy import create_engine
|
|
@@ -58,7 +59,6 @@ def pytest_addoption(parser):
|
|
|
58
59
|
|
|
59
60
|
def pytest_collection_modifyitems(config, items):
|
|
60
61
|
if config.getoption("--ignore_v20_test"):
|
|
61
|
-
# --ignore_v20_test given in cli: skip sqlalchemy 2.0 tests
|
|
62
62
|
skip_feature_v2 = pytest.mark.skip(
|
|
63
63
|
reason="need remove --ignore_v20_test option to run"
|
|
64
64
|
)
|
|
@@ -67,37 +67,11 @@ def pytest_collection_modifyitems(config, items):
|
|
|
67
67
|
item.add_marker(skip_feature_v2)
|
|
68
68
|
|
|
69
69
|
|
|
70
|
-
@pytest.fixture(scope="session")
|
|
71
|
-
def on_travis():
|
|
72
|
-
return os.getenv("TRAVIS", "").lower() == "true"
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
@pytest.fixture(scope="session")
|
|
76
|
-
def on_appveyor():
|
|
77
|
-
return os.getenv("APPVEYOR", "").lower() == "true"
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
@pytest.fixture(scope="session")
|
|
81
|
-
def on_public_ci(on_travis, on_appveyor):
|
|
82
|
-
return on_travis or on_appveyor
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
def help():
|
|
86
|
-
print(
|
|
87
|
-
"""Connection parameter must be specified in parameters.py,
|
|
88
|
-
for example:
|
|
89
|
-
CONNECTION_PARAMETERS = {
|
|
90
|
-
'account': 'testaccount',
|
|
91
|
-
'user': 'user1',
|
|
92
|
-
'password': 'test',
|
|
93
|
-
'database': 'testdb',
|
|
94
|
-
'schema': 'public',
|
|
95
|
-
}"""
|
|
96
|
-
)
|
|
97
|
-
|
|
98
|
-
|
|
99
70
|
logger = getLogger(__name__)
|
|
100
71
|
|
|
72
|
+
TZ_ENV_VAR: Literal["TZ"] = "TZ"
|
|
73
|
+
DEFAULT_TZ_VALUE: Literal["UTC"] = "UTC"
|
|
74
|
+
|
|
101
75
|
DEFAULT_PARAMETERS = {
|
|
102
76
|
"account": "<account_name>",
|
|
103
77
|
"user": "<user_name>",
|
|
@@ -133,6 +107,11 @@ def external_stage():
|
|
|
133
107
|
raise ValueError("External_stage is not set")
|
|
134
108
|
|
|
135
109
|
|
|
110
|
+
@pytest.fixture(scope="session")
|
|
111
|
+
def on_public_ci():
|
|
112
|
+
return running_on_public_ci()
|
|
113
|
+
|
|
114
|
+
|
|
136
115
|
@pytest.fixture(scope="function")
|
|
137
116
|
def base_location(external_stage, engine_testaccount):
|
|
138
117
|
unique_id = str(uuid.uuid4())
|
|
@@ -150,9 +129,12 @@ def get_db_parameters() -> dict:
|
|
|
150
129
|
Sets the db connection parameters
|
|
151
130
|
"""
|
|
152
131
|
ret = {}
|
|
153
|
-
os.environ[
|
|
154
|
-
if
|
|
155
|
-
|
|
132
|
+
os.environ[TZ_ENV_VAR] = DEFAULT_TZ_VALUE
|
|
133
|
+
if hasattr(time, "tzset"):
|
|
134
|
+
if not IS_WINDOWS:
|
|
135
|
+
time.tzset()
|
|
136
|
+
else:
|
|
137
|
+
logger.warning("time.tzset is unavailable on this platform")
|
|
156
138
|
|
|
157
139
|
ret.update(DEFAULT_PARAMETERS)
|
|
158
140
|
ret.update(CONNECTION_PARAMETERS)
|
|
@@ -318,6 +300,7 @@ def running_on_public_ci() -> bool:
|
|
|
318
300
|
|
|
319
301
|
def pytest_runtest_setup(item) -> None:
|
|
320
302
|
"""Ran before calling each test, used to decide whether a test should be skipped."""
|
|
303
|
+
_ensure_optional_dependencies(item)
|
|
321
304
|
test_tags = [mark.name for mark in item.iter_markers()]
|
|
322
305
|
|
|
323
306
|
# Get what cloud providers the test is marked for if any
|
|
@@ -335,3 +318,9 @@ def pytest_runtest_setup(item) -> None:
|
|
|
335
318
|
pytest.skip("cannot run this test on external CI")
|
|
336
319
|
elif INTERNAL_SKIP_TAGS.intersection(test_tags) and not running_on_public_ci():
|
|
337
320
|
pytest.skip("cannot run this test on internal CI")
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def _ensure_optional_dependencies(item):
|
|
324
|
+
"""Skip optional-dependency tests when the dependency is unavailable."""
|
|
325
|
+
if "pandas" in item.keywords:
|
|
326
|
+
pytest.importorskip("pandas")
|
|
@@ -171,7 +171,6 @@ def test_connect_args():
|
|
|
171
171
|
|
|
172
172
|
|
|
173
173
|
def test_boolean_query_argument_parsing():
|
|
174
|
-
|
|
175
174
|
engine = create_engine(
|
|
176
175
|
URL(
|
|
177
176
|
**CONNECTION_PARAMETERS,
|
|
@@ -1385,12 +1384,12 @@ def test_special_schema_character(db_parameters, on_public_ci):
|
|
|
1385
1384
|
with connect(**options) as sf_conn:
|
|
1386
1385
|
sf_connection = (
|
|
1387
1386
|
sf_conn.cursor()
|
|
1388
|
-
.execute("select current_database(),
|
|
1387
|
+
.execute("select current_database(), current_schema();")
|
|
1389
1388
|
.fetchall()
|
|
1390
1389
|
)
|
|
1391
1390
|
with create_engine(URL(**options)).connect() as sa_conn:
|
|
1392
1391
|
sa_connection = sa_conn.execute(
|
|
1393
|
-
text("select current_database(),
|
|
1392
|
+
text("select current_database(), current_schema();")
|
|
1394
1393
|
).fetchall()
|
|
1395
1394
|
# Teardown
|
|
1396
1395
|
with connect(**options) as conn:
|
|
@@ -1823,6 +1822,7 @@ CREATE OR REPLACE TEMP TABLE {table_name}
|
|
|
1823
1822
|
)
|
|
1824
1823
|
|
|
1825
1824
|
|
|
1825
|
+
@pytest.mark.pandas
|
|
1826
1826
|
def test_snowflake_sqlalchemy_as_valid_client_type():
|
|
1827
1827
|
engine = create_engine(
|
|
1828
1828
|
URL(**CONNECTION_PARAMETERS),
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright (c) 2012-2023 Snowflake Computing Inc. All rights reserved.
|
|
3
|
+
#
|
|
4
|
+
|
|
5
|
+
from sys import modules
|
|
6
|
+
from types import SimpleNamespace
|
|
7
|
+
from unittest import mock
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
from sqlalchemy import __version__ as SQLALCHEMY_VERSION
|
|
11
|
+
from sqlalchemy.engine import default as sqla_default
|
|
12
|
+
|
|
13
|
+
from snowflake.sqlalchemy.snowdialect import (
|
|
14
|
+
SnowflakeDialect,
|
|
15
|
+
TelemetryEvents,
|
|
16
|
+
TelemetryField,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@pytest.fixture
|
|
21
|
+
def fake_connection():
|
|
22
|
+
return SimpleNamespace(
|
|
23
|
+
host="example.snowflakecomputing.com",
|
|
24
|
+
port=443,
|
|
25
|
+
application="test_app",
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@mock.patch.object(sqla_default.DefaultDialect, "connect")
|
|
30
|
+
@mock.patch("snowflake.sqlalchemy.snowdialect.TelemetryClient")
|
|
31
|
+
@mock.patch("snowflake.sqlalchemy.snowdialect.SnowflakeRestful")
|
|
32
|
+
def test_connect_sends_telemetry(
|
|
33
|
+
mock_restful, mock_telemetry_client, mock_connect, fake_connection
|
|
34
|
+
):
|
|
35
|
+
"""Ensure telemetry is sent with the expected payload on connect."""
|
|
36
|
+
mock_connect.return_value = fake_connection
|
|
37
|
+
|
|
38
|
+
# Mock out pandas to ensure deterministic behavior
|
|
39
|
+
with mock.patch.dict(modules, {"pandas": None}):
|
|
40
|
+
dialect = SnowflakeDialect()
|
|
41
|
+
result = dialect.connect()
|
|
42
|
+
|
|
43
|
+
assert result is fake_connection
|
|
44
|
+
|
|
45
|
+
# Verify add_log_to_batch was called with correct payload
|
|
46
|
+
telemetry_instance = mock_telemetry_client.return_value
|
|
47
|
+
payload = telemetry_instance.add_log_to_batch.call_args[0][0]
|
|
48
|
+
assert (
|
|
49
|
+
payload.message[TelemetryField.KEY_TYPE.value]
|
|
50
|
+
== TelemetryEvents.NEW_CONNECTION.value
|
|
51
|
+
)
|
|
52
|
+
assert payload.message[TelemetryField.KEY_VALUE.value] == str(
|
|
53
|
+
{"SQLAlchemy": SQLALCHEMY_VERSION}
|
|
54
|
+
)
|
|
55
|
+
assert payload.timestamp != 0
|
|
56
|
+
|
|
57
|
+
# Verify send_batch was called
|
|
58
|
+
telemetry_instance.send_batch.assert_called_once()
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@mock.patch.object(sqla_default.DefaultDialect, "connect")
|
|
62
|
+
@mock.patch("snowflake.sqlalchemy.snowdialect.TelemetryClient")
|
|
63
|
+
@mock.patch("snowflake.sqlalchemy.snowdialect.SnowflakeRestful")
|
|
64
|
+
def test_connect_telemetry_includes_pandas_when_available(
|
|
65
|
+
mock_restful, mock_telemetry_client, mock_connect, fake_connection
|
|
66
|
+
):
|
|
67
|
+
"""Ensure telemetry includes pandas version when pandas is installed."""
|
|
68
|
+
mock_connect.return_value = fake_connection
|
|
69
|
+
|
|
70
|
+
# Create a mock pandas module with a version
|
|
71
|
+
mock_pandas = mock.MagicMock()
|
|
72
|
+
mock_pandas.__version__ = "2.1.0"
|
|
73
|
+
|
|
74
|
+
with mock.patch.dict(modules, {"pandas": mock_pandas}):
|
|
75
|
+
dialect = SnowflakeDialect()
|
|
76
|
+
dialect.connect()
|
|
77
|
+
|
|
78
|
+
telemetry_instance = mock_telemetry_client.return_value
|
|
79
|
+
payload = telemetry_instance.add_log_to_batch.call_args[0][0]
|
|
80
|
+
telemetry_value = payload.message[TelemetryField.KEY_VALUE.value]
|
|
81
|
+
|
|
82
|
+
assert telemetry_value == str({"SQLAlchemy": SQLALCHEMY_VERSION, "pandas": "2.1.0"})
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@mock.patch.object(sqla_default.DefaultDialect, "connect")
|
|
86
|
+
@mock.patch("snowflake.sqlalchemy.snowdialect.TelemetryClient")
|
|
87
|
+
@mock.patch("snowflake.sqlalchemy.snowdialect.SnowflakeRestful")
|
|
88
|
+
def test_connect_telemetry_excludes_pandas_when_not_available(
|
|
89
|
+
mock_restful, mock_telemetry_client, mock_connect, fake_connection
|
|
90
|
+
):
|
|
91
|
+
"""Ensure telemetry does not include pandas when it is not installed."""
|
|
92
|
+
mock_connect.return_value = fake_connection
|
|
93
|
+
|
|
94
|
+
# Simulate pandas not being installed
|
|
95
|
+
with mock.patch.dict(modules, {"pandas": None}):
|
|
96
|
+
dialect = SnowflakeDialect()
|
|
97
|
+
dialect.connect()
|
|
98
|
+
|
|
99
|
+
telemetry_instance = mock_telemetry_client.return_value
|
|
100
|
+
payload = telemetry_instance.add_log_to_batch.call_args[0][0]
|
|
101
|
+
telemetry_value = payload.message[TelemetryField.KEY_VALUE.value]
|
|
102
|
+
|
|
103
|
+
assert telemetry_value == str({"SQLAlchemy": SQLALCHEMY_VERSION})
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@mock.patch.object(sqla_default.DefaultDialect, "connect")
|
|
107
|
+
@mock.patch("snowflake.sqlalchemy.snowdialect.TelemetryClient")
|
|
108
|
+
@mock.patch("snowflake.sqlalchemy.snowdialect.SnowflakeRestful")
|
|
109
|
+
def test_connect_logs_when_telemetry_fails(
|
|
110
|
+
mock_restful, mock_telemetry_client, mock_connect, caplog, fake_connection
|
|
111
|
+
):
|
|
112
|
+
"""Ensure failures in telemetry do not break connect and are logged."""
|
|
113
|
+
mock_connect.return_value = fake_connection
|
|
114
|
+
mock_telemetry_client.side_effect = RuntimeError("boom")
|
|
115
|
+
|
|
116
|
+
caplog.set_level("DEBUG", logger="snowflake.sqlalchemy.snowdialect")
|
|
117
|
+
|
|
118
|
+
dialect = SnowflakeDialect()
|
|
119
|
+
result = dialect.connect()
|
|
120
|
+
|
|
121
|
+
assert result is fake_connection
|
|
122
|
+
assert any(
|
|
123
|
+
"Failed to send telemetry data" in message for message in caplog.messages
|
|
124
|
+
)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[tox]
|
|
2
2
|
min_version = 4.0.0
|
|
3
3
|
envlist = fix_lint,
|
|
4
|
-
py{
|
|
4
|
+
py{38,39,310,311,312,313,314}{,-pandas},
|
|
5
5
|
coverage,
|
|
6
6
|
skip_missing_interpreters = true
|
|
7
7
|
|
|
@@ -10,14 +10,17 @@ package = external
|
|
|
10
10
|
description = run the tests with pytest under {basepython}
|
|
11
11
|
extras =
|
|
12
12
|
development
|
|
13
|
-
pandas
|
|
13
|
+
pandas: pandas
|
|
14
14
|
external_wheels =
|
|
15
|
-
py37-ci: dist/*.whl
|
|
16
15
|
py38-ci: dist/*.whl
|
|
17
16
|
py39-ci: dist/.whl
|
|
18
17
|
py310-ci: dist/.whl
|
|
19
18
|
py311-ci: dist/.whl
|
|
19
|
+
py312-ci: dist/.whl
|
|
20
|
+
py313-ci: dist/.whl
|
|
21
|
+
py314-ci: dist/.whl
|
|
20
22
|
deps = pip
|
|
23
|
+
pytest-xdist
|
|
21
24
|
passenv =
|
|
22
25
|
AWS_ACCESS_KEY_ID
|
|
23
26
|
AWS_SECRET_ACCESS_KEY
|
|
@@ -32,6 +35,7 @@ passenv =
|
|
|
32
35
|
USERNAME
|
|
33
36
|
PYTEST_ADDOPTS
|
|
34
37
|
setenv =
|
|
38
|
+
PIP_ONLY_BINARY=pyarrow
|
|
35
39
|
COVERAGE_FILE = {env:COVERAGE_FILE:{toxworkdir}/.coverage.{envname}}
|
|
36
40
|
SQLALCHEMY_WARN_20 = 1
|
|
37
41
|
ci: SNOWFLAKE_PYTEST_OPTS = -vvv --tb=long
|
|
@@ -40,10 +44,12 @@ commands = pytest \
|
|
|
40
44
|
--cov "snowflake.sqlalchemy" \
|
|
41
45
|
--junitxml {toxworkdir}/junit_{envname}.xml \
|
|
42
46
|
--ignore=tests/sqlalchemy_test_suite \
|
|
47
|
+
-n8 \
|
|
43
48
|
{posargs:tests}
|
|
44
49
|
pytest {env:SNOWFLAKE_PYTEST_OPTS:} \
|
|
45
50
|
--cov "snowflake.sqlalchemy" --cov-append \
|
|
46
51
|
--junitxml {toxworkdir}/junit_{envname}.xml \
|
|
52
|
+
-n8 \
|
|
47
53
|
{posargs:tests/sqlalchemy_test_suite}
|
|
48
54
|
|
|
49
55
|
[testenv:.pkg_external]
|
|
@@ -66,7 +72,7 @@ commands = coverage combine
|
|
|
66
72
|
coverage xml -o {toxworkdir}/coverage.xml
|
|
67
73
|
coverage html -d {toxworkdir}/htmlcov
|
|
68
74
|
;diff-cover --compare-branch {env:DIFF_AGAINST:origin/main} {toxworkdir}/coverage.xml
|
|
69
|
-
depends =
|
|
75
|
+
depends = py38, py39, py310, py311, py312, py313, py314
|
|
70
76
|
|
|
71
77
|
[testenv:fix_lint]
|
|
72
78
|
description = format the code base to adhere to our styles, and complain about what we cannot do automatically
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/__init__.py
RENAMED
|
File without changes
|
{snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/_constants.py
RENAMED
|
File without changes
|
|
File without changes
|
{snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/compat.py
RENAMED
|
File without changes
|
|
File without changes
|
{snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/custom_types.py
RENAMED
|
File without changes
|
|
File without changes
|
{snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/functions.py
RENAMED
|
File without changes
|
{snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/name_utils.py
RENAMED
|
File without changes
|
|
File without changes
|
{snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/provision.py
RENAMED
|
File without changes
|
{snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/requirements.py
RENAMED
|
File without changes
|
{snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/src/snowflake/sqlalchemy/sql/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tested_requirements/requirements_310.reqs
RENAMED
|
File without changes
|
{snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tested_requirements/requirements_37.reqs
RENAMED
|
File without changes
|
{snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tested_requirements/requirements_38.reqs
RENAMED
|
File without changes
|
{snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tested_requirements/requirements_39.reqs
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/__snapshots__/test_core.ambr
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/sqlalchemy_test_suite/README.md
RENAMED
|
File without changes
|
{snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/sqlalchemy_test_suite/__init__.py
RENAMED
|
File without changes
|
{snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/sqlalchemy_test_suite/conftest.py
RENAMED
|
File without changes
|
{snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/sqlalchemy_test_suite/test_suite.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/test_semi_structured_datatypes.py
RENAMED
|
File without changes
|
|
File without changes
|
{snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/test_structured_datatypes.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{snowflake_sqlalchemy-1.7.7 → snowflake_sqlalchemy-1.8.0}/tests/test_unit_structured_types.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|