#!/usr/bin/env python # -*- coding: utf-8 -*- # # Copyright 2011-2013 Codernity (http://codernity.com) # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import os import io from inspect import getsource # for custom indexes from CodernityDB.storage import Storage, IU_Storage from CodernityDB.hash_index import (IU_UniqueHashIndex, IU_HashIndex, HashIndex, UniqueHashIndex) # normal imports from CodernityDB.index import (ElemNotFound, DocIdNotFound, IndexException, Index, TryReindexException, ReindexException, IndexNotFoundException, IndexConflict) from CodernityDB.misc import NONE from CodernityDB.env import cdb_environment from random import randrange import warnings def header_for_indexes(index_name, index_class, db_custom="", ind_custom="", classes_code=""): return """# %s # %s # inserted automatically import os import marshal import struct import shutil from hashlib import md5 # custom db code start # db_custom %s # custom index code start # ind_custom %s # source of classes in index.classes_code # classes_code %s # index code start """ % (index_name, index_class, db_custom, ind_custom, classes_code) class DatabaseException(Exception): pass class PreconditionsException(DatabaseException): pass class RecordDeleted(DatabaseException): pass class RecordNotFound(DatabaseException): pass class RevConflict(DatabaseException): pass class DatabaseConflict(DatabaseException): pass class DatabasePathException(DatabaseException): pass class DatabaseIsNotOpened(PreconditionsException): pass class Database(object): """ A default single thread database object. """ custom_header = "" # : use it for imports required by your database def __init__(self, path): self.path = path self.storage = None self.indexes = [] self.id_ind = None self.indexes_names = {} self.opened = False def create_new_rev(self, old_rev=None): """ Creates new revision number based on previous one. Increments it + random bytes. On overflow starts from 0 again. """ if old_rev: try: rev_num = int(old_rev[:4], 16) except: raise RevConflict() rev_num += 1 if rev_num > 65025: # starting the counter from 0 again rev_num = 0 rnd = randrange(65536) return "%04x%04x" % (rev_num, rnd) else: # new rev rnd = randrange(256 ** 2) return '0001%04x' % rnd def __not_opened(self): if not self.opened: raise DatabaseIsNotOpened("Database is not opened") def set_indexes(self, indexes=[]): """ Set indexes using ``indexes`` param :param indexes: indexes to set in db :type indexes: iterable of :py:class:`CodernityDB.index.Index` objects. """ for ind in indexes: self.add_index(ind, create=False) def _add_single_index(self, p, i, index): """ Adds single index to a database. It will use :py:meth:`inspect.getsource` to get class source. Then it will build real index file, save it in ``_indexes`` directory. """ code = getsource(index.__class__) if not code.startswith('c'): # fix for indented index codes import textwrap code = textwrap.dedent(code) index._order = i cls_code = getattr(index, 'classes_code', []) classes_code = "" for curr in cls_code: classes_code += getsource(curr) + '\n\n' with io.FileIO(os.path.join(p, "%.2d%s" % (i, index.name) + '.py'), 'w') as f: f.write(header_for_indexes(index.name, index.__class__.__name__, getattr(self, 'custom_header', ''), getattr(index, 'custom_header', ''), classes_code)) f.write(code) return True def _read_index_single(self, p, ind, ind_kwargs={}): """ It will read single index from index file (ie. generated in :py:meth:`._add_single_index`). Then it will perform ``exec`` on that code If error will occur the index file will be saved with ``_broken`` suffix :param p: path :param ind: index name (will be joined with *p*) :returns: new index object """ with io.FileIO(os.path.join(p, ind), 'r') as f: name = f.readline()[2:].strip() _class = f.readline()[2:].strip() code = f.read() try: obj = compile(code, '