Source code for alkali.database
# -*- coding: utf-8 -*-
"""
::
from alkali import Database, JSONStorage, Model, fields
class MyModel( Model ):
id = fields.IntField(primary_key=True)
title = fields.StringField()
db = Database(models=[MyModel], storage=JSONStorage, root_dir='/tmp', save_on_exit=True)
m = MyModel(id=1,title='old title')
m.save() # adds model instance to MyModel.objects
db.store() # creates /tmp/MyModel.json
db.load() # read /tmp/MyModel.json
m = MyModel.objects.get(pk=1) # do a search on primary key
m.title = "my new title" # change the title
# don't need to call m.save() since the database "knows" about m
# db.store() is automatically called as db goes out of scope, save_on_exit==True
"""
from collections import OrderedDict
import types
import inspect
import os
from .storage import JSONStorage
import logging
logger = logging.getLogger(__name__)
[docs]class Database(object):
"""
This is the parent object that owns and coordinates all the different
classes and objects defined in this module.
:ivar _storage_type:
default storage type for all models, defaults to :class:`alkali.storage.JSONStorage`
:ivar _root_dir:
directory where all models are stored, defaults to current working directory
:ivar _save_on_exit:
automatically save all models before Database object is destroyed. call
:func:`Database.store` explicitly if ``_save_on_exit`` is false.
"""
def __init__( self, models=[], **kw ):
"""
:param models: a list of :class:`alkali.model.Model` classes
:param kw:
* root_dir: default save path directory
* save_on_exit: save all models to disk on exit
* storage: default storage class for all models
"""
logger.debug( "Database: creating database" )
self._models = OrderedDict()
self._storage = OrderedDict()
self._storage_type = kw.pop('storage', JSONStorage)
self._save_on_exit = kw.pop('save_on_exit', False)
self._root_dir = kw.pop('root_dir', '.')
self._root_dir = os.path.expanduser(self._root_dir)
self._root_dir = os.path.abspath(self._root_dir)
assert len(kw) == 0, "unknown kwargs: {}".format(kw.keys())
for model in models:
assert inspect.isclass(model)
logger.debug( "Database: adding model to database: %s", model.__name__ )
self._models[model.__name__.lower()] = model
self.set_storage(model)
def __del__(self):
if self._save_on_exit:
self.store()
@property
def models(self):
"""
**property**: return ``list`` of models in the database
"""
return self._models.values()
[docs] def get_model(self, model_name):
"""
:param model_name: the name of the model, **note**: all model names are
converted to lowercase
:rtype: :class:`alkali.model.Model`
"""
try:
return self._models[model_name.lower()]
except KeyError:
pass
return None
[docs] def get_filename(self, model, storage=None):
"""
get the filename for the specified model. allow models to
specify their own filename or generate one based on storage
class. prepend Database._root_dir.
eg. <_root_dir>/<model name>.<storage.extension>
:param model: the model name or model class
:param storage: the storage class, uses the database default if None
:return: returns a filename path
:rtype: str
"""
if isinstance(model, types.StringTypes):
model = self.get_model(model)
filename = model.Meta.filename
if not filename:
storage = storage or model.Meta.storage or self._storage_type
filename = "{}.{}".format(model.__name__, storage.extension)
# if filename is absolute, then self._root_dir gets filtered out
return os.path.join( self._root_dir, filename )
[docs] def set_storage(self, model, storage=None):
"""
set the storage instance for the specified model
precedence is:
#. passed in storage class
#. model defined storage class
#. default storage class of database (JSONStorage)
:param model: the model name or model class
:param IStorage storage: override model storage class
:rtype: :class:`alkali.storage.Storage` instance or None
"""
if isinstance(model, types.StringTypes):
model = self.get_model(model)
storage = storage or model.Meta.storage or self._storage_type
assert inspect.isclass(storage)
filename = self.get_filename(model, storage)
self._storage[model] = storage(filename)
return self._storage[model]
[docs] def get_storage(self, model):
"""
get the storage instance for the specified model
:param model: the model name or model class
:rtype: :class:`alkali.storage.Storage` instance or None
"""
if isinstance(model, types.StringTypes):
model = self.get_model(model)
try:
return self._storage[model]
except KeyError:
pass
return None
[docs] def store(self, force=False):
"""
persistantly store all model data
:param bool force: force store even if :class:`alkali.manager.Manager`
thinks data is clean
"""
# you can't save more than one model with a single storage
# instance because the file will get over written
for model in self.models:
logger.debug( "Database: storing model: %s", model.__name__ )
storage = self.get_storage(model)
model.objects.store(storage, force=force)
return True
[docs] def load(self):
"""
load all model data from disk
:param IStorage storage: override model storage class
"""
logger.debug( "Database: loading models" )
for model in self.models:
logger.debug( "Database: loading model: %s", model.__name__ )
storage = self.get_storage(model)
model.objects.load(storage)