Elara DB is an easy to use, lightweight NoSQL database written for python that can also be used as a fast in-memory cache for JSON-serializable data. Includes various methods to manipulate data structures in-memory, secure database files and export data.
$ pip install elara
- Offers two modes of execution - normal and secure - exe_secure() generates a key file and encrypts the key-value storage for additional security.
- Manipulate data structures in-memory.
- Can be used as a fast in-memory cache.
- Choose between manual commits after performing operations in-memory or automatically commit every change into the storage.
- Includes methods to export certain keys from the database or the entire storage.
- Based on python's in-built json module for data serialization.
From pypi :
$ pip install elara
OR,
Clone the repository and install the dependencies :
$ pip install -r requirements.py
$ python -m unittest -v # Run tests
Copyright (c) 2021, Saurabh Pujari All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree.
You can choose between normally transacting data from the file or you can transact data from an encrypted file.
import elara
# exe_secure() encrypts the db file
db = elara.exe_secure("new.db", True, "newdb.key")
db.set("name", "Elara")
print(db.get("name"))
# Elara
exe_secure(db_file_path, commit=False, key_file_path)
- Loads the contents of the encrypted database (using the key file) into the program memory or generates a new key file and/or database file if they don't exist in the given path and it encrypts/decrypts the database file. Data is encoded intoutf-8
and then encrypted usingFernet encryption
Using exe_secure()
without a key file or without the correct key
file corresponding to the database will result in errors. Key files and
DB files can be included inside the .gitignore
to ensure they're not
pushed into an upstream repository.
commit
- this argument defaults toFalse
ie. you will have to manually call thecommit()
method to write the in-memory changes into the database. If set toTrue
, changes will be written into the file after every operation.
import elara
db = elara.exe("new.db", "newdb.key") # commit=False
db.set("num", 20)
print(db.get("num"))
# 20
db.commit() # Writes in-memory changes into the file
exe(db_file_path, commit=False)
- Loads the contents of the database into the program memory or generates a new database file if it doesn't exist in the given path. The database file is NOT encrypted and is present in a human-readable json format.
import elara as elara
db = elara.exe("new.db", True)
db.set("name", "Elara")
print(db.get("name"))
# Elara
All the following operations are methods that can be applied to the
instance returned from exe()
or exe_secure()
. These operations
manipulate/analyse data in-memory after the Data is loaded from the
file. Set the commit
argument to True
else manually use the
commit()
method to sync in-memory data with the database file.
get(key)
- returns the corresponding value from the db orNone
set(key, value)
- returnsTrue
or an Exception. Thekey
has to be a String.rem(key)
- deletes the key-value pair if it exists.clear()
- clears the database data currently stored in-memory.exists(key)
- returnsTrue
if the key exists.commit()
- write in-memory changes into the database file.getset(key, value)
- Sets the new value and returns the old value for that key or returnsFalse
.getkeys()
- returns the list of keys in the database with. The list is ordered with themost recently accessed
keys starting from index 0.numkeys()
- returns the number of keys in the database.retkey()
- returns the Key used to encrypt/decrypt the db file; returnsNone
if the file is unprotected.retmem()
- returns all the in-memory db contents.retdb()
- returns all the db file contents.
import elara
db = elara.exe("new.db")
db.set("num1", 20)
# ("num1", 20) is written into the file db
db.commit()
db.set("num2", 30)
print(db.retmem())
# {'num1': 20, 'num2': 30}
print(db.retdb())
# {'num1': 20}
Note - retmem()
and retdb()
will return the same value if
commit
is set to True
or if the commit()
method is used
before calling retdb()
Elara can also be used as a fast in-memory cache. Start/open a new
instance and ensure the commit
argument is False
or left empty
(commit
defaults to False
), to prevent writes into the database
file.
cull(percentage)
-percentage
(0 <= percentage <= 100) defines the percentage of Key-Value pairs to be deleted, with theLeast recently accessed
keys being deleted first. Elara maintains a simple LRU list to track key access.
import elara
cache = elara.exe("new.db")
cache.set("num1", 10)
cache.set("num2", 20)
cache.set("num3", 30)
cache.set("num4", 40)
if cache.exists("num1"):
print(cache.get("num1"))
# 10
print(cache.retmem())
# {'num1': 10, 'num2': 20, 'num3': 30, 'num4': 40}
# most recently accessed keys come first
print(cache.getkeys())
# ['num1', 'num4', 'num3', 'num2']
# delete 25% of the stale keys (follows LRU)
cache.cull(25)
# most recently accessed keys come first
print(cache.getkeys())
# ['num1', 'num4', 'num3']
Elara uses the json
module to serialize data, hence it only supports basic python datatypes (int, str, dict, list etc.).
However, objects (simple and complex) can be stored and retrieved using get, set and other functions that apply to them
as long as they are in-memory
and not persisted in the file
, as that would lead to serialization errors.
import elara
cache = elara.exe("new.db") # commit = False by default
class MyObj():
def __init__(self, num):
self.num = num
obj = MyObj(19)
cache.set("obj", obj)
print(cache.get("obj").num)
# 19
To persist a simple object as a dictionary, use the __dict__
attribute.
mget(keys)
- takes a list of keys as an argument and returns a list with all the corresponding values IF they exist; returns an empty list otherwise.mset(dict)
- takes a dictionary of key-value pairs as an argument and calls theset(key, value)
method for each pair. Keys have to be a String.setnx(key, value)
- Sets the key-value if the key does not exist and returnsTrue
; returnsFalse
otherwise.msetnx(dict)
- takes a dictionary of key-value pairs as an argument and calls thesetnx(key, value)
method for each pair. Keys have to be a string.slen(key)
- returns the length of the string value if the key exists; returns-1
otherwise.append(key, data)
- Append the data (String) to an existing string value; returnsFalse
if it fails.
lnew(key)
- Initialises an empty list for the given key and returnsTrue
or an Exception; key has to be a string.lpush(key, value)
- Appends the given value to the list and returnsTrue
; returnsFalse
if the key does not exist.lpop(key)
- Pops and returns the last element of the list if it exists; returnsFalse
otherwise. Index of the element can be passed to delete a specific element usinglpop(key, pos)
.pos
defaults to-1
(last element of the list).lrem(key, value)
- remove a value from the list. ReturnsTrue
on success andFalse
otherwise.llen(key)
- returns length of the list if the key exists; returns-1
otherwise.lindex(key, index)
- takes the index as an argument and returns the value if the key and list exist; returnsFalse
otherwise.lrange(key, start, end)
- takesstart
andend
index as arguments and returns the list within the given range. Value atend
not included. Returns empty list if start/end are invalid.lextend(key, new_list)
- Extend the list withnew_list
if the key exists. ReturnsTrue
orFalse
if the key does not exist.lexists(key, value)
- returnsTrue
if the value is present in the list; returnsFalse
otherwise.lappend(key, pos, value)
- appendsvalue
to the existing data at indexpos
using the+
operator. ReturnsTrue
orFalse
.
import elara
db = elara.exe('new.db', True)
db.lnew('newlist')
db.lpush('newlist', 3)
db.lpush('newlist', 4)
db.lpush('newlist', 5)
print(db.lpop('newlist'))
# 5
print(db.lindex('newlist', 0))
# 3
new_list = [6, 7, 8, 9]
db.lextend('newlist', new_list)
print(db.get('newlist'))
# [3, 4, 6, 7, 8, 9]
hnew(key)
- Initialises an empty dictionary for the given key and returnsTrue
or an Exception; key has to be a string.hadd(key, dict_key, value)
- Assigns a value to a dictionary key and returnsTrue
; returnsFalse
if the dictionary doesn't exist.haddt(key, tuple)
- Add a new key-value tuple into the dictionary. ReturnsTrue
if the dictionary exists; returnsFalse
otherwise.hget(key, dict_key)
- Returns the value from the dictionary; returnsFalse
if the dictionary doesn't exist.hpop(key, dict_key)
- Deletes the given key-value pair from the dictionary and returns the deleted value; returnsFalse
if the dictionary doesn't exist.hkeys(key)
- returns all the Keys present in the dictionary.hvals(key)
- returns all the values present in the dictionary.hmerge(key, dict)
- updates (dict.update()) the dictionary pointed by the key with the new dictionarydict
passed as an argument.
updatekey(key_path)
- This method works for instances produced byexe_secure()
. It updates the key in the key file path and re-encyrpts the database with the new key. If the file doesn't exist, the method generates a new file with a key and uses that to encrypt the database file.
import elara
# exe_secure() encrypts the db file
db = elara.exe_secure("new.db", True, "newdb.key")
db.set("name", "Elara")
print(db.get("name"))
# Elara
db.updatekey('newkeypath.key')
# Regular program flow doesn't get affected by key update
print(db.get("name"))
# Elara
However, the next time you run the program, you have to pass the new
updated key (newkeypath.key
in this case) to avoid errors.
securedb(key_path)
- Callsupdatekey(key_path)
for instances which are already protected with a key. For an unprotected instance ofexe()
, it generates a new key in the given key_path and encrypts the database file. This db file can henceforth only be used with theexe_secure()
function.
exportdb(export_path, sort=True)
- Copies the entire content of the database file into the specified export file path usingjson.dump()
. To prevent sorting of Keys, useexportdb(export_path, False)
exportmem(export_path, sort=True)
- Copies the current database contents stored in-memory into the specified export file path usingjson.dump()
. To prevent sorting of Keys, useexportmem(export_path, False)
.exportkeys(export_path, keys = [], sort=True)
- Takes a list of keys as an argument and exports those specific keys from the in-memory data to the given export file path.
import elara
db = elara.exe('new.db', False)
db.set("one", 100)
db.set("two", 200)
db.commit()
db.set("three", 300)
db.exportdb('exportdb.txt')
db.exportmem('exportmem.txt')
db.exportkeys('exportkeys.txt', keys = ['one', 'three'])
'''
# exportdb.txt
{
"one": 100,
"two": 200
}
# exportmem.txt
{
"one": 100,
"two": 200,
"three": 300
}
# exportkeys.txt
{
"one": 100,
"three": 300
}
'''
Run this command inside the base directory to execute all tests inside
the test
folder:
$ python -m unittest -v
- Latest -
v0.3.0
(utf-8
encoding) :
v0.2.1
and earlier used a mix of ascii
and base64
encoding. v0.3.0
uses utf-8
instead. To safeguard data, its better to export all existing data from any encrypted file before upgrading.
You can then use the securedb()
method to re-encrypt it.
- Previous -
v0.2.1
Donwload the latest release from here.