Viewing file: base.py (8.85 KB) -rw-r--r-- Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
# Copyright (C) 2010, 2011 Sebastian Thiel (byronimo@gmail.com) and contributors # # This module is part of GitDB and is released under # the New BSD License: http://www.opensource.org/licenses/bsd-license.php """Contains implementations of database retrieveing objects""" from gitdb.util import ( join, LazyMixin, hex_to_bin )
from gitdb.utils.encoding import force_text from gitdb.exc import ( BadObject, AmbiguousObjectName )
from itertools import chain from functools import reduce
__all__ = ('ObjectDBR', 'ObjectDBW', 'FileDBBase', 'CompoundDB', 'CachingDB')
class ObjectDBR:
"""Defines an interface for object database lookup. Objects are identified either by their 20 byte bin sha"""
def __contains__(self, sha): return self.has_obj
#{ Query Interface def has_object(self, sha): """ Whether the object identified by the given 20 bytes binary sha is contained in the database
:return: True if the object identified by the given 20 bytes binary sha is contained in the database""" raise NotImplementedError("To be implemented in subclass")
def info(self, sha): """ :return: OInfo instance :param sha: bytes binary sha :raise BadObject:""" raise NotImplementedError("To be implemented in subclass")
def stream(self, sha): """:return: OStream instance :param sha: 20 bytes binary sha :raise BadObject:""" raise NotImplementedError("To be implemented in subclass")
def size(self): """:return: amount of objects in this database""" raise NotImplementedError()
def sha_iter(self): """Return iterator yielding 20 byte shas for all objects in this data base""" raise NotImplementedError()
#} END query interface
class ObjectDBW:
"""Defines an interface to create objects in the database"""
def __init__(self, *args, **kwargs): self._ostream = None
#{ Edit Interface def set_ostream(self, stream): """ Adjusts the stream to which all data should be sent when storing new objects
:param stream: if not None, the stream to use, if None the default stream will be used. :return: previously installed stream, or None if there was no override :raise TypeError: if the stream doesn't have the supported functionality""" cstream = self._ostream self._ostream = stream return cstream
def ostream(self): """ Return the output stream
:return: overridden output stream this instance will write to, or None if it will write to the default stream""" return self._ostream
def store(self, istream): """ Create a new object in the database :return: the input istream object with its sha set to its corresponding value
:param istream: IStream compatible instance. If its sha is already set to a value, the object will just be stored in the our database format, in which case the input stream is expected to be in object format ( header + contents ). :raise IOError: if data could not be written""" raise NotImplementedError("To be implemented in subclass")
#} END edit interface
class FileDBBase:
"""Provides basic facilities to retrieve files of interest, including caching facilities to help mapping hexsha's to objects"""
def __init__(self, root_path): """Initialize this instance to look for its files at the given root path All subsequent operations will be relative to this path :raise InvalidDBRoot: **Note:** The base will not perform any accessablity checking as the base might not yet be accessible, but become accessible before the first access.""" super().__init__() self._root_path = root_path
#{ Interface def root_path(self): """:return: path at which this db operates""" return self._root_path
def db_path(self, rela_path): """ :return: the given relative path relative to our database root, allowing to pontentially access datafiles""" return join(self._root_path, force_text(rela_path)) #} END interface
class CachingDB:
"""A database which uses caches to speed-up access"""
#{ Interface def update_cache(self, force=False): """ Call this method if the underlying data changed to trigger an update of the internal caching structures.
:param force: if True, the update must be performed. Otherwise the implementation may decide not to perform an update if it thinks nothing has changed. :return: True if an update was performed as something change indeed"""
# END interface
def _databases_recursive(database, output): """Fill output list with database from db, in order. Deals with Loose, Packed and compound databases.""" if isinstance(database, CompoundDB): dbs = database.databases() output.extend(db for db in dbs if not isinstance(db, CompoundDB)) for cdb in (db for db in dbs if isinstance(db, CompoundDB)): _databases_recursive(cdb, output) else: output.append(database) # END handle database type
class CompoundDB(ObjectDBR, LazyMixin, CachingDB):
"""A database which delegates calls to sub-databases.
Databases are stored in the lazy-loaded _dbs attribute. Define _set_cache_ to update it with your databases"""
def _set_cache_(self, attr): if attr == '_dbs': self._dbs = list() elif attr == '_db_cache': self._db_cache = dict() else: super()._set_cache_(attr)
def _db_query(self, sha): """:return: database containing the given 20 byte sha :raise BadObject:""" # most databases use binary representations, prevent converting # it every time a database is being queried try: return self._db_cache[sha] except KeyError: pass # END first level cache
for db in self._dbs: if db.has_object(sha): self._db_cache[sha] = db return db # END for each database raise BadObject(sha)
#{ ObjectDBR interface
def has_object(self, sha): try: self._db_query(sha) return True except BadObject: return False # END handle exceptions
def info(self, sha): return self._db_query(sha).info(sha)
def stream(self, sha): return self._db_query(sha).stream(sha)
def size(self): """:return: total size of all contained databases""" return reduce(lambda x, y: x + y, (db.size() for db in self._dbs), 0)
def sha_iter(self): return chain(*(db.sha_iter() for db in self._dbs))
#} END object DBR Interface
#{ Interface
def databases(self): """:return: tuple of database instances we use for lookups""" return tuple(self._dbs)
def update_cache(self, force=False): # something might have changed, clear everything self._db_cache.clear() stat = False for db in self._dbs: if isinstance(db, CachingDB): stat |= db.update_cache(force) # END if is caching db # END for each database to update return stat
def partial_to_complete_sha_hex(self, partial_hexsha): """ :return: 20 byte binary sha1 from the given less-than-40 byte hexsha (bytes or str) :param partial_hexsha: hexsha with less than 40 byte :raise AmbiguousObjectName: """ databases = list() _databases_recursive(self, databases) partial_hexsha = force_text(partial_hexsha) len_partial_hexsha = len(partial_hexsha) if len_partial_hexsha % 2 != 0: partial_binsha = hex_to_bin(partial_hexsha + "0") else: partial_binsha = hex_to_bin(partial_hexsha) # END assure successful binary conversion
candidate = None for db in databases: full_bin_sha = None try: if hasattr(db, 'partial_to_complete_sha_hex'): full_bin_sha = db.partial_to_complete_sha_hex(partial_hexsha) else: full_bin_sha = db.partial_to_complete_sha(partial_binsha, len_partial_hexsha) # END handle database type except BadObject: continue # END ignore bad objects if full_bin_sha: if candidate and candidate != full_bin_sha: raise AmbiguousObjectName(partial_hexsha) candidate = full_bin_sha # END handle candidate # END for each db if not candidate: raise BadObject(partial_binsha) return candidate
#} END interface
|