snowflake-sqlalchemy 1.5.3__tar.gz → 1.6.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {snowflake_sqlalchemy-1.5.3 → snowflake_sqlalchemy-1.6.1}/.pre-commit-config.yaml +7 -0
- {snowflake_sqlalchemy-1.5.3 → snowflake_sqlalchemy-1.6.1}/DESCRIPTION.md +15 -2
- {snowflake_sqlalchemy-1.5.3 → snowflake_sqlalchemy-1.6.1}/PKG-INFO +23 -12
- {snowflake_sqlalchemy-1.5.3 → snowflake_sqlalchemy-1.6.1}/README.md +21 -10
- {snowflake_sqlalchemy-1.5.3 → snowflake_sqlalchemy-1.6.1}/ci/build.sh +7 -5
- snowflake_sqlalchemy-1.6.1/ci/test_linux.sh +25 -0
- {snowflake_sqlalchemy-1.5.3 → snowflake_sqlalchemy-1.6.1}/pyproject.toml +18 -3
- snowflake_sqlalchemy-1.6.1/snyk/requirements.txt +2 -0
- snowflake_sqlalchemy-1.6.1/snyk/requiremtnts.txt +2 -0
- snowflake_sqlalchemy-1.6.1/snyk/update_requirements.py +17 -0
- {snowflake_sqlalchemy-1.5.3 → snowflake_sqlalchemy-1.6.1}/src/snowflake/sqlalchemy/base.py +26 -23
- snowflake_sqlalchemy-1.6.1/src/snowflake/sqlalchemy/compat.py +36 -0
- {snowflake_sqlalchemy-1.5.3 → snowflake_sqlalchemy-1.6.1}/src/snowflake/sqlalchemy/custom_commands.py +2 -1
- snowflake_sqlalchemy-1.6.1/src/snowflake/sqlalchemy/functions.py +16 -0
- {snowflake_sqlalchemy-1.5.3 → snowflake_sqlalchemy-1.6.1}/src/snowflake/sqlalchemy/requirements.py +16 -0
- {snowflake_sqlalchemy-1.5.3 → snowflake_sqlalchemy-1.6.1}/src/snowflake/sqlalchemy/snowdialect.py +37 -30
- {snowflake_sqlalchemy-1.5.3 → snowflake_sqlalchemy-1.6.1}/src/snowflake/sqlalchemy/util.py +10 -2
- {snowflake_sqlalchemy-1.5.3 → snowflake_sqlalchemy-1.6.1}/src/snowflake/sqlalchemy/version.py +1 -1
- {snowflake_sqlalchemy-1.5.3 → snowflake_sqlalchemy-1.6.1}/tests/conftest.py +9 -23
- {snowflake_sqlalchemy-1.5.3 → snowflake_sqlalchemy-1.6.1}/tests/sqlalchemy_test_suite/conftest.py +7 -0
- {snowflake_sqlalchemy-1.5.3 → snowflake_sqlalchemy-1.6.1}/tests/sqlalchemy_test_suite/test_suite.py +4 -0
- snowflake_sqlalchemy-1.6.1/tests/sqlalchemy_test_suite/test_suite_20.py +205 -0
- {snowflake_sqlalchemy-1.5.3 → snowflake_sqlalchemy-1.6.1}/tests/test_compiler.py +1 -1
- {snowflake_sqlalchemy-1.5.3 → snowflake_sqlalchemy-1.6.1}/tests/test_core.py +32 -53
- snowflake_sqlalchemy-1.6.1/tests/test_custom_functions.py +25 -0
- {snowflake_sqlalchemy-1.5.3 → snowflake_sqlalchemy-1.6.1}/tests/test_orm.py +22 -20
- {snowflake_sqlalchemy-1.5.3 → snowflake_sqlalchemy-1.6.1}/tests/test_pandas.py +6 -5
- {snowflake_sqlalchemy-1.5.3 → snowflake_sqlalchemy-1.6.1}/tests/test_qmark.py +2 -2
- {snowflake_sqlalchemy-1.5.3 → snowflake_sqlalchemy-1.6.1}/tests/test_sequence.py +26 -0
- {snowflake_sqlalchemy-1.5.3 → snowflake_sqlalchemy-1.6.1}/tox.ini +4 -8
- snowflake_sqlalchemy-1.5.3/ci/test_linux.sh +0 -25
- {snowflake_sqlalchemy-1.5.3 → snowflake_sqlalchemy-1.6.1}/.gitignore +0 -0
- {snowflake_sqlalchemy-1.5.3 → snowflake_sqlalchemy-1.6.1}/.gitmodules +0 -0
- {snowflake_sqlalchemy-1.5.3 → snowflake_sqlalchemy-1.6.1}/LICENSE.txt +0 -0
- {snowflake_sqlalchemy-1.5.3 → snowflake_sqlalchemy-1.6.1}/MANIFEST.in +0 -0
- {snowflake_sqlalchemy-1.5.3 → snowflake_sqlalchemy-1.6.1}/ci/build_docker.sh +0 -0
- {snowflake_sqlalchemy-1.5.3 → snowflake_sqlalchemy-1.6.1}/ci/docker/sqlalchemy_build/Dockerfile +0 -0
- {snowflake_sqlalchemy-1.5.3 → snowflake_sqlalchemy-1.6.1}/ci/docker/sqlalchemy_build/scripts/entrypoint.sh +0 -0
- {snowflake_sqlalchemy-1.5.3 → snowflake_sqlalchemy-1.6.1}/ci/set_base_image.sh +0 -0
- {snowflake_sqlalchemy-1.5.3 → snowflake_sqlalchemy-1.6.1}/ci/test.sh +0 -0
- {snowflake_sqlalchemy-1.5.3 → snowflake_sqlalchemy-1.6.1}/ci/test_docker.sh +0 -0
- {snowflake_sqlalchemy-1.5.3 → snowflake_sqlalchemy-1.6.1}/license_header.txt +0 -0
- {snowflake_sqlalchemy-1.5.3 → snowflake_sqlalchemy-1.6.1}/setup.cfg +0 -0
- {snowflake_sqlalchemy-1.5.3 → snowflake_sqlalchemy-1.6.1}/src/snowflake/sqlalchemy/__init__.py +0 -0
- {snowflake_sqlalchemy-1.5.3 → snowflake_sqlalchemy-1.6.1}/src/snowflake/sqlalchemy/_constants.py +0 -0
- {snowflake_sqlalchemy-1.5.3 → snowflake_sqlalchemy-1.6.1}/src/snowflake/sqlalchemy/custom_types.py +0 -0
- {snowflake_sqlalchemy-1.5.3 → snowflake_sqlalchemy-1.6.1}/src/snowflake/sqlalchemy/provision.py +0 -0
- {snowflake_sqlalchemy-1.5.3 → snowflake_sqlalchemy-1.6.1}/tested_requirements/requirements_310.reqs +0 -0
- {snowflake_sqlalchemy-1.5.3 → snowflake_sqlalchemy-1.6.1}/tested_requirements/requirements_37.reqs +0 -0
- {snowflake_sqlalchemy-1.5.3 → snowflake_sqlalchemy-1.6.1}/tested_requirements/requirements_38.reqs +0 -0
- {snowflake_sqlalchemy-1.5.3 → snowflake_sqlalchemy-1.6.1}/tested_requirements/requirements_39.reqs +0 -0
- {snowflake_sqlalchemy-1.5.3 → snowflake_sqlalchemy-1.6.1}/tests/README.rst +0 -0
- {snowflake_sqlalchemy-1.5.3 → snowflake_sqlalchemy-1.6.1}/tests/__init__.py +0 -0
- {snowflake_sqlalchemy-1.5.3 → snowflake_sqlalchemy-1.6.1}/tests/data/users.txt +0 -0
- {snowflake_sqlalchemy-1.5.3 → snowflake_sqlalchemy-1.6.1}/tests/sqlalchemy_test_suite/README.md +0 -0
- {snowflake_sqlalchemy-1.5.3 → snowflake_sqlalchemy-1.6.1}/tests/sqlalchemy_test_suite/__init__.py +0 -0
- {snowflake_sqlalchemy-1.5.3 → snowflake_sqlalchemy-1.6.1}/tests/test_copy.py +0 -0
- {snowflake_sqlalchemy-1.5.3 → snowflake_sqlalchemy-1.6.1}/tests/test_create.py +0 -0
- {snowflake_sqlalchemy-1.5.3 → snowflake_sqlalchemy-1.6.1}/tests/test_custom_types.py +0 -0
- {snowflake_sqlalchemy-1.5.3 → snowflake_sqlalchemy-1.6.1}/tests/test_geography.py +0 -0
- {snowflake_sqlalchemy-1.5.3 → snowflake_sqlalchemy-1.6.1}/tests/test_geometry.py +0 -0
- {snowflake_sqlalchemy-1.5.3 → snowflake_sqlalchemy-1.6.1}/tests/test_multivalues_insert.py +0 -0
- {snowflake_sqlalchemy-1.5.3 → snowflake_sqlalchemy-1.6.1}/tests/test_quote.py +0 -0
- {snowflake_sqlalchemy-1.5.3 → snowflake_sqlalchemy-1.6.1}/tests/test_semi_structured_datatypes.py +0 -0
- {snowflake_sqlalchemy-1.5.3 → snowflake_sqlalchemy-1.6.1}/tests/test_timestamp.py +0 -0
- {snowflake_sqlalchemy-1.5.3 → snowflake_sqlalchemy-1.6.1}/tests/test_unit_core.py +0 -0
- {snowflake_sqlalchemy-1.5.3 → snowflake_sqlalchemy-1.6.1}/tests/test_unit_cte.py +0 -0
- {snowflake_sqlalchemy-1.5.3 → snowflake_sqlalchemy-1.6.1}/tests/test_unit_types.py +0 -0
- {snowflake_sqlalchemy-1.5.3 → snowflake_sqlalchemy-1.6.1}/tests/test_unit_url.py +0 -0
- {snowflake_sqlalchemy-1.5.3 → snowflake_sqlalchemy-1.6.1}/tests/util.py +0 -0
|
@@ -44,3 +44,10 @@ repos:
|
|
|
44
44
|
- id: flake8
|
|
45
45
|
additional_dependencies:
|
|
46
46
|
- flake8-bugbear
|
|
47
|
+
- repo: local
|
|
48
|
+
hooks:
|
|
49
|
+
- id: requirements-update
|
|
50
|
+
name: "Update dependencies from pyproject.toml to snyk/requirements.txt"
|
|
51
|
+
language: system
|
|
52
|
+
entry: python snyk/update_requirements.py
|
|
53
|
+
files: ^pyproject.toml$
|
|
@@ -9,9 +9,22 @@ Source code is also available at:
|
|
|
9
9
|
|
|
10
10
|
# Release Notes
|
|
11
11
|
|
|
12
|
-
- v1.
|
|
12
|
+
- v1.6.1(July 9, 2024)
|
|
13
13
|
|
|
14
|
-
-
|
|
14
|
+
- Update internal project workflow with pypi publishing
|
|
15
|
+
|
|
16
|
+
- v1.6.0(July 8, 2024)
|
|
17
|
+
|
|
18
|
+
- support for installing with SQLAlchemy 2.0.x
|
|
19
|
+
- use `hatch` & `uv` for managing project virtual environments
|
|
20
|
+
|
|
21
|
+
- v1.5.4
|
|
22
|
+
|
|
23
|
+
- Add ability to set ORDER / NOORDER sequence on columns with IDENTITY
|
|
24
|
+
|
|
25
|
+
- v1.5.3(April 16, 2024)
|
|
26
|
+
|
|
27
|
+
- Limit SQLAlchemy to < 2.0.0 before releasing version compatible with 2.0
|
|
15
28
|
|
|
16
29
|
- v1.5.2(April 11, 2024)
|
|
17
30
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: snowflake-sqlalchemy
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.6.1
|
|
4
4
|
Summary: Snowflake SQLAlchemy Dialect
|
|
5
5
|
Project-URL: Changelog, https://github.com/snowflakedb/snowflake-sqlalchemy/blob/main/DESCRIPTION.md
|
|
6
6
|
Project-URL: Documentation, https://docs.snowflake.com/en/user-guide/sqlalchemy.html
|
|
@@ -36,7 +36,7 @@ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
|
36
36
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
37
37
|
Requires-Python: >=3.8
|
|
38
38
|
Requires-Dist: snowflake-connector-python<4.0.0
|
|
39
|
-
Requires-Dist: sqlalchemy
|
|
39
|
+
Requires-Dist: sqlalchemy>=1.4.19
|
|
40
40
|
Provides-Extra: development
|
|
41
41
|
Requires-Dist: mock; extra == 'development'
|
|
42
42
|
Requires-Dist: numpy; extra == 'development'
|
|
@@ -153,6 +153,7 @@ containing special characters need to be URL encoded to be parsed correctly. Thi
|
|
|
153
153
|
characters could lead to authentication failure.
|
|
154
154
|
|
|
155
155
|
The encoding for the password can be generated using `urllib.parse`:
|
|
156
|
+
|
|
156
157
|
```python
|
|
157
158
|
import urllib.parse
|
|
158
159
|
urllib.parse.quote("kx@% jj5/g")
|
|
@@ -163,6 +164,7 @@ urllib.parse.quote("kx@% jj5/g")
|
|
|
163
164
|
|
|
164
165
|
To create an engine with the proper encodings, either manually constructing the url string by formatting
|
|
165
166
|
or taking advantage of the `snowflake.sqlalchemy.URL` helper method:
|
|
167
|
+
|
|
166
168
|
```python
|
|
167
169
|
import urllib.parse
|
|
168
170
|
from snowflake.sqlalchemy import URL
|
|
@@ -243,14 +245,23 @@ engine = create_engine(...)
|
|
|
243
245
|
engine.execute(<SQL>)
|
|
244
246
|
engine.dispose()
|
|
245
247
|
|
|
246
|
-
#
|
|
248
|
+
# Better.
|
|
247
249
|
engine = create_engine(...)
|
|
248
250
|
connection = engine.connect()
|
|
249
251
|
try:
|
|
250
|
-
|
|
252
|
+
connection.execute(text(<SQL>))
|
|
251
253
|
finally:
|
|
252
254
|
connection.close()
|
|
253
255
|
engine.dispose()
|
|
256
|
+
|
|
257
|
+
# Best
|
|
258
|
+
try:
|
|
259
|
+
with engine.connext() as connection:
|
|
260
|
+
connection.execute(text(<SQL>))
|
|
261
|
+
# or
|
|
262
|
+
connection.exec_driver_sql(<SQL>)
|
|
263
|
+
finally:
|
|
264
|
+
engine.dispose()
|
|
254
265
|
```
|
|
255
266
|
|
|
256
267
|
### Auto-increment Behavior
|
|
@@ -294,14 +305,14 @@ engine = create_engine(URL(
|
|
|
294
305
|
|
|
295
306
|
specific_date = np.datetime64('2016-03-04T12:03:05.123456789Z')
|
|
296
307
|
|
|
297
|
-
|
|
298
|
-
connection.
|
|
299
|
-
|
|
300
|
-
connection.
|
|
301
|
-
|
|
302
|
-
)
|
|
303
|
-
df = pd.read_sql_query("SELECT * FROM ts_tbl",
|
|
304
|
-
assert df.c1.values[0] == specific_date
|
|
308
|
+
with engine.connect() as connection:
|
|
309
|
+
connection.exec_driver_sql(
|
|
310
|
+
"CREATE OR REPLACE TABLE ts_tbl(c1 TIMESTAMP_NTZ)")
|
|
311
|
+
connection.exec_driver_sql(
|
|
312
|
+
"INSERT INTO ts_tbl(c1) values(%s)", (specific_date,)
|
|
313
|
+
)
|
|
314
|
+
df = pd.read_sql_query("SELECT * FROM ts_tbl", connection)
|
|
315
|
+
assert df.c1.values[0] == specific_date
|
|
305
316
|
```
|
|
306
317
|
|
|
307
318
|
The following `NumPy` data types are supported:
|
|
@@ -101,6 +101,7 @@ containing special characters need to be URL encoded to be parsed correctly. Thi
|
|
|
101
101
|
characters could lead to authentication failure.
|
|
102
102
|
|
|
103
103
|
The encoding for the password can be generated using `urllib.parse`:
|
|
104
|
+
|
|
104
105
|
```python
|
|
105
106
|
import urllib.parse
|
|
106
107
|
urllib.parse.quote("kx@% jj5/g")
|
|
@@ -111,6 +112,7 @@ urllib.parse.quote("kx@% jj5/g")
|
|
|
111
112
|
|
|
112
113
|
To create an engine with the proper encodings, either manually constructing the url string by formatting
|
|
113
114
|
or taking advantage of the `snowflake.sqlalchemy.URL` helper method:
|
|
115
|
+
|
|
114
116
|
```python
|
|
115
117
|
import urllib.parse
|
|
116
118
|
from snowflake.sqlalchemy import URL
|
|
@@ -191,14 +193,23 @@ engine = create_engine(...)
|
|
|
191
193
|
engine.execute(<SQL>)
|
|
192
194
|
engine.dispose()
|
|
193
195
|
|
|
194
|
-
#
|
|
196
|
+
# Better.
|
|
195
197
|
engine = create_engine(...)
|
|
196
198
|
connection = engine.connect()
|
|
197
199
|
try:
|
|
198
|
-
|
|
200
|
+
connection.execute(text(<SQL>))
|
|
199
201
|
finally:
|
|
200
202
|
connection.close()
|
|
201
203
|
engine.dispose()
|
|
204
|
+
|
|
205
|
+
# Best
|
|
206
|
+
try:
|
|
207
|
+
with engine.connext() as connection:
|
|
208
|
+
connection.execute(text(<SQL>))
|
|
209
|
+
# or
|
|
210
|
+
connection.exec_driver_sql(<SQL>)
|
|
211
|
+
finally:
|
|
212
|
+
engine.dispose()
|
|
202
213
|
```
|
|
203
214
|
|
|
204
215
|
### Auto-increment Behavior
|
|
@@ -242,14 +253,14 @@ engine = create_engine(URL(
|
|
|
242
253
|
|
|
243
254
|
specific_date = np.datetime64('2016-03-04T12:03:05.123456789Z')
|
|
244
255
|
|
|
245
|
-
|
|
246
|
-
connection.
|
|
247
|
-
|
|
248
|
-
connection.
|
|
249
|
-
|
|
250
|
-
)
|
|
251
|
-
df = pd.read_sql_query("SELECT * FROM ts_tbl",
|
|
252
|
-
assert df.c1.values[0] == specific_date
|
|
256
|
+
with engine.connect() as connection:
|
|
257
|
+
connection.exec_driver_sql(
|
|
258
|
+
"CREATE OR REPLACE TABLE ts_tbl(c1 TIMESTAMP_NTZ)")
|
|
259
|
+
connection.exec_driver_sql(
|
|
260
|
+
"INSERT INTO ts_tbl(c1) values(%s)", (specific_date,)
|
|
261
|
+
)
|
|
262
|
+
df = pd.read_sql_query("SELECT * FROM ts_tbl", connection)
|
|
263
|
+
assert df.c1.values[0] == specific_date
|
|
253
264
|
```
|
|
254
265
|
|
|
255
266
|
The following `NumPy` data types are supported:
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# Build snowflake-sqlalchemy
|
|
4
4
|
set -o pipefail
|
|
5
5
|
|
|
6
|
-
PYTHON="python3.
|
|
6
|
+
PYTHON="python3.8"
|
|
7
7
|
THIS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
8
8
|
SQLALCHEMY_DIR="$(dirname "${THIS_DIR}")"
|
|
9
9
|
DIST_DIR="${SQLALCHEMY_DIR}/dist"
|
|
@@ -11,14 +11,16 @@ DIST_DIR="${SQLALCHEMY_DIR}/dist"
|
|
|
11
11
|
cd "$SQLALCHEMY_DIR"
|
|
12
12
|
# Clean up previously built DIST_DIR
|
|
13
13
|
if [ -d "${DIST_DIR}" ]; then
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
echo "[WARN] ${DIST_DIR} already existing, deleting it..."
|
|
15
|
+
rm -rf "${DIST_DIR}"
|
|
16
16
|
fi
|
|
17
17
|
|
|
18
18
|
# Constants and setup
|
|
19
|
+
export PATH=$PATH:$HOME/.local/bin
|
|
19
20
|
|
|
20
21
|
echo "[Info] Building snowflake-sqlalchemy with $PYTHON"
|
|
21
22
|
# Clean up possible build artifacts
|
|
22
23
|
rm -rf build generated_version.py
|
|
23
|
-
|
|
24
|
-
${PYTHON} -m
|
|
24
|
+
export UV_NO_CACHE=true
|
|
25
|
+
${PYTHON} -m pip install uv hatch
|
|
26
|
+
${PYTHON} -m hatch build
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/bin/bash -e
|
|
2
|
+
#
|
|
3
|
+
# Test Snowflake SQLAlchemy in Linux
|
|
4
|
+
# NOTES:
|
|
5
|
+
# - Versions to be tested should be passed in as the first argument, e.g: "3.7 3.8". If omitted 3.7-3.11 will be assumed.
|
|
6
|
+
# - This script assumes that ../dist/repaired_wheels has the wheel(s) built for all versions to be tested
|
|
7
|
+
# - This is the script that test_docker.sh runs inside of the docker container
|
|
8
|
+
|
|
9
|
+
PYTHON_VERSIONS="${1:-3.8 3.9 3.10 3.11}"
|
|
10
|
+
THIS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
11
|
+
SQLALCHEMY_DIR="$(dirname "${THIS_DIR}")"
|
|
12
|
+
|
|
13
|
+
# Install one copy of tox
|
|
14
|
+
python3 -m pip install -U tox
|
|
15
|
+
|
|
16
|
+
# Run tests
|
|
17
|
+
cd $SQLALCHEMY_DIR
|
|
18
|
+
for PYTHON_VERSION in ${PYTHON_VERSIONS}; do
|
|
19
|
+
echo "[Info] Testing with ${PYTHON_VERSION}"
|
|
20
|
+
SHORT_VERSION=$(python3 -c "print('${PYTHON_VERSION}'.replace('.', ''))")
|
|
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
|
|
23
|
+
echo "[Info] Running tox for ${TEST_ENVLIST}"
|
|
24
|
+
python3 -m tox -e ${TEST_ENVLIST} --installpkg ${SQLALCHEMY_WHL}
|
|
25
|
+
done
|
|
@@ -38,7 +38,7 @@ classifiers = [
|
|
|
38
38
|
"Topic :: Software Development :: Libraries :: Application Frameworks",
|
|
39
39
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
40
40
|
]
|
|
41
|
-
dependencies = ["snowflake-connector-python<4.0.0"
|
|
41
|
+
dependencies = ["SQLAlchemy>=1.4.19", "snowflake-connector-python<4.0.0"]
|
|
42
42
|
|
|
43
43
|
[tool.hatch.version]
|
|
44
44
|
path = "src/snowflake/sqlalchemy/version.py"
|
|
@@ -73,6 +73,13 @@ exclude = ["/.github"]
|
|
|
73
73
|
packages = ["src/snowflake"]
|
|
74
74
|
|
|
75
75
|
[tool.hatch.envs.default]
|
|
76
|
+
extra-dependencies = ["SQLAlchemy>=1.4.19,<2.1.0"]
|
|
77
|
+
features = ["development", "pandas"]
|
|
78
|
+
python = "3.8"
|
|
79
|
+
installer = "uv"
|
|
80
|
+
|
|
81
|
+
[tool.hatch.envs.sa14]
|
|
82
|
+
extra-dependencies = ["SQLAlchemy>=1.4.19,<2.0.0"]
|
|
76
83
|
features = ["development", "pandas"]
|
|
77
84
|
python = "3.8"
|
|
78
85
|
|
|
@@ -82,10 +89,18 @@ SQLACHEMY_WARN_20 = "1"
|
|
|
82
89
|
|
|
83
90
|
[tool.hatch.envs.default.scripts]
|
|
84
91
|
check = "pre-commit run --all-files"
|
|
85
|
-
test-dialect = "pytest -ra -vvv --tb=short --cov snowflake.sqlalchemy --cov-append --junitxml ./junit.xml --ignore=tests/sqlalchemy_test_suite"
|
|
92
|
+
test-dialect = "pytest -ra -vvv --tb=short --cov snowflake.sqlalchemy --cov-append --junitxml ./junit.xml --ignore=tests/sqlalchemy_test_suite tests/"
|
|
86
93
|
test-dialect-compatibility = "pytest -ra -vvv --tb=short --cov snowflake.sqlalchemy --cov-append --junitxml ./junit.xml tests/sqlalchemy_test_suite"
|
|
87
|
-
test-run_v20 = "pytest -ra -vvv --tb=short --cov snowflake.sqlalchemy --cov-append --junitxml ./junit.xml --ignore=tests/sqlalchemy_test_suite --run_v20_sqlalchemy"
|
|
88
94
|
gh-cache-sum = "python -VV | sha256sum | cut -d' ' -f1"
|
|
95
|
+
check-import = "python -c 'import snowflake.sqlalchemy; print(snowflake.sqlalchemy.__version__)'"
|
|
96
|
+
|
|
97
|
+
[[tool.hatch.envs.release.matrix]]
|
|
98
|
+
python = ["3.8", "3.9", "3.10", "3.11", "3.12"]
|
|
99
|
+
features = ["development", "pandas"]
|
|
100
|
+
|
|
101
|
+
[tool.hatch.envs.release.scripts]
|
|
102
|
+
test-dialect = "pytest -ra -vvv --tb=short --ignore=tests/sqlalchemy_test_suite tests/"
|
|
103
|
+
test-compatibility = "pytest -ra -vvv --tb=short tests/sqlalchemy_test_suite tests/"
|
|
89
104
|
|
|
90
105
|
[tool.ruff]
|
|
91
106
|
line-length = 88
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
import tomlkit
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def sync():
|
|
7
|
+
pyproject = tomlkit.loads(Path("pyproject.toml").read_text())
|
|
8
|
+
snyk_reqiurements = Path("snyk/requirements.txt")
|
|
9
|
+
dependencies = pyproject.get("project", {}).get("dependencies", [])
|
|
10
|
+
|
|
11
|
+
with snyk_reqiurements.open("w") as fh:
|
|
12
|
+
fh.write("\n".join(dependencies))
|
|
13
|
+
fh.write("\n")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
if __name__ == "__main__":
|
|
17
|
+
sync()
|
|
@@ -13,13 +13,14 @@ from sqlalchemy.engine import default
|
|
|
13
13
|
from sqlalchemy.orm import context
|
|
14
14
|
from sqlalchemy.orm.context import _MapperEntity
|
|
15
15
|
from sqlalchemy.schema import Sequence, Table
|
|
16
|
-
from sqlalchemy.sql import compiler, expression
|
|
16
|
+
from sqlalchemy.sql import compiler, expression, functions
|
|
17
17
|
from sqlalchemy.sql.base import CompileState
|
|
18
18
|
from sqlalchemy.sql.elements import quoted_name
|
|
19
19
|
from sqlalchemy.sql.selectable import Lateral, SelectState
|
|
20
|
-
from sqlalchemy.util.compat import string_types
|
|
21
20
|
|
|
21
|
+
from .compat import IS_VERSION_20, args_reducer, string_types
|
|
22
22
|
from .custom_commands import AWSBucket, AzureContainer, ExternalStage
|
|
23
|
+
from .functions import flatten
|
|
23
24
|
from .util import (
|
|
24
25
|
_find_left_clause_to_join_from,
|
|
25
26
|
_set_connection_interpolate_empty_sequences,
|
|
@@ -324,17 +325,9 @@ class SnowflakeORMSelectCompileState(context.ORMSelectCompileState):
|
|
|
324
325
|
|
|
325
326
|
return left, replace_from_obj_index, use_entity_index
|
|
326
327
|
|
|
328
|
+
@args_reducer(positions_to_drop=(6, 7))
|
|
327
329
|
def _join_left_to_right(
|
|
328
|
-
self,
|
|
329
|
-
entities_collection,
|
|
330
|
-
left,
|
|
331
|
-
right,
|
|
332
|
-
onclause,
|
|
333
|
-
prop,
|
|
334
|
-
create_aliases,
|
|
335
|
-
aliased_generation,
|
|
336
|
-
outerjoin,
|
|
337
|
-
full,
|
|
330
|
+
self, entities_collection, left, right, onclause, prop, outerjoin, full
|
|
338
331
|
):
|
|
339
332
|
"""given raw "left", "right", "onclause" parameters consumed from
|
|
340
333
|
a particular key within _join(), add a real ORMJoin object to
|
|
@@ -364,7 +357,7 @@ class SnowflakeORMSelectCompileState(context.ORMSelectCompileState):
|
|
|
364
357
|
use_entity_index,
|
|
365
358
|
) = self._join_place_explicit_left_side(entities_collection, left)
|
|
366
359
|
|
|
367
|
-
if left is right
|
|
360
|
+
if left is right:
|
|
368
361
|
raise sa_exc.InvalidRequestError(
|
|
369
362
|
"Can't construct a join from %s to %s, they "
|
|
370
363
|
"are the same entity" % (left, right)
|
|
@@ -373,9 +366,15 @@ class SnowflakeORMSelectCompileState(context.ORMSelectCompileState):
|
|
|
373
366
|
# the right side as given often needs to be adapted. additionally
|
|
374
367
|
# a lot of things can be wrong with it. handle all that and
|
|
375
368
|
# get back the new effective "right" side
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
369
|
+
|
|
370
|
+
if IS_VERSION_20:
|
|
371
|
+
r_info, right, onclause = self._join_check_and_adapt_right_side(
|
|
372
|
+
left, right, onclause, prop
|
|
373
|
+
)
|
|
374
|
+
else:
|
|
375
|
+
r_info, right, onclause = self._join_check_and_adapt_right_side(
|
|
376
|
+
left, right, onclause, prop, False, False
|
|
377
|
+
)
|
|
379
378
|
|
|
380
379
|
if not r_info.is_selectable:
|
|
381
380
|
extra_criteria = self._get_extra_criteria(r_info)
|
|
@@ -966,34 +965,36 @@ class SnowflakeDDLCompiler(compiler.DDLCompiler):
|
|
|
966
965
|
)
|
|
967
966
|
|
|
968
967
|
def visit_identity_column(self, identity, **kw):
|
|
969
|
-
text = "
|
|
968
|
+
text = "IDENTITY"
|
|
970
969
|
if identity.start is not None or identity.increment is not None:
|
|
971
970
|
start = 1 if identity.start is None else identity.start
|
|
972
971
|
increment = 1 if identity.increment is None else identity.increment
|
|
973
972
|
text += f"({start},{increment})"
|
|
973
|
+
if identity.order is not None:
|
|
974
|
+
order = "ORDER" if identity.order else "NOORDER"
|
|
975
|
+
text += f" {order}"
|
|
974
976
|
return text
|
|
975
977
|
|
|
976
978
|
def get_identity_options(self, identity_options):
|
|
977
979
|
text = []
|
|
978
980
|
if identity_options.increment is not None:
|
|
979
|
-
text.append(
|
|
981
|
+
text.append("INCREMENT BY %d" % identity_options.increment)
|
|
980
982
|
if identity_options.start is not None:
|
|
981
|
-
text.append(
|
|
983
|
+
text.append("START WITH %d" % identity_options.start)
|
|
982
984
|
if identity_options.minvalue is not None:
|
|
983
|
-
text.append(
|
|
985
|
+
text.append("MINVALUE %d" % identity_options.minvalue)
|
|
984
986
|
if identity_options.maxvalue is not None:
|
|
985
|
-
text.append(
|
|
987
|
+
text.append("MAXVALUE %d" % identity_options.maxvalue)
|
|
986
988
|
if identity_options.nominvalue is not None:
|
|
987
989
|
text.append("NO MINVALUE")
|
|
988
990
|
if identity_options.nomaxvalue is not None:
|
|
989
991
|
text.append("NO MAXVALUE")
|
|
990
992
|
if identity_options.cache is not None:
|
|
991
|
-
text.append(
|
|
993
|
+
text.append("CACHE %d" % identity_options.cache)
|
|
992
994
|
if identity_options.cycle is not None:
|
|
993
995
|
text.append("CYCLE" if identity_options.cycle else "NO CYCLE")
|
|
994
996
|
if identity_options.order is not None:
|
|
995
997
|
text.append("ORDER" if identity_options.order else "NOORDER")
|
|
996
|
-
|
|
997
998
|
return " ".join(text)
|
|
998
999
|
|
|
999
1000
|
|
|
@@ -1063,3 +1064,5 @@ class SnowflakeTypeCompiler(compiler.GenericTypeCompiler):
|
|
|
1063
1064
|
|
|
1064
1065
|
|
|
1065
1066
|
construct_arguments = [(Table, {"clusterby": None})]
|
|
1067
|
+
|
|
1068
|
+
functions.register_function("flatten", flatten)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright (c) 2012-2023 Snowflake Computing Inc. All rights reserved.
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import functools
|
|
6
|
+
from typing import Callable
|
|
7
|
+
|
|
8
|
+
from sqlalchemy import __version__ as SA_VERSION
|
|
9
|
+
from sqlalchemy import util
|
|
10
|
+
|
|
11
|
+
string_types = (str,)
|
|
12
|
+
returns_unicode = util.symbol("RETURNS_UNICODE")
|
|
13
|
+
|
|
14
|
+
IS_VERSION_20 = tuple(int(v) for v in SA_VERSION.split(".")) >= (2, 0, 0)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def args_reducer(positions_to_drop: tuple):
|
|
18
|
+
"""Removes args at positions provided in tuple positions_to_drop.
|
|
19
|
+
|
|
20
|
+
For example tuple (3, 5) will remove items at third and fifth position.
|
|
21
|
+
Keep in mind that on class methods first postion is cls or self.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def fn_wrapper(fn: Callable):
|
|
25
|
+
@functools.wraps(fn)
|
|
26
|
+
def wrapper(*args):
|
|
27
|
+
reduced_args = args
|
|
28
|
+
if not IS_VERSION_20:
|
|
29
|
+
reduced_args = tuple(
|
|
30
|
+
arg for idx, arg in enumerate(args) if idx not in positions_to_drop
|
|
31
|
+
)
|
|
32
|
+
fn(*reduced_args)
|
|
33
|
+
|
|
34
|
+
return wrapper
|
|
35
|
+
|
|
36
|
+
return fn_wrapper
|
|
@@ -10,7 +10,8 @@ from sqlalchemy.sql.ddl import DDLElement
|
|
|
10
10
|
from sqlalchemy.sql.dml import UpdateBase
|
|
11
11
|
from sqlalchemy.sql.elements import ClauseElement
|
|
12
12
|
from sqlalchemy.sql.roles import FromClauseRole
|
|
13
|
-
|
|
13
|
+
|
|
14
|
+
from .compat import string_types
|
|
14
15
|
|
|
15
16
|
NoneType = type(None)
|
|
16
17
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright (c) 2012-2023 Snowflake Computing Inc. All rights reserved.
|
|
3
|
+
|
|
4
|
+
import warnings
|
|
5
|
+
|
|
6
|
+
from sqlalchemy.sql import functions as sqlfunc
|
|
7
|
+
|
|
8
|
+
FLATTEN_WARNING = "For backward compatibility params are not rendered."
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class flatten(sqlfunc.GenericFunction):
|
|
12
|
+
name = "flatten"
|
|
13
|
+
|
|
14
|
+
def __init__(self, *args, **kwargs):
|
|
15
|
+
warnings.warn(FLATTEN_WARNING, DeprecationWarning, stacklevel=2)
|
|
16
|
+
super().__init__(*args, **kwargs)
|
{snowflake_sqlalchemy-1.5.3 → snowflake_sqlalchemy-1.6.1}/src/snowflake/sqlalchemy/requirements.py
RENAMED
|
@@ -289,9 +289,25 @@ class Requirements(SuiteRequirements):
|
|
|
289
289
|
# Check https://snowflakecomputing.atlassian.net/browse/SNOW-640134 for details on breaking changes discussion.
|
|
290
290
|
return exclusions.closed()
|
|
291
291
|
|
|
292
|
+
@property
|
|
293
|
+
def date_implicit_bound(self):
|
|
294
|
+
# Supporting this would require behavior breaking change to implicitly convert str to timestamp when binding
|
|
295
|
+
# parameters in string forms of timestamp values.
|
|
296
|
+
return exclusions.closed()
|
|
297
|
+
|
|
298
|
+
@property
|
|
299
|
+
def time_implicit_bound(self):
|
|
300
|
+
# Supporting this would require behavior breaking change to implicitly convert str to timestamp when binding
|
|
301
|
+
# parameters in string forms of timestamp values.
|
|
302
|
+
return exclusions.closed()
|
|
303
|
+
|
|
292
304
|
@property
|
|
293
305
|
def timestamp_microseconds_implicit_bound(self):
|
|
294
306
|
# Supporting this would require behavior breaking change to implicitly convert str to timestamp when binding
|
|
295
307
|
# parameters in string forms of timestamp values.
|
|
296
308
|
# Check https://snowflakecomputing.atlassian.net/browse/SNOW-640134 for details on breaking changes discussion.
|
|
297
309
|
return exclusions.closed()
|
|
310
|
+
|
|
311
|
+
@property
|
|
312
|
+
def array_type(self):
|
|
313
|
+
return exclusions.closed()
|
{snowflake_sqlalchemy-1.5.3 → snowflake_sqlalchemy-1.6.1}/src/snowflake/sqlalchemy/snowdialect.py
RENAMED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import operator
|
|
6
6
|
from collections import defaultdict
|
|
7
7
|
from functools import reduce
|
|
8
|
+
from typing import Any
|
|
8
9
|
from urllib.parse import unquote_plus
|
|
9
10
|
|
|
10
11
|
import sqlalchemy.types as sqltypes
|
|
@@ -15,7 +16,6 @@ from sqlalchemy.engine import URL, default, reflection
|
|
|
15
16
|
from sqlalchemy.schema import Table
|
|
16
17
|
from sqlalchemy.sql import text
|
|
17
18
|
from sqlalchemy.sql.elements import quoted_name
|
|
18
|
-
from sqlalchemy.sql.sqltypes import String
|
|
19
19
|
from sqlalchemy.types import (
|
|
20
20
|
BIGINT,
|
|
21
21
|
BINARY,
|
|
@@ -40,6 +40,7 @@ from sqlalchemy.types import (
|
|
|
40
40
|
from snowflake.connector import errors as sf_errors
|
|
41
41
|
from snowflake.connector.connection import DEFAULT_CONFIGURATION
|
|
42
42
|
from snowflake.connector.constants import UTF8
|
|
43
|
+
from snowflake.sqlalchemy.compat import returns_unicode
|
|
43
44
|
|
|
44
45
|
from .base import (
|
|
45
46
|
SnowflakeCompiler,
|
|
@@ -63,7 +64,11 @@ from .custom_types import (
|
|
|
63
64
|
_CUSTOM_Float,
|
|
64
65
|
_CUSTOM_Time,
|
|
65
66
|
)
|
|
66
|
-
from .util import
|
|
67
|
+
from .util import (
|
|
68
|
+
_update_connection_application_name,
|
|
69
|
+
parse_url_boolean,
|
|
70
|
+
parse_url_integer,
|
|
71
|
+
)
|
|
67
72
|
|
|
68
73
|
colspecs = {
|
|
69
74
|
Date: _CUSTOM_Date,
|
|
@@ -134,7 +139,7 @@ class SnowflakeDialect(default.DefaultDialect):
|
|
|
134
139
|
# unicode strings
|
|
135
140
|
supports_unicode_statements = True
|
|
136
141
|
supports_unicode_binds = True
|
|
137
|
-
returns_unicode_strings =
|
|
142
|
+
returns_unicode_strings = returns_unicode
|
|
138
143
|
description_encoding = None
|
|
139
144
|
|
|
140
145
|
# No lastrowid support. See SNOW-11155
|
|
@@ -195,10 +200,34 @@ class SnowflakeDialect(default.DefaultDialect):
|
|
|
195
200
|
|
|
196
201
|
@classmethod
|
|
197
202
|
def dbapi(cls):
|
|
203
|
+
return cls.import_dbapi()
|
|
204
|
+
|
|
205
|
+
@classmethod
|
|
206
|
+
def import_dbapi(cls):
|
|
198
207
|
from snowflake import connector
|
|
199
208
|
|
|
200
209
|
return connector
|
|
201
210
|
|
|
211
|
+
@staticmethod
|
|
212
|
+
def parse_query_param_type(name: str, value: Any) -> Any:
|
|
213
|
+
"""Cast param value if possible to type defined in connector-python."""
|
|
214
|
+
if not (maybe_type_configuration := DEFAULT_CONFIGURATION.get(name)):
|
|
215
|
+
return value
|
|
216
|
+
|
|
217
|
+
_, expected_type = maybe_type_configuration
|
|
218
|
+
if not isinstance(expected_type, tuple):
|
|
219
|
+
expected_type = (expected_type,)
|
|
220
|
+
|
|
221
|
+
if isinstance(value, expected_type):
|
|
222
|
+
return value
|
|
223
|
+
|
|
224
|
+
elif bool in expected_type:
|
|
225
|
+
return parse_url_boolean(value)
|
|
226
|
+
elif int in expected_type:
|
|
227
|
+
return parse_url_integer(value)
|
|
228
|
+
else:
|
|
229
|
+
return value
|
|
230
|
+
|
|
202
231
|
def create_connect_args(self, url: URL):
|
|
203
232
|
opts = url.translate_connect_args(username="user")
|
|
204
233
|
if "database" in opts:
|
|
@@ -235,47 +264,25 @@ class SnowflakeDialect(default.DefaultDialect):
|
|
|
235
264
|
|
|
236
265
|
# URL sets the query parameter values as strings, we need to cast to expected types when necessary
|
|
237
266
|
for name, value in query.items():
|
|
238
|
-
|
|
239
|
-
if (
|
|
240
|
-
not maybe_type_configuration
|
|
241
|
-
): # if the parameter is not found in the type mapping, pass it through as a string
|
|
242
|
-
opts[name] = value
|
|
243
|
-
continue
|
|
244
|
-
|
|
245
|
-
(_, expected_type) = maybe_type_configuration
|
|
246
|
-
if not isinstance(expected_type, tuple):
|
|
247
|
-
expected_type = (expected_type,)
|
|
248
|
-
|
|
249
|
-
if isinstance(
|
|
250
|
-
value, expected_type
|
|
251
|
-
): # if the expected type is str, pass it through as a string
|
|
252
|
-
opts[name] = value
|
|
253
|
-
|
|
254
|
-
elif (
|
|
255
|
-
bool in expected_type
|
|
256
|
-
): # if the expected type is bool, parse it and pass as a boolean
|
|
257
|
-
opts[name] = parse_url_boolean(value)
|
|
258
|
-
else:
|
|
259
|
-
# TODO: other types like int are stil passed through as string
|
|
260
|
-
# https://github.com/snowflakedb/snowflake-sqlalchemy/issues/447
|
|
261
|
-
opts[name] = value
|
|
267
|
+
opts[name] = self.parse_query_param_type(name, value)
|
|
262
268
|
|
|
263
269
|
return ([], opts)
|
|
264
270
|
|
|
265
|
-
|
|
271
|
+
@reflection.cache
|
|
272
|
+
def has_table(self, connection, table_name, schema=None, **kw):
|
|
266
273
|
"""
|
|
267
274
|
Checks if the table exists
|
|
268
275
|
"""
|
|
269
276
|
return self._has_object(connection, "TABLE", table_name, schema)
|
|
270
277
|
|
|
271
|
-
|
|
278
|
+
@reflection.cache
|
|
279
|
+
def has_sequence(self, connection, sequence_name, schema=None, **kw):
|
|
272
280
|
"""
|
|
273
281
|
Checks if the sequence exists
|
|
274
282
|
"""
|
|
275
283
|
return self._has_object(connection, "SEQUENCE", sequence_name, schema)
|
|
276
284
|
|
|
277
285
|
def _has_object(self, connection, object_type, object_name, schema=None):
|
|
278
|
-
|
|
279
286
|
full_name = self._denormalize_quote_join(schema, object_name)
|
|
280
287
|
try:
|
|
281
288
|
results = connection.execute(
|