`TIMESTAMP`, `TIMESTAMP_NTZ`, `TINYINT` and the other `TypeDecorator`s don't declare `python_type`
Personne n'a encore pris cette issue.
Évaluation
- Difficulté
- 2/5
- Temps estimé
- 1-3 heures
- Accessibilité débutants
- 78/100
Piste de recherche
Commencez dans src/databricks/sqlalchemy/_types.py, où sont définies les cinq sous-classes de TypeDecorator, et lisez leurs implémentations ainsi que les tests de types existants. Vérifiez que le python_type de chaque type corresponde aux valeurs qu’il renvoie : datetime pour TIMESTAMP et TIMESTAMP_NTZ, int pour TINYINT, time pour DatabricksTimeType et str pour DatabricksStringType. Le travail est terminé lorsque ces types indiquent les classes Python attendues avec les versions de SQLAlchemy prises en charge.
Rédigé par le modèle d'indexation à partir du texte de l'issue.
Description
Description of the bug
Our use case
We generate SQLAlchemy ORM models from Databricks tables with sqlacodegen. It reflects the tables through this dialect and annotates each column with the column type's python_type, for example Mapped[str | None] for a STRING column.
For TIMESTAMP, TIMESTAMP_NTZ and TINYINT columns we get Mapped[object | None] instead of Mapped[datetime | None] and Mapped[int | None]. These are the types reflection returns for timestamp, timestamp_ntz and tinyint. Type checkers then accept any value for these attributes, and any code reading them sees object.
The same happens to any other tool that reads python_type, and to the two types the dialect uses in place of Time and String through its colspecs (DatabricksTimeType and DatabricksStringType).
The bug
These five types are sqlalchemy.types.TypeDecorator subclasses that don't declare python_type:
| Type | impl |
Values returned | python_type |
|---|---|---|---|
TIMESTAMP |
DateTime |
datetime |
object |
TIMESTAMP_NTZ |
DateTime |
datetime |
object |
TINYINT |
SmallInteger |
int |
object |
DatabricksTimeType |
Time |
time |
object |
DatabricksStringType |
String |
str |
object |
TypeDecorator doesn't take python_type from its impl, so these types get the TypeEngine default: object in SQLAlchemy 2.1, and a NotImplementedError in 2.0.
The SQLAlchemy maintainers consider this the intended behaviour. A TypeDecorator can return different values from its impl through process_result_value, so only the decorator knows its Python type, and it's expected to declare python_type itself: link to the SQLAlchemy discussion.
Versions
- databricks-sqlalchemy: 2.0.10 (also on
main) - databricks-sql-connector: 4.6.0
- SQLAlchemy: 2.1.4, and 2.0.54
- Python: 3.12
- OS: Linux
To Reproduce
No connection is needed:
import datetime
from importlib.metadata import version
import sqlalchemy as sa
from databricks.sqlalchemy import TIMESTAMP, TIMESTAMP_NTZ, TINYINT, DatabricksDialect
print("SQLAlchemy", sa.__version__)
print("databricks-sqlalchemy", version("databricks-sqlalchemy"))
dialect = DatabricksDialect()
expected = {
# The types reflection returns for timestamp, timestamp_ntz and tinyint.
"TIMESTAMP": (TIMESTAMP(), datetime.datetime),
"TIMESTAMP_NTZ": (TIMESTAMP_NTZ(), datetime.datetime),
"TINYINT": (TINYINT(), int),
# The types the dialect uses in place of Time and String.
"Time": (sa.Time().dialect_impl(dialect), datetime.time),
"String": (sa.String().dialect_impl(dialect), str),
}
for name, (type_, python_type) in expected.items():
print(f"{name:14} {type(type_).__name__:20} python_type: {type_.python_type}")
for name, (type_, python_type) in expected.items():
assert type_.python_type is python_type, name
Output
With SQLAlchemy 2.1.4:
SQLAlchemy 2.1.4
databricks-sqlalchemy 2.0.10
TIMESTAMP TIMESTAMP python_type: <class 'object'>
TIMESTAMP_NTZ TIMESTAMP_NTZ python_type: <class 'object'>
TINYINT TINYINT python_type: <class 'object'>
Time DatabricksTimeType python_type: <class 'object'>
String DatabricksStringType python_type: <class 'object'>
Traceback (most recent call last):
...
AssertionError: TIMESTAMP
With SQLAlchemy 2.0.54, the first python_type raises NotImplementedError.
Expected behaviour
Each type's python_type is the type of the values it returns: datetime for TIMESTAMP and TIMESTAMP_NTZ, int for TINYINT, time for DatabricksTimeType and str for DatabricksStringType.
Possible fix
Declare python_type on each type in src/databricks/sqlalchemy/_types.py, which already imports datetime and time from datetime:
class TIMESTAMP_NTZ(sqlalchemy.types.TypeDecorator):
...
@property
def python_type(self) -> type:
return datetime
The same goes for TIMESTAMP (datetime), TINYINT (int), DatabricksTimeType (time) and DatabricksStringType (str).
Additional context
-
DatabricksArray,DatabricksMapandDatabricksVariantalso reportobject, becauseUserDefinedTypehas the same default. That's right for all three today.VARIANTcan hold any value, andARRAYandMAPhave noresult_processor, so their values are whatever the connector returns, which depends on its settings:databricks-sql-connector 4.6.0 ARRAYMAPdefault numpy.ndarraylistof(key, value)tuplesdisable_pandas=Truelistlistof(key, value)tuples_use_arrow_native_complex_types=Falsestr(JSON)str(JSON)A
result_processorconverting the values tolistanddictwould let these two types declarepython_typetoo, but that changes the values users get, so it's not part of this request. -
Our workaround: when a
TypeDecoratorreportsobject, we use itsimpl'spython_type. That's correct for the five types above, but as the SQLAlchemy maintainers point out, it's only a guess in general.
Thank you!
- Langage dominant
- Python
- Étoiles
- 24
- Forks
- 20
- Métriques de merge des PR
- Aucune PR mergée en 30 j
Préparer son environnement
- Aucun Dockerfile ni fichier Docker Compose
- Aucun modèle de pull request
- Lire le guide de contribution
Par où commencer
- Lisez l'issue en entier, puis le guide de contribution du projet.
- Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
- Forkez le dépôt et travaillez sur une branche.
- Ouvrez une pull request qui référence le numéro de l'issue.
Autres issues de databricks/databricks-sqlalchemy
-
get_columns() missing @reflection.cache causes a warehouse round-trip on every reflection callPeut-être pris @TangoEnSkai l’a pris il y a 52 jours. Ouverte
Difficulté 1/5 Moins d'une heure Accessibilité débutants 92/100
-
get_foreign_keys() missing @reflection.cache causes excessive DESCRIBE TABLE EXTENDED queriesPeut-être pris @TangoEnSkai l’a pris il y a 52 jours. Ouverte
Difficulté 1/5 Moins d'une heure Accessibilité débutants 90/100
-
TypeError when using Enum columns with the Databricks dialect (all 2.0.x releases)Peut-être pris @TangoEnSkai l’a pris il y a 52 jours. Ouverte
Difficulté 2/5 1-3 heures Accessibilité débutants 70/100
-
Difficulté 3/5 1-2 jours Accessibilité débutants 64/100
databricks/databricks-sqlalchemy#77 · 3 commentaires ·
-
Unable to insert Python lists into ARRAY<STRING> columns using pandas to_sqlPeut-être pris @aminghadersohi l’a pris il y a 18 jours. Ouverte
Difficulté 4/5 3-5 jours Accessibilité débutants 48/100
databricks/databricks-sqlalchemy#73 · 1 commentaire ·
Toutes les issues de databricks/databricks-sqlalchemy
Issues similaires
-
vis.image() reports "Unsupported number of image channels: 224" for an HxWxC imagePeut-être pris @piyush182004 l’a pris aujourd’hui. Ouverte
Difficulté 2/5 1-3 heures Accessibilité débutants 62/100
fossasia/visdom#1997 · 3 commentaires ·
Les mainteneurs répondent en général sous 6 jours
-
Byse new domainOuverte
Difficulté 2/5 1-3 heures Accessibilité débutants 62/100
Gujal00/ResolveURL#1525 ·
Les mainteneurs répondent en général sous 1 jour
-
Serialisation fails with pydantic 2.14: wrap serialisers pass SerializationInfo as index_keyPeut-être pris @hugohills-regnosys l’a pris aujourd’hui. Ouverte
Difficulté 2/5 1-3 heures Accessibilité débutants 82/100
finos/rune-python-runtime#45 ·
-
[Bug]: LangGraph Store sync put bypasses namespace validationPeut-être pris @ktz03 l’a pris aujourd’hui. Ouverte
Difficulté 2/5 1-3 heures Accessibilité débutants 72/100
volcengine/OpenViking#6050 ·
Les mainteneurs répondent en général sous 1 jour
-
bug
Difficulté 2/5 1-3 heures Accessibilité débutants 72/100
hummingbot/condor#283 ·
Les mainteneurs répondent en général sous 1 jour