Oggetti datetime con python e SQLite

Ogni dato memorizzato nel database ha un tipo associato detto “storage type” e naturalmente le operazioni eseguibili sul dato dipenderanno dal suo tipo. La maggioranza dei database SQL utilizzano lo static typing: a ciascuna colonna di una tabella è associato un tipo e solo i dati di quel particolare tipo potranno essere inseriti in quella colonna. In SQLite viene invece utilizzato il manifest typing secondo cui il tipo è un attributo del dato stesso e non della colonna in cui esso andrà inserito: il tipo di dato verrà registrato da SQLite come parte del dato stesso ovvero come un suo attributo.

Conseguenza di questa filosofia è che l’utente può inserire un qualsiasi dato in una qualsiasi colonna a prescindere dal tipo SQL dichiarato per quella colonna (unica eccezione le colonne dichiarate chiavi primarie e di tipo integer che accetteranno solo integer).
La VM di SQLite supporta solo cinque storage types: integer con segno, numeri floating point con segno, stringhe, BLOB e NULL. Ogni dato registrato in un file o in memoria deve essere di uno di questi cinque tipi.
Tra i vari tipi possono avvenire delle implicite conversioni ove necessario.

Programmando in Python questa caratteristica di SQLite non è di solito un problema vista la natura dinamica del linguaggio: l’unico caso in cui si potrebbe trovare in difficoltà è quello delle date (oggetti datetime.date) e dei cosiddetti timestamp (oggetti datetime.datetime) che come noto sono gestiti dal modulo Python datetime.

Vediamo un esempio:

>>>import sqlite3
>>>import datetime
#creo una connessione in memoria
>>>con = sqlite3.connect(":memory:")
>>>cur=con.cursor()
#creo una tabella con una colonna data e una colonna timestamp come tipi SQL
>>>cur.execute("create table test(d date, ts timestamp)")
<sqlite3.Cursor object at 0x00D38350>
>>>oggi = datetime.date.today()
>>>adesso = datetime.datetime.now()
#inserisco nella tabella i 2 oggetti datetime
>>>cur.execute("insert into test(d, ts) values (?, ?)", (oggi, adesso))
<sqlite3.Cursor object at 0x00D38350>
#seleziono la riga appena inserita
>>>cur.execute("select d, ts from test")
<sqlite3.Cursor object at 0x00D38350>
row=cur.fetchone()
#stampo i 2 valori inseriti
>>>print "data odierna=",row[0],"timestamp=",row[1]
data odierna= 2010-04-13 timestamp= 2010-04-13 16:17:17.718000

Apparentemente potrebbe apparire corretto ma in realtà i dati restituiti non sono di tipo datetime ma sono stringhe di testo. La conseguenza è che se adesso cercassi di applicare una delle varie funzioni del modulo python datetime (che si aspettano un oggetto datetime) ad uno di questi dati, otterrei un errore:

#cerco di convertire la data in una stringa espressa in formato italiano
>>> datetime.datetime.strftime(row[0],"%d-%m-%Y")
Traceback (most recent call last):
  File "<pyshell#14>", line 1, in <module>
    datetime.datetime.strftime(row[0],"%d-%m-%Y")
TypeError: descriptor 'strftime' requires a 'datetime.date' object but received a 'unicode'

Come si vede il modulo datetime ci dice che ha ricevuto una stringa e non un oggetto datetime.

La soluzione è molto semplice in realtà: il modulo python sqlite prevede dei convertitori di default per i tipi date e datetime che provvedono ad una conversione al volo in modo del tutto trasparente all’utente.
Questi convertitori sono registrati rispettivamente con il nome “date” per gli oggetti datetime.date e “timestamp” per gli oggetti datetime.datetime ma, affinchè funzionino, necessitano di una piccola modifica alla stringa di connessione vista sopra. Si deve forzare infatti SQLite, durante ad esempio un SELECT, a leggere oltre che il dato stesso anche il tipo con cui è stato dichiarato (che come abbiamo detto è un suo attributo): in questo modo i convertitori potranno svolgere il loro lavoro.

Vediamo:

>>>import sqlite3
>>>import datetime
>>>con = sqlite3.connect(":memory:", detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES)
>>>cur = con.cursor()
>>>cur.execute("create table test(d date, ts timestamp)")
>>> oggi = datetime.date.today()
>>> adesso = datetime.datetime.now()
>>> cur.execute("insert into test(d, ts) values (?, ?)", (oggi, adesso))
<sqlite3.Cursor object at 0x00C50710>
>>> cur.execute("select d, ts from test")
>>> row = cur.fetchone()
>>> print today, "=>", row[0], type(row[0])
2010-04-13 => 2010-04-13 <type 'datetime.date'>
>>> print now, "=>", row[1], type(row[1])
2010-04-13 15:27:05.031000 => 2010-04-13 15:27:05.031000 <type 'datetime.datetime'>
>>> row[1]
datetime.datetime(2010, 4, 13, 13, 29, 10)
>>> datetime.datetime.strftime(row[1],"%d-%m-%Y")
'13-04-2010'

Come si vede adesso abbiamo gli oggetti datetime che ci aspettavamo e la loro gestione in lettura e scrittura sarà completamente trasparente come sono abituati, ad esempio, gli utilizzatori di MySQL.

About the Author

Carlo Bazzo
Carlo Bazzo è fondatore di Epysoft, una start up tecnologica con sede a Treviso e CTO di Hdemo Network Business Solutions. Puoi contattare Carlo Bazzo su Linkedin.

Comments are closed.