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(suite, session=None):
2557 Returns Suite object for given C{suite name}.
2560 @param suite: The name of the suite
2562 @type session: Session
2563 @param session: Optional SQLA session object (a temporary one will be
2564 generated if not supplied)
2567 @return: Suite object for the requested suite name (None if not present)
2570 q = session.query(Suite).filter_by(suite_name=suite)
2574 except NoResultFound:
2577 __all__.append('get_suite')
2579 ################################################################################
2581 # TODO: should be removed because the implementation is too trivial
2583 def get_suite_architectures(suite, skipsrc=False, skipall=False, session=None):
2585 Returns list of Architecture objects for given C{suite} name
2588 @param suite: Suite name to search for
2590 @type skipsrc: boolean
2591 @param skipsrc: Whether to skip returning the 'source' architecture entry
2594 @type skipall: boolean
2595 @param skipall: Whether to skip returning the 'all' architecture entry
2598 @type session: Session
2599 @param session: Optional SQL session object (a temporary one will be
2600 generated if not supplied)
2603 @return: list of Architecture objects for the given name (may be empty)
2606 return get_suite(suite, session).get_architectures(skipsrc, skipall)
2608 __all__.append('get_suite_architectures')
2610 ################################################################################
2612 class SuiteSrcFormat(object):
2613 def __init__(self, *args, **kwargs):
2617 return '<SuiteSrcFormat (%s, %s)>' % (self.suite_id, self.src_format_id)
2619 __all__.append('SuiteSrcFormat')
2622 def get_suite_src_formats(suite, session=None):
2624 Returns list of allowed SrcFormat for C{suite}.
2627 @param suite: Suite name to search for
2629 @type session: Session
2630 @param session: Optional SQL session object (a temporary one will be
2631 generated if not supplied)
2634 @return: the list of allowed source formats for I{suite}
2637 q = session.query(SrcFormat)
2638 q = q.join(SuiteSrcFormat)
2639 q = q.join(Suite).filter_by(suite_name=suite)
2640 q = q.order_by('format_name')
2644 __all__.append('get_suite_src_formats')
2646 ################################################################################
2649 def __init__(self, uid = None, name = None):
2653 def __eq__(self, val):
2654 if isinstance(val, str):
2655 return (self.uid == val)
2656 # This signals to use the normal comparison operator
2657 return NotImplemented
2659 def __ne__(self, val):
2660 if isinstance(val, str):
2661 return (self.uid != val)
2662 # This signals to use the normal comparison operator
2663 return NotImplemented
2666 return '<Uid %s (%s)>' % (self.uid, self.name)
2668 __all__.append('Uid')
2671 def get_or_set_uid(uidname, session=None):
2673 Returns uid object for given uidname.
2675 If no matching uidname is found, a row is inserted.
2677 @type uidname: string
2678 @param uidname: The uid to add
2680 @type session: SQLAlchemy
2681 @param session: Optional SQL session object (a temporary one will be
2682 generated if not supplied). If not passed, a commit will be performed at
2683 the end of the function, otherwise the caller is responsible for commiting.
2686 @return: the uid object for the given uidname
2689 q = session.query(Uid).filter_by(uid=uidname)
2693 except NoResultFound:
2697 session.commit_or_flush()
2702 __all__.append('get_or_set_uid')
2705 def get_uid_from_fingerprint(fpr, session=None):
2706 q = session.query(Uid)
2707 q = q.join(Fingerprint).filter_by(fingerprint=fpr)
2711 except NoResultFound:
2714 __all__.append('get_uid_from_fingerprint')
2716 ################################################################################
2718 class UploadBlock(object):
2719 def __init__(self, *args, **kwargs):
2723 return '<UploadBlock %s (%s)>' % (self.source, self.upload_block_id)
2725 __all__.append('UploadBlock')
2727 ################################################################################
2729 class DBConn(object):
2731 database module init.
2735 def __init__(self, *args, **kwargs):
2736 self.__dict__ = self.__shared_state
2738 if not getattr(self, 'initialised', False):
2739 self.initialised = True
2740 self.debug = kwargs.has_key('debug')
2743 def __setuptables(self):
2744 tables_with_primary = (
2755 'changes_pending_binaries',
2756 'changes_pending_files',
2757 'changes_pending_source',
2767 'pending_bin_contents',
2779 # The following tables have primary keys but sqlalchemy
2780 # version 0.5 fails to reflect them correctly with database
2781 # versions before upgrade #41.
2783 #'build_queue_files',
2786 tables_no_primary = (
2788 'changes_pending_files_map',
2789 'changes_pending_source_files',
2790 'changes_pool_files',
2793 'suite_architectures',
2794 'suite_src_formats',
2795 'suite_build_queue_copy',
2797 # see the comment above
2799 'build_queue_files',
2803 'almost_obsolete_all_associations',
2804 'almost_obsolete_src_associations',
2805 'any_associations_source',
2806 'bin_assoc_by_arch',
2807 'bin_associations_binaries',
2808 'binaries_suite_arch',
2809 'binfiles_suite_component_arch',
2812 'newest_all_associations',
2813 'newest_any_associations',
2815 'newest_src_association',
2816 'obsolete_all_associations',
2817 'obsolete_any_associations',
2818 'obsolete_any_by_all_associations',
2819 'obsolete_src_associations',
2821 'src_associations_bin',
2822 'src_associations_src',
2823 'suite_arch_by_name',
2826 # Sqlalchemy version 0.5 fails to reflect the SERIAL type
2827 # correctly and that is why we have to use a workaround. It can
2828 # be removed as soon as we switch to version 0.6.
2829 for table_name in tables_with_primary:
2830 table = Table(table_name, self.db_meta, \
2831 Column('id', Integer, primary_key = True), \
2832 autoload=True, useexisting=True)
2833 setattr(self, 'tbl_%s' % table_name, table)
2835 for table_name in tables_no_primary:
2836 table = Table(table_name, self.db_meta, autoload=True)
2837 setattr(self, 'tbl_%s' % table_name, table)
2839 for view_name in views:
2840 view = Table(view_name, self.db_meta, autoload=True)
2841 setattr(self, 'view_%s' % view_name, view)
2843 def __setupmappers(self):
2844 mapper(Architecture, self.tbl_architecture,
2845 properties = dict(arch_id = self.tbl_architecture.c.id,
2846 suites = relation(Suite, secondary=self.tbl_suite_architectures, backref='architectures', order_by='suite_name')))
2848 mapper(Archive, self.tbl_archive,
2849 properties = dict(archive_id = self.tbl_archive.c.id,
2850 archive_name = self.tbl_archive.c.name))
2852 mapper(BinAssociation, self.tbl_bin_associations,
2853 properties = dict(ba_id = self.tbl_bin_associations.c.id,
2854 suite_id = self.tbl_bin_associations.c.suite,
2855 suite = relation(Suite),
2856 binary_id = self.tbl_bin_associations.c.bin,
2857 binary = relation(DBBinary)))
2859 mapper(PendingBinContents, self.tbl_pending_bin_contents,
2860 properties = dict(contents_id =self.tbl_pending_bin_contents.c.id,
2861 filename = self.tbl_pending_bin_contents.c.filename,
2862 package = self.tbl_pending_bin_contents.c.package,
2863 version = self.tbl_pending_bin_contents.c.version,
2864 arch = self.tbl_pending_bin_contents.c.arch,
2865 otype = self.tbl_pending_bin_contents.c.type))
2867 mapper(DebContents, self.tbl_deb_contents,
2868 properties = dict(binary_id=self.tbl_deb_contents.c.binary_id,
2869 package=self.tbl_deb_contents.c.package,
2870 suite=self.tbl_deb_contents.c.suite,
2871 arch=self.tbl_deb_contents.c.arch,
2872 section=self.tbl_deb_contents.c.section,
2873 filename=self.tbl_deb_contents.c.filename))
2875 mapper(UdebContents, self.tbl_udeb_contents,
2876 properties = dict(binary_id=self.tbl_udeb_contents.c.binary_id,
2877 package=self.tbl_udeb_contents.c.package,
2878 suite=self.tbl_udeb_contents.c.suite,
2879 arch=self.tbl_udeb_contents.c.arch,
2880 section=self.tbl_udeb_contents.c.section,
2881 filename=self.tbl_udeb_contents.c.filename))
2883 mapper(BuildQueue, self.tbl_build_queue,
2884 properties = dict(queue_id = self.tbl_build_queue.c.id))
2886 mapper(BuildQueueFile, self.tbl_build_queue_files,
2887 properties = dict(buildqueue = relation(BuildQueue, backref='queuefiles'),
2888 poolfile = relation(PoolFile, backref='buildqueueinstances')))
2890 mapper(DBBinary, self.tbl_binaries,
2891 properties = dict(binary_id = self.tbl_binaries.c.id,
2892 package = self.tbl_binaries.c.package,
2893 version = self.tbl_binaries.c.version,
2894 maintainer_id = self.tbl_binaries.c.maintainer,
2895 maintainer = relation(Maintainer),
2896 source_id = self.tbl_binaries.c.source,
2897 source = relation(DBSource),
2898 arch_id = self.tbl_binaries.c.architecture,
2899 architecture = relation(Architecture),
2900 poolfile_id = self.tbl_binaries.c.file,
2901 poolfile = relation(PoolFile),
2902 binarytype = self.tbl_binaries.c.type,
2903 fingerprint_id = self.tbl_binaries.c.sig_fpr,
2904 fingerprint = relation(Fingerprint),
2905 install_date = self.tbl_binaries.c.install_date,
2906 binassociations = relation(BinAssociation,
2907 primaryjoin=(self.tbl_binaries.c.id==self.tbl_bin_associations.c.bin))))
2909 mapper(BinaryACL, self.tbl_binary_acl,
2910 properties = dict(binary_acl_id = self.tbl_binary_acl.c.id))
2912 mapper(BinaryACLMap, self.tbl_binary_acl_map,
2913 properties = dict(binary_acl_map_id = self.tbl_binary_acl_map.c.id,
2914 fingerprint = relation(Fingerprint, backref="binary_acl_map"),
2915 architecture = relation(Architecture)))
2917 mapper(Component, self.tbl_component,
2918 properties = dict(component_id = self.tbl_component.c.id,
2919 component_name = self.tbl_component.c.name))
2921 mapper(DBConfig, self.tbl_config,
2922 properties = dict(config_id = self.tbl_config.c.id))
2924 mapper(DSCFile, self.tbl_dsc_files,
2925 properties = dict(dscfile_id = self.tbl_dsc_files.c.id,
2926 source_id = self.tbl_dsc_files.c.source,
2927 source = relation(DBSource),
2928 poolfile_id = self.tbl_dsc_files.c.file,
2929 poolfile = relation(PoolFile)))
2931 mapper(PoolFile, self.tbl_files,
2932 properties = dict(file_id = self.tbl_files.c.id,
2933 filesize = self.tbl_files.c.size,
2934 location_id = self.tbl_files.c.location,
2935 location = relation(Location)))
2937 mapper(Fingerprint, self.tbl_fingerprint,
2938 properties = dict(fingerprint_id = self.tbl_fingerprint.c.id,
2939 uid_id = self.tbl_fingerprint.c.uid,
2940 uid = relation(Uid),
2941 keyring_id = self.tbl_fingerprint.c.keyring,
2942 keyring = relation(Keyring),
2943 source_acl = relation(SourceACL),
2944 binary_acl = relation(BinaryACL)))
2946 mapper(Keyring, self.tbl_keyrings,
2947 properties = dict(keyring_name = self.tbl_keyrings.c.name,
2948 keyring_id = self.tbl_keyrings.c.id))
2950 mapper(DBChange, self.tbl_changes,
2951 properties = dict(change_id = self.tbl_changes.c.id,
2952 poolfiles = relation(PoolFile,
2953 secondary=self.tbl_changes_pool_files,
2954 backref="changeslinks"),
2955 seen = self.tbl_changes.c.seen,
2956 source = self.tbl_changes.c.source,
2957 binaries = self.tbl_changes.c.binaries,
2958 architecture = self.tbl_changes.c.architecture,
2959 distribution = self.tbl_changes.c.distribution,
2960 urgency = self.tbl_changes.c.urgency,
2961 maintainer = self.tbl_changes.c.maintainer,
2962 changedby = self.tbl_changes.c.changedby,
2963 date = self.tbl_changes.c.date,
2964 version = self.tbl_changes.c.version,
2965 files = relation(ChangePendingFile,
2966 secondary=self.tbl_changes_pending_files_map,
2967 backref="changesfile"),
2968 in_queue_id = self.tbl_changes.c.in_queue,
2969 in_queue = relation(PolicyQueue,
2970 primaryjoin=(self.tbl_changes.c.in_queue==self.tbl_policy_queue.c.id)),
2971 approved_for_id = self.tbl_changes.c.approved_for))
2973 mapper(ChangePendingBinary, self.tbl_changes_pending_binaries,
2974 properties = dict(change_pending_binary_id = self.tbl_changes_pending_binaries.c.id))
2976 mapper(ChangePendingFile, self.tbl_changes_pending_files,
2977 properties = dict(change_pending_file_id = self.tbl_changes_pending_files.c.id,
2978 filename = self.tbl_changes_pending_files.c.filename,
2979 size = self.tbl_changes_pending_files.c.size,
2980 md5sum = self.tbl_changes_pending_files.c.md5sum,
2981 sha1sum = self.tbl_changes_pending_files.c.sha1sum,
2982 sha256sum = self.tbl_changes_pending_files.c.sha256sum))
2984 mapper(ChangePendingSource, self.tbl_changes_pending_source,
2985 properties = dict(change_pending_source_id = self.tbl_changes_pending_source.c.id,
2986 change = relation(DBChange),
2987 maintainer = relation(Maintainer,
2988 primaryjoin=(self.tbl_changes_pending_source.c.maintainer_id==self.tbl_maintainer.c.id)),
2989 changedby = relation(Maintainer,
2990 primaryjoin=(self.tbl_changes_pending_source.c.changedby_id==self.tbl_maintainer.c.id)),
2991 fingerprint = relation(Fingerprint),
2992 source_files = relation(ChangePendingFile,
2993 secondary=self.tbl_changes_pending_source_files,
2994 backref="pending_sources")))
2997 mapper(KeyringACLMap, self.tbl_keyring_acl_map,
2998 properties = dict(keyring_acl_map_id = self.tbl_keyring_acl_map.c.id,
2999 keyring = relation(Keyring, backref="keyring_acl_map"),
3000 architecture = relation(Architecture)))
3002 mapper(Location, self.tbl_location,
3003 properties = dict(location_id = self.tbl_location.c.id,
3004 component_id = self.tbl_location.c.component,
3005 component = relation(Component),
3006 archive_id = self.tbl_location.c.archive,
3007 archive = relation(Archive),
3008 archive_type = self.tbl_location.c.type))
3010 mapper(Maintainer, self.tbl_maintainer,
3011 properties = dict(maintainer_id = self.tbl_maintainer.c.id))
3013 mapper(NewComment, self.tbl_new_comments,
3014 properties = dict(comment_id = self.tbl_new_comments.c.id))
3016 mapper(Override, self.tbl_override,
3017 properties = dict(suite_id = self.tbl_override.c.suite,
3018 suite = relation(Suite),
3019 package = self.tbl_override.c.package,
3020 component_id = self.tbl_override.c.component,
3021 component = relation(Component),
3022 priority_id = self.tbl_override.c.priority,
3023 priority = relation(Priority),
3024 section_id = self.tbl_override.c.section,
3025 section = relation(Section),
3026 overridetype_id = self.tbl_override.c.type,
3027 overridetype = relation(OverrideType)))
3029 mapper(OverrideType, self.tbl_override_type,
3030 properties = dict(overridetype = self.tbl_override_type.c.type,
3031 overridetype_id = self.tbl_override_type.c.id))
3033 mapper(PolicyQueue, self.tbl_policy_queue,
3034 properties = dict(policy_queue_id = self.tbl_policy_queue.c.id))
3036 mapper(Priority, self.tbl_priority,
3037 properties = dict(priority_id = self.tbl_priority.c.id))
3039 mapper(Section, self.tbl_section,
3040 properties = dict(section_id = self.tbl_section.c.id,
3041 section=self.tbl_section.c.section))
3043 mapper(DBSource, self.tbl_source,
3044 properties = dict(source_id = self.tbl_source.c.id,
3045 version = self.tbl_source.c.version,
3046 maintainer_id = self.tbl_source.c.maintainer,
3047 maintainer = relation(Maintainer,
3048 primaryjoin=(self.tbl_source.c.maintainer==self.tbl_maintainer.c.id)),
3049 poolfile_id = self.tbl_source.c.file,
3050 poolfile = relation(PoolFile),
3051 fingerprint_id = self.tbl_source.c.sig_fpr,
3052 fingerprint = relation(Fingerprint),
3053 changedby_id = self.tbl_source.c.changedby,
3054 changedby = relation(Maintainer,
3055 primaryjoin=(self.tbl_source.c.changedby==self.tbl_maintainer.c.id)),
3056 srcfiles = relation(DSCFile,
3057 primaryjoin=(self.tbl_source.c.id==self.tbl_dsc_files.c.source)),
3058 srcassociations = relation(SrcAssociation,
3059 primaryjoin=(self.tbl_source.c.id==self.tbl_src_associations.c.source)),
3060 srcuploaders = relation(SrcUploader)))
3062 mapper(SourceACL, self.tbl_source_acl,
3063 properties = dict(source_acl_id = self.tbl_source_acl.c.id))
3065 mapper(SrcAssociation, self.tbl_src_associations,
3066 properties = dict(sa_id = self.tbl_src_associations.c.id,
3067 suite_id = self.tbl_src_associations.c.suite,
3068 suite = relation(Suite),
3069 source_id = self.tbl_src_associations.c.source,
3070 source = relation(DBSource)))
3072 mapper(SrcFormat, self.tbl_src_format,
3073 properties = dict(src_format_id = self.tbl_src_format.c.id,
3074 format_name = self.tbl_src_format.c.format_name))
3076 mapper(SrcUploader, self.tbl_src_uploaders,
3077 properties = dict(uploader_id = self.tbl_src_uploaders.c.id,
3078 source_id = self.tbl_src_uploaders.c.source,
3079 source = relation(DBSource,
3080 primaryjoin=(self.tbl_src_uploaders.c.source==self.tbl_source.c.id)),
3081 maintainer_id = self.tbl_src_uploaders.c.maintainer,
3082 maintainer = relation(Maintainer,
3083 primaryjoin=(self.tbl_src_uploaders.c.maintainer==self.tbl_maintainer.c.id))))
3085 mapper(Suite, self.tbl_suite,
3086 properties = dict(suite_id = self.tbl_suite.c.id,
3087 policy_queue = relation(PolicyQueue),
3088 copy_queues = relation(BuildQueue, secondary=self.tbl_suite_build_queue_copy)))
3090 mapper(SuiteSrcFormat, self.tbl_suite_src_formats,
3091 properties = dict(suite_id = self.tbl_suite_src_formats.c.suite,
3092 suite = relation(Suite, backref='suitesrcformats'),
3093 src_format_id = self.tbl_suite_src_formats.c.src_format,
3094 src_format = relation(SrcFormat)))
3096 mapper(Uid, self.tbl_uid,
3097 properties = dict(uid_id = self.tbl_uid.c.id,
3098 fingerprint = relation(Fingerprint)))
3100 mapper(UploadBlock, self.tbl_upload_blocks,
3101 properties = dict(upload_block_id = self.tbl_upload_blocks.c.id,
3102 fingerprint = relation(Fingerprint, backref="uploadblocks"),
3103 uid = relation(Uid, backref="uploadblocks")))
3105 ## Connection functions
3106 def __createconn(self):
3107 from config import Config
3111 connstr = "postgres://%s" % cnf["DB::Host"]
3112 if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
3113 connstr += ":%s" % cnf["DB::Port"]
3114 connstr += "/%s" % cnf["DB::Name"]
3117 connstr = "postgres:///%s" % cnf["DB::Name"]
3118 if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
3119 connstr += "?port=%s" % cnf["DB::Port"]
3121 self.db_pg = create_engine(connstr, echo=self.debug)
3122 self.db_meta = MetaData()
3123 self.db_meta.bind = self.db_pg
3124 self.db_smaker = sessionmaker(bind=self.db_pg,
3128 self.__setuptables()
3129 self.__setupmappers()
3132 return self.db_smaker()
3134 __all__.append('DBConn')