summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/sql
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2022-01-05 12:20:46 -0500
committerMike Bayer <mike_mp@zzzcomputing.com>2022-01-11 09:24:34 -0500
commit8a62aa58fa5eeaad4e6bdd994a00293f41eb3a71 (patch)
treeaa650722adc96ffd85a2988f9a9d2f4bf1f7c6ff /lib/sqlalchemy/sql
parent9298ce03e1181d5bc00c1891663433606ead1223 (diff)
downloadsqlalchemy-8a62aa58fa5eeaad4e6bdd994a00293f41eb3a71.tar.gz
implement second-level type resolution for literals
Added additional rule to the system that determines ``TypeEngine`` implementations from Python literals to apply a second level of adjustment to the type, so that a Python datetime with or without tzinfo can set the ``timezone=True`` parameter on the returned :class:`.DateTime` object, as well as :class:`.Time`. This helps with some round-trip scenarios on type-sensitive PostgreSQL dialects such as asyncpg, psycopg3 (2.0 only). For 1.4 specifically, the backport improves support for asyncpg handling of TIME WITH TIMEZONE, which was not fully implemented. 2.0's reworked PostgreSQL architecture had this handled already. Fixes: #7537 Change-Id: Icdb07db85af5f7f39f1c1ef855fe27609770094b
Diffstat (limited to 'lib/sqlalchemy/sql')
-rw-r--r--lib/sqlalchemy/sql/sqltypes.py18
-rw-r--r--lib/sqlalchemy/sql/type_api.py11
2 files changed, 28 insertions, 1 deletions
diff --git a/lib/sqlalchemy/sql/sqltypes.py b/lib/sqlalchemy/sql/sqltypes.py
index cda7b35cd..8a862d121 100644
--- a/lib/sqlalchemy/sql/sqltypes.py
+++ b/lib/sqlalchemy/sql/sqltypes.py
@@ -622,6 +622,13 @@ class DateTime(_LookupExpressionAdapter, TypeEngine):
def get_dbapi_type(self, dbapi):
return dbapi.DATETIME
+ def _resolve_for_literal(self, value):
+ with_timezone = value.tzinfo is not None
+ if with_timezone and not self.timezone:
+ return DATETIME_TIMEZONE
+ else:
+ return self
+
@property
def python_type(self):
return dt.datetime
@@ -692,6 +699,13 @@ class Time(_LookupExpressionAdapter, TypeEngine):
def python_type(self):
return dt.time
+ def _resolve_for_literal(self, value):
+ with_timezone = value.tzinfo is not None
+ if with_timezone and not self.timezone:
+ return TIME_TIMEZONE
+ else:
+ return self
+
@util.memoized_property
def _expression_adaptations(self):
# Based on https://www.postgresql.org/docs/current/\
@@ -2994,6 +3008,8 @@ STRINGTYPE = String()
INTEGERTYPE = Integer()
MATCHTYPE = MatchType()
TABLEVALUE = TableValueType()
+DATETIME_TIMEZONE = DateTime(timezone=True)
+TIME_TIMEZONE = Time(timezone=True)
_type_map = {
int: Integer(),
@@ -3031,7 +3047,7 @@ def _resolve_value_to_type(value):
)
return NULLTYPE
else:
- return _result_type
+ return _result_type._resolve_for_literal(value)
# back-assign to type_api
diff --git a/lib/sqlalchemy/sql/type_api.py b/lib/sqlalchemy/sql/type_api.py
index 07cd4d95f..b087dd1e9 100644
--- a/lib/sqlalchemy/sql/type_api.py
+++ b/lib/sqlalchemy/sql/type_api.py
@@ -592,6 +592,17 @@ class TypeEngine(Traversible):
)
return new_type
+ def _resolve_for_literal(self, value):
+ """adjust this type given a literal Python value that will be
+ stored in a bound parameter.
+
+ Used exclusively by _resolve_value_to_type().
+
+ .. versionadded:: 1.4.30 or 2.0
+
+ """
+ return self
+
@util.memoized_property
def _type_affinity(self):
"""Return a rudimental 'affinity' value expressing the general class