pip install rabaDB #for the latest stable version
That's it, no need for anything else.
rabaDB is a lightweight, borderline NoSQL, uncomplicated, ORM on top of sqlite3.
Basically it means:
- Uncomplicated syntax
- No SQL knowledge needed: supports queries by example
- You can still use SQL if you want
- Lazy and Optimized: objects are only fully loaded if you need them to be
- No dependencies: super easy installation
- Somewhat Schemaless: you can modify field definitions whenever you want and it will automatically adapt to the new schema
rabaDB is the backend behind pyGeno, a python package for genomics and proteomics where it is typically used to store whole genomes annonations, along with huge sets of polymorphisms: millions of entries. and it's performing really well.
#The initialisation
from rabaDB.rabaSetup import *
RabaConfiguration('test_namespace', './dbTest_BasicExample.db')
import rabaDB.Raba as R
import rabaDB.fields as rf
class Human(R.Raba) :
_raba_namespace = 'test_namespace'
#Everything that is not a raba object is primitive
name = rf.Primitive()
age = rf.Primitive()
city = rf.Primitive()
#Only Cars can fit into this relation
cars = rf.Relation('Car')
#best friend can only be a human
bestFriend = rf.RabaObject('Human')
def __init__(self) :
pass
class Car(R.Raba) :
_raba_namespace = 'test_namespace'
number = rf.Primitive()
def __init__(self) :
pass
if __name__ == '__main__':
georges = Human()
georges.name = 'Georges'
for i in range(10) :
car = Car()
car.number = i
georges.cars.append(car)
#saving georges also saves all his cars to the db
georges.save()
You can think of rabaDB's namespace as independent databases. Each namespace has a name and a file where all the data will be saved. Here's how you initialise rabaDb:
#The initialisation
from rabaDB.rabaSetup import *
RabaConfiguration('test', './dbTest_BasicExample.db')
Once you've done that, the configuration is a singleton attached to the namespace. If the filename does not exist it will be created for you.
You can access it everywhere in you script by simply doing
myConf = RabaConfiguration('test')
There's also a connection object associated to the namespace
myConn = RabaConnnection('test')
To know what you can do with that, have a look at the debugging part.
RabaDB has only four variable types:
- *Primitive:
- Numbers
- Strings
- Serialized objects
- *RabaObject
- An object whose class derives from Raba.Raba
- *Relation:
- A list of only a certain type of RabaObject
- *RList:
- A list of anything
rabaDB allows you to change the schemas of your classes on the fly. That means that you can add and remove fields from your class definitions at any moment during the developement and rabaDB will take care of composing with the SQL backend. However keep in mind that whenever you remove a field, all the information relative to that field are lost forever.
You can even erase whole class definitions from you code, and rabaDB will automatically update the database.
No problem:
Human.ensureIndex('name')
#even on several fields
Human.ensureIndex(('name', 'age', 'city'))
#To drop an index
Human.dropIndex('name')
You can do things like:
georges = Human(name = 'Georges')
And rabaDB will try to find a match for you.
Querying by example is done by creating filters, all the conditions inside the same filter are merged by And and filters between them are merged by Or.
f = RabaQuery(SomeClass) f.addFilter(A1, A2, A3) f.addFilter(B1, B2) Means: (A1 AND A2 AND A3) OR (B1 AND B2)
There are several syntaxes that you can use.
from rabaDB.filters import *
f = RabaQuery(Human)
#Or
f = RabaQuery('Human')
f.addFilter(name = "Fela", age = "22")
#Or the fancier
f.addFilter({"age >=" : 22, "name like": "fel%"})
#Or
f.addFilter(['age = "22"', 'name = Fela'])
And then here's how you get your results:
for r in f.run() :
print r
You can add an SQL statement at the end
for r in f.run(sqlTail = "ORDER By age") :
print r
You can also write your own SQL WHERE conditions
from rabaDB.filters import *
f = RabaQuery(Human)
for r in f.runWhere("age = ?, name = ?" , (22, "fela")) :
print r
By default all querying functions return raba Object, but you can always ask for the raw SQL tuple:
f.run(raw = True)
f.runWere(("age = ?, name = ?" , (22, "fela"), raw = True)
The supported operators are: 'LIKE', '=', '<', '>', '=', '>=', '<=', '<>', '!=', 'IS'
There are also iterative versions. They have the same interface but they are faster and less memory consuming
- f.iterRun
- f.iterRunWhere
Here's how you do counts
from rabaDB.filters import *
f = RabaQuery(Human)
f.addFilter(age = "22")
print f.count()
rabaDB keeps an internal registry to ensure a strong object consistency. If you do:
georges = Human(name = 'Georges')
sameGeorges = Human(name = 'Georges')
You get two times the same object, every modification you do to georges is also applied to sameGeorges, because georges is sameGeorges. This rules applies to any form of queries.
However keep in mind that the registery will also prevent the garbage collector from erasing raba objects, and that can lead to "memory leak"-like situations. The way that is by telling raba that you no longer need some objects to be registered:
form rabaDB.Raba import *
_unregisterRabaObjectInstance(georges)
RabaDB has debugging tools that you can access through the namespace's connection.
import rabaDB.rabaSetup conn = rabaDB.rabaSetup.RabaConnection("mynamespace") #printing the SQL queries conn.enableQueryPrint(True) #the part you want to debug conn.enableQueryPrint(False) #debug: print each SQL querie and asks the permition to continue conn.enableDebug(True) #the part you want to debug conn.enableDebug(False) #record all the queries performed conn.enableStats(True, logQueries = True) #the part you want to debug conn.enableStats(False) #a pretty print conn.printStats() #when you're done conn.eraseStats()
You can group several queries into one single transaction
conn.beginTransaction() #a lot of object saving conn.endTransaction()
rabaDB fully supports inheritence. Children classes automatically inherit the fields of their parents. rabaDB also supports abstract classes, that is to say classes that are never meant to be instanciated and that only serve as templates for other classes. Abstract classes have no effect on the database
Here's how you declare an abstract class:
class pyGenoRabaObject(Raba) :
_raba_namespace = "pyGeno"
_raba_abstract = True # abstractness
name = rf.Primitive()