from inspect import getargspec
import sqlalchemy
-from sqlalchemy import create_engine, Table, MetaData, Column, Integer
+from sqlalchemy import create_engine, Table, MetaData, Column, Integer, desc, \
+ Text, ForeignKey
from sqlalchemy.orm import sessionmaker, mapper, relation, object_session, \
- backref, MapperExtension, EXT_CONTINUE
+ backref, MapperExtension, EXT_CONTINUE, object_mapper
from sqlalchemy import types as sqltypes
# Don't remove this, we re-export the exceptions to scripts which import us
'''
return session.query(cls).get(primary_key)
+ def session(self, replace = False):
+ '''
+ Returns the current session that is associated with the object. May
+ return None is object is in detached state.
+ '''
+
+ return object_session(self)
+
+ def clone(self, session = None):
+ '''
+ Clones the current object in a new session and returns the new clone. A
+ fresh session is created if the optional session parameter is not
+ provided. The function will fail if a session is provided and has
+ unflushed changes.
+
+ RATIONALE: SQLAlchemy's session is not thread safe. This method clones
+ an existing object to allow several threads to work with their own
+ instances of an ORMObject.
+
+ WARNING: Only persistent (committed) objects can be cloned. Changes
+ made to the original object that are not committed yet will get lost.
+ The session of the new object will always be rolled back to avoid
+ ressource leaks.
+ '''
+
+ if self.session() is None:
+ raise RuntimeError( \
+ 'Method clone() failed for detached object:\n%s' % self)
+ self.session().flush()
+ mapper = object_mapper(self)
+ primary_key = mapper.primary_key_from_instance(self)
+ object_class = self.__class__
+ if session is None:
+ session = DBConn().session()
+ elif len(session.new) + len(session.dirty) + len(session.deleted) > 0:
+ raise RuntimeError( \
+ 'Method clone() failed due to unflushed changes in session.')
+ new_object = session.query(object_class).get(primary_key)
+ session.rollback()
+ if new_object is None:
+ raise RuntimeError( \
+ 'Method clone() failed for non-persistent object:\n%s' % self)
+ return new_object
+
__all__.append('ORMObject')
################################################################################
################################################################################
-class BinContents(object):
- def __init__(self, *args, **kwargs):
- pass
-
- def __repr__(self):
- return '<BinContents (%s, %s)>' % (self.binary, self.filename)
+class BinContents(ORMObject):
+ def properties(silf):
+ return ['file', 'binary']
__all__.append('BinContents')
def properties(self):
return ['package', 'version', 'maintainer', 'source', 'architecture', \
'poolfile', 'binarytype', 'fingerprint', 'install_date', \
- 'suites_count', 'binary_id']
+ 'suites_count', 'binary_id', 'contents_count']
def not_null_constraints(self):
return ['package', 'version', 'maintainer', 'source', 'poolfile', \
'binarytype']
+ def get_component_name(self):
+ return self.poolfile.location.component.component_name
+
__all__.append('DBBinary')
@session_wrapper
__all__.append('get_suites_binary_in')
@session_wrapper
-def get_binary_from_name_suite(package, suitename, session=None):
- ### For dak examine-package
- ### XXX: Doesn't use object API yet
-
- sql = """SELECT DISTINCT(b.package), b.version, c.name, su.suite_name
- FROM binaries b, files fi, location l, component c, bin_associations ba, suite su
- WHERE b.package='%(package)s'
- AND b.file = fi.id
- AND fi.location = l.id
- AND l.component = c.id
- AND ba.bin=b.id
- AND ba.suite = su.id
- AND su.suite_name %(suitename)s
- ORDER BY b.version DESC"""
+def get_component_by_package_suite(package, suite_list, arch_list=[], session=None):
+ '''
+ Returns the component name of the newest binary package in suite_list or
+ None if no package is found. The result can be optionally filtered by a list
+ of architecture names.
- return session.execute(sql % {'package': package, 'suitename': suitename})
+ @type package: str
+ @param package: DBBinary package name to search for
-__all__.append('get_binary_from_name_suite')
+ @type suite_list: list of str
+ @param suite_list: list of suite_name items
-@session_wrapper
-def get_binary_components(package, suitename, arch, session=None):
- # Check for packages that have moved from one component to another
- query = """SELECT c.name FROM binaries b, bin_associations ba, suite s, location l, component c, architecture a, files f
- WHERE b.package=:package AND s.suite_name=:suitename
- AND (a.arch_string = :arch OR a.arch_string = 'all')
- AND ba.bin = b.id AND ba.suite = s.id AND b.architecture = a.id
- AND f.location = l.id
- AND l.component = c.id
- AND b.file = f.id"""
+ @type arch_list: list of str
+ @param arch_list: optional list of arch_string items that defaults to []
- vals = {'package': package, 'suitename': suitename, 'arch': arch}
+ @rtype: str or NoneType
+ @return: name of component or None
+ '''
- return session.execute(query, vals)
+ q = session.query(DBBinary).filter_by(package = package). \
+ join(DBBinary.suites).filter(Suite.suite_name.in_(suite_list))
+ if len(arch_list) > 0:
+ q = q.join(DBBinary.architecture). \
+ filter(Architecture.arch_string.in_(arch_list))
+ binary = q.order_by(desc(DBBinary.version)).first()
+ if binary is None:
+ return None
+ else:
+ return binary.get_component_name()
-__all__.append('get_binary_components')
+__all__.append('get_component_by_package_suite')
################################################################################
def fullpath(self):
return os.path.join(self.location.path, self.filename)
- def is_valid(self, filesize = -1, md5sum = None):\
- return self.filesize == filesize and self.md5sum == md5sum
+ def is_valid(self, filesize = -1, md5sum = None):
+ return self.filesize == long(filesize) and self.md5sum == md5sum
def properties(self):
return ['filename', 'file_id', 'filesize', 'md5sum', 'sha1sum', \
################################################################################
-# TODO: Why do we have a separate Location class? Can't it be fully integrated
-# into class Component?
class Location(ORMObject):
def __init__(self, path = None, component = None):
self.path = path
self.archive_type = 'pool'
def properties(self):
- return ['path', 'archive_type', 'component', 'files_count']
+ return ['path', 'location_id', 'archive_type', 'component', \
+ 'files_count']
def not_null_constraints(self):
return ['path', 'archive_type']
)
tables_no_primary = (
- 'bin_contents',
'changes_pending_files_map',
'changes_pending_source_files',
'changes_pool_files',
table = Table(table_name, self.db_meta, autoload=True)
setattr(self, 'tbl_%s' % table_name, table)
+ # bin_contents needs special attention until update #41 has been
+ # applied
+ self.tbl_bin_contents = Table('bin_contents', self.db_meta, \
+ Column('file', Text, primary_key = True),
+ Column('binary_id', Integer, ForeignKey('binaries.id'), \
+ primary_key = True),
+ autoload=True, useexisting=True)
+
for view_name in views:
view = Table(view_name, self.db_meta, autoload=True)
setattr(self, 'view_%s' % view_name, view)
fingerprint = relation(Fingerprint, backref="uploadblocks"),
uid = relation(Uid, backref="uploadblocks")))
+ mapper(BinContents, self.tbl_bin_contents,
+ properties = dict(
+ binary = relation(DBBinary,
+ backref=backref('contents', lazy='dynamic')),
+ file = self.tbl_bin_contents.c.file))
+
## Connection functions
def __createconn(self):
from config import Config