88
99from databricks .sql .utils import ParamEscaper
1010
11+
1112@compiles (sqlalchemy .types .Enum ,"databricks" )
1213@compiles (sqlalchemy .types .String ,"databricks" )
1314@compiles (sqlalchemy .types .Text ,"databricks" )
@@ -88,11 +89,11 @@ def compile_array_databricks(type_, compiler, **kw):
8889
8990class DatabricksDateTimeNoTimezoneType (sqlalchemy .types .TypeDecorator ):
9091"""The decimal that pysql creates when it receives the contents of a TIMESTAMP_NTZ
91- includes a timezone of 'Etc/UTC'. But since SQLAlchemy's test suite assumes that
92- the sqlalchemy.types.DateTime type will return a datetime.datetime _without_ any
93- timezone set, we need to strip the timezone off the value received from pysql.
92+ includes a timezone of 'Etc/UTC'. But since SQLAlchemy's test suite assumes that
93+ the sqlalchemy.types.DateTime type will return a datetime.datetime _without_ any
94+ timezone set, we need to strip the timezone off the value received from pysql.
9495
95- It's not clear if DBR sends a timezone to pysql or if pysql is adding it. This could be a bug.
96+ It's not clear if DBR sends a timezone to pysql or if pysql is adding it. This could be a bug.
9697 """
9798
9899impl = sqlalchemy .types .DateTime
@@ -103,10 +104,10 @@ def process_result_value(self, value: Union[None, datetime], dialect):
103104if value is None :
104105return None
105106return value .replace (tzinfo = None )
106-
107+
108+
107109class DatabricksTimeType (sqlalchemy .types .TypeDecorator ):
108- """Databricks has no native TIME type. So we store it as a string.
109- """
110+ """Databricks has no native TIME type. So we store it as a string."""
110111
111112impl = sqlalchemy .types .Time
112113cache_ok = True
@@ -115,12 +116,11 @@ class DatabricksTimeType(sqlalchemy.types.TypeDecorator):
115116TIME_NO_MICROSECONDS_FMT = "%H:%M:%S"
116117
117118def process_bind_param (self ,value :Union [datetime .time ,None ],dialect )-> str :
118- """Values sent to the database are converted to %:H:%M:%S strings.
119- """
119+ """Values sent to the database are converted to %:H:%M:%S strings."""
120120if value is None :
121121return None
122122return value .strftime (self .TIME_WITH_MICROSECONDS_FMT )
123-
123+
124124def process_literal_param (self ,value ,dialect )-> datetime .time :
125125"""It's not clear to me why this is necessary. Without it, SQLAlchemy's Timetest:test_literal fails
126126 because the string literal renderer receives a str() object and calls .isoformat() on it.
@@ -136,21 +136,22 @@ def process_literal_param(self, value, dialect) -> datetime.time:
136136 """
137137return value
138138
139-
140- def process_result_value ( self ,value :Union [None ,str ],dialect ) -> Union [ datetime . time , None ]:
141- """Values received from the database are parsed into datetime.time() objects
142- """
139+ def process_result_value (
140+ self ,value :Union [None ,str ],dialect
141+ ) -> Union [ datetime .time , None ]:
142+ """Values received from the database are parsed into datetime.time() objects"""
143143if value is None :
144144return None
145-
145+
146146try :
147147_parsed = datetime .strptime (value ,self .TIME_WITH_MICROSECONDS_FMT )
148148except ValueError :
149149# If the string doesn't have microseconds, try parsing it without them
150150_parsed = datetime .strptime (value ,self .TIME_NO_MICROSECONDS_FMT )
151-
151+
152152return _parsed .time ()
153-
153+
154+
154155class DatabricksStringType (sqlalchemy .types .TypeDecorator ):
155156"""We have to implement our own String() type because SQLAlchemy's default implementation
156157 wants to escape single-quotes with a doubled single-quote. Databricks uses a backslash for
@@ -160,18 +161,18 @@ class DatabricksStringType(sqlalchemy.types.TypeDecorator):
160161impl = sqlalchemy .types .String
161162cache_ok = True
162163pe = ParamEscaper ()
163-
164+
164165def process_literal_param (self ,value ,dialect )-> str :
165166"""SQLAlchemy's default string escaping for backslashes doesn't work for databricks. The logic here
166167 implements the same logic as our legacy inline escaping logic.
167168 """
168169
169170return self .pe .escape_string (value )
170-
171+
171172def literal_processor (self ,dialect ):
172173"""We manually override this method to prevent further processing of the string literal beyond
173174 what happens in the process_literal_param() method.
174-
175+
175176 The SQLAlchemy docs _specifically_ say to not override this method.
176177
177178 It appears that any processing that happens from TypeEngine.process_literal_param happens _before_
@@ -180,7 +181,7 @@ def literal_processor(self, dialect):
180181 error in Databricks. And it's not necessary because ParamEscaper() already implements all the escaping we need.
181182
182183 We should consider opening an issue on the SQLAlchemy project to see if I'm using it wrong.
183-
184+
184185 See type_api.py::TypeEngine.literal_processor:
185186
186187 ```python
@@ -192,9 +193,10 @@ def process(value: Any) -> str:
192193
193194 That call to fixed_impl_processor wraps the result of fixed_process_literal_param (which is the
194195 process_literal_param defined in our Databricks dialect)
195-
196+
196197 https://docs.sqlalchemy.org/en/20/core/custom_types.html#sqlalchemy.types.TypeDecorator.literal_processor
197198 """
199+
198200def process (value ):
199201"""This is a copy of the default String.literal_processor() method but stripping away
200202 its double-escaping behaviour for single-quotes.
@@ -208,4 +210,4 @@ def process(value):
208210
209211return "%s" % _step2
210212
211- return process
213+ return process