Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,6 @@ generated_version.py
# Editor specific
.idea/
.vscode/
*.code-workspace

# WhiteSource Scan
wss-*agent.config
Expand Down
4 changes: 4 additions & 0 deletions DESCRIPTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ Source code is also available at:
# Unreleased Notes

# Release Notes
- v1.8.0(TBD)
- Add support for `DECFLOAT`
- BREAKING CHANGE: The minimum supported version for the `snowflake-python-connector` has been increased to v3.14.1

- v1.7.6(July 10, 2025)
- Fix get_multi_indexes issue, wrong assign of returned indexes when processing multiple indexes in a table

Expand Down
5 changes: 5 additions & 0 deletions junit.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?><testsuites name="pytest tests"><testsuite name="pytest" errors="0" failures="1" skipped="0" tests="1" time="4.775" timestamp="2025-07-18T14:56:49.960659+00:00" hostname="V9P2792XJ5"><testcase classname="tests.E2E.test_select_decfloat_type" name="test_select_decfloat_native_connector" time="4.279"><failure message="AssertionError: Return type is &lt;class 'str'&gt; 3.16.0&#10;assert False&#10; + where False = isinstance('123456789.12345678', Decimal)">tests/E2E/test_select_decfloat_type.py:39: in test_select_decfloat_native_connector
assert isinstance(ret[0], Decimal), f"Return type is {type(ret[0])} {snowflake.connector.__version__}"
E AssertionError: Return type is &lt;class 'str'&gt; 3.16.0
E assert False
E + where False = isinstance('123456789.12345678', Decimal)</failure></testcase></testsuite></testsuites>
11 changes: 5 additions & 6 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ dynamic = ["version"]
description = "Snowflake SQLAlchemy Dialect"
readme = "README.md"
license = "Apache-2.0"
requires-python = ">=3.8"
requires-python = ">=3.9"
authors = [
{ name = "Snowflake Inc.", email = "[email protected]" },
]
Expand All @@ -25,7 +25,6 @@ classifiers = [
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
Expand All @@ -38,7 +37,7 @@ classifiers = [
"Topic :: Software Development :: Libraries :: Application Frameworks",
"Topic :: Software Development :: Libraries :: Python Modules",
]
dependencies = ["SQLAlchemy>=1.4.19", "snowflake-connector-python<4.0.0"]
dependencies = ["SQLAlchemy>=1.4.19", "snowflake-connector-python>=3.14.1,<4.0.0"]

[tool.hatch.version]
path = "src/snowflake/sqlalchemy/version.py"
Expand Down Expand Up @@ -79,13 +78,13 @@ path = ".venv"
type = "virtual"
extra-dependencies = ["SQLAlchemy>=1.4.19,<2.1.0"]
features = ["development", "pandas"]
python = "3.8"
python = "3.9"
installer = "uv"

[tool.hatch.envs.sa14]
extra-dependencies = ["SQLAlchemy>=1.4.19,<2.0.0"]
features = ["development", "pandas"]
python = "3.8"
python = "3.9"

[tool.hatch.envs.sa14.scripts]
test-dialect = "pytest --ignore_v20_test -ra -vvv --tb=short --cov snowflake.sqlalchemy --cov-append --junitxml ./junit.xml --ignore=tests/sqlalchemy_test_suite tests/"
Expand All @@ -105,7 +104,7 @@ gh-cache-sum = "python -VV | sha256sum | cut -d' ' -f1"
check-import = "python -c 'import snowflake.sqlalchemy; print(snowflake.sqlalchemy.__version__)'"

[[tool.hatch.envs.release.matrix]]
python = ["3.8", "3.9", "3.10", "3.11", "3.12"]
python = ["3.9", "3.10", "3.11", "3.12"]
features = ["development", "pandas"]

[tool.hatch.envs.release.scripts]
Expand Down
28 changes: 28 additions & 0 deletions snowflake-sqlalchemy.code-workspace
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"folders": [
{
"path": "."
},
],
"settings": {
"python.defaultInterpreterPath": ".venv/bin/python",
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true,
"debugpy.debugJustMyCode": false,
"python.testing.pytestArgs": [
"tests",
"-ra",
"-vvv",
"--tb=short",
"--junitxml=./junit.xml",
"--ignore=tests/sqlalchemy_test_suite"
]
},
"extensions": {
"recommendations": [
"ms-python.python",
"ms-python.vscode-pylance",
"ms-python.debugpy",
]
}
}
2 changes: 1 addition & 1 deletion snyk/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
SQLAlchemy>=1.4.19
snowflake-connector-python<4.0.0
snowflake-connector-python>=3.14.1,<4.0.0
2 changes: 2 additions & 0 deletions src/snowflake/sqlalchemy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
BYTEINT,
CHARACTER,
DEC,
DECFLOAT,
DOUBLE,
FIXED,
GEOGRAPHY,
Expand Down Expand Up @@ -121,6 +122,7 @@
"VARBINARY",
"VARIANT",
"MAP",
"DECFLOAT",
)

