snowflake-sqlalchemy 1.7.2__tar.gz → 1.7.4__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.2 → snowflake_sqlalchemy-1.7.4}/DESCRIPTION.md +15 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/PKG-INFO +24 -2
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/README.md +23 -1
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/pyproject.toml +6 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/src/snowflake/sqlalchemy/base.py +41 -1
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/src/snowflake/sqlalchemy/custom_types.py +1 -1
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/src/snowflake/sqlalchemy/snowdialect.py +19 -27
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/src/snowflake/sqlalchemy/sql/custom_schema/hybrid_table.py +1 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/src/snowflake/sqlalchemy/version.py +1 -1
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/__snapshots__/test_structured_datatypes.ambr +3 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/conftest.py +26 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/custom_tables/__snapshots__/test_compile_hybrid_table.ambr +3 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/custom_tables/__snapshots__/test_reflect_snowflake_table.ambr +3 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/custom_tables/test_compile_hybrid_table.py +21 -4
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/custom_tables/test_create_iceberg_table.py +6 -3
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/custom_tables/test_reflect_snowflake_table.py +30 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/test_compiler.py +94 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/test_core.py +87 -1
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/test_structured_datatypes.py +15 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/.gitignore +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/.gitmodules +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/.pre-commit-config.yaml +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/LICENSE.txt +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/MANIFEST.in +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/ci/build.sh +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/ci/build_docker.sh +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/ci/docker/sqlalchemy_build/Dockerfile +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/ci/docker/sqlalchemy_build/scripts/entrypoint.sh +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/ci/set_base_image.sh +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/ci/test.sh +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/ci/test_docker.sh +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/ci/test_linux.sh +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/license_header.txt +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/setup.cfg +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/snyk/requirements.txt +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/snyk/requiremtnts.txt +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/snyk/update_requirements.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/src/snowflake/sqlalchemy/__init__.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/src/snowflake/sqlalchemy/_constants.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/src/snowflake/sqlalchemy/compat.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/src/snowflake/sqlalchemy/custom_commands.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/src/snowflake/sqlalchemy/exc.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/src/snowflake/sqlalchemy/functions.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/src/snowflake/sqlalchemy/parser/custom_type_parser.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/src/snowflake/sqlalchemy/provision.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/src/snowflake/sqlalchemy/requirements.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/src/snowflake/sqlalchemy/sql/__init__.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/src/snowflake/sqlalchemy/sql/custom_schema/__init__.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/src/snowflake/sqlalchemy/sql/custom_schema/clustered_table.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/src/snowflake/sqlalchemy/sql/custom_schema/custom_table_base.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/src/snowflake/sqlalchemy/sql/custom_schema/custom_table_prefix.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/src/snowflake/sqlalchemy/sql/custom_schema/dynamic_table.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/src/snowflake/sqlalchemy/sql/custom_schema/iceberg_table.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/src/snowflake/sqlalchemy/sql/custom_schema/options/__init__.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/src/snowflake/sqlalchemy/sql/custom_schema/options/as_query_option.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/src/snowflake/sqlalchemy/sql/custom_schema/options/cluster_by_option.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/src/snowflake/sqlalchemy/sql/custom_schema/options/identifier_option.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/src/snowflake/sqlalchemy/sql/custom_schema/options/invalid_table_option.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/src/snowflake/sqlalchemy/sql/custom_schema/options/keyword_option.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/src/snowflake/sqlalchemy/sql/custom_schema/options/keywords.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/src/snowflake/sqlalchemy/sql/custom_schema/options/literal_option.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/src/snowflake/sqlalchemy/sql/custom_schema/options/table_option.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/src/snowflake/sqlalchemy/sql/custom_schema/options/target_lag_option.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/src/snowflake/sqlalchemy/sql/custom_schema/snowflake_table.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/src/snowflake/sqlalchemy/sql/custom_schema/table_from_query.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/src/snowflake/sqlalchemy/util.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tested_requirements/requirements_310.reqs +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tested_requirements/requirements_37.reqs +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tested_requirements/requirements_38.reqs +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tested_requirements/requirements_39.reqs +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/README.rst +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/__init__.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/__snapshots__/test_compile_dynamic_table.ambr +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/__snapshots__/test_core.ambr +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/__snapshots__/test_orm.ambr +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/__snapshots__/test_reflect_dynamic_table.ambr +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/__snapshots__/test_unit_structured_types.ambr +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/custom_tables/__init__.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/custom_tables/__snapshots__/test_compile_dynamic_table.ambr +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/custom_tables/__snapshots__/test_compile_iceberg_table.ambr +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/custom_tables/__snapshots__/test_compile_snowflake_table.ambr +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/custom_tables/__snapshots__/test_create_dynamic_table.ambr +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/custom_tables/__snapshots__/test_create_hybrid_table.ambr +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/custom_tables/__snapshots__/test_create_iceberg_table.ambr +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/custom_tables/__snapshots__/test_create_snowflake_table.ambr +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/custom_tables/__snapshots__/test_generic_options.ambr +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/custom_tables/__snapshots__/test_reflect_hybrid_table.ambr +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/custom_tables/test_compile_dynamic_table.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/custom_tables/test_compile_iceberg_table.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/custom_tables/test_compile_snowflake_table.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/custom_tables/test_create_dynamic_table.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/custom_tables/test_create_hybrid_table.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/custom_tables/test_create_snowflake_table.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/custom_tables/test_generic_options.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/custom_tables/test_reflect_dynamic_table.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/custom_tables/test_reflect_hybrid_table.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/data/users.txt +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/sqlalchemy_test_suite/README.md +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/sqlalchemy_test_suite/__init__.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/sqlalchemy_test_suite/conftest.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/sqlalchemy_test_suite/test_suite.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/sqlalchemy_test_suite/test_suite_20.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/test_copy.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/test_create.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/test_custom_functions.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/test_custom_types.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/test_geography.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/test_geometry.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/test_imports.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/test_index_reflection.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/test_multivalues_insert.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/test_orm.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/test_pandas.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/test_qmark.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/test_quote.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/test_quote_identifiers.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/test_semi_structured_datatypes.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/test_sequence.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/test_timestamp.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/test_transactions.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/test_unit_core.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/test_unit_cte.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/test_unit_structured_types.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/test_unit_types.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/test_unit_url.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/util.py +0 -0
- {snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tox.ini +0 -0
|
@@ -6,8 +6,23 @@ Snowflake Documentation is available at:
|
|
|
6
6
|
|
|
7
7
|
Source code is also available at:
|
|
8
8
|
<https://github.com/snowflakedb/snowflake-sqlalchemy>
|
|
9
|
+
# Unreleased Notes
|
|
9
10
|
|
|
10
11
|
# Release Notes
|
|
12
|
+
- v1.7.4(June 10, 2025)
|
|
13
|
+
- Fix dependency on DESCRIBE TABLE columns quantity (differences in columns caused by Snowflake parameters).
|
|
14
|
+
- Fix unnecessary condition was causing issues when parsing StructuredTypes columns.
|
|
15
|
+
- Update README.md to include instructions on how to verify package signatures using cosign.
|
|
16
|
+
|
|
17
|
+
- v1.7.3(January 15, 2025)
|
|
18
|
+
- Fix support for SqlAlchemy ARRAY.
|
|
19
|
+
- Fix return value of snowflake get_table_names.
|
|
20
|
+
- Fix incorrect quoting of identifiers with `_` as initial character.
|
|
21
|
+
- Fix ARRAY type not supported in HYBRID tables.
|
|
22
|
+
- Add `force_div_is_floordiv` flag to override `div_is_floordiv` new default value `False` in `SnowflakeDialect`.
|
|
23
|
+
- With the flag in `False`, the `/` division operator will be treated as a float division and `//` as a floor division.
|
|
24
|
+
- This flag is added to maintain backward compatibility with the previous behavior of Snowflake Dialect division.
|
|
25
|
+
- This flag will be removed in the future and Snowflake Dialect will use `div_is_floor_div` as `False`.
|
|
11
26
|
|
|
12
27
|
- v1.7.2(December 18, 2024)
|
|
13
28
|
- Fix quoting of `_` as column name
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: snowflake-sqlalchemy
|
|
3
|
-
Version: 1.7.
|
|
3
|
+
Version: 1.7.4
|
|
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
|
|
@@ -63,7 +63,7 @@ Description-Content-Type: text/markdown
|
|
|
63
63
|
Snowflake SQLAlchemy runs on the top of the Snowflake Connector for Python as a [dialect](http://docs.sqlalchemy.org/en/latest/dialects/) to bridge a Snowflake database and SQLAlchemy applications.
|
|
64
64
|
|
|
65
65
|
|
|
66
|
-
| :exclamation:
|
|
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
67
|
|---------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
68
68
|
|
|
69
69
|
|
|
@@ -731,6 +731,28 @@ dynamic_test_table_1 = DynamicTable(
|
|
|
731
731
|
- Direct data insertion into Dynamic Tables is not supported.
|
|
732
732
|
|
|
733
733
|
|
|
734
|
+
## Verifying Package Signatures
|
|
735
|
+
|
|
736
|
+
To ensure the authenticity and integrity of the Python package, follow the steps below to verify the package signature using `cosign`.
|
|
737
|
+
|
|
738
|
+
**Steps to verify the signature:**
|
|
739
|
+
- Install cosign:
|
|
740
|
+
- This example is using golang installation: [installing-cosign-with-go](https://edu.chainguard.dev/open-source/sigstore/cosign/how-to-install-cosign/#installing-cosign-with-go)
|
|
741
|
+
- Download the file from the repository like pypi:
|
|
742
|
+
- https://pypi.org/project/snowflake-sqlalchemy/#files
|
|
743
|
+
- Download the signature files from the release tag, replace the version number with the version you are verifying:
|
|
744
|
+
- https://github.com/snowflakedb/snowflake-sqlalchemy/releases/tag/v1.7.3
|
|
745
|
+
- Verify signature:
|
|
746
|
+
````bash
|
|
747
|
+
# replace the version number with the version you are verifying
|
|
748
|
+
./cosign verify-blob snowflake_sqlalchemy-1.7.3-py3-none-any.whl \
|
|
749
|
+
--certificate snowflake_sqlalchemy-1.7.3-py3-none-any.whl.crt \
|
|
750
|
+
--certificate-identity https://github.com/snowflakedb/snowflake-sqlalchemy/.github/workflows/python-publish.yml@refs/tags/v1.7.3 \
|
|
751
|
+
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
|
|
752
|
+
--signature snowflake_sqlalchemy-1.7.3-py3-none-any.whl.sig
|
|
753
|
+
Verified OK
|
|
754
|
+
````
|
|
755
|
+
|
|
734
756
|
## Support
|
|
735
757
|
|
|
736
758
|
Feel free to file an issue or submit a PR here for general cases. For official support, contact Snowflake support at:
|
|
@@ -9,7 +9,7 @@
|
|
|
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:
|
|
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
13
|
|---------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
14
14
|
|
|
15
15
|
|
|
@@ -677,6 +677,28 @@ dynamic_test_table_1 = DynamicTable(
|
|
|
677
677
|
- Direct data insertion into Dynamic Tables is not supported.
|
|
678
678
|
|
|
679
679
|
|
|
680
|
+
## Verifying Package Signatures
|
|
681
|
+
|
|
682
|
+
To ensure the authenticity and integrity of the Python package, follow the steps below to verify the package signature using `cosign`.
|
|
683
|
+
|
|
684
|
+
**Steps to verify the signature:**
|
|
685
|
+
- Install cosign:
|
|
686
|
+
- This example is using golang installation: [installing-cosign-with-go](https://edu.chainguard.dev/open-source/sigstore/cosign/how-to-install-cosign/#installing-cosign-with-go)
|
|
687
|
+
- Download the file from the repository like pypi:
|
|
688
|
+
- https://pypi.org/project/snowflake-sqlalchemy/#files
|
|
689
|
+
- Download the signature files from the release tag, replace the version number with the version you are verifying:
|
|
690
|
+
- https://github.com/snowflakedb/snowflake-sqlalchemy/releases/tag/v1.7.3
|
|
691
|
+
- Verify signature:
|
|
692
|
+
````bash
|
|
693
|
+
# replace the version number with the version you are verifying
|
|
694
|
+
./cosign verify-blob snowflake_sqlalchemy-1.7.3-py3-none-any.whl \
|
|
695
|
+
--certificate snowflake_sqlalchemy-1.7.3-py3-none-any.whl.crt \
|
|
696
|
+
--certificate-identity https://github.com/snowflakedb/snowflake-sqlalchemy/.github/workflows/python-publish.yml@refs/tags/v1.7.3 \
|
|
697
|
+
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
|
|
698
|
+
--signature snowflake_sqlalchemy-1.7.3-py3-none-any.whl.sig
|
|
699
|
+
Verified OK
|
|
700
|
+
````
|
|
701
|
+
|
|
680
702
|
## Support
|
|
681
703
|
|
|
682
704
|
Feel free to file an issue or submit a PR here for general cases. For official support, contact Snowflake support at:
|
|
@@ -87,6 +87,11 @@ extra-dependencies = ["SQLAlchemy>=1.4.19,<2.0.0"]
|
|
|
87
87
|
features = ["development", "pandas"]
|
|
88
88
|
python = "3.8"
|
|
89
89
|
|
|
90
|
+
[tool.hatch.envs.sa14.scripts]
|
|
91
|
+
test-dialect = "pytest --ignore_v20_test -ra -vvv --tb=short --cov snowflake.sqlalchemy --cov-append --junitxml ./junit.xml --ignore=tests/sqlalchemy_test_suite tests/"
|
|
92
|
+
test-dialect-compatibility = "pytest --ignore_v20_test -ra -vvv --tb=short --cov snowflake.sqlalchemy --cov-append --junitxml ./junit.xml tests/sqlalchemy_test_suite"
|
|
93
|
+
test-dialect-aws = "pytest --ignore_v20_test -m \"aws\" -ra -vvv --tb=short --cov snowflake.sqlalchemy --cov-append --junitxml ./junit.xml --ignore=tests/sqlalchemy_test_suite tests/"
|
|
94
|
+
|
|
90
95
|
[tool.hatch.envs.default.env-vars]
|
|
91
96
|
COVERAGE_FILE = "coverage.xml"
|
|
92
97
|
SQLACHEMY_WARN_20 = "1"
|
|
@@ -134,4 +139,5 @@ markers = [
|
|
|
134
139
|
"requires_external_volume: tests that needs a external volume to be executed",
|
|
135
140
|
"external: tests that could but should only run on our external CI",
|
|
136
141
|
"feature_max_lob_size: tests that could but should only run on our external CI",
|
|
142
|
+
"feature_v20: tests that could but should only run on SqlAlchemy v20",
|
|
137
143
|
]
|
|
@@ -6,6 +6,7 @@ import itertools
|
|
|
6
6
|
import operator
|
|
7
7
|
import re
|
|
8
8
|
import string
|
|
9
|
+
import warnings
|
|
9
10
|
from typing import List
|
|
10
11
|
|
|
11
12
|
from sqlalchemy import exc as sa_exc
|
|
@@ -116,7 +117,11 @@ AUTOCOMMIT_REGEXP = re.compile(
|
|
|
116
117
|
r"\s*(?:UPDATE|INSERT|DELETE|MERGE|COPY)", re.I | re.UNICODE
|
|
117
118
|
)
|
|
118
119
|
# used for quoting identifiers ie. table names, column names, etc.
|
|
119
|
-
ILLEGAL_INITIAL_CHARACTERS = frozenset({d for d in string.digits}.union({"
|
|
120
|
+
ILLEGAL_INITIAL_CHARACTERS = frozenset({d for d in string.digits}.union({"$"}))
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
# used for quoting identifiers ie. table names, column names, etc.
|
|
124
|
+
ILLEGAL_IDENTIFIERS = frozenset({d for d in string.digits}.union({"_"}))
|
|
120
125
|
|
|
121
126
|
"""
|
|
122
127
|
Overwrite methods to handle Snowflake BCR change:
|
|
@@ -442,6 +447,7 @@ class SnowflakeORMSelectCompileState(context.ORMSelectCompileState):
|
|
|
442
447
|
class SnowflakeIdentifierPreparer(compiler.IdentifierPreparer):
|
|
443
448
|
reserved_words = {x.lower() for x in RESERVED_WORDS}
|
|
444
449
|
illegal_initial_characters = ILLEGAL_INITIAL_CHARACTERS
|
|
450
|
+
illegal_identifiers = ILLEGAL_IDENTIFIERS
|
|
445
451
|
|
|
446
452
|
def __init__(self, dialect, **kw):
|
|
447
453
|
quote = '"'
|
|
@@ -470,6 +476,17 @@ class SnowflakeIdentifierPreparer(compiler.IdentifierPreparer):
|
|
|
470
476
|
|
|
471
477
|
return self.quote_identifier(s) if n.quote else s
|
|
472
478
|
|
|
479
|
+
def _requires_quotes(self, value: str) -> bool:
|
|
480
|
+
"""Return True if the given identifier requires quoting."""
|
|
481
|
+
lc_value = value.lower()
|
|
482
|
+
return (
|
|
483
|
+
lc_value in self.reserved_words
|
|
484
|
+
or lc_value in self.illegal_identifiers
|
|
485
|
+
or value[0] in self.illegal_initial_characters
|
|
486
|
+
or not self.legal_characters.match(str(value))
|
|
487
|
+
or (lc_value != value)
|
|
488
|
+
)
|
|
489
|
+
|
|
473
490
|
def _split_schema_by_dot(self, schema):
|
|
474
491
|
ret = []
|
|
475
492
|
idx = 0
|
|
@@ -802,6 +819,26 @@ class SnowflakeCompiler(compiler.SQLCompiler):
|
|
|
802
819
|
+ join.onclause._compiler_dispatch(self, from_linter=from_linter, **kwargs)
|
|
803
820
|
)
|
|
804
821
|
|
|
822
|
+
def visit_truediv_binary(self, binary, operator, **kw):
|
|
823
|
+
if self.dialect.div_is_floordiv:
|
|
824
|
+
warnings.warn(
|
|
825
|
+
"div_is_floordiv value will be changed to False in a future release. This will generate a behavior change on true and floor division. Please review https://docs.sqlalchemy.org/en/20/changelog/whatsnew_20.html#python-division-operator-performs-true-division-for-all-backends-added-floor-division",
|
|
826
|
+
PendingDeprecationWarning,
|
|
827
|
+
stacklevel=2,
|
|
828
|
+
)
|
|
829
|
+
return (
|
|
830
|
+
self.process(binary.left, **kw) + " / " + self.process(binary.right, **kw)
|
|
831
|
+
)
|
|
832
|
+
|
|
833
|
+
def visit_floordiv_binary(self, binary, operator, **kw):
|
|
834
|
+
if self.dialect.div_is_floordiv and IS_VERSION_20:
|
|
835
|
+
warnings.warn(
|
|
836
|
+
"div_is_floordiv value will be changed to False in a future release. This will generate a behavior change on true and floor division. Please review https://docs.sqlalchemy.org/en/20/changelog/whatsnew_20.html#python-division-operator-performs-true-division-for-all-backends-added-floor-division",
|
|
837
|
+
PendingDeprecationWarning,
|
|
838
|
+
stacklevel=2,
|
|
839
|
+
)
|
|
840
|
+
return super().visit_floordiv_binary(binary, operator, **kw)
|
|
841
|
+
|
|
805
842
|
def render_literal_value(self, value, type_):
|
|
806
843
|
# escape backslash
|
|
807
844
|
return super().render_literal_value(value, type_).replace("\\", "\\\\")
|
|
@@ -1096,6 +1133,9 @@ class SnowflakeTypeCompiler(compiler.GenericTypeCompiler):
|
|
|
1096
1133
|
)
|
|
1097
1134
|
|
|
1098
1135
|
def visit_ARRAY(self, type_, **kw):
|
|
1136
|
+
return "ARRAY"
|
|
1137
|
+
|
|
1138
|
+
def visit_SNOWFLAKE_ARRAY(self, type_, **kw):
|
|
1099
1139
|
if type_.is_semi_structured:
|
|
1100
1140
|
return "ARRAY"
|
|
1101
1141
|
not_null = f" {NOT_NULL}" if type_.not_null else ""
|
{snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/src/snowflake/sqlalchemy/snowdialect.py
RENAMED
|
@@ -79,6 +79,9 @@ class SnowflakeDialect(default.DefaultDialect):
|
|
|
79
79
|
colspecs = colspecs
|
|
80
80
|
ischema_names = ischema_names
|
|
81
81
|
|
|
82
|
+
# target database treats the / division operator as “floor division”
|
|
83
|
+
div_is_floordiv = False
|
|
84
|
+
|
|
82
85
|
# all str types must be converted in Unicode
|
|
83
86
|
convert_unicode = True
|
|
84
87
|
|
|
@@ -147,10 +150,17 @@ class SnowflakeDialect(default.DefaultDialect):
|
|
|
147
150
|
|
|
148
151
|
def __init__(
|
|
149
152
|
self,
|
|
153
|
+
force_div_is_floordiv: bool = True,
|
|
150
154
|
isolation_level: Optional[str] = SnowflakeIsolationLevel.READ_COMMITTED.value,
|
|
151
155
|
**kwargs: Any,
|
|
152
156
|
):
|
|
153
157
|
super().__init__(isolation_level=isolation_level, **kwargs)
|
|
158
|
+
self.force_div_is_floordiv = force_div_is_floordiv
|
|
159
|
+
self.div_is_floordiv = force_div_is_floordiv
|
|
160
|
+
|
|
161
|
+
def initialize(self, connection):
|
|
162
|
+
super().initialize(connection)
|
|
163
|
+
self.div_is_floordiv = self.force_div_is_floordiv
|
|
154
164
|
|
|
155
165
|
@classmethod
|
|
156
166
|
def dbapi(cls):
|
|
@@ -501,13 +511,6 @@ class SnowflakeDialect(default.DefaultDialect):
|
|
|
501
511
|
)
|
|
502
512
|
schema_name = self.denormalize_name(schema)
|
|
503
513
|
|
|
504
|
-
iceberg_table_names = self.get_table_names_with_prefix(
|
|
505
|
-
connection,
|
|
506
|
-
schema=schema_name,
|
|
507
|
-
prefix=CustomTablePrefix.ICEBERG.name,
|
|
508
|
-
info_cache=kw.get("info_cache", None),
|
|
509
|
-
)
|
|
510
|
-
|
|
511
514
|
result = connection.execute(
|
|
512
515
|
text(
|
|
513
516
|
"""
|
|
@@ -568,10 +571,7 @@ class SnowflakeDialect(default.DefaultDialect):
|
|
|
568
571
|
col_type_kw["scale"] = numeric_scale
|
|
569
572
|
elif issubclass(col_type, (sqltypes.String, sqltypes.BINARY)):
|
|
570
573
|
col_type_kw["length"] = character_maximum_length
|
|
571
|
-
elif (
|
|
572
|
-
issubclass(col_type, StructuredType)
|
|
573
|
-
and table_name in iceberg_table_names
|
|
574
|
-
):
|
|
574
|
+
elif issubclass(col_type, StructuredType):
|
|
575
575
|
if (schema_name, table_name) not in full_columns_descriptions:
|
|
576
576
|
full_columns_descriptions[(schema_name, table_name)] = (
|
|
577
577
|
self.table_columns_as_dict(
|
|
@@ -644,21 +644,13 @@ class SnowflakeDialect(default.DefaultDialect):
|
|
|
644
644
|
f" TABLE {table_schema}.{table_name} TYPE = COLUMNS"
|
|
645
645
|
)
|
|
646
646
|
)
|
|
647
|
-
for
|
|
648
|
-
column_name
|
|
649
|
-
coltype
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
_unique_key,
|
|
655
|
-
_check,
|
|
656
|
-
_expression,
|
|
657
|
-
comment,
|
|
658
|
-
_policy_name,
|
|
659
|
-
_privacy_domain,
|
|
660
|
-
_name_mapping,
|
|
661
|
-
) in result:
|
|
647
|
+
for desc_data in result:
|
|
648
|
+
column_name = desc_data[0]
|
|
649
|
+
coltype = desc_data[1]
|
|
650
|
+
is_nullable = desc_data[3]
|
|
651
|
+
column_default = desc_data[4]
|
|
652
|
+
primary_key = desc_data[5]
|
|
653
|
+
comment = desc_data[9]
|
|
662
654
|
|
|
663
655
|
column_name = self.normalize_name(column_name)
|
|
664
656
|
if column_name.startswith("sys_clustering_column"):
|
|
@@ -756,7 +748,7 @@ class SnowflakeDialect(default.DefaultDialect):
|
|
|
756
748
|
ret = self._get_schema_tables_info(
|
|
757
749
|
connection, schema, info_cache=kw.get("info_cache", None)
|
|
758
750
|
).keys()
|
|
759
|
-
return ret
|
|
751
|
+
return list(ret)
|
|
760
752
|
|
|
761
753
|
@reflection.cache
|
|
762
754
|
def get_view_names(self, connection, schema=None, **kw):
|
|
@@ -5,6 +5,9 @@
|
|
|
5
5
|
# name: test_compile_table_with_double_map
|
|
6
6
|
'CREATE TABLE clustered_user (\t"Id" INTEGER NOT NULL AUTOINCREMENT, \tname MAP(DECIMAL, MAP(DECIMAL, VARCHAR)), \tPRIMARY KEY ("Id"))'
|
|
7
7
|
# ---
|
|
8
|
+
# name: test_compile_table_with_sqlalchemy_array
|
|
9
|
+
'CREATE TABLE clustered_user (\t"Id" INTEGER NOT NULL AUTOINCREMENT, \tname ARRAY, \tPRIMARY KEY ("Id"))'
|
|
10
|
+
# ---
|
|
8
11
|
# name: test_compile_table_with_structured_data_type[structured_type0]
|
|
9
12
|
'CREATE TABLE clustered_user (\t"Id" INTEGER NOT NULL AUTOINCREMENT, \tname MAP(DECIMAL(10, 0), MAP(DECIMAL(10, 0), VARCHAR(16777216))), \tPRIMARY KEY ("Id"))'
|
|
10
13
|
# ---
|
|
@@ -47,6 +47,26 @@ snowflake.connector.connection.DEFAULT_CONFIGURATION[
|
|
|
47
47
|
TEST_SCHEMA = f"sqlalchemy_tests_{str(uuid.uuid4()).replace('-', '_')}"
|
|
48
48
|
|
|
49
49
|
|
|
50
|
+
def pytest_addoption(parser):
|
|
51
|
+
parser.addoption(
|
|
52
|
+
"--ignore_v20_test",
|
|
53
|
+
action="store_true",
|
|
54
|
+
default=False,
|
|
55
|
+
help="skip sqlalchemy 2.0 exclusive tests",
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def pytest_collection_modifyitems(config, items):
|
|
60
|
+
if config.getoption("--ignore_v20_test"):
|
|
61
|
+
# --ignore_v20_test given in cli: skip sqlalchemy 2.0 tests
|
|
62
|
+
skip_feature_v2 = pytest.mark.skip(
|
|
63
|
+
reason="need remove --ignore_v20_test option to run"
|
|
64
|
+
)
|
|
65
|
+
for item in items:
|
|
66
|
+
if "feature_v20" in item.keywords:
|
|
67
|
+
item.add_marker(skip_feature_v2)
|
|
68
|
+
|
|
69
|
+
|
|
50
70
|
@pytest.fixture(scope="session")
|
|
51
71
|
def on_travis():
|
|
52
72
|
return os.getenv("TRAVIS", "").lower() == "true"
|
|
@@ -183,6 +203,12 @@ def get_engine(url: URL, **engine_kwargs):
|
|
|
183
203
|
"echo": True,
|
|
184
204
|
}
|
|
185
205
|
engine_params.update(engine_kwargs)
|
|
206
|
+
|
|
207
|
+
connect_args = engine_params.get("connect_args", {}).copy()
|
|
208
|
+
connect_args["disable_ocsp_checks"] = True
|
|
209
|
+
connect_args["insecure_mode"] = True
|
|
210
|
+
engine_params["connect_args"] = connect_args
|
|
211
|
+
|
|
186
212
|
engine = create_engine(url, **engine_params)
|
|
187
213
|
return engine
|
|
188
214
|
|
|
@@ -5,3 +5,6 @@
|
|
|
5
5
|
# name: test_compile_hybrid_table_orm
|
|
6
6
|
'CREATE HYBRID TABLE test_hybrid_table_orm (\tid INTEGER NOT NULL AUTOINCREMENT, \tname VARCHAR, \tPRIMARY KEY (id))'
|
|
7
7
|
# ---
|
|
8
|
+
# name: test_compile_hybrid_table_with_array
|
|
9
|
+
'CREATE HYBRID TABLE test_hybrid_table (\tid INTEGER NOT NULL AUTOINCREMENT, \tname VARCHAR, \tgeom GEOMETRY, \tarray ARRAY, \tPRIMARY KEY (id))'
|
|
10
|
+
# ---
|
|
@@ -21,6 +21,9 @@
|
|
|
21
21
|
}),
|
|
22
22
|
])
|
|
23
23
|
# ---
|
|
24
|
+
# name: test_reflection_of_table_with_object_data_type
|
|
25
|
+
'CREATE TABLE test_snowflake_table_reflection (\tid DECIMAL(38, 0) NOT NULL, \tname OBJECT, \tCONSTRAINT demo_name PRIMARY KEY (id))'
|
|
26
|
+
# ---
|
|
24
27
|
# name: test_simple_reflection_of_table_as_snowflake_table
|
|
25
28
|
'CREATE TABLE test_snowflake_table_reflection (\tid DECIMAL(38, 0) NOT NULL, \tname VARCHAR(16777216), \tCONSTRAINT demo_name PRIMARY KEY (id))'
|
|
26
29
|
# ---
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
#
|
|
2
2
|
# Copyright (c) 2012-2023 Snowflake Computing Inc. All rights reserved.
|
|
3
3
|
#
|
|
4
|
-
|
|
4
|
+
|
|
5
5
|
from sqlalchemy import Column, Integer, MetaData, String
|
|
6
6
|
from sqlalchemy.orm import declarative_base
|
|
7
7
|
from sqlalchemy.sql.ddl import CreateTable
|
|
8
8
|
|
|
9
|
-
from snowflake.sqlalchemy import GEOMETRY, HybridTable
|
|
9
|
+
from snowflake.sqlalchemy import ARRAY, GEOMETRY, HybridTable
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
@pytest.mark.aws
|
|
13
12
|
def test_compile_hybrid_table(sql_compiler, snapshot):
|
|
14
13
|
metadata = MetaData()
|
|
15
14
|
table_name = "test_hybrid_table"
|
|
@@ -28,7 +27,25 @@ def test_compile_hybrid_table(sql_compiler, snapshot):
|
|
|
28
27
|
assert actual == snapshot
|
|
29
28
|
|
|
30
29
|
|
|
31
|
-
|
|
30
|
+
def test_compile_hybrid_table_with_array(sql_compiler, snapshot):
|
|
31
|
+
metadata = MetaData()
|
|
32
|
+
table_name = "test_hybrid_table"
|
|
33
|
+
test_geometry = HybridTable(
|
|
34
|
+
table_name,
|
|
35
|
+
metadata,
|
|
36
|
+
Column("id", Integer, primary_key=True),
|
|
37
|
+
Column("name", String),
|
|
38
|
+
Column("geom", GEOMETRY),
|
|
39
|
+
Column("array", ARRAY),
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
value = CreateTable(test_geometry)
|
|
43
|
+
|
|
44
|
+
actual = sql_compiler(value)
|
|
45
|
+
|
|
46
|
+
assert actual == snapshot
|
|
47
|
+
|
|
48
|
+
|
|
32
49
|
def test_compile_hybrid_table_orm(sql_compiler, snapshot):
|
|
33
50
|
Base = declarative_base()
|
|
34
51
|
|
|
@@ -9,7 +9,7 @@ from snowflake.sqlalchemy import IcebergTable
|
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
@pytest.mark.aws
|
|
12
|
-
def test_create_iceberg_table(engine_testaccount
|
|
12
|
+
def test_create_iceberg_table(engine_testaccount):
|
|
13
13
|
metadata = MetaData()
|
|
14
14
|
external_volume_name = "exvol"
|
|
15
15
|
create_external_volume = f"""
|
|
@@ -19,7 +19,7 @@ def test_create_iceberg_table(engine_testaccount, snapshot):
|
|
|
19
19
|
(
|
|
20
20
|
NAME = 'my-s3-us-west-2'
|
|
21
21
|
STORAGE_PROVIDER = 'S3'
|
|
22
|
-
STORAGE_BASE_URL = 's3://
|
|
22
|
+
STORAGE_BASE_URL = 's3://myexamplebucket/'
|
|
23
23
|
STORAGE_AWS_ROLE_ARN = 'arn:aws:iam::123456789012:role/myrole'
|
|
24
24
|
ENCRYPTION=(TYPE='AWS_SSE_KMS' KMS_KEY_ID='1234abcd-12ab-34cd-56ef-1234567890ab')
|
|
25
25
|
)
|
|
@@ -40,4 +40,7 @@ def test_create_iceberg_table(engine_testaccount, snapshot):
|
|
|
40
40
|
metadata.create_all(engine_testaccount)
|
|
41
41
|
|
|
42
42
|
error_str = str(argument_error.value)
|
|
43
|
-
assert
|
|
43
|
+
assert (
|
|
44
|
+
"(snowflake.connector.errors.ProgrammingError)"
|
|
45
|
+
in error_str[: error_str.rfind("\n")]
|
|
46
|
+
)
|
|
@@ -7,6 +7,36 @@ from sqlalchemy.sql.ddl import CreateTable
|
|
|
7
7
|
from snowflake.sqlalchemy import SnowflakeTable
|
|
8
8
|
|
|
9
9
|
|
|
10
|
+
def test_reflection_of_table_with_object_data_type(
|
|
11
|
+
engine_testaccount, db_parameters, sql_compiler, snapshot
|
|
12
|
+
):
|
|
13
|
+
metadata = MetaData()
|
|
14
|
+
table_name = "test_snowflake_table_reflection"
|
|
15
|
+
|
|
16
|
+
create_table_sql = f"""
|
|
17
|
+
CREATE TABLE {table_name} (id INT primary key, name OBJECT);
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
with engine_testaccount.connect() as connection:
|
|
21
|
+
connection.exec_driver_sql(create_table_sql)
|
|
22
|
+
|
|
23
|
+
snowflake_test_table = Table(table_name, metadata, autoload_with=engine_testaccount)
|
|
24
|
+
constraint = snowflake_test_table.constraints.pop()
|
|
25
|
+
constraint.name = "demo_name"
|
|
26
|
+
snowflake_test_table.constraints.add(constraint)
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
with engine_testaccount.connect():
|
|
30
|
+
value = CreateTable(snowflake_test_table)
|
|
31
|
+
|
|
32
|
+
actual = sql_compiler(value)
|
|
33
|
+
|
|
34
|
+
assert actual == snapshot
|
|
35
|
+
|
|
36
|
+
finally:
|
|
37
|
+
metadata.drop_all(engine_testaccount)
|
|
38
|
+
|
|
39
|
+
|
|
10
40
|
def test_simple_reflection_of_table_as_sqlalchemy_table(
|
|
11
41
|
engine_testaccount, db_parameters, sql_compiler, snapshot
|
|
12
42
|
):
|
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
# Copyright (c) 2012-2023 Snowflake Computing Inc. All rights reserved.
|
|
3
3
|
#
|
|
4
4
|
|
|
5
|
+
import pytest
|
|
5
6
|
from sqlalchemy import Integer, String, and_, func, insert, select
|
|
6
7
|
from sqlalchemy.schema import DropColumnComment, DropTableComment
|
|
7
8
|
from sqlalchemy.sql import column, quoted_name, table
|
|
8
9
|
from sqlalchemy.testing.assertions import AssertsCompiledSQL
|
|
9
10
|
|
|
10
11
|
from snowflake.sqlalchemy import snowdialect
|
|
12
|
+
from src.snowflake.sqlalchemy.snowdialect import SnowflakeDialect
|
|
11
13
|
|
|
12
14
|
table1 = table(
|
|
13
15
|
"table1", column("id", Integer), column("name", String), column("value", Integer)
|
|
@@ -48,6 +50,21 @@ class TestSnowflakeCompiler(AssertsCompiledSQL):
|
|
|
48
50
|
dialect="snowflake",
|
|
49
51
|
)
|
|
50
52
|
|
|
53
|
+
def test_underscore_as_initial_character_as_non_quoted_identifier(self):
|
|
54
|
+
_table = table(
|
|
55
|
+
"table_1745924",
|
|
56
|
+
column("ca", Integer),
|
|
57
|
+
column("cb", String),
|
|
58
|
+
column("_identifier", String),
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
stmt = insert(_table).values(ca=1, cb="test", _identifier="test_")
|
|
62
|
+
self.assert_compile(
|
|
63
|
+
stmt,
|
|
64
|
+
"INSERT INTO table_1745924 (ca, cb, _identifier) VALUES (%(ca)s, %(cb)s, %(_identifier)s)",
|
|
65
|
+
dialect="snowflake",
|
|
66
|
+
)
|
|
67
|
+
|
|
51
68
|
def test_multi_table_delete(self):
|
|
52
69
|
statement = table1.delete().where(table1.c.id == table2.c.id)
|
|
53
70
|
self.assert_compile(
|
|
@@ -135,3 +152,80 @@ def test_outer_lateral_join():
|
|
|
135
152
|
str(stmt.compile(dialect=snowdialect.dialect()))
|
|
136
153
|
== "SELECT colname AS label \nFROM abc JOIN LATERAL flatten(PARSE_JSON(colname2)) AS anon_1 GROUP BY colname"
|
|
137
154
|
)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
@pytest.mark.feature_v20
|
|
158
|
+
def test_division_operator_with_force_div_is_floordiv_false():
|
|
159
|
+
col1 = column("col1", Integer)
|
|
160
|
+
col2 = column("col2", Integer)
|
|
161
|
+
stmt = col1 / col2
|
|
162
|
+
assert (
|
|
163
|
+
str(stmt.compile(dialect=SnowflakeDialect(force_div_is_floordiv=False)))
|
|
164
|
+
== "col1 / col2"
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
@pytest.mark.feature_v20
|
|
169
|
+
def test_division_operator_with_denominator_expr_force_div_is_floordiv_false():
|
|
170
|
+
col1 = column("col1", Integer)
|
|
171
|
+
col2 = column("col2", Integer)
|
|
172
|
+
stmt = col1 / func.sqrt(col2)
|
|
173
|
+
assert (
|
|
174
|
+
str(stmt.compile(dialect=SnowflakeDialect(force_div_is_floordiv=False)))
|
|
175
|
+
== "col1 / sqrt(col2)"
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
@pytest.mark.feature_v20
|
|
180
|
+
def test_division_operator_with_force_div_is_floordiv_default_true():
|
|
181
|
+
col1 = column("col1", Integer)
|
|
182
|
+
col2 = column("col2", Integer)
|
|
183
|
+
stmt = col1 / col2
|
|
184
|
+
assert str(stmt.compile(dialect=SnowflakeDialect())) == "col1 / col2"
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
@pytest.mark.feature_v20
|
|
188
|
+
def test_division_operator_with_denominator_expr_force_div_is_floordiv_default_true():
|
|
189
|
+
col1 = column("col1", Integer)
|
|
190
|
+
col2 = column("col2", Integer)
|
|
191
|
+
stmt = col1 / func.sqrt(col2)
|
|
192
|
+
assert str(stmt.compile(dialect=SnowflakeDialect())) == "col1 / sqrt(col2)"
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
@pytest.mark.feature_v20
|
|
196
|
+
def test_floor_division_operator_force_div_is_floordiv_false():
|
|
197
|
+
col1 = column("col1", Integer)
|
|
198
|
+
col2 = column("col2", Integer)
|
|
199
|
+
stmt = col1 // col2
|
|
200
|
+
assert (
|
|
201
|
+
str(stmt.compile(dialect=SnowflakeDialect(force_div_is_floordiv=False)))
|
|
202
|
+
== "FLOOR(col1 / col2)"
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
@pytest.mark.feature_v20
|
|
207
|
+
def test_floor_division_operator_with_denominator_expr_force_div_is_floordiv_false():
|
|
208
|
+
col1 = column("col1", Integer)
|
|
209
|
+
col2 = column("col2", Integer)
|
|
210
|
+
stmt = col1 // func.sqrt(col2)
|
|
211
|
+
assert (
|
|
212
|
+
str(stmt.compile(dialect=SnowflakeDialect(force_div_is_floordiv=False)))
|
|
213
|
+
== "FLOOR(col1 / sqrt(col2))"
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
@pytest.mark.feature_v20
|
|
218
|
+
def test_floor_division_operator_force_div_is_floordiv_default_true():
|
|
219
|
+
col1 = column("col1", Integer)
|
|
220
|
+
col2 = column("col2", Integer)
|
|
221
|
+
stmt = col1 // col2
|
|
222
|
+
assert str(stmt.compile(dialect=SnowflakeDialect())) == "col1 / col2"
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
@pytest.mark.feature_v20
|
|
226
|
+
def test_floor_division_operator_with_denominator_expr_force_div_is_floordiv_default_true():
|
|
227
|
+
col1 = column("col1", Integer)
|
|
228
|
+
col2 = column("col2", Integer)
|
|
229
|
+
stmt = col1 // func.sqrt(col2)
|
|
230
|
+
res = stmt.compile(dialect=SnowflakeDialect())
|
|
231
|
+
assert str(res) == "FLOOR(col1 / sqrt(col2))"
|
|
@@ -31,13 +31,15 @@ from sqlalchemy import (
|
|
|
31
31
|
create_engine,
|
|
32
32
|
dialects,
|
|
33
33
|
exc,
|
|
34
|
+
func,
|
|
34
35
|
insert,
|
|
35
36
|
inspect,
|
|
36
37
|
text,
|
|
37
38
|
)
|
|
38
39
|
from sqlalchemy.exc import DBAPIError, NoSuchTableError, OperationalError
|
|
39
|
-
from sqlalchemy.sql import and_, not_, or_, select
|
|
40
|
+
from sqlalchemy.sql import and_, literal, not_, or_, select
|
|
40
41
|
from sqlalchemy.sql.ddl import CreateTable
|
|
42
|
+
from sqlalchemy.testing.assertions import eq_
|
|
41
43
|
|
|
42
44
|
import snowflake.connector.errors
|
|
43
45
|
import snowflake.sqlalchemy.snowdialect
|
|
@@ -469,6 +471,7 @@ def test_inspect_column(engine_testaccount):
|
|
|
469
471
|
try:
|
|
470
472
|
inspector = inspect(engine_testaccount)
|
|
471
473
|
all_table_names = inspector.get_table_names()
|
|
474
|
+
assert isinstance(all_table_names, list)
|
|
472
475
|
assert "users" in all_table_names
|
|
473
476
|
assert "addresses" in all_table_names
|
|
474
477
|
|
|
@@ -1863,3 +1866,86 @@ def test_snowflake_sqlalchemy_as_valid_client_type():
|
|
|
1863
1866
|
snowflake.connector.connection.DEFAULT_CONFIGURATION[
|
|
1864
1867
|
"internal_application_version"
|
|
1865
1868
|
] = origin_internal_app_version
|
|
1869
|
+
|
|
1870
|
+
|
|
1871
|
+
@pytest.mark.parametrize(
|
|
1872
|
+
"operation",
|
|
1873
|
+
[
|
|
1874
|
+
[
|
|
1875
|
+
literal(5),
|
|
1876
|
+
literal(10),
|
|
1877
|
+
0.5,
|
|
1878
|
+
],
|
|
1879
|
+
[literal(5), func.sqrt(literal(10)), 1.5811388300841895],
|
|
1880
|
+
[literal(4), literal(5), decimal.Decimal("0.800000")],
|
|
1881
|
+
[literal(2), literal(2), 1.0],
|
|
1882
|
+
[literal(3), literal(2), 1.5],
|
|
1883
|
+
[literal(4), literal(1.5), 2.666667],
|
|
1884
|
+
[literal(5.5), literal(10.7), 0.5140187],
|
|
1885
|
+
[literal(5.5), literal(8), 0.6875],
|
|
1886
|
+
],
|
|
1887
|
+
)
|
|
1888
|
+
def test_true_division_operation(engine_testaccount, operation):
|
|
1889
|
+
# expected_warning = "div_is_floordiv value will be changed to False in a future release. This will generate a behavior change on true and floor division. Please review https://docs.sqlalchemy.org/en/20/changelog/whatsnew_20.html#python-division-operator-performs-true-division-for-all-backends-added-floor-division"
|
|
1890
|
+
# with pytest.warns(PendingDeprecationWarning, match=expected_warning):
|
|
1891
|
+
with engine_testaccount.connect() as conn:
|
|
1892
|
+
eq_(
|
|
1893
|
+
conn.execute(select(operation[0] / operation[1])).fetchall(),
|
|
1894
|
+
[((operation[2]),)],
|
|
1895
|
+
)
|
|
1896
|
+
|
|
1897
|
+
|
|
1898
|
+
@pytest.mark.parametrize(
|
|
1899
|
+
"operation",
|
|
1900
|
+
[
|
|
1901
|
+
[literal(5), literal(10), 0.5, 0.5],
|
|
1902
|
+
[literal(5), func.sqrt(literal(10)), 1.5811388300841895, 1.0],
|
|
1903
|
+
[
|
|
1904
|
+
literal(4),
|
|
1905
|
+
literal(5),
|
|
1906
|
+
decimal.Decimal("0.800000"),
|
|
1907
|
+
decimal.Decimal("0.800000"),
|
|
1908
|
+
],
|
|
1909
|
+
[literal(2), literal(2), 1.0, 1.0],
|
|
1910
|
+
[literal(3), literal(2), 1.5, 1.5],
|
|
1911
|
+
[literal(4), literal(1.5), 2.666667, 2.0],
|
|
1912
|
+
[literal(5.5), literal(10.7), 0.5140187, 0],
|
|
1913
|
+
[literal(5.5), literal(8), 0.6875, 0.6875],
|
|
1914
|
+
],
|
|
1915
|
+
)
|
|
1916
|
+
@pytest.mark.feature_v20
|
|
1917
|
+
def test_division_force_div_is_floordiv_default(engine_testaccount, operation):
|
|
1918
|
+
expected_warning = "div_is_floordiv value will be changed to False in a future release. This will generate a behavior change on true and floor division. Please review https://docs.sqlalchemy.org/en/20/changelog/whatsnew_20.html#python-division-operator-performs-true-division-for-all-backends-added-floor-division"
|
|
1919
|
+
with pytest.warns(PendingDeprecationWarning, match=expected_warning):
|
|
1920
|
+
with engine_testaccount.connect() as conn:
|
|
1921
|
+
eq_(
|
|
1922
|
+
conn.execute(
|
|
1923
|
+
select(operation[0] / operation[1], operation[0] // operation[1])
|
|
1924
|
+
).fetchall(),
|
|
1925
|
+
[(operation[2], operation[3])],
|
|
1926
|
+
)
|
|
1927
|
+
|
|
1928
|
+
|
|
1929
|
+
@pytest.mark.parametrize(
|
|
1930
|
+
"operation",
|
|
1931
|
+
[
|
|
1932
|
+
[literal(5), literal(10), 0.5, 0],
|
|
1933
|
+
[literal(5), func.sqrt(literal(10)), 1.5811388300841895, 1.0],
|
|
1934
|
+
[literal(4), literal(5), decimal.Decimal("0.800000"), 0],
|
|
1935
|
+
[literal(2), literal(2), 1.0, 1.0],
|
|
1936
|
+
[literal(3), literal(2), 1.5, 1],
|
|
1937
|
+
[literal(4), literal(1.5), 2.666667, 2.0],
|
|
1938
|
+
[literal(5.5), literal(10.7), 0.5140187, 0],
|
|
1939
|
+
[literal(5.5), literal(8), 0.6875, 0],
|
|
1940
|
+
],
|
|
1941
|
+
)
|
|
1942
|
+
@pytest.mark.feature_v20
|
|
1943
|
+
def test_division_force_div_is_floordiv_false(db_parameters, operation):
|
|
1944
|
+
engine = create_engine(URL(**db_parameters), **{"force_div_is_floordiv": False})
|
|
1945
|
+
with engine.connect() as conn:
|
|
1946
|
+
eq_(
|
|
1947
|
+
conn.execute(
|
|
1948
|
+
select(operation[0] / operation[1], operation[0] // operation[1])
|
|
1949
|
+
).fetchall(),
|
|
1950
|
+
[(operation[2], operation[3])],
|
|
1951
|
+
)
|
{snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/test_structured_datatypes.py
RENAMED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
# Copyright (c) 2012-2023 Snowflake Computing Inc. All rights reserved.
|
|
3
3
|
#
|
|
4
4
|
import pytest
|
|
5
|
+
import sqlalchemy as sa
|
|
5
6
|
from sqlalchemy import (
|
|
6
7
|
Column,
|
|
7
8
|
Integer,
|
|
@@ -47,6 +48,20 @@ def test_compile_table_with_structured_data_type(
|
|
|
47
48
|
assert sql_compiler(create_table) == snapshot
|
|
48
49
|
|
|
49
50
|
|
|
51
|
+
def test_compile_table_with_sqlalchemy_array(sql_compiler, snapshot):
|
|
52
|
+
metadata = MetaData()
|
|
53
|
+
user_table = Table(
|
|
54
|
+
"clustered_user",
|
|
55
|
+
metadata,
|
|
56
|
+
Column("Id", Integer, primary_key=True),
|
|
57
|
+
Column("name", sa.ARRAY(sa.String)),
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
create_table = CreateTable(user_table)
|
|
61
|
+
|
|
62
|
+
assert sql_compiler(create_table) == snapshot
|
|
63
|
+
|
|
64
|
+
|
|
50
65
|
@pytest.mark.requires_external_volume
|
|
51
66
|
def test_insert_map(engine_testaccount, external_volume, base_location, snapshot):
|
|
52
67
|
metadata = MetaData()
|
|
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.2 → snowflake_sqlalchemy-1.7.4}/ci/docker/sqlalchemy_build/Dockerfile
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
|
{snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/src/snowflake/sqlalchemy/__init__.py
RENAMED
|
File without changes
|
{snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/src/snowflake/sqlalchemy/_constants.py
RENAMED
|
File without changes
|
{snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/src/snowflake/sqlalchemy/compat.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/src/snowflake/sqlalchemy/functions.py
RENAMED
|
File without changes
|
|
File without changes
|
{snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/src/snowflake/sqlalchemy/provision.py
RENAMED
|
File without changes
|
{snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/src/snowflake/sqlalchemy/requirements.py
RENAMED
|
File without changes
|
{snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/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
|
{snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tested_requirements/requirements_310.reqs
RENAMED
|
File without changes
|
{snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tested_requirements/requirements_37.reqs
RENAMED
|
File without changes
|
{snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tested_requirements/requirements_38.reqs
RENAMED
|
File without changes
|
{snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tested_requirements/requirements_39.reqs
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/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
|
{snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/sqlalchemy_test_suite/README.md
RENAMED
|
File without changes
|
{snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/sqlalchemy_test_suite/__init__.py
RENAMED
|
File without changes
|
{snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/sqlalchemy_test_suite/conftest.py
RENAMED
|
File without changes
|
{snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/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
|
|
File without changes
|
{snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/test_semi_structured_datatypes.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{snowflake_sqlalchemy-1.7.2 → snowflake_sqlalchemy-1.7.4}/tests/test_unit_structured_types.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|