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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | >>>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:
1 2 3 4 5 6 | #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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | >>>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.
[...] This post was mentioned on Twitter by Python Italia. Python Italia said: Oggetti datetime con python e SQLite http://bit.ly/c7Pnlk [...]