_custom_commands = (
Expand Down
9 changes: 8 additions & 1 deletion src/snowflake/sqlalchemy/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@
import re
import string
import warnings
from typing import List
from typing import Any, List

from sqlalchemy import exc as sa_exc
from sqlalchemy import inspect, sql
from sqlalchemy import types as sqltypes
from sqlalchemy import util as sa_util
from sqlalchemy.engine import default
from sqlalchemy.orm import context
Expand Down Expand Up @@ -1181,6 +1182,12 @@ def visit_GEOGRAPHY(self, type_, **kw):
def visit_GEOMETRY(self, type_, **kw):
return "GEOMETRY"

def visit_DECFLOAT(self, type_: sqltypes.DECIMAL[Any], **kw: Any) -> str:
if type_.precision is None:
return "DECFLOAT"
else:
return f"DECFLOAT({type_.precision})"


construct_arguments = [(Table, {"clusterby": None})]

Expand Down
4 changes: 4 additions & 0 deletions src/snowflake/sqlalchemy/custom_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,10 @@ class GEOMETRY(SnowflakeType):
__visit_name__ = "GEOMETRY"


class DECFLOAT(SnowflakeType, sqltypes.DECIMAL):
__visit_name__ = "DECFLOAT"


class _CUSTOM_Date(SnowflakeType, sqltypes.Date):
def literal_processor(self, dialect):
def process(value):
Expand Down
2 changes: 2 additions & 0 deletions src/snowflake/sqlalchemy/parser/custom_type_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from ..custom_types import (
_CUSTOM_DECIMAL,
ARRAY,
DECFLOAT,
DOUBLE,
GEOGRAPHY,
GEOMETRY,
Expand All @@ -47,6 +48,7 @@
"DATETIME": DATETIME,
"DEC": DECIMAL,
"DECIMAL": DECIMAL,
"DECFLOAT": DECFLOAT,
"DOUBLE": DOUBLE,
"FIXED": DECIMAL,
"FLOAT": FLOAT, # Snowflake FLOAT datatype doesn't have parameters
Expand Down
21 changes: 21 additions & 0 deletions tests/E2E/__snapshots__/test_select_decfloat_type.ambr
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# serializer version: 1
# name: test_create_table_with_decfloat
'''

CREATE TABLE mock_table (
id DECFLOAT(38)
)


'''
# ---
# name: test_create_table_with_decfloat_without_precision
'''

CREATE TABLE mock_table (
id DECFLOAT
)


'''
# ---
29 changes: 29 additions & 0 deletions tests/E2E/test_select_decfloat_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#
# Copyright (c) 2012-2023 Snowflake Computing Inc. All rights reserved.

from decimal import Decimal

from sqlalchemy import cast, func, literal, select

from snowflake.sqlalchemy import DECFLOAT


def test_select_decfloat(engine_testaccount):
select_stmt = select(cast(literal("123.45"), DECFLOAT))

with engine_testaccount.connect() as connection:
result = connection.execute(select_stmt)
value = result.scalar_one()

assert value == Decimal("123.45")


def test_select_decfloat_sum(engine_testaccount):
expr = cast(literal("123.45"), DECFLOAT) + cast(literal("100.55"), DECFLOAT)
select_stmt = select(func.sum(expr))

with engine_testaccount.connect() as connection:
result = connection.execute(select_stmt)
value = result.scalar_one()

assert value == Decimal("224.00")
20 changes: 20 additions & 0 deletions tests/__snapshots__/test_core.ambr
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,23 @@
# name: test_compile_table_with_cluster_by_with_expression
'CREATE TABLE clustered_user (\t"Id" INTEGER NOT NULL AUTOINCREMENT, \tname VARCHAR, \tPRIMARY KEY ("Id")) CLUSTER BY ("Id", name, "Id" > 5)'
# ---
# name: test_create_table_with_decfloat
'''

CREATE TABLE mock_table (
id DECFLOAT(38)
)


'''
# ---
# name: test_create_table_with_decfloat_without_precision
'''

CREATE TABLE mock_table (
id DECFLOAT
)


'''
# ---
36 changes: 35 additions & 1 deletion tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
import snowflake.connector.errors
import snowflake.sqlalchemy.snowdialect
from snowflake.connector import Error, ProgrammingError, connect
from snowflake.sqlalchemy import URL, MergeInto, dialect
from snowflake.sqlalchemy import DECFLOAT, URL, MergeInto, dialect, snowdialect
from snowflake.sqlalchemy._constants import (
APPLICATION_NAME,
SNOWFLAKE_SQLALCHEMY_VERSION,
Expand Down Expand Up @@ -1806,6 +1806,40 @@ def test_normalize_and_denormalize_empty_string_column_name(engine_testaccount):
)


def test_create_table_with_decfloat_without_precision(snapshot):
metadata = MetaData()

mock_table = Table(
"mock_table",
metadata,
Column("id", DECFLOAT),
)

compiled_sql = str(CreateTable(mock_table).compile(dialect=snowdialect.dialect()))

column = mock_table.c.id
assert isinstance(column.type, DECFLOAT)
assert column.type.precision is None
assert compiled_sql == snapshot


def test_create_table_with_decfloat(snapshot):
metadata = MetaData()

mock_table = Table(
"mock_table",
metadata,
Column("id", DECFLOAT(38)),
)

compiled_sql = str(CreateTable(mock_table).compile(dialect=snowdialect.dialect()))

column = mock_table.c.id
assert isinstance(column.type, DECFLOAT)
assert column.type.precision == 38
assert compiled_sql == snapshot


def test_snowflake_sqlalchemy_as_valid_client_type():
engine = create_engine(
URL(**CONNECTION_PARAMETERS),
Expand Down
2 changes: 2 additions & 0 deletions tests/test_unit_structured_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ def test_extract_parameters():
("DATETIME(3)", "DATETIME"),
("DECIMAL(10, 2)", "DECIMAL(10, 2)"),
("DEC(10, 2)", "DECIMAL(10, 2)"),
("DECFLOAT(38)", "DECFLOAT(38)"),
("DECFLOAT", "DECFLOAT"),
("DOUBLE", "FLOAT"),
("FLOAT", "FLOAT"),
("FIXED(10, 2)", "DECIMAL(10, 2)"),
Expand Down
Loading