TinyDB per Python - Quando JSON si crede un database

Premessa noiosa

Durante la realizzazione di un progetto per un cliente (Python + GTK, giusto per curiosità) mi sono ritrovato davanti alla situazione di dover salvare dei dati collezionati durante l'uso dell'applicativo che sarebbero poi stati inviati ad un sistema ERP esterno.
Essendo i dati comunque pochi, legati strettamente alla singola macchina e non eccessivamente interconnessi tra di loro, mi sembrava eccessivo utilizzare un database (sì, anche sqlite mi sembrava "troppo"). Dovendo però prevedere alcune situazioni di distaster più o meno complesse, anche il mantenimento delle informazioni in memoria era un via non proprio percorribile. Ho cercato così una strada che mi permettesse di salvare le informazioni in un file mantenendo però la praticità delle query e di ciò che un database sa offrire. La mia ricerca mi ha portato a scoprire TinyDB.

tinydb_logo
tinydb

La parte Superquark

TinyDB è una libreria per Python che può essere vista come la versione dei programmatori con davvero pochissime esigenze di MongoDB. Grazie all'uso di dati strutturati JSON, TinyDB permette di avere un database simulato all'interno di un comune file di testo con annesse tabelle, campi, valori e query di ricerca e gestione.

Punti di forza

  • Possibilità di gestire i dati come dizionari essendo gli stessi salvati e caricati dal formato JSON
  • 100% codice Python senza bisogno di librerie esterne (né pip né di sistema)
  • Uso di simil-query per interrogare e manipolare i dati
  • Accesso ai dati organizzati in tabelle
  • Il database è un file di testo. Lo copi, lo invii, lo apri e lo leggi.
  • Può essere esteso per coprire ogni tipo di esigenza (creare ad esempio base di dati customizzate)

Debolezze

  • Non permette di avere dati relazionati
  • Non gestisce gli indici autoincrementali
  • Le performance calano al crescere del file
  • Non gestisce le scritture concorrenti

Codice divertente

Tralascio la parte dove si parla di installazioni così possiamo passare all'analisi di un po' di codice (finalmente!!!).

Come prima cosa importiamo il minimo indispensabile per aprire il database

>>> from tinydb import TinyDB
>>> db = TinyDB('/tmp/test.tdb')

Abbiamo appena istanziato il nostro dabase generando il file necessario nel nostro sistema. Qualora il file non dovesse esistere, verrà generato con la prima chiamata utile. Recandoci nella cartella /tmp possiamo trovare test.tdb (il nome e l'estensione sono a discrezione di chi scrive il codice) che conterrà quanto segue

/tmp/test.tdb
{"_default": {}}

_default è la tabella base in cui verranno collocati tuttii record non associati ad una precisa tabella. Facciamo un esempio.

>>> db.insert({'name': 'Phil', 'age': 45})
1
>>> db.insert({'name': 'Logan', 'age': 56})
2

Il contenuto del nostro file ora sarà

/tmp/test.tdb
{"_default": {"1": {"age": 45, "name": "Phil"}, "2": {"age": 56, "name": "Logan"}}}

Proviamo ora a vedere come gestire le tabelle

>>> superhero = db.table('superhero')

Ora nel nostro file sarà presente che la chiave che indica la nuova tabella

/tmp/test.tdb
{"_default": { "1": {"age": 45, "name": "Phil"}, "2": {"age": 56, "name": "Logan"}}, "superhero": {}}

A questo punto sarà possibile usare la tabella per eseguire le operazioni

>>> superhero.insert({'name': 'Batman', 'is_cool': True})
1
>>> superhero.insert({'name': 'Superman', 'is_cool': True})
2
>>> superhero.insert({'name': 'Zuppaman', 'is_cool': False})
3

È possibile ottenere tutti i valori di una tabella (o di _default interrogandlo direttamente l'istanza del db) mediante la funzione all()

>>> superhero.all()
[{u'is_cool': True, u'name': u'Batman'}, {u'is_cool': True, u'name': u'Superman'}, {u'is_cool': False, u'name': u'Zuppaman'}]

Le ricerche possono essere effettuate in diversi modi. Guardiamone alcuni.

Uso di ricerche semplici

>>> from tinidb import Query

>>> # Ricerca semplice
>>> superhero.search(Query().name == 'Superman')
[{u'is_cool': True, u'name': u'Superman'}]
>>> superhero.search(Query()['name'] == 'Superman')
[{u'is_cool': True, u'name': u'Superman'}]

>>> # OR tra due clausole
>>> superhero.search((Query().name == 'Superman') | (Query().name == 'Zuppaman'))
[{u'is_cool': True, u'name': u'Superman'}, {u'is_cool': False, u'name': u'Zuppaman'}]

>>> # Uso delle espressioni regolari
>>> superhero.search(Query().name.matches('Bat[a-z]'))
[{u'is_cool': True, u'name': u'Batman'}]

Clausole where

>>> from tinydb import where
>>> superhero.search(where('name') == 'Superman')
[{u'is_cool': True, u'name': u'Superman'}]

L'uso delle ricerche ci permette di gestire i dati. Con esse infatti è possibile eseguire, ad esempio, l'aggiornamento delle informazioni

>>> # Upserting dei dati con clausola
>>> superhero.upsert({'name': 'Zuppaman', 'is_cool': True}, Query().name == 'Zuppaman')

Conclusioni

Ovviamente un solo post non basterebbe per snocciolare le potenzialità di una libreria. Per questo vi rimando alla guida introduttiva e a quella più avanzata che spiegano davvero bene cosa è possibile fare e come farlo.


Apruzzese Francesco avatar
Written By

Apruzzese Francesco

Sviluppatore software FLOSS. Terrone orgoglioso. Fotteseghista di religione. Chiunque tu sia, ti amo a prescindere!

Enjoyed the post?

Clap to support the author, help others find it, and make your opinion count.


Comments

To leave a comment, you need to login first 😉