5 @contact: Debian FTPMaster <ftpmaster@debian.org>
6 @copyright: 2000, 2001, 2002, 2003, 2004, 2006 James Troup <james@nocrew.org>
7 @copyright: 2008-2009 Mark Hymers <mhy@debian.org>
8 @copyright: 2009, 2010 Joerg Jaspert <joerg@debian.org>
9 @copyright: 2009 Mike O'Connor <stew@debian.org>
10 @license: GNU General Public License version 2 or later
13 # This program is free software; you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation; either version 2 of the License, or
16 # (at your option) any later version.
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
27 ################################################################################
29 # < mhy> I need a funny comment
30 # < sgran> two peanuts were walking down a dark street
31 # < sgran> one was a-salted
32 # * mhy looks up the definition of "funny"
34 ################################################################################
41 from datetime import datetime, timedelta
42 from errno import ENOENT
43 from tempfile import mkstemp, mkdtemp
45 from inspect import getargspec
48 from sqlalchemy import create_engine, Table, MetaData, Column, Integer
49 from sqlalchemy.orm import sessionmaker, mapper, relation, object_session
50 from sqlalchemy import types as sqltypes
52 # Don't remove this, we re-export the exceptions to scripts which import us
53 from sqlalchemy.exc import *
54 from sqlalchemy.orm.exc import NoResultFound
56 # Only import Config until Queue stuff is changed to store its config
58 from config import Config
59 from textutils import fix_maintainer
60 from dak_exceptions import NoSourceFieldError
62 ################################################################################
64 # Patch in support for the debversion field type so that it works during
68 # that is for sqlalchemy 0.6
69 UserDefinedType = sqltypes.UserDefinedType
71 # this one for sqlalchemy 0.5
72 UserDefinedType = sqltypes.TypeEngine
74 class DebVersion(UserDefinedType):
75 def get_col_spec(self):
78 def bind_processor(self, dialect):
81 # ' = None' is needed for sqlalchemy 0.5:
82 def result_processor(self, dialect, coltype = None):
85 sa_major_version = sqlalchemy.__version__[0:3]
86 if sa_major_version in ["0.5", "0.6"]:
87 from sqlalchemy.databases import postgres
88 postgres.ischema_names['debversion'] = DebVersion
90 raise Exception("dak only ported to SQLA versions 0.5 and 0.6. See daklib/dbconn.py")
92 ################################################################################
94 __all__ = ['IntegrityError', 'SQLAlchemyError', 'DebVersion']
96 ################################################################################
98 def session_wrapper(fn):
100 Wrapper around common ".., session=None):" handling. If the wrapped
101 function is called without passing 'session', we create a local one
102 and destroy it when the function ends.
104 Also attaches a commit_or_flush method to the session; if we created a
105 local session, this is a synonym for session.commit(), otherwise it is a
106 synonym for session.flush().
109 def wrapped(*args, **kwargs):
110 private_transaction = False
112 # Find the session object
113 session = kwargs.get('session')
116 if len(args) <= len(getargspec(fn)[0]) - 1:
117 # No session specified as last argument or in kwargs
118 private_transaction = True
119 session = kwargs['session'] = DBConn().session()
121 # Session is last argument in args
125 session = args[-1] = DBConn().session()
126 private_transaction = True
128 if private_transaction:
129 session.commit_or_flush = session.commit
131 session.commit_or_flush = session.flush
134 return fn(*args, **kwargs)
136 if private_transaction:
137 # We created a session; close it.
140 wrapped.__doc__ = fn.__doc__
141 wrapped.func_name = fn.func_name
145 __all__.append('session_wrapper')
147 ################################################################################
149 class Architecture(object):
150 def __init__(self, arch_string = None, description = None):
151 self.arch_string = arch_string
152 self.description = description
154 def __eq__(self, val):
155 if isinstance(val, str):
156 return (self.arch_string== val)
157 # This signals to use the normal comparison operator
158 return NotImplemented
160 def __ne__(self, val):
161 if isinstance(val, str):
162 return (self.arch_string != val)
163 # This signals to use the normal comparison operator
164 return NotImplemented
167 return '<Architecture %s>' % self.arch_string
169 __all__.append('Architecture')
172 def get_architecture(architecture, session=None):
174 Returns database id for given C{architecture}.
176 @type architecture: string
177 @param architecture: The name of the architecture
179 @type session: Session
180 @param session: Optional SQLA session object (a temporary one will be
181 generated if not supplied)
184 @return: Architecture object for the given arch (None if not present)
187 q = session.query(Architecture).filter_by(arch_string=architecture)
191 except NoResultFound:
194 __all__.append('get_architecture')
197 def get_architecture_suites(architecture, session=None):
199 Returns list of Suite objects for given C{architecture} name
201 @type architecture: str
202 @param architecture: Architecture name to search for
204 @type session: Session
205 @param session: Optional SQL session object (a temporary one will be
206 generated if not supplied)
209 @return: list of Suite objects for the given name (may be empty)
212 q = session.query(Suite)
213 q = q.join(SuiteArchitecture)
214 q = q.join(Architecture).filter_by(arch_string=architecture).order_by('suite_name')
220 __all__.append('get_architecture_suites')
222 ################################################################################
224 class Archive(object):
225 def __init__(self, *args, **kwargs):
229 return '<Archive %s>' % self.archive_name
231 __all__.append('Archive')
234 def get_archive(archive, session=None):
236 returns database id for given C{archive}.
238 @type archive: string
239 @param archive: the name of the arhive
241 @type session: Session
242 @param session: Optional SQLA session object (a temporary one will be
243 generated if not supplied)
246 @return: Archive object for the given name (None if not present)
249 archive = archive.lower()
251 q = session.query(Archive).filter_by(archive_name=archive)
255 except NoResultFound:
258 __all__.append('get_archive')
260 ################################################################################
262 class BinAssociation(object):
263 def __init__(self, *args, **kwargs):
267 return '<BinAssociation %s (%s, %s)>' % (self.ba_id, self.binary, self.suite)
269 __all__.append('BinAssociation')
271 ################################################################################
273 class BinContents(object):
274 def __init__(self, *args, **kwargs):
278 return '<BinContents (%s, %s)>' % (self.binary, self.filename)
280 __all__.append('BinContents')
282 ################################################################################
284 class DBBinary(object):
285 def __init__(self, *args, **kwargs):
289 return '<DBBinary %s (%s, %s)>' % (self.package, self.version, self.architecture)
291 __all__.append('DBBinary')
294 def get_suites_binary_in(package, session=None):
296 Returns list of Suite objects which given C{package} name is in
299 @param package: DBBinary package name to search for
302 @return: list of Suite objects for the given package
305 return session.query(Suite).join(BinAssociation).join(DBBinary).filter_by(package=package).all()
307 __all__.append('get_suites_binary_in')
310 def get_binary_from_id(binary_id, session=None):
312 Returns DBBinary object for given C{id}
315 @param binary_id: Id of the required binary
317 @type session: Session
318 @param session: Optional SQLA session object (a temporary one will be
319 generated if not supplied)
322 @return: DBBinary object for the given binary (None if not present)
325 q = session.query(DBBinary).filter_by(binary_id=binary_id)
329 except NoResultFound:
332 __all__.append('get_binary_from_id')
335 def get_binaries_from_name(package, version=None, architecture=None, session=None):
337 Returns list of DBBinary objects for given C{package} name
340 @param package: DBBinary package name to search for
342 @type version: str or None
343 @param version: Version to search for (or None)
345 @type architecture: str, list or None
346 @param architecture: Architectures to limit to (or None if no limit)
348 @type session: Session
349 @param session: Optional SQL session object (a temporary one will be
350 generated if not supplied)
353 @return: list of DBBinary objects for the given name (may be empty)
356 q = session.query(DBBinary).filter_by(package=package)
358 if version is not None:
359 q = q.filter_by(version=version)
361 if architecture is not None:
362 if not isinstance(architecture, list):
363 architecture = [architecture]
364 q = q.join(Architecture).filter(Architecture.arch_string.in_(architecture))
370 __all__.append('get_binaries_from_name')
373 def get_binaries_from_source_id(source_id, session=None):
375 Returns list of DBBinary objects for given C{source_id}
378 @param source_id: source_id to search for
380 @type session: Session
381 @param session: Optional SQL session object (a temporary one will be
382 generated if not supplied)
385 @return: list of DBBinary objects for the given name (may be empty)
388 return session.query(DBBinary).filter_by(source_id=source_id).all()
390 __all__.append('get_binaries_from_source_id')
393 def get_binary_from_name_suite(package, suitename, session=None):
394 ### For dak examine-package
395 ### XXX: Doesn't use object API yet
397 sql = """SELECT DISTINCT(b.package), b.version, c.name, su.suite_name
398 FROM binaries b, files fi, location l, component c, bin_associations ba, suite su
399 WHERE b.package='%(package)s'
401 AND fi.location = l.id
402 AND l.component = c.id
405 AND su.suite_name %(suitename)s
406 ORDER BY b.version DESC"""
408 return session.execute(sql % {'package': package, 'suitename': suitename})
410 __all__.append('get_binary_from_name_suite')
413 def get_binary_components(package, suitename, arch, session=None):
414 # Check for packages that have moved from one component to another
415 query = """SELECT c.name FROM binaries b, bin_associations ba, suite s, location l, component c, architecture a, files f
416 WHERE b.package=:package AND s.suite_name=:suitename
417 AND (a.arch_string = :arch OR a.arch_string = 'all')
418 AND ba.bin = b.id AND ba.suite = s.id AND b.architecture = a.id
419 AND f.location = l.id
420 AND l.component = c.id
423 vals = {'package': package, 'suitename': suitename, 'arch': arch}
425 return session.execute(query, vals)
427 __all__.append('get_binary_components')
429 ################################################################################
431 class BinaryACL(object):
432 def __init__(self, *args, **kwargs):
436 return '<BinaryACL %s>' % self.binary_acl_id
438 __all__.append('BinaryACL')
440 ################################################################################
442 class BinaryACLMap(object):
443 def __init__(self, *args, **kwargs):
447 return '<BinaryACLMap %s>' % self.binary_acl_map_id
449 __all__.append('BinaryACLMap')
451 ################################################################################
456 ArchiveDir "%(archivepath)s";
457 OverrideDir "%(overridedir)s";
458 CacheDir "%(cachedir)s";
463 Packages::Compress ". bzip2 gzip";
464 Sources::Compress ". bzip2 gzip";
469 bindirectory "incoming"
474 BinOverride "override.sid.all3";
475 BinCacheDB "packages-accepted.db";
477 FileList "%(filelist)s";
480 Packages::Extensions ".deb .udeb";
483 bindirectory "incoming/"
486 BinOverride "override.sid.all3";
487 SrcOverride "override.sid.all3.src";
488 FileList "%(filelist)s";
492 class BuildQueue(object):
493 def __init__(self, *args, **kwargs):
497 return '<BuildQueue %s>' % self.queue_name
499 def write_metadata(self, starttime, force=False):
500 # Do we write out metafiles?
501 if not (force or self.generate_metadata):
504 session = DBConn().session().object_session(self)
506 fl_fd = fl_name = ac_fd = ac_name = None
508 arches = " ".join([ a.arch_string for a in session.query(Architecture).all() if a.arch_string != 'source' ])
509 startdir = os.getcwd()
512 # Grab files we want to include
513 newer = session.query(BuildQueueFile).filter_by(build_queue_id = self.queue_id).filter(BuildQueueFile.lastused + timedelta(seconds=self.stay_of_execution) > starttime).all()
514 # Write file list with newer files
515 (fl_fd, fl_name) = mkstemp()
517 os.write(fl_fd, '%s\n' % n.fullpath)
522 # Write minimal apt.conf
523 # TODO: Remove hardcoding from template
524 (ac_fd, ac_name) = mkstemp()
525 os.write(ac_fd, MINIMAL_APT_CONF % {'archivepath': self.path,
527 'cachedir': cnf["Dir::Cache"],
528 'overridedir': cnf["Dir::Override"],
532 # Run apt-ftparchive generate
533 os.chdir(os.path.dirname(ac_name))
534 os.system('apt-ftparchive -qq -o APT::FTPArchive::Contents=off generate %s' % os.path.basename(ac_name))
536 # Run apt-ftparchive release
537 # TODO: Eww - fix this
538 bname = os.path.basename(self.path)
542 # We have to remove the Release file otherwise it'll be included in the
545 os.unlink(os.path.join(bname, 'Release'))
549 os.system("""apt-ftparchive -qq -o APT::FTPArchive::Release::Origin="%s" -o APT::FTPArchive::Release::Label="%s" -o APT::FTPArchive::Release::Description="%s" -o APT::FTPArchive::Release::Architectures="%s" release %s > Release""" % (self.origin, self.label, self.releasedescription, arches, bname))
551 # Crude hack with open and append, but this whole section is and should be redone.
552 if self.notautomatic:
553 release=open("Release", "a")
554 release.write("NotAutomatic: yes")
559 keyring = "--secret-keyring \"%s\"" % cnf["Dinstall::SigningKeyring"]
560 if cnf.has_key("Dinstall::SigningPubKeyring"):
561 keyring += " --keyring \"%s\"" % cnf["Dinstall::SigningPubKeyring"]
563 os.system("gpg %s --no-options --batch --no-tty --armour --default-key %s --detach-sign -o Release.gpg Release""" % (keyring, self.signingkey))
565 # Move the files if we got this far
566 os.rename('Release', os.path.join(bname, 'Release'))
568 os.rename('Release.gpg', os.path.join(bname, 'Release.gpg'))
570 # Clean up any left behind files
597 def clean_and_update(self, starttime, Logger, dryrun=False):
598 """WARNING: This routine commits for you"""
599 session = DBConn().session().object_session(self)
601 if self.generate_metadata and not dryrun:
602 self.write_metadata(starttime)
604 # Grab files older than our execution time
605 older = session.query(BuildQueueFile).filter_by(build_queue_id = self.queue_id).filter(BuildQueueFile.lastused + timedelta(seconds=self.stay_of_execution) <= starttime).all()
611 Logger.log(["I: Would have removed %s from the queue" % o.fullpath])
613 Logger.log(["I: Removing %s from the queue" % o.fullpath])
614 os.unlink(o.fullpath)
617 # If it wasn't there, don't worry
618 if e.errno == ENOENT:
621 # TODO: Replace with proper logging call
622 Logger.log(["E: Could not remove %s" % o.fullpath])
629 for f in os.listdir(self.path):
630 if f.startswith('Packages') or f.startswith('Source') or f.startswith('Release') or f.startswith('advisory'):
634 r = session.query(BuildQueueFile).filter_by(build_queue_id = self.queue_id).filter_by(filename = f).one()
635 except NoResultFound:
636 fp = os.path.join(self.path, f)
638 Logger.log(["I: Would remove unused link %s" % fp])
640 Logger.log(["I: Removing unused link %s" % fp])
644 Logger.log(["E: Failed to unlink unreferenced file %s" % r.fullpath])
646 def add_file_from_pool(self, poolfile):
647 """Copies a file into the pool. Assumes that the PoolFile object is
648 attached to the same SQLAlchemy session as the Queue object is.
650 The caller is responsible for committing after calling this function."""
651 poolfile_basename = poolfile.filename[poolfile.filename.rindex(os.sep)+1:]
653 # Check if we have a file of this name or this ID already
654 for f in self.queuefiles:
655 if f.fileid is not None and f.fileid == poolfile.file_id or \
656 f.poolfile.filename == poolfile_basename:
657 # In this case, update the BuildQueueFile entry so we
658 # don't remove it too early
659 f.lastused = datetime.now()
660 DBConn().session().object_session(poolfile).add(f)
663 # Prepare BuildQueueFile object
664 qf = BuildQueueFile()
665 qf.build_queue_id = self.queue_id
666 qf.lastused = datetime.now()
667 qf.filename = poolfile_basename
669 targetpath = poolfile.fullpath
670 queuepath = os.path.join(self.path, poolfile_basename)
674 # We need to copy instead of symlink
676 utils.copy(targetpath, queuepath)
677 # NULL in the fileid field implies a copy
680 os.symlink(targetpath, queuepath)
681 qf.fileid = poolfile.file_id
685 # Get the same session as the PoolFile is using and add the qf to it
686 DBConn().session().object_session(poolfile).add(qf)
691 __all__.append('BuildQueue')
694 def get_build_queue(queuename, session=None):
696 Returns BuildQueue object for given C{queue name}, creating it if it does not
699 @type queuename: string
700 @param queuename: The name of the queue
702 @type session: Session
703 @param session: Optional SQLA session object (a temporary one will be
704 generated if not supplied)
707 @return: BuildQueue object for the given queue
710 q = session.query(BuildQueue).filter_by(queue_name=queuename)
714 except NoResultFound:
717 __all__.append('get_build_queue')
719 ################################################################################
721 class BuildQueueFile(object):
722 def __init__(self, *args, **kwargs):
726 return '<BuildQueueFile %s (%s)>' % (self.filename, self.build_queue_id)
730 return os.path.join(self.buildqueue.path, self.filename)
733 __all__.append('BuildQueueFile')
735 ################################################################################
737 class ChangePendingBinary(object):
738 def __init__(self, *args, **kwargs):
742 return '<ChangePendingBinary %s>' % self.change_pending_binary_id
744 __all__.append('ChangePendingBinary')
746 ################################################################################
748 class ChangePendingFile(object):
749 def __init__(self, *args, **kwargs):
753 return '<ChangePendingFile %s>' % self.change_pending_file_id
755 __all__.append('ChangePendingFile')
757 ################################################################################
759 class ChangePendingSource(object):
760 def __init__(self, *args, **kwargs):
764 return '<ChangePendingSource %s>' % self.change_pending_source_id
766 __all__.append('ChangePendingSource')
768 ################################################################################
770 class Component(object):
771 def __init__(self, *args, **kwargs):
774 def __eq__(self, val):
775 if isinstance(val, str):
776 return (self.component_name == val)
777 # This signals to use the normal comparison operator
778 return NotImplemented
780 def __ne__(self, val):
781 if isinstance(val, str):
782 return (self.component_name != val)
783 # This signals to use the normal comparison operator
784 return NotImplemented
787 return '<Component %s>' % self.component_name
790 __all__.append('Component')
793 def get_component(component, session=None):
795 Returns database id for given C{component}.
797 @type component: string
798 @param component: The name of the override type
801 @return: the database id for the given component
804 component = component.lower()
806 q = session.query(Component).filter_by(component_name=component)
810 except NoResultFound:
813 __all__.append('get_component')
815 ################################################################################
817 class DBConfig(object):
818 def __init__(self, *args, **kwargs):
822 return '<DBConfig %s>' % self.name
824 __all__.append('DBConfig')
826 ################################################################################
829 def get_or_set_contents_file_id(filename, session=None):
831 Returns database id for given filename.
833 If no matching file is found, a row is inserted.
835 @type filename: string
836 @param filename: The filename
837 @type session: SQLAlchemy
838 @param session: Optional SQL session object (a temporary one will be
839 generated if not supplied). If not passed, a commit will be performed at
840 the end of the function, otherwise the caller is responsible for commiting.
843 @return: the database id for the given component
846 q = session.query(ContentFilename).filter_by(filename=filename)
849 ret = q.one().cafilename_id
850 except NoResultFound:
851 cf = ContentFilename()
852 cf.filename = filename
854 session.commit_or_flush()
855 ret = cf.cafilename_id
859 __all__.append('get_or_set_contents_file_id')
862 def get_contents(suite, overridetype, section=None, session=None):
864 Returns contents for a suite / overridetype combination, limiting
865 to a section if not None.
868 @param suite: Suite object
870 @type overridetype: OverrideType
871 @param overridetype: OverrideType object
873 @type section: Section
874 @param section: Optional section object to limit results to
876 @type session: SQLAlchemy
877 @param session: Optional SQL session object (a temporary one will be
878 generated if not supplied)
881 @return: ResultsProxy object set up to return tuples of (filename, section,
885 # find me all of the contents for a given suite
886 contents_q = """SELECT (p.path||'/'||n.file) AS fn,
890 FROM content_associations c join content_file_paths p ON (c.filepath=p.id)
891 JOIN content_file_names n ON (c.filename=n.id)
892 JOIN binaries b ON (b.id=c.binary_pkg)
893 JOIN override o ON (o.package=b.package)
894 JOIN section s ON (s.id=o.section)
895 WHERE o.suite = :suiteid AND o.type = :overridetypeid
896 AND b.type=:overridetypename"""
898 vals = {'suiteid': suite.suite_id,
899 'overridetypeid': overridetype.overridetype_id,
900 'overridetypename': overridetype.overridetype}
902 if section is not None:
903 contents_q += " AND s.id = :sectionid"
904 vals['sectionid'] = section.section_id
906 contents_q += " ORDER BY fn"
908 return session.execute(contents_q, vals)
910 __all__.append('get_contents')
912 ################################################################################
914 class ContentFilepath(object):
915 def __init__(self, *args, **kwargs):
919 return '<ContentFilepath %s>' % self.filepath
921 __all__.append('ContentFilepath')
924 def get_or_set_contents_path_id(filepath, session=None):
926 Returns database id for given path.
928 If no matching file is found, a row is inserted.
930 @type filepath: string
931 @param filepath: The filepath
933 @type session: SQLAlchemy
934 @param session: Optional SQL session object (a temporary one will be
935 generated if not supplied). If not passed, a commit will be performed at
936 the end of the function, otherwise the caller is responsible for commiting.
939 @return: the database id for the given path
942 q = session.query(ContentFilepath).filter_by(filepath=filepath)
945 ret = q.one().cafilepath_id
946 except NoResultFound:
947 cf = ContentFilepath()
948 cf.filepath = filepath
950 session.commit_or_flush()
951 ret = cf.cafilepath_id
955 __all__.append('get_or_set_contents_path_id')
957 ################################################################################
959 class ContentAssociation(object):
960 def __init__(self, *args, **kwargs):
964 return '<ContentAssociation %s>' % self.ca_id
966 __all__.append('ContentAssociation')
968 def insert_content_paths(binary_id, fullpaths, session=None):
970 Make sure given path is associated with given binary id
973 @param binary_id: the id of the binary
974 @type fullpaths: list
975 @param fullpaths: the list of paths of the file being associated with the binary
976 @type session: SQLAlchemy session
977 @param session: Optional SQLAlchemy session. If this is passed, the caller
978 is responsible for ensuring a transaction has begun and committing the
979 results or rolling back based on the result code. If not passed, a commit
980 will be performed at the end of the function, otherwise the caller is
981 responsible for commiting.
983 @return: True upon success
988 session = DBConn().session()
993 def generate_path_dicts():
994 for fullpath in fullpaths:
995 if fullpath.startswith( './' ):
996 fullpath = fullpath[2:]
998 yield {'filename':fullpath, 'id': binary_id }
1000 for d in generate_path_dicts():
1001 session.execute( "INSERT INTO bin_contents ( file, binary_id ) VALUES ( :filename, :id )",
1010 traceback.print_exc()
1012 # Only rollback if we set up the session ourself
1019 __all__.append('insert_content_paths')
1021 ################################################################################
1023 class DSCFile(object):
1024 def __init__(self, *args, **kwargs):
1028 return '<DSCFile %s>' % self.dscfile_id
1030 __all__.append('DSCFile')
1033 def get_dscfiles(dscfile_id=None, source_id=None, poolfile_id=None, session=None):
1035 Returns a list of DSCFiles which may be empty
1037 @type dscfile_id: int (optional)
1038 @param dscfile_id: the dscfile_id of the DSCFiles to find
1040 @type source_id: int (optional)
1041 @param source_id: the source id related to the DSCFiles to find
1043 @type poolfile_id: int (optional)
1044 @param poolfile_id: the poolfile id related to the DSCFiles to find
1047 @return: Possibly empty list of DSCFiles
1050 q = session.query(DSCFile)
1052 if dscfile_id is not None:
1053 q = q.filter_by(dscfile_id=dscfile_id)
1055 if source_id is not None:
1056 q = q.filter_by(source_id=source_id)
1058 if poolfile_id is not None:
1059 q = q.filter_by(poolfile_id=poolfile_id)
1063 __all__.append('get_dscfiles')
1065 ################################################################################
1067 class PoolFile(object):
1068 def __init__(self, *args, **kwargs):
1072 return '<PoolFile %s>' % self.filename
1076 return os.path.join(self.location.path, self.filename)
1078 __all__.append('PoolFile')
1081 def check_poolfile(filename, filesize, md5sum, location_id, session=None):
1084 (ValidFileFound [boolean or None], PoolFile object or None)
1086 @type filename: string
1087 @param filename: the filename of the file to check against the DB
1090 @param filesize: the size of the file to check against the DB
1092 @type md5sum: string
1093 @param md5sum: the md5sum of the file to check against the DB
1095 @type location_id: int
1096 @param location_id: the id of the location to look in
1099 @return: Tuple of length 2.
1100 - If more than one file found with that name: (C{None}, C{None})
1101 - If valid pool file found: (C{True}, C{PoolFile object})
1102 - If valid pool file not found:
1103 - (C{False}, C{None}) if no file found
1104 - (C{False}, C{PoolFile object}) if file found with size/md5sum mismatch
1107 q = session.query(PoolFile).filter_by(filename=filename)
1108 q = q.join(Location).filter_by(location_id=location_id)
1118 if obj.md5sum != md5sum or obj.filesize != int(filesize):
1126 __all__.append('check_poolfile')
1129 def get_poolfile_by_id(file_id, session=None):
1131 Returns a PoolFile objects or None for the given id
1134 @param file_id: the id of the file to look for
1136 @rtype: PoolFile or None
1137 @return: either the PoolFile object or None
1140 q = session.query(PoolFile).filter_by(file_id=file_id)
1144 except NoResultFound:
1147 __all__.append('get_poolfile_by_id')
1151 def get_poolfile_by_name(filename, location_id=None, session=None):
1153 Returns an array of PoolFile objects for the given filename and
1154 (optionally) location_id
1156 @type filename: string
1157 @param filename: the filename of the file to check against the DB
1159 @type location_id: int
1160 @param location_id: the id of the location to look in (optional)
1163 @return: array of PoolFile objects
1166 q = session.query(PoolFile).filter_by(filename=filename)
1168 if location_id is not None:
1169 q = q.join(Location).filter_by(location_id=location_id)
1173 __all__.append('get_poolfile_by_name')
1176 def get_poolfile_like_name(filename, session=None):
1178 Returns an array of PoolFile objects which are like the given name
1180 @type filename: string
1181 @param filename: the filename of the file to check against the DB
1184 @return: array of PoolFile objects
1187 # TODO: There must be a way of properly using bind parameters with %FOO%
1188 q = session.query(PoolFile).filter(PoolFile.filename.like('%%/%s' % filename))
1192 __all__.append('get_poolfile_like_name')
1195 def add_poolfile(filename, datadict, location_id, session=None):
1197 Add a new file to the pool
1199 @type filename: string
1200 @param filename: filename
1202 @type datadict: dict
1203 @param datadict: dict with needed data
1205 @type location_id: int
1206 @param location_id: database id of the location
1209 @return: the PoolFile object created
1211 poolfile = PoolFile()
1212 poolfile.filename = filename
1213 poolfile.filesize = datadict["size"]
1214 poolfile.md5sum = datadict["md5sum"]
1215 poolfile.sha1sum = datadict["sha1sum"]
1216 poolfile.sha256sum = datadict["sha256sum"]
1217 poolfile.location_id = location_id
1219 session.add(poolfile)
1220 # Flush to get a file id (NB: This is not a commit)
1225 __all__.append('add_poolfile')
1227 ################################################################################
1229 class Fingerprint(object):
1230 def __init__(self, fingerprint = None):
1231 self.fingerprint = fingerprint
1234 return '<Fingerprint %s>' % self.fingerprint
1236 __all__.append('Fingerprint')
1239 def get_fingerprint(fpr, session=None):
1241 Returns Fingerprint object for given fpr.
1244 @param fpr: The fpr to find / add
1246 @type session: SQLAlchemy
1247 @param session: Optional SQL session object (a temporary one will be
1248 generated if not supplied).
1251 @return: the Fingerprint object for the given fpr or None
1254 q = session.query(Fingerprint).filter_by(fingerprint=fpr)
1258 except NoResultFound:
1263 __all__.append('get_fingerprint')
1266 def get_or_set_fingerprint(fpr, session=None):
1268 Returns Fingerprint object for given fpr.
1270 If no matching fpr is found, a row is inserted.
1273 @param fpr: The fpr to find / add
1275 @type session: SQLAlchemy
1276 @param session: Optional SQL session object (a temporary one will be
1277 generated if not supplied). If not passed, a commit will be performed at
1278 the end of the function, otherwise the caller is responsible for commiting.
1279 A flush will be performed either way.
1282 @return: the Fingerprint object for the given fpr
1285 q = session.query(Fingerprint).filter_by(fingerprint=fpr)
1289 except NoResultFound:
1290 fingerprint = Fingerprint()
1291 fingerprint.fingerprint = fpr
1292 session.add(fingerprint)
1293 session.commit_or_flush()
1298 __all__.append('get_or_set_fingerprint')
1300 ################################################################################
1302 # Helper routine for Keyring class
1303 def get_ldap_name(entry):
1305 for k in ["cn", "mn", "sn"]:
1307 if ret and ret[0] != "" and ret[0] != "-":
1309 return " ".join(name)
1311 ################################################################################
1313 class Keyring(object):
1314 gpg_invocation = "gpg --no-default-keyring --keyring %s" +\
1315 " --with-colons --fingerprint --fingerprint"
1320 def __init__(self, *args, **kwargs):
1324 return '<Keyring %s>' % self.keyring_name
1326 def de_escape_gpg_str(self, txt):
1327 esclist = re.split(r'(\\x..)', txt)
1328 for x in range(1,len(esclist),2):
1329 esclist[x] = "%c" % (int(esclist[x][2:],16))
1330 return "".join(esclist)
1332 def parse_address(self, uid):
1333 """parses uid and returns a tuple of real name and email address"""
1335 (name, address) = email.Utils.parseaddr(uid)
1336 name = re.sub(r"\s*[(].*[)]", "", name)
1337 name = self.de_escape_gpg_str(name)
1340 return (name, address)
1342 def load_keys(self, keyring):
1343 if not self.keyring_id:
1344 raise Exception('Must be initialized with database information')
1346 k = os.popen(self.gpg_invocation % keyring, "r")
1350 for line in k.xreadlines():
1351 field = line.split(":")
1352 if field[0] == "pub":
1355 (name, addr) = self.parse_address(field[9])
1357 self.keys[key]["email"] = addr
1358 self.keys[key]["name"] = name
1359 self.keys[key]["fingerprints"] = []
1361 elif key and field[0] == "sub" and len(field) >= 12:
1362 signingkey = ("s" in field[11])
1363 elif key and field[0] == "uid":
1364 (name, addr) = self.parse_address(field[9])
1365 if "email" not in self.keys[key] and "@" in addr:
1366 self.keys[key]["email"] = addr
1367 self.keys[key]["name"] = name
1368 elif signingkey and field[0] == "fpr":
1369 self.keys[key]["fingerprints"].append(field[9])
1370 self.fpr_lookup[field[9]] = key
1372 def import_users_from_ldap(self, session):
1376 LDAPDn = cnf["Import-LDAP-Fingerprints::LDAPDn"]
1377 LDAPServer = cnf["Import-LDAP-Fingerprints::LDAPServer"]
1379 l = ldap.open(LDAPServer)
1380 l.simple_bind_s("","")
1381 Attrs = l.search_s(LDAPDn, ldap.SCOPE_ONELEVEL,
1382 "(&(keyfingerprint=*)(gidnumber=%s))" % (cnf["Import-Users-From-Passwd::ValidGID"]),
1383 ["uid", "keyfingerprint", "cn", "mn", "sn"])
1385 ldap_fin_uid_id = {}
1392 uid = entry["uid"][0]
1393 name = get_ldap_name(entry)
1394 fingerprints = entry["keyFingerPrint"]
1396 for f in fingerprints:
1397 key = self.fpr_lookup.get(f, None)
1398 if key not in self.keys:
1400 self.keys[key]["uid"] = uid
1404 keyid = get_or_set_uid(uid, session).uid_id
1405 byuid[keyid] = (uid, name)
1406 byname[uid] = (keyid, name)
1408 return (byname, byuid)
1410 def generate_users_from_keyring(self, format, session):
1414 for x in self.keys.keys():
1415 if "email" not in self.keys[x]:
1417 self.keys[x]["uid"] = format % "invalid-uid"
1419 uid = format % self.keys[x]["email"]
1420 keyid = get_or_set_uid(uid, session).uid_id
1421 byuid[keyid] = (uid, self.keys[x]["name"])
1422 byname[uid] = (keyid, self.keys[x]["name"])
1423 self.keys[x]["uid"] = uid
1426 uid = format % "invalid-uid"
1427 keyid = get_or_set_uid(uid, session).uid_id
1428 byuid[keyid] = (uid, "ungeneratable user id")
1429 byname[uid] = (keyid, "ungeneratable user id")
1431 return (byname, byuid)
1433 __all__.append('Keyring')
1436 def get_keyring(keyring, session=None):
1438 If C{keyring} does not have an entry in the C{keyrings} table yet, return None
1439 If C{keyring} already has an entry, simply return the existing Keyring
1441 @type keyring: string
1442 @param keyring: the keyring name
1445 @return: the Keyring object for this keyring
1448 q = session.query(Keyring).filter_by(keyring_name=keyring)
1452 except NoResultFound:
1455 __all__.append('get_keyring')
1457 ################################################################################
1459 class KeyringACLMap(object):
1460 def __init__(self, *args, **kwargs):
1464 return '<KeyringACLMap %s>' % self.keyring_acl_map_id
1466 __all__.append('KeyringACLMap')
1468 ################################################################################
1470 class DBChange(object):
1471 def __init__(self, *args, **kwargs):
1475 return '<DBChange %s>' % self.changesname
1477 def clean_from_queue(self):
1478 session = DBConn().session().object_session(self)
1480 # Remove changes_pool_files entries
1483 # Remove changes_pending_files references
1486 # Clear out of queue
1487 self.in_queue = None
1488 self.approved_for_id = None
1490 __all__.append('DBChange')
1493 def get_dbchange(filename, session=None):
1495 returns DBChange object for given C{filename}.
1497 @type filename: string
1498 @param filename: the name of the file
1500 @type session: Session
1501 @param session: Optional SQLA session object (a temporary one will be
1502 generated if not supplied)
1505 @return: DBChange object for the given filename (C{None} if not present)
1508 q = session.query(DBChange).filter_by(changesname=filename)
1512 except NoResultFound:
1515 __all__.append('get_dbchange')
1517 ################################################################################
1519 class Location(object):
1520 def __init__(self, *args, **kwargs):
1524 return '<Location %s (%s)>' % (self.path, self.location_id)
1526 __all__.append('Location')
1529 def get_location(location, component=None, archive=None, session=None):
1531 Returns Location object for the given combination of location, component
1534 @type location: string
1535 @param location: the path of the location, e.g. I{/srv/ftp-master.debian.org/ftp/pool/}
1537 @type component: string
1538 @param component: the component name (if None, no restriction applied)
1540 @type archive: string
1541 @param archive: the archive name (if None, no restriction applied)
1543 @rtype: Location / None
1544 @return: Either a Location object or None if one can't be found
1547 q = session.query(Location).filter_by(path=location)
1549 if archive is not None:
1550 q = q.join(Archive).filter_by(archive_name=archive)
1552 if component is not None:
1553 q = q.join(Component).filter_by(component_name=component)
1557 except NoResultFound:
1560 __all__.append('get_location')
1562 ################################################################################
1564 class Maintainer(object):
1565 def __init__(self, *args, **kwargs):
1569 return '''<Maintainer '%s' (%s)>''' % (self.name, self.maintainer_id)
1571 def get_split_maintainer(self):
1572 if not hasattr(self, 'name') or self.name is None:
1573 return ('', '', '', '')
1575 return fix_maintainer(self.name.strip())
1577 __all__.append('Maintainer')
1580 def get_or_set_maintainer(name, session=None):
1582 Returns Maintainer object for given maintainer name.
1584 If no matching maintainer name is found, a row is inserted.
1587 @param name: The maintainer name to add
1589 @type session: SQLAlchemy
1590 @param session: Optional SQL session object (a temporary one will be
1591 generated if not supplied). If not passed, a commit will be performed at
1592 the end of the function, otherwise the caller is responsible for commiting.
1593 A flush will be performed either way.
1596 @return: the Maintainer object for the given maintainer
1599 q = session.query(Maintainer).filter_by(name=name)
1602 except NoResultFound:
1603 maintainer = Maintainer()
1604 maintainer.name = name
1605 session.add(maintainer)
1606 session.commit_or_flush()
1611 __all__.append('get_or_set_maintainer')
1614 def get_maintainer(maintainer_id, session=None):
1616 Return the name of the maintainer behind C{maintainer_id} or None if that
1617 maintainer_id is invalid.
1619 @type maintainer_id: int
1620 @param maintainer_id: the id of the maintainer
1623 @return: the Maintainer with this C{maintainer_id}
1626 return session.query(Maintainer).get(maintainer_id)
1628 __all__.append('get_maintainer')
1630 ################################################################################
1632 class NewComment(object):
1633 def __init__(self, *args, **kwargs):
1637 return '''<NewComment for '%s %s' (%s)>''' % (self.package, self.version, self.comment_id)
1639 __all__.append('NewComment')
1642 def has_new_comment(package, version, session=None):
1644 Returns true if the given combination of C{package}, C{version} has a comment.
1646 @type package: string
1647 @param package: name of the package
1649 @type version: string
1650 @param version: package version
1652 @type session: Session
1653 @param session: Optional SQLA session object (a temporary one will be
1654 generated if not supplied)
1660 q = session.query(NewComment)
1661 q = q.filter_by(package=package)
1662 q = q.filter_by(version=version)
1664 return bool(q.count() > 0)
1666 __all__.append('has_new_comment')
1669 def get_new_comments(package=None, version=None, comment_id=None, session=None):
1671 Returns (possibly empty) list of NewComment objects for the given
1674 @type package: string (optional)
1675 @param package: name of the package
1677 @type version: string (optional)
1678 @param version: package version
1680 @type comment_id: int (optional)
1681 @param comment_id: An id of a comment
1683 @type session: Session
1684 @param session: Optional SQLA session object (a temporary one will be
1685 generated if not supplied)
1688 @return: A (possibly empty) list of NewComment objects will be returned
1691 q = session.query(NewComment)
1692 if package is not None: q = q.filter_by(package=package)
1693 if version is not None: q = q.filter_by(version=version)
1694 if comment_id is not None: q = q.filter_by(comment_id=comment_id)
1698 __all__.append('get_new_comments')
1700 ################################################################################
1702 class Override(object):
1703 def __init__(self, *args, **kwargs):
1707 return '<Override %s (%s)>' % (self.package, self.suite_id)
1709 __all__.append('Override')
1712 def get_override(package, suite=None, component=None, overridetype=None, session=None):
1714 Returns Override object for the given parameters
1716 @type package: string
1717 @param package: The name of the package
1719 @type suite: string, list or None
1720 @param suite: The name of the suite (or suites if a list) to limit to. If
1721 None, don't limit. Defaults to None.
1723 @type component: string, list or None
1724 @param component: The name of the component (or components if a list) to
1725 limit to. If None, don't limit. Defaults to None.
1727 @type overridetype: string, list or None
1728 @param overridetype: The name of the overridetype (or overridetypes if a list) to
1729 limit to. If None, don't limit. Defaults to None.
1731 @type session: Session
1732 @param session: Optional SQLA session object (a temporary one will be
1733 generated if not supplied)
1736 @return: A (possibly empty) list of Override objects will be returned
1739 q = session.query(Override)
1740 q = q.filter_by(package=package)
1742 if suite is not None:
1743 if not isinstance(suite, list): suite = [suite]
1744 q = q.join(Suite).filter(Suite.suite_name.in_(suite))
1746 if component is not None:
1747 if not isinstance(component, list): component = [component]
1748 q = q.join(Component).filter(Component.component_name.in_(component))
1750 if overridetype is not None:
1751 if not isinstance(overridetype, list): overridetype = [overridetype]
1752 q = q.join(OverrideType).filter(OverrideType.overridetype.in_(overridetype))
1756 __all__.append('get_override')
1759 ################################################################################
1761 class OverrideType(object):
1762 def __init__(self, *args, **kwargs):
1766 return '<OverrideType %s>' % self.overridetype
1768 __all__.append('OverrideType')
1771 def get_override_type(override_type, session=None):
1773 Returns OverrideType object for given C{override type}.
1775 @type override_type: string
1776 @param override_type: The name of the override type
1778 @type session: Session
1779 @param session: Optional SQLA session object (a temporary one will be
1780 generated if not supplied)
1783 @return: the database id for the given override type
1786 q = session.query(OverrideType).filter_by(overridetype=override_type)
1790 except NoResultFound:
1793 __all__.append('get_override_type')
1795 ################################################################################
1797 class DebContents(object):
1798 def __init__(self, *args, **kwargs):
1802 return '<DebConetnts %s: %s>' % (self.package.package,self.file)
1804 __all__.append('DebContents')
1807 class UdebContents(object):
1808 def __init__(self, *args, **kwargs):
1812 return '<UdebConetnts %s: %s>' % (self.package.package,self.file)
1814 __all__.append('UdebContents')
1816 class PendingBinContents(object):
1817 def __init__(self, *args, **kwargs):
1821 return '<PendingBinContents %s>' % self.contents_id
1823 __all__.append('PendingBinContents')
1825 def insert_pending_content_paths(package,
1830 Make sure given paths are temporarily associated with given
1834 @param package: the package to associate with should have been read in from the binary control file
1835 @type fullpaths: list
1836 @param fullpaths: the list of paths of the file being associated with the binary
1837 @type session: SQLAlchemy session
1838 @param session: Optional SQLAlchemy session. If this is passed, the caller
1839 is responsible for ensuring a transaction has begun and committing the
1840 results or rolling back based on the result code. If not passed, a commit
1841 will be performed at the end of the function
1843 @return: True upon success, False if there is a problem
1846 privatetrans = False
1849 session = DBConn().session()
1853 arch = get_architecture(package['Architecture'], session)
1854 arch_id = arch.arch_id
1856 # Remove any already existing recorded files for this package
1857 q = session.query(PendingBinContents)
1858 q = q.filter_by(package=package['Package'])
1859 q = q.filter_by(version=package['Version'])
1860 q = q.filter_by(architecture=arch_id)
1863 for fullpath in fullpaths:
1865 if fullpath.startswith( "./" ):
1866 fullpath = fullpath[2:]
1868 pca = PendingBinContents()
1869 pca.package = package['Package']
1870 pca.version = package['Version']
1872 pca.architecture = arch_id
1875 pca.type = 8 # gross
1877 pca.type = 7 # also gross
1880 # Only commit if we set up the session ourself
1888 except Exception, e:
1889 traceback.print_exc()
1891 # Only rollback if we set up the session ourself
1898 __all__.append('insert_pending_content_paths')
1900 ################################################################################
1902 class PolicyQueue(object):
1903 def __init__(self, *args, **kwargs):
1907 return '<PolicyQueue %s>' % self.queue_name
1909 __all__.append('PolicyQueue')
1912 def get_policy_queue(queuename, session=None):
1914 Returns PolicyQueue object for given C{queue name}
1916 @type queuename: string
1917 @param queuename: The name of the queue
1919 @type session: Session
1920 @param session: Optional SQLA session object (a temporary one will be
1921 generated if not supplied)
1924 @return: PolicyQueue object for the given queue
1927 q = session.query(PolicyQueue).filter_by(queue_name=queuename)
1931 except NoResultFound:
1934 __all__.append('get_policy_queue')
1937 def get_policy_queue_from_path(pathname, session=None):
1939 Returns PolicyQueue object for given C{path name}
1941 @type queuename: string
1942 @param queuename: The path
1944 @type session: Session
1945 @param session: Optional SQLA session object (a temporary one will be
1946 generated if not supplied)
1949 @return: PolicyQueue object for the given queue
1952 q = session.query(PolicyQueue).filter_by(path=pathname)
1956 except NoResultFound:
1959 __all__.append('get_policy_queue_from_path')
1961 ################################################################################
1963 class Priority(object):
1964 def __init__(self, *args, **kwargs):
1967 def __eq__(self, val):
1968 if isinstance(val, str):
1969 return (self.priority == val)
1970 # This signals to use the normal comparison operator
1971 return NotImplemented
1973 def __ne__(self, val):
1974 if isinstance(val, str):
1975 return (self.priority != val)
1976 # This signals to use the normal comparison operator
1977 return NotImplemented
1980 return '<Priority %s (%s)>' % (self.priority, self.priority_id)
1982 __all__.append('Priority')
1985 def get_priority(priority, session=None):
1987 Returns Priority object for given C{priority name}.
1989 @type priority: string
1990 @param priority: The name of the priority
1992 @type session: Session
1993 @param session: Optional SQLA session object (a temporary one will be
1994 generated if not supplied)
1997 @return: Priority object for the given priority
2000 q = session.query(Priority).filter_by(priority=priority)
2004 except NoResultFound:
2007 __all__.append('get_priority')
2010 def get_priorities(session=None):
2012 Returns dictionary of priority names -> id mappings
2014 @type session: Session
2015 @param session: Optional SQL session object (a temporary one will be
2016 generated if not supplied)
2019 @return: dictionary of priority names -> id mappings
2023 q = session.query(Priority)
2025 ret[x.priority] = x.priority_id
2029 __all__.append('get_priorities')
2031 ################################################################################
2033 class Section(object):
2034 def __init__(self, *args, **kwargs):
2037 def __eq__(self, val):
2038 if isinstance(val, str):
2039 return (self.section == val)
2040 # This signals to use the normal comparison operator
2041 return NotImplemented
2043 def __ne__(self, val):
2044 if isinstance(val, str):
2045 return (self.section != val)
2046 # This signals to use the normal comparison operator
2047 return NotImplemented
2050 return '<Section %s>' % self.section
2052 __all__.append('Section')
2055 def get_section(section, session=None):
2057 Returns Section object for given C{section name}.
2059 @type section: string
2060 @param section: The name of the section
2062 @type session: Session
2063 @param session: Optional SQLA session object (a temporary one will be
2064 generated if not supplied)
2067 @return: Section object for the given section name
2070 q = session.query(Section).filter_by(section=section)
2074 except NoResultFound:
2077 __all__.append('get_section')
2080 def get_sections(session=None):
2082 Returns dictionary of section names -> id mappings
2084 @type session: Session
2085 @param session: Optional SQL session object (a temporary one will be
2086 generated if not supplied)
2089 @return: dictionary of section names -> id mappings
2093 q = session.query(Section)
2095 ret[x.section] = x.section_id
2099 __all__.append('get_sections')
2101 ################################################################################
2103 class DBSource(object):
2104 def __init__(self, *args, **kwargs):
2108 return '<DBSource %s (%s)>' % (self.source, self.version)
2110 __all__.append('DBSource')
2113 def source_exists(source, source_version, suites = ["any"], session=None):
2115 Ensure that source exists somewhere in the archive for the binary
2116 upload being processed.
2117 1. exact match => 1.0-3
2118 2. bin-only NMU => 1.0-3+b1 , 1.0-3.1+b1
2120 @type source: string
2121 @param source: source name
2123 @type source_version: string
2124 @param source_version: expected source version
2127 @param suites: list of suites to check in, default I{any}
2129 @type session: Session
2130 @param session: Optional SQLA session object (a temporary one will be
2131 generated if not supplied)
2134 @return: returns 1 if a source with expected version is found, otherwise 0
2141 for suite in suites:
2142 q = session.query(DBSource).filter_by(source=source)
2144 # source must exist in suite X, or in some other suite that's
2145 # mapped to X, recursively... silent-maps are counted too,
2146 # unreleased-maps aren't.
2147 maps = cnf.ValueList("SuiteMappings")[:]
2149 maps = [ m.split() for m in maps ]
2150 maps = [ (x[1], x[2]) for x in maps
2151 if x[0] == "map" or x[0] == "silent-map" ]
2154 if x[1] in s and x[0] not in s:
2157 q = q.join(SrcAssociation).join(Suite)
2158 q = q.filter(Suite.suite_name.in_(s))
2160 # Reduce the query results to a list of version numbers
2161 ql = [ j.version for j in q.all() ]
2164 if source_version in ql:
2168 from daklib.regexes import re_bin_only_nmu
2169 orig_source_version = re_bin_only_nmu.sub('', source_version)
2170 if orig_source_version in ql:
2173 # No source found so return not ok
2178 __all__.append('source_exists')
2181 def get_suites_source_in(source, session=None):
2183 Returns list of Suite objects which given C{source} name is in
2186 @param source: DBSource package name to search for
2189 @return: list of Suite objects for the given source
2192 return session.query(Suite).join(SrcAssociation).join(DBSource).filter_by(source=source).all()
2194 __all__.append('get_suites_source_in')
2197 def get_sources_from_name(source, version=None, dm_upload_allowed=None, session=None):
2199 Returns list of DBSource objects for given C{source} name and other parameters
2202 @param source: DBSource package name to search for
2204 @type version: str or None
2205 @param version: DBSource version name to search for or None if not applicable
2207 @type dm_upload_allowed: bool
2208 @param dm_upload_allowed: If None, no effect. If True or False, only
2209 return packages with that dm_upload_allowed setting
2211 @type session: Session
2212 @param session: Optional SQL session object (a temporary one will be
2213 generated if not supplied)
2216 @return: list of DBSource objects for the given name (may be empty)
2219 q = session.query(DBSource).filter_by(source=source)
2221 if version is not None:
2222 q = q.filter_by(version=version)
2224 if dm_upload_allowed is not None:
2225 q = q.filter_by(dm_upload_allowed=dm_upload_allowed)
2229 __all__.append('get_sources_from_name')
2232 def get_source_in_suite(source, suite, session=None):
2234 Returns list of DBSource objects for a combination of C{source} and C{suite}.
2236 - B{source} - source package name, eg. I{mailfilter}, I{bbdb}, I{glibc}
2237 - B{suite} - a suite name, eg. I{unstable}
2239 @type source: string
2240 @param source: source package name
2243 @param suite: the suite name
2246 @return: the version for I{source} in I{suite}
2250 q = session.query(SrcAssociation)
2251 q = q.join('source').filter_by(source=source)
2252 q = q.join('suite').filter_by(suite_name=suite)
2255 return q.one().source
2256 except NoResultFound:
2259 __all__.append('get_source_in_suite')
2261 ################################################################################
2264 def add_dsc_to_db(u, filename, session=None):
2265 entry = u.pkg.files[filename]
2269 source.source = u.pkg.dsc["source"]
2270 source.version = u.pkg.dsc["version"] # NB: not files[file]["version"], that has no epoch
2271 source.maintainer_id = get_or_set_maintainer(u.pkg.dsc["maintainer"], session).maintainer_id
2272 source.changedby_id = get_or_set_maintainer(u.pkg.changes["changed-by"], session).maintainer_id
2273 source.fingerprint_id = get_or_set_fingerprint(u.pkg.changes["fingerprint"], session).fingerprint_id
2274 source.install_date = datetime.now().date()
2276 dsc_component = entry["component"]
2277 dsc_location_id = entry["location id"]
2279 source.dm_upload_allowed = (u.pkg.dsc.get("dm-upload-allowed", '') == "yes")
2281 # Set up a new poolfile if necessary
2282 if not entry.has_key("files id") or not entry["files id"]:
2283 filename = entry["pool name"] + filename
2284 poolfile = add_poolfile(filename, entry, dsc_location_id, session)
2286 pfs.append(poolfile)
2287 entry["files id"] = poolfile.file_id
2289 source.poolfile_id = entry["files id"]
2293 for suite_name in u.pkg.changes["distribution"].keys():
2294 sa = SrcAssociation()
2295 sa.source_id = source.source_id
2296 sa.suite_id = get_suite(suite_name).suite_id
2301 # Add the source files to the DB (files and dsc_files)
2303 dscfile.source_id = source.source_id
2304 dscfile.poolfile_id = entry["files id"]
2305 session.add(dscfile)
2307 for dsc_file, dentry in u.pkg.dsc_files.items():
2309 df.source_id = source.source_id
2311 # If the .orig tarball is already in the pool, it's
2312 # files id is stored in dsc_files by check_dsc().
2313 files_id = dentry.get("files id", None)
2315 # Find the entry in the files hash
2316 # TODO: Bail out here properly
2318 for f, e in u.pkg.files.items():
2323 if files_id is None:
2324 filename = dfentry["pool name"] + dsc_file
2326 (found, obj) = check_poolfile(filename, dentry["size"], dentry["md5sum"], dsc_location_id)
2327 # FIXME: needs to check for -1/-2 and or handle exception
2328 if found and obj is not None:
2329 files_id = obj.file_id
2332 # If still not found, add it
2333 if files_id is None:
2334 # HACK: Force sha1sum etc into dentry
2335 dentry["sha1sum"] = dfentry["sha1sum"]
2336 dentry["sha256sum"] = dfentry["sha256sum"]
2337 poolfile = add_poolfile(filename, dentry, dsc_location_id, session)
2338 pfs.append(poolfile)
2339 files_id = poolfile.file_id
2341 poolfile = get_poolfile_by_id(files_id, session)
2342 if poolfile is None:
2343 utils.fubar("INTERNAL ERROR. Found no poolfile with id %d" % files_id)
2344 pfs.append(poolfile)
2346 df.poolfile_id = files_id
2351 # Add the src_uploaders to the DB
2352 uploader_ids = [source.maintainer_id]
2353 if u.pkg.dsc.has_key("uploaders"):
2354 for up in u.pkg.dsc["uploaders"].replace(">, ", ">\t").split("\t"):
2356 uploader_ids.append(get_or_set_maintainer(up, session).maintainer_id)
2359 for up_id in uploader_ids:
2360 if added_ids.has_key(up_id):
2362 utils.warn("Already saw uploader %s for source %s" % (up_id, source.source))
2368 su.maintainer_id = up_id
2369 su.source_id = source.source_id
2374 return source, dsc_component, dsc_location_id, pfs
2376 __all__.append('add_dsc_to_db')
2379 def add_deb_to_db(u, filename, session=None):
2381 Contrary to what you might expect, this routine deals with both
2382 debs and udebs. That info is in 'dbtype', whilst 'type' is
2383 'deb' for both of them
2386 entry = u.pkg.files[filename]
2389 bin.package = entry["package"]
2390 bin.version = entry["version"]
2391 bin.maintainer_id = get_or_set_maintainer(entry["maintainer"], session).maintainer_id
2392 bin.fingerprint_id = get_or_set_fingerprint(u.pkg.changes["fingerprint"], session).fingerprint_id
2393 bin.arch_id = get_architecture(entry["architecture"], session).arch_id
2394 bin.binarytype = entry["dbtype"]
2397 filename = entry["pool name"] + filename
2398 fullpath = os.path.join(cnf["Dir::Pool"], filename)
2399 if not entry.get("location id", None):
2400 entry["location id"] = get_location(cnf["Dir::Pool"], entry["component"], session=session).location_id
2402 if entry.get("files id", None):
2403 poolfile = get_poolfile_by_id(bin.poolfile_id)
2404 bin.poolfile_id = entry["files id"]
2406 poolfile = add_poolfile(filename, entry, entry["location id"], session)
2407 bin.poolfile_id = entry["files id"] = poolfile.file_id
2410 bin_sources = get_sources_from_name(entry["source package"], entry["source version"], session=session)
2411 if len(bin_sources) != 1:
2412 raise NoSourceFieldError, "Unable to find a unique source id for %s (%s), %s, file %s, type %s, signed by %s" % \
2413 (bin.package, bin.version, entry["architecture"],
2414 filename, bin.binarytype, u.pkg.changes["fingerprint"])
2416 bin.source_id = bin_sources[0].source_id
2418 # Add and flush object so it has an ID
2422 # Add BinAssociations
2423 for suite_name in u.pkg.changes["distribution"].keys():
2424 ba = BinAssociation()
2425 ba.binary_id = bin.binary_id
2426 ba.suite_id = get_suite(suite_name).suite_id
2431 # Deal with contents - disabled for now
2432 #contents = copy_temporary_contents(bin.package, bin.version, bin.architecture.arch_string, os.path.basename(filename), None, session)
2434 # print "REJECT\nCould not determine contents of package %s" % bin.package
2435 # session.rollback()
2436 # raise MissingContents, "No contents stored for package %s, and couldn't determine contents of %s" % (bin.package, filename)
2440 __all__.append('add_deb_to_db')
2442 ################################################################################
2444 class SourceACL(object):
2445 def __init__(self, *args, **kwargs):
2449 return '<SourceACL %s>' % self.source_acl_id
2451 __all__.append('SourceACL')
2453 ################################################################################
2455 class SrcAssociation(object):
2456 def __init__(self, *args, **kwargs):
2460 return '<SrcAssociation %s (%s, %s)>' % (self.sa_id, self.source, self.suite)
2462 __all__.append('SrcAssociation')
2464 ################################################################################
2466 class SrcFormat(object):
2467 def __init__(self, *args, **kwargs):
2471 return '<SrcFormat %s>' % (self.format_name)
2473 __all__.append('SrcFormat')
2475 ################################################################################
2477 class SrcUploader(object):
2478 def __init__(self, *args, **kwargs):
2482 return '<SrcUploader %s>' % self.uploader_id
2484 __all__.append('SrcUploader')
2486 ################################################################################
2488 SUITE_FIELDS = [ ('SuiteName', 'suite_name'),
2489 ('SuiteID', 'suite_id'),
2490 ('Version', 'version'),
2491 ('Origin', 'origin'),
2493 ('Description', 'description'),
2494 ('Untouchable', 'untouchable'),
2495 ('Announce', 'announce'),
2496 ('Codename', 'codename'),
2497 ('OverrideCodename', 'overridecodename'),
2498 ('ValidTime', 'validtime'),
2499 ('Priority', 'priority'),
2500 ('NotAutomatic', 'notautomatic'),
2501 ('CopyChanges', 'copychanges'),
2502 ('OverrideSuite', 'overridesuite')]
2504 class Suite(object):
2505 def __init__(self, suite_name = None, version = None):
2506 self.suite_name = suite_name
2507 self.version = version
2510 return '<Suite %s>' % self.suite_name
2512 def __eq__(self, val):
2513 if isinstance(val, str):
2514 return (self.suite_name == val)
2515 # This signals to use the normal comparison operator
2516 return NotImplemented
2518 def __ne__(self, val):
2519 if isinstance(val, str):
2520 return (self.suite_name != val)
2521 # This signals to use the normal comparison operator
2522 return NotImplemented
2526 for disp, field in SUITE_FIELDS:
2527 val = getattr(self, field, None)
2529 ret.append("%s: %s" % (disp, val))
2531 return "\n".join(ret)
2533 def get_architectures(self, skipsrc=False, skipall=False):
2535 Returns list of Architecture objects
2537 @type skipsrc: boolean
2538 @param skipsrc: Whether to skip returning the 'source' architecture entry
2541 @type skipall: boolean
2542 @param skipall: Whether to skip returning the 'all' architecture entry
2546 @return: list of Architecture objects for the given name (may be empty)
2549 q = object_session(self).query(Architecture). \
2550 filter(Architecture.suites.contains(self))
2552 q = q.filter(Architecture.arch_string != 'source')
2554 q = q.filter(Architecture.arch_string != 'all')
2555 return q.order_by(Architecture.arch_string).all()
2557 __all__.append('Suite')
2560 def get_suite_architecture(suite, architecture, session=None):
2562 Returns a SuiteArchitecture object given C{suite} and ${arch} or None if it
2566 @param suite: Suite name to search for
2568 @type architecture: str
2569 @param architecture: Architecture name to search for
2571 @type session: Session
2572 @param session: Optional SQL session object (a temporary one will be
2573 generated if not supplied)
2575 @rtype: SuiteArchitecture
2576 @return: the SuiteArchitecture object or None
2579 q = session.query(SuiteArchitecture)
2580 q = q.join(Architecture).filter_by(arch_string=architecture)
2581 q = q.join(Suite).filter_by(suite_name=suite)
2585 except NoResultFound:
2588 __all__.append('get_suite_architecture')
2591 def get_suite(suite, session=None):
2593 Returns Suite object for given C{suite name}.
2596 @param suite: The name of the suite
2598 @type session: Session
2599 @param session: Optional SQLA session object (a temporary one will be
2600 generated if not supplied)
2603 @return: Suite object for the requested suite name (None if not present)
2606 q = session.query(Suite).filter_by(suite_name=suite)
2610 except NoResultFound:
2613 __all__.append('get_suite')
2615 ################################################################################
2617 # TODO: remove SuiteArchitecture class
2618 class SuiteArchitecture(object):
2619 def __init__(self, *args, **kwargs):
2623 return '<SuiteArchitecture (%s, %s)>' % (self.suite_id, self.arch_id)
2625 __all__.append('SuiteArchitecture')
2627 # TODO: should be removed because the implementation is too trivial
2629 def get_suite_architectures(suite, skipsrc=False, skipall=False, session=None):
2631 Returns list of Architecture objects for given C{suite} name
2634 @param suite: Suite name to search for
2636 @type skipsrc: boolean
2637 @param skipsrc: Whether to skip returning the 'source' architecture entry
2640 @type skipall: boolean
2641 @param skipall: Whether to skip returning the 'all' architecture entry
2644 @type session: Session
2645 @param session: Optional SQL session object (a temporary one will be
2646 generated if not supplied)
2649 @return: list of Architecture objects for the given name (may be empty)
2652 return get_suite(suite, session).get_architectures(skipsrc, skipall)
2654 __all__.append('get_suite_architectures')
2656 ################################################################################
2658 class SuiteSrcFormat(object):
2659 def __init__(self, *args, **kwargs):
2663 return '<SuiteSrcFormat (%s, %s)>' % (self.suite_id, self.src_format_id)
2665 __all__.append('SuiteSrcFormat')
2668 def get_suite_src_formats(suite, session=None):
2670 Returns list of allowed SrcFormat for C{suite}.
2673 @param suite: Suite name to search for
2675 @type session: Session
2676 @param session: Optional SQL session object (a temporary one will be
2677 generated if not supplied)
2680 @return: the list of allowed source formats for I{suite}
2683 q = session.query(SrcFormat)
2684 q = q.join(SuiteSrcFormat)
2685 q = q.join(Suite).filter_by(suite_name=suite)
2686 q = q.order_by('format_name')
2690 __all__.append('get_suite_src_formats')
2692 ################################################################################
2695 def __init__(self, uid = None, name = None):
2699 def __eq__(self, val):
2700 if isinstance(val, str):
2701 return (self.uid == val)
2702 # This signals to use the normal comparison operator
2703 return NotImplemented
2705 def __ne__(self, val):
2706 if isinstance(val, str):
2707 return (self.uid != val)
2708 # This signals to use the normal comparison operator
2709 return NotImplemented
2712 return '<Uid %s (%s)>' % (self.uid, self.name)
2714 __all__.append('Uid')
2717 def get_or_set_uid(uidname, session=None):
2719 Returns uid object for given uidname.
2721 If no matching uidname is found, a row is inserted.
2723 @type uidname: string
2724 @param uidname: The uid to add
2726 @type session: SQLAlchemy
2727 @param session: Optional SQL session object (a temporary one will be
2728 generated if not supplied). If not passed, a commit will be performed at
2729 the end of the function, otherwise the caller is responsible for commiting.
2732 @return: the uid object for the given uidname
2735 q = session.query(Uid).filter_by(uid=uidname)
2739 except NoResultFound:
2743 session.commit_or_flush()
2748 __all__.append('get_or_set_uid')
2751 def get_uid_from_fingerprint(fpr, session=None):
2752 q = session.query(Uid)
2753 q = q.join(Fingerprint).filter_by(fingerprint=fpr)
2757 except NoResultFound:
2760 __all__.append('get_uid_from_fingerprint')
2762 ################################################################################
2764 class UploadBlock(object):
2765 def __init__(self, *args, **kwargs):
2769 return '<UploadBlock %s (%s)>' % (self.source, self.upload_block_id)
2771 __all__.append('UploadBlock')
2773 ################################################################################
2775 class DBConn(object):
2777 database module init.
2781 def __init__(self, *args, **kwargs):
2782 self.__dict__ = self.__shared_state
2784 if not getattr(self, 'initialised', False):
2785 self.initialised = True
2786 self.debug = kwargs.has_key('debug')
2789 def __setuptables(self):
2790 tables_with_primary = (
2801 'changes_pending_binaries',
2802 'changes_pending_files',
2803 'changes_pending_source',
2813 'pending_bin_contents',
2825 # The following tables have primary keys but sqlalchemy
2826 # version 0.5 fails to reflect them correctly with database
2827 # versions before upgrade #41.
2829 #'build_queue_files',
2832 tables_no_primary = (
2834 'changes_pending_files_map',
2835 'changes_pending_source_files',
2836 'changes_pool_files',
2839 'suite_architectures',
2840 'suite_src_formats',
2841 'suite_build_queue_copy',
2843 # see the comment above
2845 'build_queue_files',
2849 'almost_obsolete_all_associations',
2850 'almost_obsolete_src_associations',
2851 'any_associations_source',
2852 'bin_assoc_by_arch',
2853 'bin_associations_binaries',
2854 'binaries_suite_arch',
2855 'binfiles_suite_component_arch',
2858 'newest_all_associations',
2859 'newest_any_associations',
2861 'newest_src_association',
2862 'obsolete_all_associations',
2863 'obsolete_any_associations',
2864 'obsolete_any_by_all_associations',
2865 'obsolete_src_associations',
2867 'src_associations_bin',
2868 'src_associations_src',
2869 'suite_arch_by_name',
2872 # Sqlalchemy version 0.5 fails to reflect the SERIAL type
2873 # correctly and that is why we have to use a workaround. It can
2874 # be removed as soon as we switch to version 0.6.
2875 for table_name in tables_with_primary:
2876 table = Table(table_name, self.db_meta, \
2877 Column('id', Integer, primary_key = True), \
2878 autoload=True, useexisting=True)
2879 setattr(self, 'tbl_%s' % table_name, table)
2881 for table_name in tables_no_primary:
2882 table = Table(table_name, self.db_meta, autoload=True)
2883 setattr(self, 'tbl_%s' % table_name, table)
2885 for view_name in views:
2886 view = Table(view_name, self.db_meta, autoload=True)
2887 setattr(self, 'view_%s' % view_name, view)
2889 def __setupmappers(self):
2890 mapper(Architecture, self.tbl_architecture,
2891 properties = dict(arch_id = self.tbl_architecture.c.id,
2892 suites = relation(Suite, secondary=self.tbl_suite_architectures, backref='architectures')))
2894 mapper(Archive, self.tbl_archive,
2895 properties = dict(archive_id = self.tbl_archive.c.id,
2896 archive_name = self.tbl_archive.c.name))
2898 mapper(BinAssociation, self.tbl_bin_associations,
2899 properties = dict(ba_id = self.tbl_bin_associations.c.id,
2900 suite_id = self.tbl_bin_associations.c.suite,
2901 suite = relation(Suite),
2902 binary_id = self.tbl_bin_associations.c.bin,
2903 binary = relation(DBBinary)))
2905 mapper(PendingBinContents, self.tbl_pending_bin_contents,
2906 properties = dict(contents_id =self.tbl_pending_bin_contents.c.id,
2907 filename = self.tbl_pending_bin_contents.c.filename,
2908 package = self.tbl_pending_bin_contents.c.package,
2909 version = self.tbl_pending_bin_contents.c.version,
2910 arch = self.tbl_pending_bin_contents.c.arch,
2911 otype = self.tbl_pending_bin_contents.c.type))
2913 mapper(DebContents, self.tbl_deb_contents,
2914 properties = dict(binary_id=self.tbl_deb_contents.c.binary_id,
2915 package=self.tbl_deb_contents.c.package,
2916 suite=self.tbl_deb_contents.c.suite,
2917 arch=self.tbl_deb_contents.c.arch,
2918 section=self.tbl_deb_contents.c.section,
2919 filename=self.tbl_deb_contents.c.filename))
2921 mapper(UdebContents, self.tbl_udeb_contents,
2922 properties = dict(binary_id=self.tbl_udeb_contents.c.binary_id,
2923 package=self.tbl_udeb_contents.c.package,
2924 suite=self.tbl_udeb_contents.c.suite,
2925 arch=self.tbl_udeb_contents.c.arch,
2926 section=self.tbl_udeb_contents.c.section,
2927 filename=self.tbl_udeb_contents.c.filename))
2929 mapper(BuildQueue, self.tbl_build_queue,
2930 properties = dict(queue_id = self.tbl_build_queue.c.id))
2932 mapper(BuildQueueFile, self.tbl_build_queue_files,
2933 properties = dict(buildqueue = relation(BuildQueue, backref='queuefiles'),
2934 poolfile = relation(PoolFile, backref='buildqueueinstances')))
2936 mapper(DBBinary, self.tbl_binaries,
2937 properties = dict(binary_id = self.tbl_binaries.c.id,
2938 package = self.tbl_binaries.c.package,
2939 version = self.tbl_binaries.c.version,
2940 maintainer_id = self.tbl_binaries.c.maintainer,
2941 maintainer = relation(Maintainer),
2942 source_id = self.tbl_binaries.c.source,
2943 source = relation(DBSource),
2944 arch_id = self.tbl_binaries.c.architecture,
2945 architecture = relation(Architecture),
2946 poolfile_id = self.tbl_binaries.c.file,
2947 poolfile = relation(PoolFile),
2948 binarytype = self.tbl_binaries.c.type,
2949 fingerprint_id = self.tbl_binaries.c.sig_fpr,
2950 fingerprint = relation(Fingerprint),
2951 install_date = self.tbl_binaries.c.install_date,
2952 binassociations = relation(BinAssociation,
2953 primaryjoin=(self.tbl_binaries.c.id==self.tbl_bin_associations.c.bin))))
2955 mapper(BinaryACL, self.tbl_binary_acl,
2956 properties = dict(binary_acl_id = self.tbl_binary_acl.c.id))
2958 mapper(BinaryACLMap, self.tbl_binary_acl_map,
2959 properties = dict(binary_acl_map_id = self.tbl_binary_acl_map.c.id,
2960 fingerprint = relation(Fingerprint, backref="binary_acl_map"),
2961 architecture = relation(Architecture)))
2963 mapper(Component, self.tbl_component,
2964 properties = dict(component_id = self.tbl_component.c.id,
2965 component_name = self.tbl_component.c.name))
2967 mapper(DBConfig, self.tbl_config,
2968 properties = dict(config_id = self.tbl_config.c.id))
2970 mapper(DSCFile, self.tbl_dsc_files,
2971 properties = dict(dscfile_id = self.tbl_dsc_files.c.id,
2972 source_id = self.tbl_dsc_files.c.source,
2973 source = relation(DBSource),
2974 poolfile_id = self.tbl_dsc_files.c.file,
2975 poolfile = relation(PoolFile)))
2977 mapper(PoolFile, self.tbl_files,
2978 properties = dict(file_id = self.tbl_files.c.id,
2979 filesize = self.tbl_files.c.size,
2980 location_id = self.tbl_files.c.location,
2981 location = relation(Location)))
2983 mapper(Fingerprint, self.tbl_fingerprint,
2984 properties = dict(fingerprint_id = self.tbl_fingerprint.c.id,
2985 uid_id = self.tbl_fingerprint.c.uid,
2986 uid = relation(Uid),
2987 keyring_id = self.tbl_fingerprint.c.keyring,
2988 keyring = relation(Keyring),
2989 source_acl = relation(SourceACL),
2990 binary_acl = relation(BinaryACL)))
2992 mapper(Keyring, self.tbl_keyrings,
2993 properties = dict(keyring_name = self.tbl_keyrings.c.name,
2994 keyring_id = self.tbl_keyrings.c.id))
2996 mapper(DBChange, self.tbl_changes,
2997 properties = dict(change_id = self.tbl_changes.c.id,
2998 poolfiles = relation(PoolFile,
2999 secondary=self.tbl_changes_pool_files,
3000 backref="changeslinks"),
3001 seen = self.tbl_changes.c.seen,
3002 source = self.tbl_changes.c.source,
3003 binaries = self.tbl_changes.c.binaries,
3004 architecture = self.tbl_changes.c.architecture,
3005 distribution = self.tbl_changes.c.distribution,
3006 urgency = self.tbl_changes.c.urgency,
3007 maintainer = self.tbl_changes.c.maintainer,
3008 changedby = self.tbl_changes.c.changedby,
3009 date = self.tbl_changes.c.date,
3010 version = self.tbl_changes.c.version,
3011 files = relation(ChangePendingFile,
3012 secondary=self.tbl_changes_pending_files_map,
3013 backref="changesfile"),
3014 in_queue_id = self.tbl_changes.c.in_queue,
3015 in_queue = relation(PolicyQueue,
3016 primaryjoin=(self.tbl_changes.c.in_queue==self.tbl_policy_queue.c.id)),
3017 approved_for_id = self.tbl_changes.c.approved_for))
3019 mapper(ChangePendingBinary, self.tbl_changes_pending_binaries,
3020 properties = dict(change_pending_binary_id = self.tbl_changes_pending_binaries.c.id))
3022 mapper(ChangePendingFile, self.tbl_changes_pending_files,
3023 properties = dict(change_pending_file_id = self.tbl_changes_pending_files.c.id,
3024 filename = self.tbl_changes_pending_files.c.filename,
3025 size = self.tbl_changes_pending_files.c.size,
3026 md5sum = self.tbl_changes_pending_files.c.md5sum,
3027 sha1sum = self.tbl_changes_pending_files.c.sha1sum,
3028 sha256sum = self.tbl_changes_pending_files.c.sha256sum))
3030 mapper(ChangePendingSource, self.tbl_changes_pending_source,
3031 properties = dict(change_pending_source_id = self.tbl_changes_pending_source.c.id,
3032 change = relation(DBChange),
3033 maintainer = relation(Maintainer,
3034 primaryjoin=(self.tbl_changes_pending_source.c.maintainer_id==self.tbl_maintainer.c.id)),
3035 changedby = relation(Maintainer,
3036 primaryjoin=(self.tbl_changes_pending_source.c.changedby_id==self.tbl_maintainer.c.id)),
3037 fingerprint = relation(Fingerprint),
3038 source_files = relation(ChangePendingFile,
3039 secondary=self.tbl_changes_pending_source_files,
3040 backref="pending_sources")))
3043 mapper(KeyringACLMap, self.tbl_keyring_acl_map,
3044 properties = dict(keyring_acl_map_id = self.tbl_keyring_acl_map.c.id,
3045 keyring = relation(Keyring, backref="keyring_acl_map"),
3046 architecture = relation(Architecture)))
3048 mapper(Location, self.tbl_location,
3049 properties = dict(location_id = self.tbl_location.c.id,
3050 component_id = self.tbl_location.c.component,
3051 component = relation(Component),
3052 archive_id = self.tbl_location.c.archive,
3053 archive = relation(Archive),
3054 archive_type = self.tbl_location.c.type))
3056 mapper(Maintainer, self.tbl_maintainer,
3057 properties = dict(maintainer_id = self.tbl_maintainer.c.id))
3059 mapper(NewComment, self.tbl_new_comments,
3060 properties = dict(comment_id = self.tbl_new_comments.c.id))
3062 mapper(Override, self.tbl_override,
3063 properties = dict(suite_id = self.tbl_override.c.suite,
3064 suite = relation(Suite),
3065 package = self.tbl_override.c.package,
3066 component_id = self.tbl_override.c.component,
3067 component = relation(Component),
3068 priority_id = self.tbl_override.c.priority,
3069 priority = relation(Priority),
3070 section_id = self.tbl_override.c.section,
3071 section = relation(Section),
3072 overridetype_id = self.tbl_override.c.type,
3073 overridetype = relation(OverrideType)))
3075 mapper(OverrideType, self.tbl_override_type,
3076 properties = dict(overridetype = self.tbl_override_type.c.type,
3077 overridetype_id = self.tbl_override_type.c.id))
3079 mapper(PolicyQueue, self.tbl_policy_queue,
3080 properties = dict(policy_queue_id = self.tbl_policy_queue.c.id))
3082 mapper(Priority, self.tbl_priority,
3083 properties = dict(priority_id = self.tbl_priority.c.id))
3085 mapper(Section, self.tbl_section,
3086 properties = dict(section_id = self.tbl_section.c.id,
3087 section=self.tbl_section.c.section))
3089 mapper(DBSource, self.tbl_source,
3090 properties = dict(source_id = self.tbl_source.c.id,
3091 version = self.tbl_source.c.version,
3092 maintainer_id = self.tbl_source.c.maintainer,
3093 maintainer = relation(Maintainer,
3094 primaryjoin=(self.tbl_source.c.maintainer==self.tbl_maintainer.c.id)),
3095 poolfile_id = self.tbl_source.c.file,
3096 poolfile = relation(PoolFile),
3097 fingerprint_id = self.tbl_source.c.sig_fpr,
3098 fingerprint = relation(Fingerprint),
3099 changedby_id = self.tbl_source.c.changedby,
3100 changedby = relation(Maintainer,
3101 primaryjoin=(self.tbl_source.c.changedby==self.tbl_maintainer.c.id)),
3102 srcfiles = relation(DSCFile,
3103 primaryjoin=(self.tbl_source.c.id==self.tbl_dsc_files.c.source)),
3104 srcassociations = relation(SrcAssociation,
3105 primaryjoin=(self.tbl_source.c.id==self.tbl_src_associations.c.source)),
3106 srcuploaders = relation(SrcUploader)))
3108 mapper(SourceACL, self.tbl_source_acl,
3109 properties = dict(source_acl_id = self.tbl_source_acl.c.id))
3111 mapper(SrcAssociation, self.tbl_src_associations,
3112 properties = dict(sa_id = self.tbl_src_associations.c.id,
3113 suite_id = self.tbl_src_associations.c.suite,
3114 suite = relation(Suite),
3115 source_id = self.tbl_src_associations.c.source,
3116 source = relation(DBSource)))
3118 mapper(SrcFormat, self.tbl_src_format,
3119 properties = dict(src_format_id = self.tbl_src_format.c.id,
3120 format_name = self.tbl_src_format.c.format_name))
3122 mapper(SrcUploader, self.tbl_src_uploaders,
3123 properties = dict(uploader_id = self.tbl_src_uploaders.c.id,
3124 source_id = self.tbl_src_uploaders.c.source,
3125 source = relation(DBSource,
3126 primaryjoin=(self.tbl_src_uploaders.c.source==self.tbl_source.c.id)),
3127 maintainer_id = self.tbl_src_uploaders.c.maintainer,
3128 maintainer = relation(Maintainer,
3129 primaryjoin=(self.tbl_src_uploaders.c.maintainer==self.tbl_maintainer.c.id))))
3131 mapper(Suite, self.tbl_suite,
3132 properties = dict(suite_id = self.tbl_suite.c.id,
3133 policy_queue = relation(PolicyQueue),
3134 copy_queues = relation(BuildQueue, secondary=self.tbl_suite_build_queue_copy)))
3136 mapper(SuiteArchitecture, self.tbl_suite_architectures,
3137 properties = dict(suite_id = self.tbl_suite_architectures.c.suite,
3138 suite = relation(Suite, backref='suitearchitectures'),
3139 arch_id = self.tbl_suite_architectures.c.architecture,
3140 architecture = relation(Architecture)))
3142 mapper(SuiteSrcFormat, self.tbl_suite_src_formats,
3143 properties = dict(suite_id = self.tbl_suite_src_formats.c.suite,
3144 suite = relation(Suite, backref='suitesrcformats'),
3145 src_format_id = self.tbl_suite_src_formats.c.src_format,
3146 src_format = relation(SrcFormat)))
3148 mapper(Uid, self.tbl_uid,
3149 properties = dict(uid_id = self.tbl_uid.c.id,
3150 fingerprint = relation(Fingerprint)))
3152 mapper(UploadBlock, self.tbl_upload_blocks,
3153 properties = dict(upload_block_id = self.tbl_upload_blocks.c.id,
3154 fingerprint = relation(Fingerprint, backref="uploadblocks"),
3155 uid = relation(Uid, backref="uploadblocks")))
3157 ## Connection functions
3158 def __createconn(self):
3159 from config import Config
3163 connstr = "postgres://%s" % cnf["DB::Host"]
3164 if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
3165 connstr += ":%s" % cnf["DB::Port"]
3166 connstr += "/%s" % cnf["DB::Name"]
3169 connstr = "postgres:///%s" % cnf["DB::Name"]
3170 if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
3171 connstr += "?port=%s" % cnf["DB::Port"]
3173 self.db_pg = create_engine(connstr, echo=self.debug)
3174 self.db_meta = MetaData()
3175 self.db_meta.bind = self.db_pg
3176 self.db_smaker = sessionmaker(bind=self.db_pg,
3180 self.__setuptables()
3181 self.__setupmappers()
3184 return self.db_smaker()
3186 __all__.append('DBConn')