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')
196 # TODO: should be removed because the implementation is too trivial
198 def get_architecture_suites(architecture, session=None):
200 Returns list of Suite objects for given C{architecture} name
202 @type architecture: str
203 @param architecture: Architecture name to search for
205 @type session: Session
206 @param session: Optional SQL session object (a temporary one will be
207 generated if not supplied)
210 @return: list of Suite objects for the given name (may be empty)
213 return get_architecture(architecture, session).suites
215 __all__.append('get_architecture_suites')
217 ################################################################################
219 class Archive(object):
220 def __init__(self, *args, **kwargs):
224 return '<Archive %s>' % self.archive_name
226 __all__.append('Archive')
229 def get_archive(archive, session=None):
231 returns database id for given C{archive}.
233 @type archive: string
234 @param archive: the name of the arhive
236 @type session: Session
237 @param session: Optional SQLA session object (a temporary one will be
238 generated if not supplied)
241 @return: Archive object for the given name (None if not present)
244 archive = archive.lower()
246 q = session.query(Archive).filter_by(archive_name=archive)
250 except NoResultFound:
253 __all__.append('get_archive')
255 ################################################################################
257 class BinAssociation(object):
258 def __init__(self, *args, **kwargs):
262 return '<BinAssociation %s (%s, %s)>' % (self.ba_id, self.binary, self.suite)
264 __all__.append('BinAssociation')
266 ################################################################################
268 class BinContents(object):
269 def __init__(self, *args, **kwargs):
273 return '<BinContents (%s, %s)>' % (self.binary, self.filename)
275 __all__.append('BinContents')
277 ################################################################################
279 class DBBinary(object):
280 def __init__(self, *args, **kwargs):
284 return '<DBBinary %s (%s, %s)>' % (self.package, self.version, self.architecture)
286 __all__.append('DBBinary')
289 def get_suites_binary_in(package, session=None):
291 Returns list of Suite objects which given C{package} name is in
294 @param package: DBBinary package name to search for
297 @return: list of Suite objects for the given package
300 return session.query(Suite).join(BinAssociation).join(DBBinary).filter_by(package=package).all()
302 __all__.append('get_suites_binary_in')
305 def get_binary_from_id(binary_id, session=None):
307 Returns DBBinary object for given C{id}
310 @param binary_id: Id of the required binary
312 @type session: Session
313 @param session: Optional SQLA session object (a temporary one will be
314 generated if not supplied)
317 @return: DBBinary object for the given binary (None if not present)
320 q = session.query(DBBinary).filter_by(binary_id=binary_id)
324 except NoResultFound:
327 __all__.append('get_binary_from_id')
330 def get_binaries_from_name(package, version=None, architecture=None, session=None):
332 Returns list of DBBinary objects for given C{package} name
335 @param package: DBBinary package name to search for
337 @type version: str or None
338 @param version: Version to search for (or None)
340 @type architecture: str, list or None
341 @param architecture: Architectures to limit to (or None if no limit)
343 @type session: Session
344 @param session: Optional SQL session object (a temporary one will be
345 generated if not supplied)
348 @return: list of DBBinary objects for the given name (may be empty)
351 q = session.query(DBBinary).filter_by(package=package)
353 if version is not None:
354 q = q.filter_by(version=version)
356 if architecture is not None:
357 if not isinstance(architecture, list):
358 architecture = [architecture]
359 q = q.join(Architecture).filter(Architecture.arch_string.in_(architecture))
365 __all__.append('get_binaries_from_name')
368 def get_binaries_from_source_id(source_id, session=None):
370 Returns list of DBBinary objects for given C{source_id}
373 @param source_id: source_id to search for
375 @type session: Session
376 @param session: Optional SQL session object (a temporary one will be
377 generated if not supplied)
380 @return: list of DBBinary objects for the given name (may be empty)
383 return session.query(DBBinary).filter_by(source_id=source_id).all()
385 __all__.append('get_binaries_from_source_id')
388 def get_binary_from_name_suite(package, suitename, session=None):
389 ### For dak examine-package
390 ### XXX: Doesn't use object API yet
392 sql = """SELECT DISTINCT(b.package), b.version, c.name, su.suite_name
393 FROM binaries b, files fi, location l, component c, bin_associations ba, suite su
394 WHERE b.package='%(package)s'
396 AND fi.location = l.id
397 AND l.component = c.id
400 AND su.suite_name %(suitename)s
401 ORDER BY b.version DESC"""
403 return session.execute(sql % {'package': package, 'suitename': suitename})
405 __all__.append('get_binary_from_name_suite')
408 def get_binary_components(package, suitename, arch, session=None):
409 # Check for packages that have moved from one component to another
410 query = """SELECT c.name FROM binaries b, bin_associations ba, suite s, location l, component c, architecture a, files f
411 WHERE b.package=:package AND s.suite_name=:suitename
412 AND (a.arch_string = :arch OR a.arch_string = 'all')
413 AND ba.bin = b.id AND ba.suite = s.id AND b.architecture = a.id
414 AND f.location = l.id
415 AND l.component = c.id
418 vals = {'package': package, 'suitename': suitename, 'arch': arch}
420 return session.execute(query, vals)
422 __all__.append('get_binary_components')
424 ################################################################################
426 class BinaryACL(object):
427 def __init__(self, *args, **kwargs):
431 return '<BinaryACL %s>' % self.binary_acl_id
433 __all__.append('BinaryACL')
435 ################################################################################
437 class BinaryACLMap(object):
438 def __init__(self, *args, **kwargs):
442 return '<BinaryACLMap %s>' % self.binary_acl_map_id
444 __all__.append('BinaryACLMap')
446 ################################################################################
451 ArchiveDir "%(archivepath)s";
452 OverrideDir "%(overridedir)s";
453 CacheDir "%(cachedir)s";
458 Packages::Compress ". bzip2 gzip";
459 Sources::Compress ". bzip2 gzip";
464 bindirectory "incoming"
469 BinOverride "override.sid.all3";
470 BinCacheDB "packages-accepted.db";
472 FileList "%(filelist)s";
475 Packages::Extensions ".deb .udeb";
478 bindirectory "incoming/"
481 BinOverride "override.sid.all3";
482 SrcOverride "override.sid.all3.src";
483 FileList "%(filelist)s";
487 class BuildQueue(object):
488 def __init__(self, *args, **kwargs):
492 return '<BuildQueue %s>' % self.queue_name
494 def write_metadata(self, starttime, force=False):
495 # Do we write out metafiles?
496 if not (force or self.generate_metadata):
499 session = DBConn().session().object_session(self)
501 fl_fd = fl_name = ac_fd = ac_name = None
503 arches = " ".join([ a.arch_string for a in session.query(Architecture).all() if a.arch_string != 'source' ])
504 startdir = os.getcwd()
507 # Grab files we want to include
508 newer = session.query(BuildQueueFile).filter_by(build_queue_id = self.queue_id).filter(BuildQueueFile.lastused + timedelta(seconds=self.stay_of_execution) > starttime).all()
509 # Write file list with newer files
510 (fl_fd, fl_name) = mkstemp()
512 os.write(fl_fd, '%s\n' % n.fullpath)
517 # Write minimal apt.conf
518 # TODO: Remove hardcoding from template
519 (ac_fd, ac_name) = mkstemp()
520 os.write(ac_fd, MINIMAL_APT_CONF % {'archivepath': self.path,
522 'cachedir': cnf["Dir::Cache"],
523 'overridedir': cnf["Dir::Override"],
527 # Run apt-ftparchive generate
528 os.chdir(os.path.dirname(ac_name))
529 os.system('apt-ftparchive -qq -o APT::FTPArchive::Contents=off generate %s' % os.path.basename(ac_name))
531 # Run apt-ftparchive release
532 # TODO: Eww - fix this
533 bname = os.path.basename(self.path)
537 # We have to remove the Release file otherwise it'll be included in the
540 os.unlink(os.path.join(bname, 'Release'))
544 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))
546 # Crude hack with open and append, but this whole section is and should be redone.
547 if self.notautomatic:
548 release=open("Release", "a")
549 release.write("NotAutomatic: yes")
554 keyring = "--secret-keyring \"%s\"" % cnf["Dinstall::SigningKeyring"]
555 if cnf.has_key("Dinstall::SigningPubKeyring"):
556 keyring += " --keyring \"%s\"" % cnf["Dinstall::SigningPubKeyring"]
558 os.system("gpg %s --no-options --batch --no-tty --armour --default-key %s --detach-sign -o Release.gpg Release""" % (keyring, self.signingkey))
560 # Move the files if we got this far
561 os.rename('Release', os.path.join(bname, 'Release'))
563 os.rename('Release.gpg', os.path.join(bname, 'Release.gpg'))
565 # Clean up any left behind files
592 def clean_and_update(self, starttime, Logger, dryrun=False):
593 """WARNING: This routine commits for you"""
594 session = DBConn().session().object_session(self)
596 if self.generate_metadata and not dryrun:
597 self.write_metadata(starttime)
599 # Grab files older than our execution time
600 older = session.query(BuildQueueFile).filter_by(build_queue_id = self.queue_id).filter(BuildQueueFile.lastused + timedelta(seconds=self.stay_of_execution) <= starttime).all()
606 Logger.log(["I: Would have removed %s from the queue" % o.fullpath])
608 Logger.log(["I: Removing %s from the queue" % o.fullpath])
609 os.unlink(o.fullpath)
612 # If it wasn't there, don't worry
613 if e.errno == ENOENT:
616 # TODO: Replace with proper logging call
617 Logger.log(["E: Could not remove %s" % o.fullpath])
624 for f in os.listdir(self.path):
625 if f.startswith('Packages') or f.startswith('Source') or f.startswith('Release') or f.startswith('advisory'):
629 r = session.query(BuildQueueFile).filter_by(build_queue_id = self.queue_id).filter_by(filename = f).one()
630 except NoResultFound:
631 fp = os.path.join(self.path, f)
633 Logger.log(["I: Would remove unused link %s" % fp])
635 Logger.log(["I: Removing unused link %s" % fp])
639 Logger.log(["E: Failed to unlink unreferenced file %s" % r.fullpath])
641 def add_file_from_pool(self, poolfile):
642 """Copies a file into the pool. Assumes that the PoolFile object is
643 attached to the same SQLAlchemy session as the Queue object is.
645 The caller is responsible for committing after calling this function."""
646 poolfile_basename = poolfile.filename[poolfile.filename.rindex(os.sep)+1:]
648 # Check if we have a file of this name or this ID already
649 for f in self.queuefiles:
650 if f.fileid is not None and f.fileid == poolfile.file_id or \
651 f.poolfile.filename == poolfile_basename:
652 # In this case, update the BuildQueueFile entry so we
653 # don't remove it too early
654 f.lastused = datetime.now()
655 DBConn().session().object_session(poolfile).add(f)
658 # Prepare BuildQueueFile object
659 qf = BuildQueueFile()
660 qf.build_queue_id = self.queue_id
661 qf.lastused = datetime.now()
662 qf.filename = poolfile_basename
664 targetpath = poolfile.fullpath
665 queuepath = os.path.join(self.path, poolfile_basename)
669 # We need to copy instead of symlink
671 utils.copy(targetpath, queuepath)
672 # NULL in the fileid field implies a copy
675 os.symlink(targetpath, queuepath)
676 qf.fileid = poolfile.file_id
680 # Get the same session as the PoolFile is using and add the qf to it
681 DBConn().session().object_session(poolfile).add(qf)
686 __all__.append('BuildQueue')
689 def get_build_queue(queuename, session=None):
691 Returns BuildQueue object for given C{queue name}, creating it if it does not
694 @type queuename: string
695 @param queuename: The name of the queue
697 @type session: Session
698 @param session: Optional SQLA session object (a temporary one will be
699 generated if not supplied)
702 @return: BuildQueue object for the given queue
705 q = session.query(BuildQueue).filter_by(queue_name=queuename)
709 except NoResultFound:
712 __all__.append('get_build_queue')
714 ################################################################################
716 class BuildQueueFile(object):
717 def __init__(self, *args, **kwargs):
721 return '<BuildQueueFile %s (%s)>' % (self.filename, self.build_queue_id)
725 return os.path.join(self.buildqueue.path, self.filename)
728 __all__.append('BuildQueueFile')
730 ################################################################################
732 class ChangePendingBinary(object):
733 def __init__(self, *args, **kwargs):
737 return '<ChangePendingBinary %s>' % self.change_pending_binary_id
739 __all__.append('ChangePendingBinary')
741 ################################################################################
743 class ChangePendingFile(object):
744 def __init__(self, *args, **kwargs):
748 return '<ChangePendingFile %s>' % self.change_pending_file_id
750 __all__.append('ChangePendingFile')
752 ################################################################################
754 class ChangePendingSource(object):
755 def __init__(self, *args, **kwargs):
759 return '<ChangePendingSource %s>' % self.change_pending_source_id
761 __all__.append('ChangePendingSource')
763 ################################################################################
765 class Component(object):
766 def __init__(self, *args, **kwargs):
769 def __eq__(self, val):
770 if isinstance(val, str):
771 return (self.component_name == val)
772 # This signals to use the normal comparison operator
773 return NotImplemented
775 def __ne__(self, val):
776 if isinstance(val, str):
777 return (self.component_name != val)
778 # This signals to use the normal comparison operator
779 return NotImplemented
782 return '<Component %s>' % self.component_name
785 __all__.append('Component')
788 def get_component(component, session=None):
790 Returns database id for given C{component}.
792 @type component: string
793 @param component: The name of the override type
796 @return: the database id for the given component
799 component = component.lower()
801 q = session.query(Component).filter_by(component_name=component)
805 except NoResultFound:
808 __all__.append('get_component')
810 ################################################################################
812 class DBConfig(object):
813 def __init__(self, *args, **kwargs):
817 return '<DBConfig %s>' % self.name
819 __all__.append('DBConfig')
821 ################################################################################
824 def get_or_set_contents_file_id(filename, session=None):
826 Returns database id for given filename.
828 If no matching file is found, a row is inserted.
830 @type filename: string
831 @param filename: The filename
832 @type session: SQLAlchemy
833 @param session: Optional SQL session object (a temporary one will be
834 generated if not supplied). If not passed, a commit will be performed at
835 the end of the function, otherwise the caller is responsible for commiting.
838 @return: the database id for the given component
841 q = session.query(ContentFilename).filter_by(filename=filename)
844 ret = q.one().cafilename_id
845 except NoResultFound:
846 cf = ContentFilename()
847 cf.filename = filename
849 session.commit_or_flush()
850 ret = cf.cafilename_id
854 __all__.append('get_or_set_contents_file_id')
857 def get_contents(suite, overridetype, section=None, session=None):
859 Returns contents for a suite / overridetype combination, limiting
860 to a section if not None.
863 @param suite: Suite object
865 @type overridetype: OverrideType
866 @param overridetype: OverrideType object
868 @type section: Section
869 @param section: Optional section object to limit results to
871 @type session: SQLAlchemy
872 @param session: Optional SQL session object (a temporary one will be
873 generated if not supplied)
876 @return: ResultsProxy object set up to return tuples of (filename, section,
880 # find me all of the contents for a given suite
881 contents_q = """SELECT (p.path||'/'||n.file) AS fn,
885 FROM content_associations c join content_file_paths p ON (c.filepath=p.id)
886 JOIN content_file_names n ON (c.filename=n.id)
887 JOIN binaries b ON (b.id=c.binary_pkg)
888 JOIN override o ON (o.package=b.package)
889 JOIN section s ON (s.id=o.section)
890 WHERE o.suite = :suiteid AND o.type = :overridetypeid
891 AND b.type=:overridetypename"""
893 vals = {'suiteid': suite.suite_id,
894 'overridetypeid': overridetype.overridetype_id,
895 'overridetypename': overridetype.overridetype}
897 if section is not None:
898 contents_q += " AND s.id = :sectionid"
899 vals['sectionid'] = section.section_id
901 contents_q += " ORDER BY fn"
903 return session.execute(contents_q, vals)
905 __all__.append('get_contents')
907 ################################################################################
909 class ContentFilepath(object):
910 def __init__(self, *args, **kwargs):
914 return '<ContentFilepath %s>' % self.filepath
916 __all__.append('ContentFilepath')
919 def get_or_set_contents_path_id(filepath, session=None):
921 Returns database id for given path.
923 If no matching file is found, a row is inserted.
925 @type filepath: string
926 @param filepath: The filepath
928 @type session: SQLAlchemy
929 @param session: Optional SQL session object (a temporary one will be
930 generated if not supplied). If not passed, a commit will be performed at
931 the end of the function, otherwise the caller is responsible for commiting.
934 @return: the database id for the given path
937 q = session.query(ContentFilepath).filter_by(filepath=filepath)
940 ret = q.one().cafilepath_id
941 except NoResultFound:
942 cf = ContentFilepath()
943 cf.filepath = filepath
945 session.commit_or_flush()
946 ret = cf.cafilepath_id
950 __all__.append('get_or_set_contents_path_id')
952 ################################################################################
954 class ContentAssociation(object):
955 def __init__(self, *args, **kwargs):
959 return '<ContentAssociation %s>' % self.ca_id
961 __all__.append('ContentAssociation')
963 def insert_content_paths(binary_id, fullpaths, session=None):
965 Make sure given path is associated with given binary id
968 @param binary_id: the id of the binary
969 @type fullpaths: list
970 @param fullpaths: the list of paths of the file being associated with the binary
971 @type session: SQLAlchemy session
972 @param session: Optional SQLAlchemy session. If this is passed, the caller
973 is responsible for ensuring a transaction has begun and committing the
974 results or rolling back based on the result code. If not passed, a commit
975 will be performed at the end of the function, otherwise the caller is
976 responsible for commiting.
978 @return: True upon success
983 session = DBConn().session()
988 def generate_path_dicts():
989 for fullpath in fullpaths:
990 if fullpath.startswith( './' ):
991 fullpath = fullpath[2:]
993 yield {'filename':fullpath, 'id': binary_id }
995 for d in generate_path_dicts():
996 session.execute( "INSERT INTO bin_contents ( file, binary_id ) VALUES ( :filename, :id )",
1005 traceback.print_exc()
1007 # Only rollback if we set up the session ourself
1014 __all__.append('insert_content_paths')
1016 ################################################################################
1018 class DSCFile(object):
1019 def __init__(self, *args, **kwargs):
1023 return '<DSCFile %s>' % self.dscfile_id
1025 __all__.append('DSCFile')
1028 def get_dscfiles(dscfile_id=None, source_id=None, poolfile_id=None, session=None):
1030 Returns a list of DSCFiles which may be empty
1032 @type dscfile_id: int (optional)
1033 @param dscfile_id: the dscfile_id of the DSCFiles to find
1035 @type source_id: int (optional)
1036 @param source_id: the source id related to the DSCFiles to find
1038 @type poolfile_id: int (optional)
1039 @param poolfile_id: the poolfile id related to the DSCFiles to find
1042 @return: Possibly empty list of DSCFiles
1045 q = session.query(DSCFile)
1047 if dscfile_id is not None:
1048 q = q.filter_by(dscfile_id=dscfile_id)
1050 if source_id is not None:
1051 q = q.filter_by(source_id=source_id)
1053 if poolfile_id is not None:
1054 q = q.filter_by(poolfile_id=poolfile_id)
1058 __all__.append('get_dscfiles')
1060 ################################################################################
1062 class PoolFile(object):
1063 def __init__(self, *args, **kwargs):
1067 return '<PoolFile %s>' % self.filename
1071 return os.path.join(self.location.path, self.filename)
1073 __all__.append('PoolFile')
1076 def check_poolfile(filename, filesize, md5sum, location_id, session=None):
1079 (ValidFileFound [boolean or None], PoolFile object or None)
1081 @type filename: string
1082 @param filename: the filename of the file to check against the DB
1085 @param filesize: the size of the file to check against the DB
1087 @type md5sum: string
1088 @param md5sum: the md5sum of the file to check against the DB
1090 @type location_id: int
1091 @param location_id: the id of the location to look in
1094 @return: Tuple of length 2.
1095 - If more than one file found with that name: (C{None}, C{None})
1096 - If valid pool file found: (C{True}, C{PoolFile object})
1097 - If valid pool file not found:
1098 - (C{False}, C{None}) if no file found
1099 - (C{False}, C{PoolFile object}) if file found with size/md5sum mismatch
1102 q = session.query(PoolFile).filter_by(filename=filename)
1103 q = q.join(Location).filter_by(location_id=location_id)
1113 if obj.md5sum != md5sum or obj.filesize != int(filesize):
1121 __all__.append('check_poolfile')
1124 def get_poolfile_by_id(file_id, session=None):
1126 Returns a PoolFile objects or None for the given id
1129 @param file_id: the id of the file to look for
1131 @rtype: PoolFile or None
1132 @return: either the PoolFile object or None
1135 q = session.query(PoolFile).filter_by(file_id=file_id)
1139 except NoResultFound:
1142 __all__.append('get_poolfile_by_id')
1146 def get_poolfile_by_name(filename, location_id=None, session=None):
1148 Returns an array of PoolFile objects for the given filename and
1149 (optionally) location_id
1151 @type filename: string
1152 @param filename: the filename of the file to check against the DB
1154 @type location_id: int
1155 @param location_id: the id of the location to look in (optional)
1158 @return: array of PoolFile objects
1161 q = session.query(PoolFile).filter_by(filename=filename)
1163 if location_id is not None:
1164 q = q.join(Location).filter_by(location_id=location_id)
1168 __all__.append('get_poolfile_by_name')
1171 def get_poolfile_like_name(filename, session=None):
1173 Returns an array of PoolFile objects which are like the given name
1175 @type filename: string
1176 @param filename: the filename of the file to check against the DB
1179 @return: array of PoolFile objects
1182 # TODO: There must be a way of properly using bind parameters with %FOO%
1183 q = session.query(PoolFile).filter(PoolFile.filename.like('%%/%s' % filename))
1187 __all__.append('get_poolfile_like_name')
1190 def add_poolfile(filename, datadict, location_id, session=None):
1192 Add a new file to the pool
1194 @type filename: string
1195 @param filename: filename
1197 @type datadict: dict
1198 @param datadict: dict with needed data
1200 @type location_id: int
1201 @param location_id: database id of the location
1204 @return: the PoolFile object created
1206 poolfile = PoolFile()
1207 poolfile.filename = filename
1208 poolfile.filesize = datadict["size"]
1209 poolfile.md5sum = datadict["md5sum"]
1210 poolfile.sha1sum = datadict["sha1sum"]
1211 poolfile.sha256sum = datadict["sha256sum"]
1212 poolfile.location_id = location_id
1214 session.add(poolfile)
1215 # Flush to get a file id (NB: This is not a commit)
1220 __all__.append('add_poolfile')
1222 ################################################################################
1224 class Fingerprint(object):
1225 def __init__(self, fingerprint = None):
1226 self.fingerprint = fingerprint
1229 return '<Fingerprint %s>' % self.fingerprint
1231 __all__.append('Fingerprint')
1234 def get_fingerprint(fpr, session=None):
1236 Returns Fingerprint object for given fpr.
1239 @param fpr: The fpr to find / add
1241 @type session: SQLAlchemy
1242 @param session: Optional SQL session object (a temporary one will be
1243 generated if not supplied).
1246 @return: the Fingerprint object for the given fpr or None
1249 q = session.query(Fingerprint).filter_by(fingerprint=fpr)
1253 except NoResultFound:
1258 __all__.append('get_fingerprint')
1261 def get_or_set_fingerprint(fpr, session=None):
1263 Returns Fingerprint object for given fpr.
1265 If no matching fpr is found, a row is inserted.
1268 @param fpr: The fpr to find / add
1270 @type session: SQLAlchemy
1271 @param session: Optional SQL session object (a temporary one will be
1272 generated if not supplied). If not passed, a commit will be performed at
1273 the end of the function, otherwise the caller is responsible for commiting.
1274 A flush will be performed either way.
1277 @return: the Fingerprint object for the given fpr
1280 q = session.query(Fingerprint).filter_by(fingerprint=fpr)
1284 except NoResultFound:
1285 fingerprint = Fingerprint()
1286 fingerprint.fingerprint = fpr
1287 session.add(fingerprint)
1288 session.commit_or_flush()
1293 __all__.append('get_or_set_fingerprint')
1295 ################################################################################
1297 # Helper routine for Keyring class
1298 def get_ldap_name(entry):
1300 for k in ["cn", "mn", "sn"]:
1302 if ret and ret[0] != "" and ret[0] != "-":
1304 return " ".join(name)
1306 ################################################################################
1308 class Keyring(object):
1309 gpg_invocation = "gpg --no-default-keyring --keyring %s" +\
1310 " --with-colons --fingerprint --fingerprint"
1315 def __init__(self, *args, **kwargs):
1319 return '<Keyring %s>' % self.keyring_name
1321 def de_escape_gpg_str(self, txt):
1322 esclist = re.split(r'(\\x..)', txt)
1323 for x in range(1,len(esclist),2):
1324 esclist[x] = "%c" % (int(esclist[x][2:],16))
1325 return "".join(esclist)
1327 def parse_address(self, uid):
1328 """parses uid and returns a tuple of real name and email address"""
1330 (name, address) = email.Utils.parseaddr(uid)
1331 name = re.sub(r"\s*[(].*[)]", "", name)
1332 name = self.de_escape_gpg_str(name)
1335 return (name, address)
1337 def load_keys(self, keyring):
1338 if not self.keyring_id:
1339 raise Exception('Must be initialized with database information')
1341 k = os.popen(self.gpg_invocation % keyring, "r")
1345 for line in k.xreadlines():
1346 field = line.split(":")
1347 if field[0] == "pub":
1350 (name, addr) = self.parse_address(field[9])
1352 self.keys[key]["email"] = addr
1353 self.keys[key]["name"] = name
1354 self.keys[key]["fingerprints"] = []
1356 elif key and field[0] == "sub" and len(field) >= 12:
1357 signingkey = ("s" in field[11])
1358 elif key and field[0] == "uid":
1359 (name, addr) = self.parse_address(field[9])
1360 if "email" not in self.keys[key] and "@" in addr:
1361 self.keys[key]["email"] = addr
1362 self.keys[key]["name"] = name
1363 elif signingkey and field[0] == "fpr":
1364 self.keys[key]["fingerprints"].append(field[9])
1365 self.fpr_lookup[field[9]] = key
1367 def import_users_from_ldap(self, session):
1371 LDAPDn = cnf["Import-LDAP-Fingerprints::LDAPDn"]
1372 LDAPServer = cnf["Import-LDAP-Fingerprints::LDAPServer"]
1374 l = ldap.open(LDAPServer)
1375 l.simple_bind_s("","")
1376 Attrs = l.search_s(LDAPDn, ldap.SCOPE_ONELEVEL,
1377 "(&(keyfingerprint=*)(gidnumber=%s))" % (cnf["Import-Users-From-Passwd::ValidGID"]),
1378 ["uid", "keyfingerprint", "cn", "mn", "sn"])
1380 ldap_fin_uid_id = {}
1387 uid = entry["uid"][0]
1388 name = get_ldap_name(entry)
1389 fingerprints = entry["keyFingerPrint"]
1391 for f in fingerprints:
1392 key = self.fpr_lookup.get(f, None)
1393 if key not in self.keys:
1395 self.keys[key]["uid"] = uid
1399 keyid = get_or_set_uid(uid, session).uid_id
1400 byuid[keyid] = (uid, name)
1401 byname[uid] = (keyid, name)
1403 return (byname, byuid)
1405 def generate_users_from_keyring(self, format, session):
1409 for x in self.keys.keys():
1410 if "email" not in self.keys[x]:
1412 self.keys[x]["uid"] = format % "invalid-uid"
1414 uid = format % self.keys[x]["email"]
1415 keyid = get_or_set_uid(uid, session).uid_id
1416 byuid[keyid] = (uid, self.keys[x]["name"])
1417 byname[uid] = (keyid, self.keys[x]["name"])
1418 self.keys[x]["uid"] = uid
1421 uid = format % "invalid-uid"
1422 keyid = get_or_set_uid(uid, session).uid_id
1423 byuid[keyid] = (uid, "ungeneratable user id")
1424 byname[uid] = (keyid, "ungeneratable user id")
1426 return (byname, byuid)
1428 __all__.append('Keyring')
1431 def get_keyring(keyring, session=None):
1433 If C{keyring} does not have an entry in the C{keyrings} table yet, return None
1434 If C{keyring} already has an entry, simply return the existing Keyring
1436 @type keyring: string
1437 @param keyring: the keyring name
1440 @return: the Keyring object for this keyring
1443 q = session.query(Keyring).filter_by(keyring_name=keyring)
1447 except NoResultFound:
1450 __all__.append('get_keyring')
1452 ################################################################################
1454 class KeyringACLMap(object):
1455 def __init__(self, *args, **kwargs):
1459 return '<KeyringACLMap %s>' % self.keyring_acl_map_id
1461 __all__.append('KeyringACLMap')
1463 ################################################################################
1465 class DBChange(object):
1466 def __init__(self, *args, **kwargs):
1470 return '<DBChange %s>' % self.changesname
1472 def clean_from_queue(self):
1473 session = DBConn().session().object_session(self)
1475 # Remove changes_pool_files entries
1478 # Remove changes_pending_files references
1481 # Clear out of queue
1482 self.in_queue = None
1483 self.approved_for_id = None
1485 __all__.append('DBChange')
1488 def get_dbchange(filename, session=None):
1490 returns DBChange object for given C{filename}.
1492 @type filename: string
1493 @param filename: the name of the file
1495 @type session: Session
1496 @param session: Optional SQLA session object (a temporary one will be
1497 generated if not supplied)
1500 @return: DBChange object for the given filename (C{None} if not present)
1503 q = session.query(DBChange).filter_by(changesname=filename)
1507 except NoResultFound:
1510 __all__.append('get_dbchange')
1512 ################################################################################
1514 class Location(object):
1515 def __init__(self, *args, **kwargs):
1519 return '<Location %s (%s)>' % (self.path, self.location_id)
1521 __all__.append('Location')
1524 def get_location(location, component=None, archive=None, session=None):
1526 Returns Location object for the given combination of location, component
1529 @type location: string
1530 @param location: the path of the location, e.g. I{/srv/ftp-master.debian.org/ftp/pool/}
1532 @type component: string
1533 @param component: the component name (if None, no restriction applied)
1535 @type archive: string
1536 @param archive: the archive name (if None, no restriction applied)
1538 @rtype: Location / None
1539 @return: Either a Location object or None if one can't be found
1542 q = session.query(Location).filter_by(path=location)
1544 if archive is not None:
1545 q = q.join(Archive).filter_by(archive_name=archive)
1547 if component is not None:
1548 q = q.join(Component).filter_by(component_name=component)
1552 except NoResultFound:
1555 __all__.append('get_location')
1557 ################################################################################
1559 class Maintainer(object):
1560 def __init__(self, *args, **kwargs):
1564 return '''<Maintainer '%s' (%s)>''' % (self.name, self.maintainer_id)
1566 def get_split_maintainer(self):
1567 if not hasattr(self, 'name') or self.name is None:
1568 return ('', '', '', '')
1570 return fix_maintainer(self.name.strip())
1572 __all__.append('Maintainer')
1575 def get_or_set_maintainer(name, session=None):
1577 Returns Maintainer object for given maintainer name.
1579 If no matching maintainer name is found, a row is inserted.
1582 @param name: The maintainer name to add
1584 @type session: SQLAlchemy
1585 @param session: Optional SQL session object (a temporary one will be
1586 generated if not supplied). If not passed, a commit will be performed at
1587 the end of the function, otherwise the caller is responsible for commiting.
1588 A flush will be performed either way.
1591 @return: the Maintainer object for the given maintainer
1594 q = session.query(Maintainer).filter_by(name=name)
1597 except NoResultFound:
1598 maintainer = Maintainer()
1599 maintainer.name = name
1600 session.add(maintainer)
1601 session.commit_or_flush()
1606 __all__.append('get_or_set_maintainer')
1609 def get_maintainer(maintainer_id, session=None):
1611 Return the name of the maintainer behind C{maintainer_id} or None if that
1612 maintainer_id is invalid.
1614 @type maintainer_id: int
1615 @param maintainer_id: the id of the maintainer
1618 @return: the Maintainer with this C{maintainer_id}
1621 return session.query(Maintainer).get(maintainer_id)
1623 __all__.append('get_maintainer')
1625 ################################################################################
1627 class NewComment(object):
1628 def __init__(self, *args, **kwargs):
1632 return '''<NewComment for '%s %s' (%s)>''' % (self.package, self.version, self.comment_id)
1634 __all__.append('NewComment')
1637 def has_new_comment(package, version, session=None):
1639 Returns true if the given combination of C{package}, C{version} has a comment.
1641 @type package: string
1642 @param package: name of the package
1644 @type version: string
1645 @param version: package version
1647 @type session: Session
1648 @param session: Optional SQLA session object (a temporary one will be
1649 generated if not supplied)
1655 q = session.query(NewComment)
1656 q = q.filter_by(package=package)
1657 q = q.filter_by(version=version)
1659 return bool(q.count() > 0)
1661 __all__.append('has_new_comment')
1664 def get_new_comments(package=None, version=None, comment_id=None, session=None):
1666 Returns (possibly empty) list of NewComment objects for the given
1669 @type package: string (optional)
1670 @param package: name of the package
1672 @type version: string (optional)
1673 @param version: package version
1675 @type comment_id: int (optional)
1676 @param comment_id: An id of a comment
1678 @type session: Session
1679 @param session: Optional SQLA session object (a temporary one will be
1680 generated if not supplied)
1683 @return: A (possibly empty) list of NewComment objects will be returned
1686 q = session.query(NewComment)
1687 if package is not None: q = q.filter_by(package=package)
1688 if version is not None: q = q.filter_by(version=version)
1689 if comment_id is not None: q = q.filter_by(comment_id=comment_id)
1693 __all__.append('get_new_comments')
1695 ################################################################################
1697 class Override(object):
1698 def __init__(self, *args, **kwargs):
1702 return '<Override %s (%s)>' % (self.package, self.suite_id)
1704 __all__.append('Override')
1707 def get_override(package, suite=None, component=None, overridetype=None, session=None):
1709 Returns Override object for the given parameters
1711 @type package: string
1712 @param package: The name of the package
1714 @type suite: string, list or None
1715 @param suite: The name of the suite (or suites if a list) to limit to. If
1716 None, don't limit. Defaults to None.
1718 @type component: string, list or None
1719 @param component: The name of the component (or components if a list) to
1720 limit to. If None, don't limit. Defaults to None.
1722 @type overridetype: string, list or None
1723 @param overridetype: The name of the overridetype (or overridetypes if a list) to
1724 limit to. If None, don't limit. Defaults to None.
1726 @type session: Session
1727 @param session: Optional SQLA session object (a temporary one will be
1728 generated if not supplied)
1731 @return: A (possibly empty) list of Override objects will be returned
1734 q = session.query(Override)
1735 q = q.filter_by(package=package)
1737 if suite is not None:
1738 if not isinstance(suite, list): suite = [suite]
1739 q = q.join(Suite).filter(Suite.suite_name.in_(suite))
1741 if component is not None:
1742 if not isinstance(component, list): component = [component]
1743 q = q.join(Component).filter(Component.component_name.in_(component))
1745 if overridetype is not None:
1746 if not isinstance(overridetype, list): overridetype = [overridetype]
1747 q = q.join(OverrideType).filter(OverrideType.overridetype.in_(overridetype))
1751 __all__.append('get_override')
1754 ################################################################################
1756 class OverrideType(object):
1757 def __init__(self, *args, **kwargs):
1761 return '<OverrideType %s>' % self.overridetype
1763 __all__.append('OverrideType')
1766 def get_override_type(override_type, session=None):
1768 Returns OverrideType object for given C{override type}.
1770 @type override_type: string
1771 @param override_type: The name of the override type
1773 @type session: Session
1774 @param session: Optional SQLA session object (a temporary one will be
1775 generated if not supplied)
1778 @return: the database id for the given override type
1781 q = session.query(OverrideType).filter_by(overridetype=override_type)
1785 except NoResultFound:
1788 __all__.append('get_override_type')
1790 ################################################################################
1792 class DebContents(object):
1793 def __init__(self, *args, **kwargs):
1797 return '<DebConetnts %s: %s>' % (self.package.package,self.file)
1799 __all__.append('DebContents')
1802 class UdebContents(object):
1803 def __init__(self, *args, **kwargs):
1807 return '<UdebConetnts %s: %s>' % (self.package.package,self.file)
1809 __all__.append('UdebContents')
1811 class PendingBinContents(object):
1812 def __init__(self, *args, **kwargs):
1816 return '<PendingBinContents %s>' % self.contents_id
1818 __all__.append('PendingBinContents')
1820 def insert_pending_content_paths(package,
1825 Make sure given paths are temporarily associated with given
1829 @param package: the package to associate with should have been read in from the binary control file
1830 @type fullpaths: list
1831 @param fullpaths: the list of paths of the file being associated with the binary
1832 @type session: SQLAlchemy session
1833 @param session: Optional SQLAlchemy session. If this is passed, the caller
1834 is responsible for ensuring a transaction has begun and committing the
1835 results or rolling back based on the result code. If not passed, a commit
1836 will be performed at the end of the function
1838 @return: True upon success, False if there is a problem
1841 privatetrans = False
1844 session = DBConn().session()
1848 arch = get_architecture(package['Architecture'], session)
1849 arch_id = arch.arch_id
1851 # Remove any already existing recorded files for this package
1852 q = session.query(PendingBinContents)
1853 q = q.filter_by(package=package['Package'])
1854 q = q.filter_by(version=package['Version'])
1855 q = q.filter_by(architecture=arch_id)
1858 for fullpath in fullpaths:
1860 if fullpath.startswith( "./" ):
1861 fullpath = fullpath[2:]
1863 pca = PendingBinContents()
1864 pca.package = package['Package']
1865 pca.version = package['Version']
1867 pca.architecture = arch_id
1870 pca.type = 8 # gross
1872 pca.type = 7 # also gross
1875 # Only commit if we set up the session ourself
1883 except Exception, e:
1884 traceback.print_exc()
1886 # Only rollback if we set up the session ourself
1893 __all__.append('insert_pending_content_paths')
1895 ################################################################################
1897 class PolicyQueue(object):
1898 def __init__(self, *args, **kwargs):
1902 return '<PolicyQueue %s>' % self.queue_name
1904 __all__.append('PolicyQueue')
1907 def get_policy_queue(queuename, session=None):
1909 Returns PolicyQueue object for given C{queue name}
1911 @type queuename: string
1912 @param queuename: The name of the queue
1914 @type session: Session
1915 @param session: Optional SQLA session object (a temporary one will be
1916 generated if not supplied)
1919 @return: PolicyQueue object for the given queue
1922 q = session.query(PolicyQueue).filter_by(queue_name=queuename)
1926 except NoResultFound:
1929 __all__.append('get_policy_queue')
1932 def get_policy_queue_from_path(pathname, session=None):
1934 Returns PolicyQueue object for given C{path name}
1936 @type queuename: string
1937 @param queuename: The path
1939 @type session: Session
1940 @param session: Optional SQLA session object (a temporary one will be
1941 generated if not supplied)
1944 @return: PolicyQueue object for the given queue
1947 q = session.query(PolicyQueue).filter_by(path=pathname)
1951 except NoResultFound:
1954 __all__.append('get_policy_queue_from_path')
1956 ################################################################################
1958 class Priority(object):
1959 def __init__(self, *args, **kwargs):
1962 def __eq__(self, val):
1963 if isinstance(val, str):
1964 return (self.priority == val)
1965 # This signals to use the normal comparison operator
1966 return NotImplemented
1968 def __ne__(self, val):
1969 if isinstance(val, str):
1970 return (self.priority != val)
1971 # This signals to use the normal comparison operator
1972 return NotImplemented
1975 return '<Priority %s (%s)>' % (self.priority, self.priority_id)
1977 __all__.append('Priority')
1980 def get_priority(priority, session=None):
1982 Returns Priority object for given C{priority name}.
1984 @type priority: string
1985 @param priority: The name of the priority
1987 @type session: Session
1988 @param session: Optional SQLA session object (a temporary one will be
1989 generated if not supplied)
1992 @return: Priority object for the given priority
1995 q = session.query(Priority).filter_by(priority=priority)
1999 except NoResultFound:
2002 __all__.append('get_priority')
2005 def get_priorities(session=None):
2007 Returns dictionary of priority names -> id mappings
2009 @type session: Session
2010 @param session: Optional SQL session object (a temporary one will be
2011 generated if not supplied)
2014 @return: dictionary of priority names -> id mappings
2018 q = session.query(Priority)
2020 ret[x.priority] = x.priority_id
2024 __all__.append('get_priorities')
2026 ################################################################################
2028 class Section(object):
2029 def __init__(self, *args, **kwargs):
2032 def __eq__(self, val):
2033 if isinstance(val, str):
2034 return (self.section == val)
2035 # This signals to use the normal comparison operator
2036 return NotImplemented
2038 def __ne__(self, val):
2039 if isinstance(val, str):
2040 return (self.section != val)
2041 # This signals to use the normal comparison operator
2042 return NotImplemented
2045 return '<Section %s>' % self.section
2047 __all__.append('Section')
2050 def get_section(section, session=None):
2052 Returns Section object for given C{section name}.
2054 @type section: string
2055 @param section: The name of the section
2057 @type session: Session
2058 @param session: Optional SQLA session object (a temporary one will be
2059 generated if not supplied)
2062 @return: Section object for the given section name
2065 q = session.query(Section).filter_by(section=section)
2069 except NoResultFound:
2072 __all__.append('get_section')
2075 def get_sections(session=None):
2077 Returns dictionary of section names -> id mappings
2079 @type session: Session
2080 @param session: Optional SQL session object (a temporary one will be
2081 generated if not supplied)
2084 @return: dictionary of section names -> id mappings
2088 q = session.query(Section)
2090 ret[x.section] = x.section_id
2094 __all__.append('get_sections')
2096 ################################################################################
2098 class DBSource(object):
2099 def __init__(self, *args, **kwargs):
2103 return '<DBSource %s (%s)>' % (self.source, self.version)
2105 __all__.append('DBSource')
2108 def source_exists(source, source_version, suites = ["any"], session=None):
2110 Ensure that source exists somewhere in the archive for the binary
2111 upload being processed.
2112 1. exact match => 1.0-3
2113 2. bin-only NMU => 1.0-3+b1 , 1.0-3.1+b1
2115 @type source: string
2116 @param source: source name
2118 @type source_version: string
2119 @param source_version: expected source version
2122 @param suites: list of suites to check in, default I{any}
2124 @type session: Session
2125 @param session: Optional SQLA session object (a temporary one will be
2126 generated if not supplied)
2129 @return: returns 1 if a source with expected version is found, otherwise 0
2136 for suite in suites:
2137 q = session.query(DBSource).filter_by(source=source)
2139 # source must exist in suite X, or in some other suite that's
2140 # mapped to X, recursively... silent-maps are counted too,
2141 # unreleased-maps aren't.
2142 maps = cnf.ValueList("SuiteMappings")[:]
2144 maps = [ m.split() for m in maps ]
2145 maps = [ (x[1], x[2]) for x in maps
2146 if x[0] == "map" or x[0] == "silent-map" ]
2149 if x[1] in s and x[0] not in s:
2152 q = q.join(SrcAssociation).join(Suite)
2153 q = q.filter(Suite.suite_name.in_(s))
2155 # Reduce the query results to a list of version numbers
2156 ql = [ j.version for j in q.all() ]
2159 if source_version in ql:
2163 from daklib.regexes import re_bin_only_nmu
2164 orig_source_version = re_bin_only_nmu.sub('', source_version)
2165 if orig_source_version in ql:
2168 # No source found so return not ok
2173 __all__.append('source_exists')
2176 def get_suites_source_in(source, session=None):
2178 Returns list of Suite objects which given C{source} name is in
2181 @param source: DBSource package name to search for
2184 @return: list of Suite objects for the given source
2187 return session.query(Suite).join(SrcAssociation).join(DBSource).filter_by(source=source).all()
2189 __all__.append('get_suites_source_in')
2192 def get_sources_from_name(source, version=None, dm_upload_allowed=None, session=None):
2194 Returns list of DBSource objects for given C{source} name and other parameters
2197 @param source: DBSource package name to search for
2199 @type version: str or None
2200 @param version: DBSource version name to search for or None if not applicable
2202 @type dm_upload_allowed: bool
2203 @param dm_upload_allowed: If None, no effect. If True or False, only
2204 return packages with that dm_upload_allowed setting
2206 @type session: Session
2207 @param session: Optional SQL session object (a temporary one will be
2208 generated if not supplied)
2211 @return: list of DBSource objects for the given name (may be empty)
2214 q = session.query(DBSource).filter_by(source=source)
2216 if version is not None:
2217 q = q.filter_by(version=version)
2219 if dm_upload_allowed is not None:
2220 q = q.filter_by(dm_upload_allowed=dm_upload_allowed)
2224 __all__.append('get_sources_from_name')
2227 def get_source_in_suite(source, suite, session=None):
2229 Returns list of DBSource objects for a combination of C{source} and C{suite}.
2231 - B{source} - source package name, eg. I{mailfilter}, I{bbdb}, I{glibc}
2232 - B{suite} - a suite name, eg. I{unstable}
2234 @type source: string
2235 @param source: source package name
2238 @param suite: the suite name
2241 @return: the version for I{source} in I{suite}
2245 q = session.query(SrcAssociation)
2246 q = q.join('source').filter_by(source=source)
2247 q = q.join('suite').filter_by(suite_name=suite)
2250 return q.one().source
2251 except NoResultFound:
2254 __all__.append('get_source_in_suite')
2256 ################################################################################
2259 def add_dsc_to_db(u, filename, session=None):
2260 entry = u.pkg.files[filename]
2264 source.source = u.pkg.dsc["source"]
2265 source.version = u.pkg.dsc["version"] # NB: not files[file]["version"], that has no epoch
2266 source.maintainer_id = get_or_set_maintainer(u.pkg.dsc["maintainer"], session).maintainer_id
2267 source.changedby_id = get_or_set_maintainer(u.pkg.changes["changed-by"], session).maintainer_id
2268 source.fingerprint_id = get_or_set_fingerprint(u.pkg.changes["fingerprint"], session).fingerprint_id
2269 source.install_date = datetime.now().date()
2271 dsc_component = entry["component"]
2272 dsc_location_id = entry["location id"]
2274 source.dm_upload_allowed = (u.pkg.dsc.get("dm-upload-allowed", '') == "yes")
2276 # Set up a new poolfile if necessary
2277 if not entry.has_key("files id") or not entry["files id"]:
2278 filename = entry["pool name"] + filename
2279 poolfile = add_poolfile(filename, entry, dsc_location_id, session)
2281 pfs.append(poolfile)
2282 entry["files id"] = poolfile.file_id
2284 source.poolfile_id = entry["files id"]
2288 for suite_name in u.pkg.changes["distribution"].keys():
2289 sa = SrcAssociation()
2290 sa.source_id = source.source_id
2291 sa.suite_id = get_suite(suite_name).suite_id
2296 # Add the source files to the DB (files and dsc_files)
2298 dscfile.source_id = source.source_id
2299 dscfile.poolfile_id = entry["files id"]
2300 session.add(dscfile)
2302 for dsc_file, dentry in u.pkg.dsc_files.items():
2304 df.source_id = source.source_id
2306 # If the .orig tarball is already in the pool, it's
2307 # files id is stored in dsc_files by check_dsc().
2308 files_id = dentry.get("files id", None)
2310 # Find the entry in the files hash
2311 # TODO: Bail out here properly
2313 for f, e in u.pkg.files.items():
2318 if files_id is None:
2319 filename = dfentry["pool name"] + dsc_file
2321 (found, obj) = check_poolfile(filename, dentry["size"], dentry["md5sum"], dsc_location_id)
2322 # FIXME: needs to check for -1/-2 and or handle exception
2323 if found and obj is not None:
2324 files_id = obj.file_id
2327 # If still not found, add it
2328 if files_id is None:
2329 # HACK: Force sha1sum etc into dentry
2330 dentry["sha1sum"] = dfentry["sha1sum"]
2331 dentry["sha256sum"] = dfentry["sha256sum"]
2332 poolfile = add_poolfile(filename, dentry, dsc_location_id, session)
2333 pfs.append(poolfile)
2334 files_id = poolfile.file_id
2336 poolfile = get_poolfile_by_id(files_id, session)
2337 if poolfile is None:
2338 utils.fubar("INTERNAL ERROR. Found no poolfile with id %d" % files_id)
2339 pfs.append(poolfile)
2341 df.poolfile_id = files_id
2346 # Add the src_uploaders to the DB
2347 uploader_ids = [source.maintainer_id]
2348 if u.pkg.dsc.has_key("uploaders"):
2349 for up in u.pkg.dsc["uploaders"].replace(">, ", ">\t").split("\t"):
2351 uploader_ids.append(get_or_set_maintainer(up, session).maintainer_id)
2354 for up_id in uploader_ids:
2355 if added_ids.has_key(up_id):
2357 utils.warn("Already saw uploader %s for source %s" % (up_id, source.source))
2363 su.maintainer_id = up_id
2364 su.source_id = source.source_id
2369 return source, dsc_component, dsc_location_id, pfs
2371 __all__.append('add_dsc_to_db')
2374 def add_deb_to_db(u, filename, session=None):
2376 Contrary to what you might expect, this routine deals with both
2377 debs and udebs. That info is in 'dbtype', whilst 'type' is
2378 'deb' for both of them
2381 entry = u.pkg.files[filename]
2384 bin.package = entry["package"]
2385 bin.version = entry["version"]
2386 bin.maintainer_id = get_or_set_maintainer(entry["maintainer"], session).maintainer_id
2387 bin.fingerprint_id = get_or_set_fingerprint(u.pkg.changes["fingerprint"], session).fingerprint_id
2388 bin.arch_id = get_architecture(entry["architecture"], session).arch_id
2389 bin.binarytype = entry["dbtype"]
2392 filename = entry["pool name"] + filename
2393 fullpath = os.path.join(cnf["Dir::Pool"], filename)
2394 if not entry.get("location id", None):
2395 entry["location id"] = get_location(cnf["Dir::Pool"], entry["component"], session=session).location_id
2397 if entry.get("files id", None):
2398 poolfile = get_poolfile_by_id(bin.poolfile_id)
2399 bin.poolfile_id = entry["files id"]
2401 poolfile = add_poolfile(filename, entry, entry["location id"], session)
2402 bin.poolfile_id = entry["files id"] = poolfile.file_id
2405 bin_sources = get_sources_from_name(entry["source package"], entry["source version"], session=session)
2406 if len(bin_sources) != 1:
2407 raise NoSourceFieldError, "Unable to find a unique source id for %s (%s), %s, file %s, type %s, signed by %s" % \
2408 (bin.package, bin.version, entry["architecture"],
2409 filename, bin.binarytype, u.pkg.changes["fingerprint"])
2411 bin.source_id = bin_sources[0].source_id
2413 # Add and flush object so it has an ID
2417 # Add BinAssociations
2418 for suite_name in u.pkg.changes["distribution"].keys():
2419 ba = BinAssociation()
2420 ba.binary_id = bin.binary_id
2421 ba.suite_id = get_suite(suite_name).suite_id
2426 # Deal with contents - disabled for now
2427 #contents = copy_temporary_contents(bin.package, bin.version, bin.architecture.arch_string, os.path.basename(filename), None, session)
2429 # print "REJECT\nCould not determine contents of package %s" % bin.package
2430 # session.rollback()
2431 # raise MissingContents, "No contents stored for package %s, and couldn't determine contents of %s" % (bin.package, filename)
2435 __all__.append('add_deb_to_db')
2437 ################################################################################
2439 class SourceACL(object):
2440 def __init__(self, *args, **kwargs):
2444 return '<SourceACL %s>' % self.source_acl_id
2446 __all__.append('SourceACL')
2448 ################################################################################
2450 class SrcAssociation(object):
2451 def __init__(self, *args, **kwargs):
2455 return '<SrcAssociation %s (%s, %s)>' % (self.sa_id, self.source, self.suite)
2457 __all__.append('SrcAssociation')
2459 ################################################################################
2461 class SrcFormat(object):
2462 def __init__(self, *args, **kwargs):
2466 return '<SrcFormat %s>' % (self.format_name)
2468 __all__.append('SrcFormat')
2470 ################################################################################
2472 class SrcUploader(object):
2473 def __init__(self, *args, **kwargs):
2477 return '<SrcUploader %s>' % self.uploader_id
2479 __all__.append('SrcUploader')
2481 ################################################################################
2483 SUITE_FIELDS = [ ('SuiteName', 'suite_name'),
2484 ('SuiteID', 'suite_id'),
2485 ('Version', 'version'),
2486 ('Origin', 'origin'),
2488 ('Description', 'description'),
2489 ('Untouchable', 'untouchable'),
2490 ('Announce', 'announce'),
2491 ('Codename', 'codename'),
2492 ('OverrideCodename', 'overridecodename'),
2493 ('ValidTime', 'validtime'),
2494 ('Priority', 'priority'),
2495 ('NotAutomatic', 'notautomatic'),
2496 ('CopyChanges', 'copychanges'),
2497 ('OverrideSuite', 'overridesuite')]
2499 class Suite(object):
2500 def __init__(self, suite_name = None, version = None):
2501 self.suite_name = suite_name
2502 self.version = version
2505 return '<Suite %s>' % self.suite_name
2507 def __eq__(self, val):
2508 if isinstance(val, str):
2509 return (self.suite_name == val)
2510 # This signals to use the normal comparison operator
2511 return NotImplemented
2513 def __ne__(self, val):
2514 if isinstance(val, str):
2515 return (self.suite_name != val)
2516 # This signals to use the normal comparison operator
2517 return NotImplemented
2521 for disp, field in SUITE_FIELDS:
2522 val = getattr(self, field, None)
2524 ret.append("%s: %s" % (disp, val))
2526 return "\n".join(ret)
2528 def get_architectures(self, skipsrc=False, skipall=False):
2530 Returns list of Architecture objects
2532 @type skipsrc: boolean
2533 @param skipsrc: Whether to skip returning the 'source' architecture entry
2536 @type skipall: boolean
2537 @param skipall: Whether to skip returning the 'all' architecture entry
2541 @return: list of Architecture objects for the given name (may be empty)
2544 q = object_session(self).query(Architecture). \
2545 filter(Architecture.suites.contains(self))
2547 q = q.filter(Architecture.arch_string != 'source')
2549 q = q.filter(Architecture.arch_string != 'all')
2550 return q.order_by(Architecture.arch_string).all()
2552 __all__.append('Suite')
2555 def get_suite_architecture(suite, architecture, session=None):
2557 Returns a SuiteArchitecture object given C{suite} and ${arch} or None if it
2561 @param suite: Suite name to search for
2563 @type architecture: str
2564 @param architecture: Architecture name to search for
2566 @type session: Session
2567 @param session: Optional SQL session object (a temporary one will be
2568 generated if not supplied)
2570 @rtype: SuiteArchitecture
2571 @return: the SuiteArchitecture object or None
2574 q = session.query(SuiteArchitecture)
2575 q = q.join(Architecture).filter_by(arch_string=architecture)
2576 q = q.join(Suite).filter_by(suite_name=suite)
2580 except NoResultFound:
2583 __all__.append('get_suite_architecture')
2586 def get_suite(suite, session=None):
2588 Returns Suite object for given C{suite name}.
2591 @param suite: The name of the suite
2593 @type session: Session
2594 @param session: Optional SQLA session object (a temporary one will be
2595 generated if not supplied)
2598 @return: Suite object for the requested suite name (None if not present)
2601 q = session.query(Suite).filter_by(suite_name=suite)
2605 except NoResultFound:
2608 __all__.append('get_suite')
2610 ################################################################################
2612 # TODO: remove SuiteArchitecture class
2613 class SuiteArchitecture(object):
2614 def __init__(self, *args, **kwargs):
2618 return '<SuiteArchitecture (%s, %s)>' % (self.suite_id, self.arch_id)
2620 __all__.append('SuiteArchitecture')
2622 # TODO: should be removed because the implementation is too trivial
2624 def get_suite_architectures(suite, skipsrc=False, skipall=False, session=None):
2626 Returns list of Architecture objects for given C{suite} name
2629 @param suite: Suite name to search for
2631 @type skipsrc: boolean
2632 @param skipsrc: Whether to skip returning the 'source' architecture entry
2635 @type skipall: boolean
2636 @param skipall: Whether to skip returning the 'all' architecture entry
2639 @type session: Session
2640 @param session: Optional SQL session object (a temporary one will be
2641 generated if not supplied)
2644 @return: list of Architecture objects for the given name (may be empty)
2647 return get_suite(suite, session).get_architectures(skipsrc, skipall)
2649 __all__.append('get_suite_architectures')
2651 ################################################################################
2653 class SuiteSrcFormat(object):
2654 def __init__(self, *args, **kwargs):
2658 return '<SuiteSrcFormat (%s, %s)>' % (self.suite_id, self.src_format_id)
2660 __all__.append('SuiteSrcFormat')
2663 def get_suite_src_formats(suite, session=None):
2665 Returns list of allowed SrcFormat for C{suite}.
2668 @param suite: Suite name to search for
2670 @type session: Session
2671 @param session: Optional SQL session object (a temporary one will be
2672 generated if not supplied)
2675 @return: the list of allowed source formats for I{suite}
2678 q = session.query(SrcFormat)
2679 q = q.join(SuiteSrcFormat)
2680 q = q.join(Suite).filter_by(suite_name=suite)
2681 q = q.order_by('format_name')
2685 __all__.append('get_suite_src_formats')
2687 ################################################################################
2690 def __init__(self, uid = None, name = None):
2694 def __eq__(self, val):
2695 if isinstance(val, str):
2696 return (self.uid == val)
2697 # This signals to use the normal comparison operator
2698 return NotImplemented
2700 def __ne__(self, val):
2701 if isinstance(val, str):
2702 return (self.uid != val)
2703 # This signals to use the normal comparison operator
2704 return NotImplemented
2707 return '<Uid %s (%s)>' % (self.uid, self.name)
2709 __all__.append('Uid')
2712 def get_or_set_uid(uidname, session=None):
2714 Returns uid object for given uidname.
2716 If no matching uidname is found, a row is inserted.
2718 @type uidname: string
2719 @param uidname: The uid to add
2721 @type session: SQLAlchemy
2722 @param session: Optional SQL session object (a temporary one will be
2723 generated if not supplied). If not passed, a commit will be performed at
2724 the end of the function, otherwise the caller is responsible for commiting.
2727 @return: the uid object for the given uidname
2730 q = session.query(Uid).filter_by(uid=uidname)
2734 except NoResultFound:
2738 session.commit_or_flush()
2743 __all__.append('get_or_set_uid')
2746 def get_uid_from_fingerprint(fpr, session=None):
2747 q = session.query(Uid)
2748 q = q.join(Fingerprint).filter_by(fingerprint=fpr)
2752 except NoResultFound:
2755 __all__.append('get_uid_from_fingerprint')
2757 ################################################################################
2759 class UploadBlock(object):
2760 def __init__(self, *args, **kwargs):
2764 return '<UploadBlock %s (%s)>' % (self.source, self.upload_block_id)
2766 __all__.append('UploadBlock')
2768 ################################################################################
2770 class DBConn(object):
2772 database module init.
2776 def __init__(self, *args, **kwargs):
2777 self.__dict__ = self.__shared_state
2779 if not getattr(self, 'initialised', False):
2780 self.initialised = True
2781 self.debug = kwargs.has_key('debug')
2784 def __setuptables(self):
2785 tables_with_primary = (
2796 'changes_pending_binaries',
2797 'changes_pending_files',
2798 'changes_pending_source',
2808 'pending_bin_contents',
2820 # The following tables have primary keys but sqlalchemy
2821 # version 0.5 fails to reflect them correctly with database
2822 # versions before upgrade #41.
2824 #'build_queue_files',
2827 tables_no_primary = (
2829 'changes_pending_files_map',
2830 'changes_pending_source_files',
2831 'changes_pool_files',
2834 'suite_architectures',
2835 'suite_src_formats',
2836 'suite_build_queue_copy',
2838 # see the comment above
2840 'build_queue_files',
2844 'almost_obsolete_all_associations',
2845 'almost_obsolete_src_associations',
2846 'any_associations_source',
2847 'bin_assoc_by_arch',
2848 'bin_associations_binaries',
2849 'binaries_suite_arch',
2850 'binfiles_suite_component_arch',
2853 'newest_all_associations',
2854 'newest_any_associations',
2856 'newest_src_association',
2857 'obsolete_all_associations',
2858 'obsolete_any_associations',
2859 'obsolete_any_by_all_associations',
2860 'obsolete_src_associations',
2862 'src_associations_bin',
2863 'src_associations_src',
2864 'suite_arch_by_name',
2867 # Sqlalchemy version 0.5 fails to reflect the SERIAL type
2868 # correctly and that is why we have to use a workaround. It can
2869 # be removed as soon as we switch to version 0.6.
2870 for table_name in tables_with_primary:
2871 table = Table(table_name, self.db_meta, \
2872 Column('id', Integer, primary_key = True), \
2873 autoload=True, useexisting=True)
2874 setattr(self, 'tbl_%s' % table_name, table)
2876 for table_name in tables_no_primary:
2877 table = Table(table_name, self.db_meta, autoload=True)
2878 setattr(self, 'tbl_%s' % table_name, table)
2880 for view_name in views:
2881 view = Table(view_name, self.db_meta, autoload=True)
2882 setattr(self, 'view_%s' % view_name, view)
2884 def __setupmappers(self):
2885 mapper(Architecture, self.tbl_architecture,
2886 properties = dict(arch_id = self.tbl_architecture.c.id,
2887 suites = relation(Suite, secondary=self.tbl_suite_architectures, backref='architectures', order_by='suite_name')))
2889 mapper(Archive, self.tbl_archive,
2890 properties = dict(archive_id = self.tbl_archive.c.id,
2891 archive_name = self.tbl_archive.c.name))
2893 mapper(BinAssociation, self.tbl_bin_associations,
2894 properties = dict(ba_id = self.tbl_bin_associations.c.id,
2895 suite_id = self.tbl_bin_associations.c.suite,
2896 suite = relation(Suite),
2897 binary_id = self.tbl_bin_associations.c.bin,
2898 binary = relation(DBBinary)))
2900 mapper(PendingBinContents, self.tbl_pending_bin_contents,
2901 properties = dict(contents_id =self.tbl_pending_bin_contents.c.id,
2902 filename = self.tbl_pending_bin_contents.c.filename,
2903 package = self.tbl_pending_bin_contents.c.package,
2904 version = self.tbl_pending_bin_contents.c.version,
2905 arch = self.tbl_pending_bin_contents.c.arch,
2906 otype = self.tbl_pending_bin_contents.c.type))
2908 mapper(DebContents, self.tbl_deb_contents,
2909 properties = dict(binary_id=self.tbl_deb_contents.c.binary_id,
2910 package=self.tbl_deb_contents.c.package,
2911 suite=self.tbl_deb_contents.c.suite,
2912 arch=self.tbl_deb_contents.c.arch,
2913 section=self.tbl_deb_contents.c.section,
2914 filename=self.tbl_deb_contents.c.filename))
2916 mapper(UdebContents, self.tbl_udeb_contents,
2917 properties = dict(binary_id=self.tbl_udeb_contents.c.binary_id,
2918 package=self.tbl_udeb_contents.c.package,
2919 suite=self.tbl_udeb_contents.c.suite,
2920 arch=self.tbl_udeb_contents.c.arch,
2921 section=self.tbl_udeb_contents.c.section,
2922 filename=self.tbl_udeb_contents.c.filename))
2924 mapper(BuildQueue, self.tbl_build_queue,
2925 properties = dict(queue_id = self.tbl_build_queue.c.id))
2927 mapper(BuildQueueFile, self.tbl_build_queue_files,
2928 properties = dict(buildqueue = relation(BuildQueue, backref='queuefiles'),
2929 poolfile = relation(PoolFile, backref='buildqueueinstances')))
2931 mapper(DBBinary, self.tbl_binaries,
2932 properties = dict(binary_id = self.tbl_binaries.c.id,
2933 package = self.tbl_binaries.c.package,
2934 version = self.tbl_binaries.c.version,
2935 maintainer_id = self.tbl_binaries.c.maintainer,
2936 maintainer = relation(Maintainer),
2937 source_id = self.tbl_binaries.c.source,
2938 source = relation(DBSource),
2939 arch_id = self.tbl_binaries.c.architecture,
2940 architecture = relation(Architecture),
2941 poolfile_id = self.tbl_binaries.c.file,
2942 poolfile = relation(PoolFile),
2943 binarytype = self.tbl_binaries.c.type,
2944 fingerprint_id = self.tbl_binaries.c.sig_fpr,
2945 fingerprint = relation(Fingerprint),
2946 install_date = self.tbl_binaries.c.install_date,
2947 binassociations = relation(BinAssociation,
2948 primaryjoin=(self.tbl_binaries.c.id==self.tbl_bin_associations.c.bin))))
2950 mapper(BinaryACL, self.tbl_binary_acl,
2951 properties = dict(binary_acl_id = self.tbl_binary_acl.c.id))
2953 mapper(BinaryACLMap, self.tbl_binary_acl_map,
2954 properties = dict(binary_acl_map_id = self.tbl_binary_acl_map.c.id,
2955 fingerprint = relation(Fingerprint, backref="binary_acl_map"),
2956 architecture = relation(Architecture)))
2958 mapper(Component, self.tbl_component,
2959 properties = dict(component_id = self.tbl_component.c.id,
2960 component_name = self.tbl_component.c.name))
2962 mapper(DBConfig, self.tbl_config,
2963 properties = dict(config_id = self.tbl_config.c.id))
2965 mapper(DSCFile, self.tbl_dsc_files,
2966 properties = dict(dscfile_id = self.tbl_dsc_files.c.id,
2967 source_id = self.tbl_dsc_files.c.source,
2968 source = relation(DBSource),
2969 poolfile_id = self.tbl_dsc_files.c.file,
2970 poolfile = relation(PoolFile)))
2972 mapper(PoolFile, self.tbl_files,
2973 properties = dict(file_id = self.tbl_files.c.id,
2974 filesize = self.tbl_files.c.size,
2975 location_id = self.tbl_files.c.location,
2976 location = relation(Location)))
2978 mapper(Fingerprint, self.tbl_fingerprint,
2979 properties = dict(fingerprint_id = self.tbl_fingerprint.c.id,
2980 uid_id = self.tbl_fingerprint.c.uid,
2981 uid = relation(Uid),
2982 keyring_id = self.tbl_fingerprint.c.keyring,
2983 keyring = relation(Keyring),
2984 source_acl = relation(SourceACL),
2985 binary_acl = relation(BinaryACL)))
2987 mapper(Keyring, self.tbl_keyrings,
2988 properties = dict(keyring_name = self.tbl_keyrings.c.name,
2989 keyring_id = self.tbl_keyrings.c.id))
2991 mapper(DBChange, self.tbl_changes,
2992 properties = dict(change_id = self.tbl_changes.c.id,
2993 poolfiles = relation(PoolFile,
2994 secondary=self.tbl_changes_pool_files,
2995 backref="changeslinks"),
2996 seen = self.tbl_changes.c.seen,
2997 source = self.tbl_changes.c.source,
2998 binaries = self.tbl_changes.c.binaries,
2999 architecture = self.tbl_changes.c.architecture,
3000 distribution = self.tbl_changes.c.distribution,
3001 urgency = self.tbl_changes.c.urgency,
3002 maintainer = self.tbl_changes.c.maintainer,
3003 changedby = self.tbl_changes.c.changedby,
3004 date = self.tbl_changes.c.date,
3005 version = self.tbl_changes.c.version,
3006 files = relation(ChangePendingFile,
3007 secondary=self.tbl_changes_pending_files_map,
3008 backref="changesfile"),
3009 in_queue_id = self.tbl_changes.c.in_queue,
3010 in_queue = relation(PolicyQueue,
3011 primaryjoin=(self.tbl_changes.c.in_queue==self.tbl_policy_queue.c.id)),
3012 approved_for_id = self.tbl_changes.c.approved_for))
3014 mapper(ChangePendingBinary, self.tbl_changes_pending_binaries,
3015 properties = dict(change_pending_binary_id = self.tbl_changes_pending_binaries.c.id))
3017 mapper(ChangePendingFile, self.tbl_changes_pending_files,
3018 properties = dict(change_pending_file_id = self.tbl_changes_pending_files.c.id,
3019 filename = self.tbl_changes_pending_files.c.filename,
3020 size = self.tbl_changes_pending_files.c.size,
3021 md5sum = self.tbl_changes_pending_files.c.md5sum,
3022 sha1sum = self.tbl_changes_pending_files.c.sha1sum,
3023 sha256sum = self.tbl_changes_pending_files.c.sha256sum))
3025 mapper(ChangePendingSource, self.tbl_changes_pending_source,
3026 properties = dict(change_pending_source_id = self.tbl_changes_pending_source.c.id,
3027 change = relation(DBChange),
3028 maintainer = relation(Maintainer,
3029 primaryjoin=(self.tbl_changes_pending_source.c.maintainer_id==self.tbl_maintainer.c.id)),
3030 changedby = relation(Maintainer,
3031 primaryjoin=(self.tbl_changes_pending_source.c.changedby_id==self.tbl_maintainer.c.id)),
3032 fingerprint = relation(Fingerprint),
3033 source_files = relation(ChangePendingFile,
3034 secondary=self.tbl_changes_pending_source_files,
3035 backref="pending_sources")))
3038 mapper(KeyringACLMap, self.tbl_keyring_acl_map,
3039 properties = dict(keyring_acl_map_id = self.tbl_keyring_acl_map.c.id,
3040 keyring = relation(Keyring, backref="keyring_acl_map"),
3041 architecture = relation(Architecture)))
3043 mapper(Location, self.tbl_location,
3044 properties = dict(location_id = self.tbl_location.c.id,
3045 component_id = self.tbl_location.c.component,
3046 component = relation(Component),
3047 archive_id = self.tbl_location.c.archive,
3048 archive = relation(Archive),
3049 archive_type = self.tbl_location.c.type))
3051 mapper(Maintainer, self.tbl_maintainer,
3052 properties = dict(maintainer_id = self.tbl_maintainer.c.id))
3054 mapper(NewComment, self.tbl_new_comments,
3055 properties = dict(comment_id = self.tbl_new_comments.c.id))
3057 mapper(Override, self.tbl_override,
3058 properties = dict(suite_id = self.tbl_override.c.suite,
3059 suite = relation(Suite),
3060 package = self.tbl_override.c.package,
3061 component_id = self.tbl_override.c.component,
3062 component = relation(Component),
3063 priority_id = self.tbl_override.c.priority,
3064 priority = relation(Priority),
3065 section_id = self.tbl_override.c.section,
3066 section = relation(Section),
3067 overridetype_id = self.tbl_override.c.type,
3068 overridetype = relation(OverrideType)))
3070 mapper(OverrideType, self.tbl_override_type,
3071 properties = dict(overridetype = self.tbl_override_type.c.type,
3072 overridetype_id = self.tbl_override_type.c.id))
3074 mapper(PolicyQueue, self.tbl_policy_queue,
3075 properties = dict(policy_queue_id = self.tbl_policy_queue.c.id))
3077 mapper(Priority, self.tbl_priority,
3078 properties = dict(priority_id = self.tbl_priority.c.id))
3080 mapper(Section, self.tbl_section,
3081 properties = dict(section_id = self.tbl_section.c.id,
3082 section=self.tbl_section.c.section))
3084 mapper(DBSource, self.tbl_source,
3085 properties = dict(source_id = self.tbl_source.c.id,
3086 version = self.tbl_source.c.version,
3087 maintainer_id = self.tbl_source.c.maintainer,
3088 maintainer = relation(Maintainer,
3089 primaryjoin=(self.tbl_source.c.maintainer==self.tbl_maintainer.c.id)),
3090 poolfile_id = self.tbl_source.c.file,
3091 poolfile = relation(PoolFile),
3092 fingerprint_id = self.tbl_source.c.sig_fpr,
3093 fingerprint = relation(Fingerprint),
3094 changedby_id = self.tbl_source.c.changedby,
3095 changedby = relation(Maintainer,
3096 primaryjoin=(self.tbl_source.c.changedby==self.tbl_maintainer.c.id)),
3097 srcfiles = relation(DSCFile,
3098 primaryjoin=(self.tbl_source.c.id==self.tbl_dsc_files.c.source)),
3099 srcassociations = relation(SrcAssociation,
3100 primaryjoin=(self.tbl_source.c.id==self.tbl_src_associations.c.source)),
3101 srcuploaders = relation(SrcUploader)))
3103 mapper(SourceACL, self.tbl_source_acl,
3104 properties = dict(source_acl_id = self.tbl_source_acl.c.id))
3106 mapper(SrcAssociation, self.tbl_src_associations,
3107 properties = dict(sa_id = self.tbl_src_associations.c.id,
3108 suite_id = self.tbl_src_associations.c.suite,
3109 suite = relation(Suite),
3110 source_id = self.tbl_src_associations.c.source,
3111 source = relation(DBSource)))
3113 mapper(SrcFormat, self.tbl_src_format,
3114 properties = dict(src_format_id = self.tbl_src_format.c.id,
3115 format_name = self.tbl_src_format.c.format_name))
3117 mapper(SrcUploader, self.tbl_src_uploaders,
3118 properties = dict(uploader_id = self.tbl_src_uploaders.c.id,
3119 source_id = self.tbl_src_uploaders.c.source,
3120 source = relation(DBSource,
3121 primaryjoin=(self.tbl_src_uploaders.c.source==self.tbl_source.c.id)),
3122 maintainer_id = self.tbl_src_uploaders.c.maintainer,
3123 maintainer = relation(Maintainer,
3124 primaryjoin=(self.tbl_src_uploaders.c.maintainer==self.tbl_maintainer.c.id))))
3126 mapper(Suite, self.tbl_suite,
3127 properties = dict(suite_id = self.tbl_suite.c.id,
3128 policy_queue = relation(PolicyQueue),
3129 copy_queues = relation(BuildQueue, secondary=self.tbl_suite_build_queue_copy)))
3131 mapper(SuiteArchitecture, self.tbl_suite_architectures,
3132 properties = dict(suite_id = self.tbl_suite_architectures.c.suite,
3133 suite = relation(Suite, backref='suitearchitectures'),
3134 arch_id = self.tbl_suite_architectures.c.architecture,
3135 architecture = relation(Architecture)))
3137 mapper(SuiteSrcFormat, self.tbl_suite_src_formats,
3138 properties = dict(suite_id = self.tbl_suite_src_formats.c.suite,
3139 suite = relation(Suite, backref='suitesrcformats'),
3140 src_format_id = self.tbl_suite_src_formats.c.src_format,
3141 src_format = relation(SrcFormat)))
3143 mapper(Uid, self.tbl_uid,
3144 properties = dict(uid_id = self.tbl_uid.c.id,
3145 fingerprint = relation(Fingerprint)))
3147 mapper(UploadBlock, self.tbl_upload_blocks,
3148 properties = dict(upload_block_id = self.tbl_upload_blocks.c.id,
3149 fingerprint = relation(Fingerprint, backref="uploadblocks"),
3150 uid = relation(Uid, backref="uploadblocks")))
3152 ## Connection functions
3153 def __createconn(self):
3154 from config import Config
3158 connstr = "postgres://%s" % cnf["DB::Host"]
3159 if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
3160 connstr += ":%s" % cnf["DB::Port"]
3161 connstr += "/%s" % cnf["DB::Name"]
3164 connstr = "postgres:///%s" % cnf["DB::Name"]
3165 if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
3166 connstr += "?port=%s" % cnf["DB::Port"]
3168 self.db_pg = create_engine(connstr, echo=self.debug)
3169 self.db_meta = MetaData()
3170 self.db_meta.bind = self.db_pg
3171 self.db_smaker = sessionmaker(bind=self.db_pg,
3175 self.__setuptables()
3176 self.__setupmappers()
3179 return self.db_smaker()
3181 __all__.append('DBConn')