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 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 ################################################################################
40 from inspect import getargspec
42 from sqlalchemy import create_engine, Table, MetaData, select
43 from sqlalchemy.orm import sessionmaker, mapper, relation
45 # Don't remove this, we re-export the exceptions to scripts which import us
46 from sqlalchemy.exc import *
47 from sqlalchemy.orm.exc import NoResultFound
49 # Only import Config until Queue stuff is changed to store its config
51 from config import Config
52 from singleton import Singleton
53 from textutils import fix_maintainer
55 ################################################################################
57 __all__ = ['IntegrityError', 'SQLAlchemyError']
59 ################################################################################
61 def session_wrapper(fn):
62 def wrapped(*args, **kwargs):
63 private_transaction = False
64 session = kwargs.get('session')
66 # No session specified as last argument or in kwargs, create one.
67 if session is None or len(args) == len(getargspec(fn)[0]) - 1:
68 private_transaction = True
69 kwargs['session'] = DBConn().session()
72 return fn(*args, **kwargs)
74 if private_transaction:
75 # We created a session; close it.
76 kwargs['session'].close()
80 ################################################################################
82 class Architecture(object):
83 def __init__(self, *args, **kwargs):
86 def __eq__(self, val):
87 if isinstance(val, str):
88 return (self.arch_string== val)
89 # This signals to use the normal comparison operator
92 def __ne__(self, val):
93 if isinstance(val, str):
94 return (self.arch_string != val)
95 # This signals to use the normal comparison operator
99 return '<Architecture %s>' % self.arch_string
101 __all__.append('Architecture')
104 def get_architecture(architecture, session=None):
106 Returns database id for given C{architecture}.
108 @type architecture: string
109 @param architecture: The name of the architecture
111 @type session: Session
112 @param session: Optional SQLA session object (a temporary one will be
113 generated if not supplied)
116 @return: Architecture object for the given arch (None if not present)
119 q = session.query(Architecture).filter_by(arch_string=architecture)
123 except NoResultFound:
126 __all__.append('get_architecture')
129 def get_architecture_suites(architecture, session=None):
131 Returns list of Suite objects for given C{architecture} name
134 @param source: Architecture name to search for
136 @type session: Session
137 @param session: Optional SQL session object (a temporary one will be
138 generated if not supplied)
141 @return: list of Suite objects for the given name (may be empty)
144 q = session.query(Suite)
145 q = q.join(SuiteArchitecture)
146 q = q.join(Architecture).filter_by(arch_string=architecture).order_by('suite_name')
152 __all__.append('get_architecture_suites')
154 ################################################################################
156 class Archive(object):
157 def __init__(self, *args, **kwargs):
161 return '<Archive %s>' % self.archive_name
163 __all__.append('Archive')
166 def get_archive(archive, session=None):
168 returns database id for given c{archive}.
170 @type archive: string
171 @param archive: the name of the arhive
173 @type session: Session
174 @param session: Optional SQLA session object (a temporary one will be
175 generated if not supplied)
178 @return: Archive object for the given name (None if not present)
181 archive = archive.lower()
183 q = session.query(Archive).filter_by(archive_name=archive)
187 except NoResultFound:
190 __all__.append('get_archive')
192 ################################################################################
194 class BinAssociation(object):
195 def __init__(self, *args, **kwargs):
199 return '<BinAssociation %s (%s, %s)>' % (self.ba_id, self.binary, self.suite)
201 __all__.append('BinAssociation')
203 ################################################################################
205 class DBBinary(object):
206 def __init__(self, *args, **kwargs):
210 return '<DBBinary %s (%s, %s)>' % (self.package, self.version, self.architecture)
212 __all__.append('DBBinary')
215 def get_suites_binary_in(package, session=None):
217 Returns list of Suite objects which given C{package} name is in
220 @param source: DBBinary package name to search for
223 @return: list of Suite objects for the given package
226 return session.query(Suite).join(BinAssociation).join(DBBinary).filter_by(package=package).all()
228 __all__.append('get_suites_binary_in')
231 def get_binary_from_id(id, session=None):
233 Returns DBBinary object for given C{id}
236 @param id: Id of the required binary
238 @type session: Session
239 @param session: Optional SQLA session object (a temporary one will be
240 generated if not supplied)
243 @return: DBBinary object for the given binary (None if not present)
246 q = session.query(DBBinary).filter_by(binary_id=id)
250 except NoResultFound:
253 __all__.append('get_binary_from_id')
256 def get_binaries_from_name(package, version=None, architecture=None, session=None):
258 Returns list of DBBinary objects for given C{package} name
261 @param package: DBBinary package name to search for
263 @type version: str or None
264 @param version: Version to search for (or None)
266 @type package: str, list or None
267 @param package: Architectures to limit to (or None if no limit)
269 @type session: Session
270 @param session: Optional SQL session object (a temporary one will be
271 generated if not supplied)
274 @return: list of DBBinary objects for the given name (may be empty)
277 q = session.query(DBBinary).filter_by(package=package)
279 if version is not None:
280 q = q.filter_by(version=version)
282 if architecture is not None:
283 if not isinstance(architecture, list):
284 architecture = [architecture]
285 q = q.join(Architecture).filter(Architecture.arch_string.in_(architecture))
291 __all__.append('get_binaries_from_name')
294 def get_binaries_from_source_id(source_id, session=None):
296 Returns list of DBBinary objects for given C{source_id}
299 @param source_id: source_id to search for
301 @type session: Session
302 @param session: Optional SQL session object (a temporary one will be
303 generated if not supplied)
306 @return: list of DBBinary objects for the given name (may be empty)
309 return session.query(DBBinary).filter_by(source_id=source_id).all()
311 __all__.append('get_binaries_from_source_id')
314 def get_binary_from_name_suite(package, suitename, session=None):
315 ### For dak examine-package
316 ### XXX: Doesn't use object API yet
318 sql = """SELECT DISTINCT(b.package), b.version, c.name, su.suite_name
319 FROM binaries b, files fi, location l, component c, bin_associations ba, suite su
320 WHERE b.package=:package
322 AND fi.location = l.id
323 AND l.component = c.id
326 AND su.suite_name=:suitename
327 ORDER BY b.version DESC"""
329 return session.execute(sql, {'package': package, 'suitename': suitename})
331 __all__.append('get_binary_from_name_suite')
334 def get_binary_components(package, suitename, arch, session=None):
335 # Check for packages that have moved from one component to another
336 query = """SELECT c.name FROM binaries b, bin_associations ba, suite s, location l, component c, architecture a, files f
337 WHERE b.package=:package AND s.suite_name=:suitename
338 AND (a.arch_string = :arch OR a.arch_string = 'all')
339 AND ba.bin = b.id AND ba.suite = s.id AND b.architecture = a.id
340 AND f.location = l.id
341 AND l.component = c.id
344 vals = {'package': package, 'suitename': suitename, 'arch': arch}
346 return session.execute(query, vals)
348 __all__.append('get_binary_components')
350 ################################################################################
352 class Component(object):
353 def __init__(self, *args, **kwargs):
356 def __eq__(self, val):
357 if isinstance(val, str):
358 return (self.component_name == val)
359 # This signals to use the normal comparison operator
360 return NotImplemented
362 def __ne__(self, val):
363 if isinstance(val, str):
364 return (self.component_name != val)
365 # This signals to use the normal comparison operator
366 return NotImplemented
369 return '<Component %s>' % self.component_name
372 __all__.append('Component')
375 def get_component(component, session=None):
377 Returns database id for given C{component}.
379 @type component: string
380 @param component: The name of the override type
383 @return: the database id for the given component
386 component = component.lower()
388 q = session.query(Component).filter_by(component_name=component)
392 except NoResultFound:
395 __all__.append('get_component')
397 ################################################################################
399 class DBConfig(object):
400 def __init__(self, *args, **kwargs):
404 return '<DBConfig %s>' % self.name
406 __all__.append('DBConfig')
408 ################################################################################
410 class ContentFilename(object):
411 def __init__(self, *args, **kwargs):
415 return '<ContentFilename %s>' % self.filename
417 __all__.append('ContentFilename')
419 def get_or_set_contents_file_id(filename, session=None):
421 Returns database id for given filename.
423 If no matching file is found, a row is inserted.
425 @type filename: string
426 @param filename: The filename
427 @type session: SQLAlchemy
428 @param session: Optional SQL session object (a temporary one will be
429 generated if not supplied). If not passed, a commit will be performed at
430 the end of the function, otherwise the caller is responsible for commiting.
433 @return: the database id for the given component
437 session = DBConn().session()
440 q = session.query(ContentFilename).filter_by(filename=filename)
443 ret = q.one().cafilename_id
444 except NoResultFound:
445 cf = ContentFilename()
446 cf.filename = filename
452 ret = cf.cafilename_id
459 __all__.append('get_or_set_contents_file_id')
462 def get_contents(suite, overridetype, section=None, session=None):
464 Returns contents for a suite / overridetype combination, limiting
465 to a section if not None.
468 @param suite: Suite object
470 @type overridetype: OverrideType
471 @param overridetype: OverrideType object
473 @type section: Section
474 @param section: Optional section object to limit results to
476 @type session: SQLAlchemy
477 @param session: Optional SQL session object (a temporary one will be
478 generated if not supplied)
481 @return: ResultsProxy object set up to return tuples of (filename, section,
485 # find me all of the contents for a given suite
486 contents_q = """SELECT (p.path||'/'||n.file) AS fn,
490 FROM content_associations c join content_file_paths p ON (c.filepath=p.id)
491 JOIN content_file_names n ON (c.filename=n.id)
492 JOIN binaries b ON (b.id=c.binary_pkg)
493 JOIN override o ON (o.package=b.package)
494 JOIN section s ON (s.id=o.section)
495 WHERE o.suite = :suiteid AND o.type = :overridetypeid
496 AND b.type=:overridetypename"""
498 vals = {'suiteid': suite.suite_id,
499 'overridetypeid': overridetype.overridetype_id,
500 'overridetypename': overridetype.overridetype}
502 if section is not None:
503 contents_q += " AND s.id = :sectionid"
504 vals['sectionid'] = section.section_id
506 contents_q += " ORDER BY fn"
508 return session.execute(contents_q, vals)
510 __all__.append('get_contents')
512 ################################################################################
514 class ContentFilepath(object):
515 def __init__(self, *args, **kwargs):
519 return '<ContentFilepath %s>' % self.filepath
521 __all__.append('ContentFilepath')
523 def get_or_set_contents_path_id(filepath, session=None):
525 Returns database id for given path.
527 If no matching file is found, a row is inserted.
529 @type filename: string
530 @param filename: The filepath
531 @type session: SQLAlchemy
532 @param session: Optional SQL session object (a temporary one will be
533 generated if not supplied). If not passed, a commit will be performed at
534 the end of the function, otherwise the caller is responsible for commiting.
537 @return: the database id for the given path
541 session = DBConn().session()
544 q = session.query(ContentFilepath).filter_by(filepath=filepath)
547 ret = q.one().cafilepath_id
548 except NoResultFound:
549 cf = ContentFilepath()
550 cf.filepath = filepath
556 ret = cf.cafilepath_id
563 __all__.append('get_or_set_contents_path_id')
565 ################################################################################
567 class ContentAssociation(object):
568 def __init__(self, *args, **kwargs):
572 return '<ContentAssociation %s>' % self.ca_id
574 __all__.append('ContentAssociation')
576 def insert_content_paths(binary_id, fullpaths, session=None):
578 Make sure given path is associated with given binary id
581 @param binary_id: the id of the binary
582 @type fullpaths: list
583 @param fullpaths: the list of paths of the file being associated with the binary
584 @type session: SQLAlchemy session
585 @param session: Optional SQLAlchemy session. If this is passed, the caller
586 is responsible for ensuring a transaction has begun and committing the
587 results or rolling back based on the result code. If not passed, a commit
588 will be performed at the end of the function, otherwise the caller is
589 responsible for commiting.
591 @return: True upon success
596 session = DBConn().session()
602 for fullpath in fullpaths:
603 # Get the necessary IDs ...
604 (path, file) = os.path.split(fullpath)
606 filepath_id = get_or_set_contents_path_id(path, session)
607 filename_id = get_or_set_contents_file_id(file, session)
609 pathcache[fullpath] = (filepath_id, filename_id)
611 for fullpath, dat in pathcache.items():
612 ca = ContentAssociation()
613 ca.binary_id = binary_id
614 ca.filepath_id = dat[0]
615 ca.filename_id = dat[1]
618 # Only commit if we set up the session ourself
628 traceback.print_exc()
630 # Only rollback if we set up the session ourself
637 __all__.append('insert_content_paths')
639 ################################################################################
641 class DSCFile(object):
642 def __init__(self, *args, **kwargs):
646 return '<DSCFile %s>' % self.dscfile_id
648 __all__.append('DSCFile')
651 def get_dscfiles(dscfile_id=None, source_id=None, poolfile_id=None, session=None):
653 Returns a list of DSCFiles which may be empty
655 @type dscfile_id: int (optional)
656 @param dscfile_id: the dscfile_id of the DSCFiles to find
658 @type source_id: int (optional)
659 @param source_id: the source id related to the DSCFiles to find
661 @type poolfile_id: int (optional)
662 @param poolfile_id: the poolfile id related to the DSCFiles to find
665 @return: Possibly empty list of DSCFiles
668 q = session.query(DSCFile)
670 if dscfile_id is not None:
671 q = q.filter_by(dscfile_id=dscfile_id)
673 if source_id is not None:
674 q = q.filter_by(source_id=source_id)
676 if poolfile_id is not None:
677 q = q.filter_by(poolfile_id=poolfile_id)
681 __all__.append('get_dscfiles')
683 ################################################################################
685 class PoolFile(object):
686 def __init__(self, *args, **kwargs):
690 return '<PoolFile %s>' % self.filename
692 __all__.append('PoolFile')
695 def check_poolfile(filename, filesize, md5sum, location_id, session=None):
698 (ValidFileFound [boolean or None], PoolFile object or None)
700 @type filename: string
701 @param filename: the filename of the file to check against the DB
704 @param filesize: the size of the file to check against the DB
707 @param md5sum: the md5sum of the file to check against the DB
709 @type location_id: int
710 @param location_id: the id of the location to look in
713 @return: Tuple of length 2.
714 If more than one file found with that name:
716 If valid pool file found: (True, PoolFile object)
717 If valid pool file not found:
718 (False, None) if no file found
719 (False, PoolFile object) if file found with size/md5sum mismatch
722 q = session.query(PoolFile).filter_by(filename=filename)
723 q = q.join(Location).filter_by(location_id=location_id)
733 if obj.md5sum != md5sum or obj.filesize != filesize:
741 __all__.append('check_poolfile')
744 def get_poolfile_by_id(file_id, session=None):
746 Returns a PoolFile objects or None for the given id
749 @param file_id: the id of the file to look for
751 @rtype: PoolFile or None
752 @return: either the PoolFile object or None
755 q = session.query(PoolFile).filter_by(file_id=file_id)
759 except NoResultFound:
762 __all__.append('get_poolfile_by_id')
766 def get_poolfile_by_name(filename, location_id=None, session=None):
768 Returns an array of PoolFile objects for the given filename and
769 (optionally) location_id
771 @type filename: string
772 @param filename: the filename of the file to check against the DB
774 @type location_id: int
775 @param location_id: the id of the location to look in (optional)
778 @return: array of PoolFile objects
781 q = session.query(PoolFile).filter_by(filename=filename)
783 if location_id is not None:
784 q = q.join(Location).filter_by(location_id=location_id)
788 __all__.append('get_poolfile_by_name')
791 def get_poolfile_like_name(filename, session=None):
793 Returns an array of PoolFile objects which are like the given name
795 @type filename: string
796 @param filename: the filename of the file to check against the DB
799 @return: array of PoolFile objects
802 # TODO: There must be a way of properly using bind parameters with %FOO%
803 q = session.query(PoolFile).filter(PoolFile.filename.like('%%%s%%' % filename))
807 __all__.append('get_poolfile_like_name')
809 ################################################################################
811 class Fingerprint(object):
812 def __init__(self, *args, **kwargs):
816 return '<Fingerprint %s>' % self.fingerprint
818 __all__.append('Fingerprint')
820 def get_or_set_fingerprint(fpr, session=None):
822 Returns Fingerprint object for given fpr.
824 If no matching fpr is found, a row is inserted.
827 @param fpr: The fpr to find / add
829 @type session: SQLAlchemy
830 @param session: Optional SQL session object (a temporary one will be
831 generated if not supplied). If not passed, a commit will be performed at
832 the end of the function, otherwise the caller is responsible for commiting.
833 A flush will be performed either way.
836 @return: the Fingerprint object for the given fpr
840 session = DBConn().session()
843 q = session.query(Fingerprint).filter_by(fingerprint=fpr)
847 except NoResultFound:
848 fingerprint = Fingerprint()
849 fingerprint.fingerprint = fpr
850 session.add(fingerprint)
862 __all__.append('get_or_set_fingerprint')
864 ################################################################################
866 class Keyring(object):
867 def __init__(self, *args, **kwargs):
871 return '<Keyring %s>' % self.keyring_name
873 __all__.append('Keyring')
875 def get_or_set_keyring(keyring, session=None):
877 If C{keyring} does not have an entry in the C{keyrings} table yet, create one
878 and return the new Keyring
879 If C{keyring} already has an entry, simply return the existing Keyring
881 @type keyring: string
882 @param keyring: the keyring name
885 @return: the Keyring object for this keyring
890 session = DBConn().session()
894 obj = session.query(Keyring).filter_by(keyring_name=keyring).first()
897 obj = Keyring(keyring_name=keyring)
909 __all__.append('get_or_set_keyring')
911 ################################################################################
913 class Location(object):
914 def __init__(self, *args, **kwargs):
918 return '<Location %s (%s)>' % (self.path, self.location_id)
920 __all__.append('Location')
923 def get_location(location, component=None, archive=None, session=None):
925 Returns Location object for the given combination of location, component
928 @type location: string
929 @param location: the path of the location, e.g. I{/srv/ftp.debian.org/ftp/pool/}
931 @type component: string
932 @param component: the component name (if None, no restriction applied)
934 @type archive: string
935 @param archive_id: the archive name (if None, no restriction applied)
937 @rtype: Location / None
938 @return: Either a Location object or None if one can't be found
941 q = session.query(Location).filter_by(path=location)
943 if archive is not None:
944 q = q.join(Archive).filter_by(archive_name=archive)
946 if component is not None:
947 q = q.join(Component).filter_by(component_name=component)
951 except NoResultFound:
954 __all__.append('get_location')
956 ################################################################################
958 class Maintainer(object):
959 def __init__(self, *args, **kwargs):
963 return '''<Maintainer '%s' (%s)>''' % (self.name, self.maintainer_id)
965 def get_split_maintainer(self):
966 if not hasattr(self, 'name') or self.name is None:
967 return ('', '', '', '')
969 return fix_maintainer(self.name.strip())
971 __all__.append('Maintainer')
973 def get_or_set_maintainer(name, session=None):
975 Returns Maintainer object for given maintainer name.
977 If no matching maintainer name is found, a row is inserted.
980 @param name: The maintainer name to add
982 @type session: SQLAlchemy
983 @param session: Optional SQL session object (a temporary one will be
984 generated if not supplied). If not passed, a commit will be performed at
985 the end of the function, otherwise the caller is responsible for commiting.
986 A flush will be performed either way.
989 @return: the Maintainer object for the given maintainer
993 session = DBConn().session()
996 q = session.query(Maintainer).filter_by(name=name)
999 except NoResultFound:
1000 maintainer = Maintainer()
1001 maintainer.name = name
1002 session.add(maintainer)
1014 __all__.append('get_or_set_maintainer')
1016 def get_maintainer(maintainer_id, session=None):
1018 Return the name of the maintainer behind C{maintainer_id} or None if that
1019 maintainer_id is invalid.
1021 @type maintainer_id: int
1022 @param maintainer_id: the id of the maintainer
1025 @return: the Maintainer with this C{maintainer_id}
1028 privatetrans = False
1030 session = DBConn().session()
1034 return session.query(Maintainer).get(maintainer_id)
1039 __all__.append('get_maintainer')
1041 ################################################################################
1043 class NewComment(object):
1044 def __init__(self, *args, **kwargs):
1048 return '''<NewComment for '%s %s' (%s)>''' % (self.package, self.version, self.comment_id)
1050 __all__.append('NewComment')
1053 def has_new_comment(package, version, session=None):
1055 Returns true if the given combination of C{package}, C{version} has a comment.
1057 @type package: string
1058 @param package: name of the package
1060 @type version: string
1061 @param version: package version
1063 @type session: Session
1064 @param session: Optional SQLA session object (a temporary one will be
1065 generated if not supplied)
1071 q = session.query(NewComment)
1072 q = q.filter_by(package=package)
1073 q = q.filter_by(version=version)
1075 return bool(q.count() > 0)
1077 __all__.append('has_new_comment')
1080 def get_new_comments(package=None, version=None, comment_id=None, session=None):
1082 Returns (possibly empty) list of NewComment objects for the given
1085 @type package: string (optional)
1086 @param package: name of the package
1088 @type version: string (optional)
1089 @param version: package version
1091 @type comment_id: int (optional)
1092 @param comment_id: An id of a comment
1094 @type session: Session
1095 @param session: Optional SQLA session object (a temporary one will be
1096 generated if not supplied)
1099 @return: A (possibly empty) list of NewComment objects will be returned
1102 q = session.query(NewComment)
1103 if package is not None: q = q.filter_by(package=package)
1104 if version is not None: q = q.filter_by(version=version)
1105 if comment_id is not None: q = q.filter_by(comment_id=comment_id)
1109 __all__.append('get_new_comments')
1111 ################################################################################
1113 class Override(object):
1114 def __init__(self, *args, **kwargs):
1118 return '<Override %s (%s)>' % (self.package, self.suite_id)
1120 __all__.append('Override')
1123 def get_override(package, suite=None, component=None, overridetype=None, session=None):
1125 Returns Override object for the given parameters
1127 @type package: string
1128 @param package: The name of the package
1130 @type suite: string, list or None
1131 @param suite: The name of the suite (or suites if a list) to limit to. If
1132 None, don't limit. Defaults to None.
1134 @type component: string, list or None
1135 @param component: The name of the component (or components if a list) to
1136 limit to. If None, don't limit. Defaults to None.
1138 @type overridetype: string, list or None
1139 @param overridetype: The name of the overridetype (or overridetypes if a list) to
1140 limit to. If None, don't limit. Defaults to None.
1142 @type session: Session
1143 @param session: Optional SQLA session object (a temporary one will be
1144 generated if not supplied)
1147 @return: A (possibly empty) list of Override objects will be returned
1150 q = session.query(Override)
1151 q = q.filter_by(package=package)
1153 if suite is not None:
1154 if not isinstance(suite, list): suite = [suite]
1155 q = q.join(Suite).filter(Suite.suite_name.in_(suite))
1157 if component is not None:
1158 if not isinstance(component, list): component = [component]
1159 q = q.join(Component).filter(Component.component_name.in_(component))
1161 if overridetype is not None:
1162 if not isinstance(overridetype, list): overridetype = [overridetype]
1163 q = q.join(OverrideType).filter(OverrideType.overridetype.in_(overridetype))
1167 __all__.append('get_override')
1170 ################################################################################
1172 class OverrideType(object):
1173 def __init__(self, *args, **kwargs):
1177 return '<OverrideType %s>' % self.overridetype
1179 __all__.append('OverrideType')
1182 def get_override_type(override_type, session=None):
1184 Returns OverrideType object for given C{override type}.
1186 @type override_type: string
1187 @param override_type: The name of the override type
1189 @type session: Session
1190 @param session: Optional SQLA session object (a temporary one will be
1191 generated if not supplied)
1194 @return: the database id for the given override type
1197 q = session.query(OverrideType).filter_by(overridetype=override_type)
1201 except NoResultFound:
1204 __all__.append('get_override_type')
1206 ################################################################################
1208 class PendingContentAssociation(object):
1209 def __init__(self, *args, **kwargs):
1213 return '<PendingContentAssociation %s>' % self.pca_id
1215 __all__.append('PendingContentAssociation')
1217 def insert_pending_content_paths(package, fullpaths, session=None):
1219 Make sure given paths are temporarily associated with given
1223 @param package: the package to associate with should have been read in from the binary control file
1224 @type fullpaths: list
1225 @param fullpaths: the list of paths of the file being associated with the binary
1226 @type session: SQLAlchemy session
1227 @param session: Optional SQLAlchemy session. If this is passed, the caller
1228 is responsible for ensuring a transaction has begun and committing the
1229 results or rolling back based on the result code. If not passed, a commit
1230 will be performed at the end of the function
1232 @return: True upon success, False if there is a problem
1235 privatetrans = False
1238 session = DBConn().session()
1242 arch = get_architecture(package['Architecture'], session)
1243 arch_id = arch.arch_id
1245 # Remove any already existing recorded files for this package
1246 q = session.query(PendingContentAssociation)
1247 q = q.filter_by(package=package['Package'])
1248 q = q.filter_by(version=package['Version'])
1249 q = q.filter_by(architecture=arch_id)
1254 for fullpath in fullpaths:
1255 (path, file) = os.path.split(fullpath)
1257 if path.startswith( "./" ):
1260 filepath_id = get_or_set_contents_path_id(path, session)
1261 filename_id = get_or_set_contents_file_id(file, session)
1263 pathcache[fullpath] = (filepath_id, filename_id)
1265 for fullpath, dat in pathcache.items():
1266 pca = PendingContentAssociation()
1267 pca.package = package['Package']
1268 pca.version = package['Version']
1269 pca.filepath_id = dat[0]
1270 pca.filename_id = dat[1]
1271 pca.architecture = arch_id
1274 # Only commit if we set up the session ourself
1282 except Exception, e:
1283 traceback.print_exc()
1285 # Only rollback if we set up the session ourself
1292 __all__.append('insert_pending_content_paths')
1294 ################################################################################
1296 class Priority(object):
1297 def __init__(self, *args, **kwargs):
1300 def __eq__(self, val):
1301 if isinstance(val, str):
1302 return (self.priority == val)
1303 # This signals to use the normal comparison operator
1304 return NotImplemented
1306 def __ne__(self, val):
1307 if isinstance(val, str):
1308 return (self.priority != val)
1309 # This signals to use the normal comparison operator
1310 return NotImplemented
1313 return '<Priority %s (%s)>' % (self.priority, self.priority_id)
1315 __all__.append('Priority')
1318 def get_priority(priority, session=None):
1320 Returns Priority object for given C{priority name}.
1322 @type priority: string
1323 @param priority: The name of the priority
1325 @type session: Session
1326 @param session: Optional SQLA session object (a temporary one will be
1327 generated if not supplied)
1330 @return: Priority object for the given priority
1333 q = session.query(Priority).filter_by(priority=priority)
1337 except NoResultFound:
1340 __all__.append('get_priority')
1343 def get_priorities(session=None):
1345 Returns dictionary of priority names -> id mappings
1347 @type session: Session
1348 @param session: Optional SQL session object (a temporary one will be
1349 generated if not supplied)
1352 @return: dictionary of priority names -> id mappings
1356 q = session.query(Priority)
1358 ret[x.priority] = x.priority_id
1362 __all__.append('get_priorities')
1364 ################################################################################
1366 class Queue(object):
1367 def __init__(self, *args, **kwargs):
1371 return '<Queue %s>' % self.queue_name
1373 def autobuild_upload(self, changes, srcpath, session=None):
1375 Update queue_build database table used for incoming autobuild support.
1377 @type changes: Changes
1378 @param changes: changes object for the upload to process
1380 @type srcpath: string
1381 @param srcpath: path for the queue file entries/link destinations
1383 @type session: SQLAlchemy session
1384 @param session: Optional SQLAlchemy session. If this is passed, the
1385 caller is responsible for ensuring a transaction has begun and
1386 committing the results or rolling back based on the result code. If
1387 not passed, a commit will be performed at the end of the function,
1388 otherwise the caller is responsible for commiting.
1390 @rtype: NoneType or string
1391 @return: None if the operation failed, a string describing the error if not
1394 privatetrans = False
1396 session = DBConn().session()
1399 # TODO: Remove by moving queue config into the database
1402 for suitename in changes.changes["distribution"].keys():
1403 # TODO: Move into database as:
1404 # buildqueuedir TEXT DEFAULT NULL (i.e. NULL is no build)
1405 # buildqueuecopy BOOLEAN NOT NULL DEFAULT FALSE (i.e. default is symlink)
1406 # This also gets rid of the SecurityQueueBuild hack below
1407 if suitename not in conf.ValueList("Dinstall::QueueBuildSuites"):
1411 s = get_suite(suitename, session)
1413 return "INTERNAL ERROR: Could not find suite %s" % suitename
1415 # TODO: Get from database as above
1416 dest_dir = conf["Dir::QueueBuild"]
1418 # TODO: Move into database as above
1419 if conf.FindB("Dinstall::SecurityQueueBuild"):
1420 dest_dir = os.path.join(dest_dir, suitename)
1422 for file_entry in changes.files.keys():
1423 src = os.path.join(srcpath, file_entry)
1424 dest = os.path.join(dest_dir, file_entry)
1426 # TODO: Move into database as above
1427 if conf.FindB("Dinstall::SecurityQueueBuild"):
1428 # Copy it since the original won't be readable by www-data
1430 utils.copy(src, dest)
1432 # Create a symlink to it
1433 os.symlink(src, dest)
1436 qb.suite_id = s.suite_id
1437 qb.queue_id = self.queue_id
1443 # If the .orig.tar.gz is in the pool, create a symlink to
1444 # it (if one doesn't already exist)
1445 if changes.orig_tar_id:
1446 # Determine the .orig.tar.gz file name
1447 for dsc_file in changes.dsc_files.keys():
1448 if dsc_file.endswith(".orig.tar.gz"):
1451 dest = os.path.join(dest_dir, filename)
1453 # If it doesn't exist, create a symlink
1454 if not os.path.exists(dest):
1455 q = session.execute("SELECT l.path, f.filename FROM location l, files f WHERE f.id = :id and f.location = l.id",
1456 {'id': changes.orig_tar_id})
1459 return "[INTERNAL ERROR] Couldn't find id %s in files table." % (changes.orig_tar_id)
1461 src = os.path.join(res[0], res[1])
1462 os.symlink(src, dest)
1464 # Add it to the list of packages for later processing by apt-ftparchive
1466 qb.suite_id = s.suite_id
1467 qb.queue_id = self.queue_id
1472 # If it does, update things to ensure it's not removed prematurely
1474 qb = get_queue_build(dest, s.suite_id, session)
1486 __all__.append('Queue')
1489 def get_queue(queuename, session=None):
1491 Returns Queue object for given C{queue name}.
1493 @type queuename: string
1494 @param queuename: The name of the queue
1496 @type session: Session
1497 @param session: Optional SQLA session object (a temporary one will be
1498 generated if not supplied)
1501 @return: Queue object for the given queue
1504 q = session.query(Queue).filter_by(queue_name=queuename)
1508 except NoResultFound:
1511 __all__.append('get_queue')
1513 ################################################################################
1515 class QueueBuild(object):
1516 def __init__(self, *args, **kwargs):
1520 return '<QueueBuild %s (%s)>' % (self.filename, self.queue_id)
1522 __all__.append('QueueBuild')
1525 def get_queue_build(filename, suite, session=None):
1527 Returns QueueBuild object for given C{filename} and C{suite}.
1529 @type filename: string
1530 @param filename: The name of the file
1532 @type suiteid: int or str
1533 @param suiteid: Suite name or ID
1535 @type session: Session
1536 @param session: Optional SQLA session object (a temporary one will be
1537 generated if not supplied)
1540 @return: Queue object for the given queue
1543 if isinstance(suite, int):
1544 q = session.query(QueueBuild).filter_by(filename=filename).filter_by(suite_id=suite)
1546 q = session.query(QueueBuild).filter_by(filename=filename)
1547 q = q.join(Suite).filter_by(suite_name=suite)
1551 except NoResultFound:
1554 __all__.append('get_queue_build')
1556 ################################################################################
1558 class Section(object):
1559 def __init__(self, *args, **kwargs):
1562 def __eq__(self, val):
1563 if isinstance(val, str):
1564 return (self.section == val)
1565 # This signals to use the normal comparison operator
1566 return NotImplemented
1568 def __ne__(self, val):
1569 if isinstance(val, str):
1570 return (self.section != val)
1571 # This signals to use the normal comparison operator
1572 return NotImplemented
1575 return '<Section %s>' % self.section
1577 __all__.append('Section')
1580 def get_section(section, session=None):
1582 Returns Section object for given C{section name}.
1584 @type section: string
1585 @param section: The name of the section
1587 @type session: Session
1588 @param session: Optional SQLA session object (a temporary one will be
1589 generated if not supplied)
1592 @return: Section object for the given section name
1595 q = session.query(Section).filter_by(section=section)
1599 except NoResultFound:
1602 __all__.append('get_section')
1605 def get_sections(session=None):
1607 Returns dictionary of section names -> id mappings
1609 @type session: Session
1610 @param session: Optional SQL session object (a temporary one will be
1611 generated if not supplied)
1614 @return: dictionary of section names -> id mappings
1618 q = session.query(Section)
1620 ret[x.section] = x.section_id
1624 __all__.append('get_sections')
1626 ################################################################################
1628 class DBSource(object):
1629 def __init__(self, *args, **kwargs):
1633 return '<DBSource %s (%s)>' % (self.source, self.version)
1635 __all__.append('DBSource')
1638 def source_exists(source, source_version, suites = ["any"], session=None):
1640 Ensure that source exists somewhere in the archive for the binary
1641 upload being processed.
1642 1. exact match => 1.0-3
1643 2. bin-only NMU => 1.0-3+b1 , 1.0-3.1+b1
1645 @type package: string
1646 @param package: package source name
1648 @type source_version: string
1649 @param source_version: expected source version
1652 @param suites: list of suites to check in, default I{any}
1654 @type session: Session
1655 @param session: Optional SQLA session object (a temporary one will be
1656 generated if not supplied)
1659 @return: returns 1 if a source with expected version is found, otherwise 0
1666 for suite in suites:
1667 q = session.query(DBSource).filter_by(source=source)
1669 # source must exist in suite X, or in some other suite that's
1670 # mapped to X, recursively... silent-maps are counted too,
1671 # unreleased-maps aren't.
1672 maps = cnf.ValueList("SuiteMappings")[:]
1674 maps = [ m.split() for m in maps ]
1675 maps = [ (x[1], x[2]) for x in maps
1676 if x[0] == "map" or x[0] == "silent-map" ]
1679 if x[1] in s and x[0] not in s:
1682 q = q.join(SrcAssociation).join(Suite)
1683 q = q.filter(Suite.suite_name.in_(s))
1685 # Reduce the query results to a list of version numbers
1686 ql = [ j.version for j in q.all() ]
1689 if source_version in ql:
1693 from daklib.regexes import re_bin_only_nmu
1694 orig_source_version = re_bin_only_nmu.sub('', source_version)
1695 if orig_source_version in ql:
1698 # No source found so return not ok
1703 __all__.append('source_exists')
1706 def get_suites_source_in(source, session=None):
1708 Returns list of Suite objects which given C{source} name is in
1711 @param source: DBSource package name to search for
1714 @return: list of Suite objects for the given source
1717 return session.query(Suite).join(SrcAssociation).join(DBSource).filter_by(source=source).all()
1719 __all__.append('get_suites_source_in')
1722 def get_sources_from_name(source, version=None, dm_upload_allowed=None, session=None):
1724 Returns list of DBSource objects for given C{source} name and other parameters
1727 @param source: DBSource package name to search for
1729 @type source: str or None
1730 @param source: DBSource version name to search for or None if not applicable
1732 @type dm_upload_allowed: bool
1733 @param dm_upload_allowed: If None, no effect. If True or False, only
1734 return packages with that dm_upload_allowed setting
1736 @type session: Session
1737 @param session: Optional SQL session object (a temporary one will be
1738 generated if not supplied)
1741 @return: list of DBSource objects for the given name (may be empty)
1744 q = session.query(DBSource).filter_by(source=source)
1746 if version is not None:
1747 q = q.filter_by(version=version)
1749 if dm_upload_allowed is not None:
1750 q = q.filter_by(dm_upload_allowed=dm_upload_allowed)
1754 __all__.append('get_sources_from_name')
1757 def get_source_in_suite(source, suite, session=None):
1759 Returns list of DBSource objects for a combination of C{source} and C{suite}.
1761 - B{source} - source package name, eg. I{mailfilter}, I{bbdb}, I{glibc}
1762 - B{suite} - a suite name, eg. I{unstable}
1764 @type source: string
1765 @param source: source package name
1768 @param suite: the suite name
1771 @return: the version for I{source} in I{suite}
1775 q = session.query(SrcAssociation)
1776 q = q.join('source').filter_by(source=source)
1777 q = q.join('suite').filter_by(suite_name=suite)
1780 return q.one().source
1781 except NoResultFound:
1784 __all__.append('get_source_in_suite')
1786 ################################################################################
1788 class SrcAssociation(object):
1789 def __init__(self, *args, **kwargs):
1793 return '<SrcAssociation %s (%s, %s)>' % (self.sa_id, self.source, self.suite)
1795 __all__.append('SrcAssociation')
1797 ################################################################################
1799 class SrcUploader(object):
1800 def __init__(self, *args, **kwargs):
1804 return '<SrcUploader %s>' % self.uploader_id
1806 __all__.append('SrcUploader')
1808 ################################################################################
1810 SUITE_FIELDS = [ ('SuiteName', 'suite_name'),
1811 ('SuiteID', 'suite_id'),
1812 ('Version', 'version'),
1813 ('Origin', 'origin'),
1815 ('Description', 'description'),
1816 ('Untouchable', 'untouchable'),
1817 ('Announce', 'announce'),
1818 ('Codename', 'codename'),
1819 ('OverrideCodename', 'overridecodename'),
1820 ('ValidTime', 'validtime'),
1821 ('Priority', 'priority'),
1822 ('NotAutomatic', 'notautomatic'),
1823 ('CopyChanges', 'copychanges'),
1824 ('CopyDotDak', 'copydotdak'),
1825 ('CommentsDir', 'commentsdir'),
1826 ('OverrideSuite', 'overridesuite'),
1827 ('ChangelogBase', 'changelogbase')]
1830 class Suite(object):
1831 def __init__(self, *args, **kwargs):
1835 return '<Suite %s>' % self.suite_name
1837 def __eq__(self, val):
1838 if isinstance(val, str):
1839 return (self.suite_name == val)
1840 # This signals to use the normal comparison operator
1841 return NotImplemented
1843 def __ne__(self, val):
1844 if isinstance(val, str):
1845 return (self.suite_name != val)
1846 # This signals to use the normal comparison operator
1847 return NotImplemented
1851 for disp, field in SUITE_FIELDS:
1852 val = getattr(self, field, None)
1854 ret.append("%s: %s" % (disp, val))
1856 return "\n".join(ret)
1858 __all__.append('Suite')
1861 def get_suite_architecture(suite, architecture, session=None):
1863 Returns a SuiteArchitecture object given C{suite} and ${arch} or None if it
1867 @param suite: Suite name to search for
1869 @type architecture: str
1870 @param architecture: Architecture name to search for
1872 @type session: Session
1873 @param session: Optional SQL session object (a temporary one will be
1874 generated if not supplied)
1876 @rtype: SuiteArchitecture
1877 @return: the SuiteArchitecture object or None
1880 q = session.query(SuiteArchitecture)
1881 q = q.join(Architecture).filter_by(arch_string=architecture)
1882 q = q.join(Suite).filter_by(suite_name=suite)
1886 except NoResultFound:
1889 __all__.append('get_suite_architecture')
1892 def get_suite(suite, session=None):
1894 Returns Suite object for given C{suite name}.
1897 @param suite: The name of the suite
1899 @type session: Session
1900 @param session: Optional SQLA session object (a temporary one will be
1901 generated if not supplied)
1904 @return: Suite object for the requested suite name (None if not presenT)
1907 q = session.query(Suite).filter_by(suite_name=suite)
1911 except NoResultFound:
1914 __all__.append('get_suite')
1916 ################################################################################
1918 class SuiteArchitecture(object):
1919 def __init__(self, *args, **kwargs):
1923 return '<SuiteArchitecture (%s, %s)>' % (self.suite_id, self.arch_id)
1925 __all__.append('SuiteArchitecture')
1928 def get_suite_architectures(suite, skipsrc=False, skipall=False, session=None):
1930 Returns list of Architecture objects for given C{suite} name
1933 @param source: Suite name to search for
1935 @type skipsrc: boolean
1936 @param skipsrc: Whether to skip returning the 'source' architecture entry
1939 @type skipall: boolean
1940 @param skipall: Whether to skip returning the 'all' architecture entry
1943 @type session: Session
1944 @param session: Optional SQL session object (a temporary one will be
1945 generated if not supplied)
1948 @return: list of Architecture objects for the given name (may be empty)
1951 q = session.query(Architecture)
1952 q = q.join(SuiteArchitecture)
1953 q = q.join(Suite).filter_by(suite_name=suite)
1956 q = q.filter(Architecture.arch_string != 'source')
1959 q = q.filter(Architecture.arch_string != 'all')
1961 q = q.order_by('arch_string')
1965 __all__.append('get_suite_architectures')
1967 ################################################################################
1970 def __init__(self, *args, **kwargs):
1973 def __eq__(self, val):
1974 if isinstance(val, str):
1975 return (self.uid == val)
1976 # This signals to use the normal comparison operator
1977 return NotImplemented
1979 def __ne__(self, val):
1980 if isinstance(val, str):
1981 return (self.uid != val)
1982 # This signals to use the normal comparison operator
1983 return NotImplemented
1986 return '<Uid %s (%s)>' % (self.uid, self.name)
1988 __all__.append('Uid')
1990 def add_database_user(uidname, session=None):
1992 Adds a database user
1994 @type uidname: string
1995 @param uidname: The uid of the user to add
1997 @type session: SQLAlchemy
1998 @param session: Optional SQL session object (a temporary one will be
1999 generated if not supplied). If not passed, a commit will be performed at
2000 the end of the function, otherwise the caller is responsible for commiting.
2003 @return: the uid object for the given uidname
2006 privatetrans = False
2008 session = DBConn().session()
2011 session.execute("CREATE USER :uid", {'uid': uidname})
2017 __all__.append('add_database_user')
2019 def get_or_set_uid(uidname, session=None):
2021 Returns uid object for given uidname.
2023 If no matching uidname is found, a row is inserted.
2025 @type uidname: string
2026 @param uidname: The uid to add
2028 @type session: SQLAlchemy
2029 @param session: Optional SQL session object (a temporary one will be
2030 generated if not supplied). If not passed, a commit will be performed at
2031 the end of the function, otherwise the caller is responsible for commiting.
2034 @return: the uid object for the given uidname
2037 privatetrans = False
2039 session = DBConn().session()
2042 q = session.query(Uid).filter_by(uid=uidname)
2046 except NoResultFound:
2061 __all__.append('get_or_set_uid')
2064 def get_uid_from_fingerprint(fpr, session=None):
2065 q = session.query(Uid)
2066 q = q.join(Fingerprint).filter_by(fingerprint=fpr)
2070 except NoResultFound:
2073 __all__.append('get_uid_from_fingerprint')
2075 ################################################################################
2077 class DBConn(Singleton):
2079 database module init.
2081 def __init__(self, *args, **kwargs):
2082 super(DBConn, self).__init__(*args, **kwargs)
2084 def _startup(self, *args, **kwargs):
2086 if kwargs.has_key('debug'):
2090 def __setuptables(self):
2091 self.tbl_architecture = Table('architecture', self.db_meta, autoload=True)
2092 self.tbl_archive = Table('archive', self.db_meta, autoload=True)
2093 self.tbl_bin_associations = Table('bin_associations', self.db_meta, autoload=True)
2094 self.tbl_binaries = Table('binaries', self.db_meta, autoload=True)
2095 self.tbl_component = Table('component', self.db_meta, autoload=True)
2096 self.tbl_config = Table('config', self.db_meta, autoload=True)
2097 self.tbl_content_associations = Table('content_associations', self.db_meta, autoload=True)
2098 self.tbl_content_file_names = Table('content_file_names', self.db_meta, autoload=True)
2099 self.tbl_content_file_paths = Table('content_file_paths', self.db_meta, autoload=True)
2100 self.tbl_dsc_files = Table('dsc_files', self.db_meta, autoload=True)
2101 self.tbl_files = Table('files', self.db_meta, autoload=True)
2102 self.tbl_fingerprint = Table('fingerprint', self.db_meta, autoload=True)
2103 self.tbl_keyrings = Table('keyrings', self.db_meta, autoload=True)
2104 self.tbl_location = Table('location', self.db_meta, autoload=True)
2105 self.tbl_maintainer = Table('maintainer', self.db_meta, autoload=True)
2106 self.tbl_new_comments = Table('new_comments', self.db_meta, autoload=True)
2107 self.tbl_override = Table('override', self.db_meta, autoload=True)
2108 self.tbl_override_type = Table('override_type', self.db_meta, autoload=True)
2109 self.tbl_pending_content_associations = Table('pending_content_associations', self.db_meta, autoload=True)
2110 self.tbl_priority = Table('priority', self.db_meta, autoload=True)
2111 self.tbl_queue = Table('queue', self.db_meta, autoload=True)
2112 self.tbl_queue_build = Table('queue_build', self.db_meta, autoload=True)
2113 self.tbl_section = Table('section', self.db_meta, autoload=True)
2114 self.tbl_source = Table('source', self.db_meta, autoload=True)
2115 self.tbl_src_associations = Table('src_associations', self.db_meta, autoload=True)
2116 self.tbl_src_uploaders = Table('src_uploaders', self.db_meta, autoload=True)
2117 self.tbl_suite = Table('suite', self.db_meta, autoload=True)
2118 self.tbl_suite_architectures = Table('suite_architectures', self.db_meta, autoload=True)
2119 self.tbl_uid = Table('uid', self.db_meta, autoload=True)
2121 def __setupmappers(self):
2122 mapper(Architecture, self.tbl_architecture,
2123 properties = dict(arch_id = self.tbl_architecture.c.id))
2125 mapper(Archive, self.tbl_archive,
2126 properties = dict(archive_id = self.tbl_archive.c.id,
2127 archive_name = self.tbl_archive.c.name))
2129 mapper(BinAssociation, self.tbl_bin_associations,
2130 properties = dict(ba_id = self.tbl_bin_associations.c.id,
2131 suite_id = self.tbl_bin_associations.c.suite,
2132 suite = relation(Suite),
2133 binary_id = self.tbl_bin_associations.c.bin,
2134 binary = relation(DBBinary)))
2136 mapper(DBBinary, self.tbl_binaries,
2137 properties = dict(binary_id = self.tbl_binaries.c.id,
2138 package = self.tbl_binaries.c.package,
2139 version = self.tbl_binaries.c.version,
2140 maintainer_id = self.tbl_binaries.c.maintainer,
2141 maintainer = relation(Maintainer),
2142 source_id = self.tbl_binaries.c.source,
2143 source = relation(DBSource),
2144 arch_id = self.tbl_binaries.c.architecture,
2145 architecture = relation(Architecture),
2146 poolfile_id = self.tbl_binaries.c.file,
2147 poolfile = relation(PoolFile),
2148 binarytype = self.tbl_binaries.c.type,
2149 fingerprint_id = self.tbl_binaries.c.sig_fpr,
2150 fingerprint = relation(Fingerprint),
2151 install_date = self.tbl_binaries.c.install_date,
2152 binassociations = relation(BinAssociation,
2153 primaryjoin=(self.tbl_binaries.c.id==self.tbl_bin_associations.c.bin))))
2155 mapper(Component, self.tbl_component,
2156 properties = dict(component_id = self.tbl_component.c.id,
2157 component_name = self.tbl_component.c.name))
2159 mapper(DBConfig, self.tbl_config,
2160 properties = dict(config_id = self.tbl_config.c.id))
2162 mapper(ContentAssociation, self.tbl_content_associations,
2163 properties = dict(ca_id = self.tbl_content_associations.c.id,
2164 filename_id = self.tbl_content_associations.c.filename,
2165 filename = relation(ContentFilename),
2166 filepath_id = self.tbl_content_associations.c.filepath,
2167 filepath = relation(ContentFilepath),
2168 binary_id = self.tbl_content_associations.c.binary_pkg,
2169 binary = relation(DBBinary)))
2172 mapper(ContentFilename, self.tbl_content_file_names,
2173 properties = dict(cafilename_id = self.tbl_content_file_names.c.id,
2174 filename = self.tbl_content_file_names.c.file))
2176 mapper(ContentFilepath, self.tbl_content_file_paths,
2177 properties = dict(cafilepath_id = self.tbl_content_file_paths.c.id,
2178 filepath = self.tbl_content_file_paths.c.path))
2180 mapper(DSCFile, self.tbl_dsc_files,
2181 properties = dict(dscfile_id = self.tbl_dsc_files.c.id,
2182 source_id = self.tbl_dsc_files.c.source,
2183 source = relation(DBSource),
2184 poolfile_id = self.tbl_dsc_files.c.file,
2185 poolfile = relation(PoolFile)))
2187 mapper(PoolFile, self.tbl_files,
2188 properties = dict(file_id = self.tbl_files.c.id,
2189 filesize = self.tbl_files.c.size,
2190 location_id = self.tbl_files.c.location,
2191 location = relation(Location)))
2193 mapper(Fingerprint, self.tbl_fingerprint,
2194 properties = dict(fingerprint_id = self.tbl_fingerprint.c.id,
2195 uid_id = self.tbl_fingerprint.c.uid,
2196 uid = relation(Uid),
2197 keyring_id = self.tbl_fingerprint.c.keyring,
2198 keyring = relation(Keyring)))
2200 mapper(Keyring, self.tbl_keyrings,
2201 properties = dict(keyring_name = self.tbl_keyrings.c.name,
2202 keyring_id = self.tbl_keyrings.c.id))
2204 mapper(Location, self.tbl_location,
2205 properties = dict(location_id = self.tbl_location.c.id,
2206 component_id = self.tbl_location.c.component,
2207 component = relation(Component),
2208 archive_id = self.tbl_location.c.archive,
2209 archive = relation(Archive),
2210 archive_type = self.tbl_location.c.type))
2212 mapper(Maintainer, self.tbl_maintainer,
2213 properties = dict(maintainer_id = self.tbl_maintainer.c.id))
2215 mapper(NewComment, self.tbl_new_comments,
2216 properties = dict(comment_id = self.tbl_new_comments.c.id))
2218 mapper(Override, self.tbl_override,
2219 properties = dict(suite_id = self.tbl_override.c.suite,
2220 suite = relation(Suite),
2221 component_id = self.tbl_override.c.component,
2222 component = relation(Component),
2223 priority_id = self.tbl_override.c.priority,
2224 priority = relation(Priority),
2225 section_id = self.tbl_override.c.section,
2226 section = relation(Section),
2227 overridetype_id = self.tbl_override.c.type,
2228 overridetype = relation(OverrideType)))
2230 mapper(OverrideType, self.tbl_override_type,
2231 properties = dict(overridetype = self.tbl_override_type.c.type,
2232 overridetype_id = self.tbl_override_type.c.id))
2234 mapper(PendingContentAssociation, self.tbl_pending_content_associations,
2235 properties = dict(pca_id = self.tbl_pending_content_associations.c.id,
2236 filepath_id = self.tbl_pending_content_associations.c.filepath,
2237 filepath = relation(ContentFilepath),
2238 filename_id = self.tbl_pending_content_associations.c.filename,
2239 filename = relation(ContentFilename)))
2241 mapper(Priority, self.tbl_priority,
2242 properties = dict(priority_id = self.tbl_priority.c.id))
2244 mapper(Queue, self.tbl_queue,
2245 properties = dict(queue_id = self.tbl_queue.c.id))
2247 mapper(QueueBuild, self.tbl_queue_build,
2248 properties = dict(suite_id = self.tbl_queue_build.c.suite,
2249 queue_id = self.tbl_queue_build.c.queue,
2250 queue = relation(Queue, backref='queuebuild')))
2252 mapper(Section, self.tbl_section,
2253 properties = dict(section_id = self.tbl_section.c.id))
2255 mapper(DBSource, self.tbl_source,
2256 properties = dict(source_id = self.tbl_source.c.id,
2257 version = self.tbl_source.c.version,
2258 maintainer_id = self.tbl_source.c.maintainer,
2259 maintainer = relation(Maintainer,
2260 primaryjoin=(self.tbl_source.c.maintainer==self.tbl_maintainer.c.id)),
2261 poolfile_id = self.tbl_source.c.file,
2262 poolfile = relation(PoolFile),
2263 fingerprint_id = self.tbl_source.c.sig_fpr,
2264 fingerprint = relation(Fingerprint),
2265 changedby_id = self.tbl_source.c.changedby,
2266 changedby = relation(Maintainer,
2267 primaryjoin=(self.tbl_source.c.changedby==self.tbl_maintainer.c.id)),
2268 srcfiles = relation(DSCFile,
2269 primaryjoin=(self.tbl_source.c.id==self.tbl_dsc_files.c.source)),
2270 srcassociations = relation(SrcAssociation,
2271 primaryjoin=(self.tbl_source.c.id==self.tbl_src_associations.c.source))))
2273 mapper(SrcAssociation, self.tbl_src_associations,
2274 properties = dict(sa_id = self.tbl_src_associations.c.id,
2275 suite_id = self.tbl_src_associations.c.suite,
2276 suite = relation(Suite),
2277 source_id = self.tbl_src_associations.c.source,
2278 source = relation(DBSource)))
2280 mapper(SrcUploader, self.tbl_src_uploaders,
2281 properties = dict(uploader_id = self.tbl_src_uploaders.c.id,
2282 source_id = self.tbl_src_uploaders.c.source,
2283 source = relation(DBSource,
2284 primaryjoin=(self.tbl_src_uploaders.c.source==self.tbl_source.c.id)),
2285 maintainer_id = self.tbl_src_uploaders.c.maintainer,
2286 maintainer = relation(Maintainer,
2287 primaryjoin=(self.tbl_src_uploaders.c.maintainer==self.tbl_maintainer.c.id))))
2289 mapper(Suite, self.tbl_suite,
2290 properties = dict(suite_id = self.tbl_suite.c.id))
2292 mapper(SuiteArchitecture, self.tbl_suite_architectures,
2293 properties = dict(suite_id = self.tbl_suite_architectures.c.suite,
2294 suite = relation(Suite, backref='suitearchitectures'),
2295 arch_id = self.tbl_suite_architectures.c.architecture,
2296 architecture = relation(Architecture)))
2298 mapper(Uid, self.tbl_uid,
2299 properties = dict(uid_id = self.tbl_uid.c.id,
2300 fingerprint = relation(Fingerprint)))
2302 ## Connection functions
2303 def __createconn(self):
2304 from config import Config
2308 connstr = "postgres://%s" % cnf["DB::Host"]
2309 if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
2310 connstr += ":%s" % cnf["DB::Port"]
2311 connstr += "/%s" % cnf["DB::Name"]
2314 connstr = "postgres:///%s" % cnf["DB::Name"]
2315 if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
2316 connstr += "?port=%s" % cnf["DB::Port"]
2318 self.db_pg = create_engine(connstr, echo=self.debug)
2319 self.db_meta = MetaData()
2320 self.db_meta.bind = self.db_pg
2321 self.db_smaker = sessionmaker(bind=self.db_pg,
2325 self.__setuptables()
2326 self.__setupmappers()
2329 return self.db_smaker()
2331 __all__.append('DBConn')