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 and 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
1429 utils.copy(src, dest)
1431 # Create a symlink to it
1432 os.symlink(src, dest)
1435 qb.suite_id = s.suite_id
1436 qb.queue_id = self.queue_id
1442 # If the .orig.tar.gz is in the pool, create a symlink to
1443 # it (if one doesn't already exist)
1444 if changes.orig_tar_id:
1445 # Determine the .orig.tar.gz file name
1446 for dsc_file in changes.dsc_files.keys():
1447 if dsc_file.endswith(".orig.tar.gz"):
1450 dest = os.path.join(dest_dir, filename)
1452 # If it doesn't exist, create a symlink
1453 if not os.path.exists(dest):
1454 q = session.execute("SELECT l.path, f.filename FROM location l, files f WHERE f.id = :id and f.location = l.id",
1455 {'id': changes.orig_tar_id})
1458 return "[INTERNAL ERROR] Couldn't find id %s in files table." % (changes.orig_tar_id)
1460 src = os.path.join(res[0], res[1])
1461 os.symlink(src, dest)
1463 # Add it to the list of packages for later processing by apt-ftparchive
1465 qb.suite_id = s.suite_id
1466 qb.queue_id = self.queue_id
1471 # If it does, update things to ensure it's not removed prematurely
1473 qb = get_queue_build(dest, s.suite_id, session)
1485 __all__.append('Queue')
1488 def get_queue(queuename, session=None):
1490 Returns Queue object for given C{queue name}.
1492 @type queuename: string
1493 @param queuename: The name of the queue
1495 @type session: Session
1496 @param session: Optional SQLA session object (a temporary one will be
1497 generated if not supplied)
1500 @return: Queue object for the given queue
1503 q = session.query(Queue).filter_by(queue_name=queuename)
1507 except NoResultFound:
1510 __all__.append('get_queue')
1512 ################################################################################
1514 class QueueBuild(object):
1515 def __init__(self, *args, **kwargs):
1519 return '<QueueBuild %s (%s)>' % (self.filename, self.queue_id)
1521 __all__.append('QueueBuild')
1524 def get_queue_build(filename, suite, session=None):
1526 Returns QueueBuild object for given C{filename} and C{suite}.
1528 @type filename: string
1529 @param filename: The name of the file
1531 @type suiteid: int or str
1532 @param suiteid: Suite name or ID
1534 @type session: Session
1535 @param session: Optional SQLA session object (a temporary one will be
1536 generated if not supplied)
1539 @return: Queue object for the given queue
1542 if isinstance(suite, int):
1543 q = session.query(QueueBuild).filter_by(filename=filename).filter_by(suite_id=suite)
1545 q = session.query(QueueBuild).filter_by(filename=filename)
1546 q = q.join(Suite).filter_by(suite_name=suite)
1550 except NoResultFound:
1553 __all__.append('get_queue_build')
1555 ################################################################################
1557 class Section(object):
1558 def __init__(self, *args, **kwargs):
1561 def __eq__(self, val):
1562 if isinstance(val, str):
1563 return (self.section == val)
1564 # This signals to use the normal comparison operator
1565 return NotImplemented
1567 def __ne__(self, val):
1568 if isinstance(val, str):
1569 return (self.section != val)
1570 # This signals to use the normal comparison operator
1571 return NotImplemented
1574 return '<Section %s>' % self.section
1576 __all__.append('Section')
1579 def get_section(section, session=None):
1581 Returns Section object for given C{section name}.
1583 @type section: string
1584 @param section: The name of the section
1586 @type session: Session
1587 @param session: Optional SQLA session object (a temporary one will be
1588 generated if not supplied)
1591 @return: Section object for the given section name
1594 q = session.query(Section).filter_by(section=section)
1598 except NoResultFound:
1601 __all__.append('get_section')
1604 def get_sections(session=None):
1606 Returns dictionary of section names -> id mappings
1608 @type session: Session
1609 @param session: Optional SQL session object (a temporary one will be
1610 generated if not supplied)
1613 @return: dictionary of section names -> id mappings
1617 q = session.query(Section)
1619 ret[x.section] = x.section_id
1623 __all__.append('get_sections')
1625 ################################################################################
1627 class DBSource(object):
1628 def __init__(self, *args, **kwargs):
1632 return '<DBSource %s (%s)>' % (self.source, self.version)
1634 __all__.append('DBSource')
1637 def source_exists(source, source_version, suites = ["any"], session=None):
1639 Ensure that source exists somewhere in the archive for the binary
1640 upload being processed.
1641 1. exact match => 1.0-3
1642 2. bin-only NMU => 1.0-3+b1 , 1.0-3.1+b1
1644 @type package: string
1645 @param package: package source name
1647 @type source_version: string
1648 @param source_version: expected source version
1651 @param suites: list of suites to check in, default I{any}
1653 @type session: Session
1654 @param session: Optional SQLA session object (a temporary one will be
1655 generated if not supplied)
1658 @return: returns 1 if a source with expected version is found, otherwise 0
1665 for suite in suites:
1666 q = session.query(DBSource).filter_by(source=source)
1668 # source must exist in suite X, or in some other suite that's
1669 # mapped to X, recursively... silent-maps are counted too,
1670 # unreleased-maps aren't.
1671 maps = cnf.ValueList("SuiteMappings")[:]
1673 maps = [ m.split() for m in maps ]
1674 maps = [ (x[1], x[2]) for x in maps
1675 if x[0] == "map" or x[0] == "silent-map" ]
1678 if x[1] in s and x[0] not in s:
1681 q = q.join(SrcAssociation).join(Suite)
1682 q = q.filter(Suite.suite_name.in_(s))
1684 # Reduce the query results to a list of version numbers
1685 ql = [ j.version for j in q.all() ]
1688 if source_version in ql:
1692 from daklib.regexes import re_bin_only_nmu
1693 orig_source_version = re_bin_only_nmu.sub('', source_version)
1694 if orig_source_version in ql:
1697 # No source found so return not ok
1702 __all__.append('source_exists')
1705 def get_suites_source_in(source, session=None):
1707 Returns list of Suite objects which given C{source} name is in
1710 @param source: DBSource package name to search for
1713 @return: list of Suite objects for the given source
1716 return session.query(Suite).join(SrcAssociation).join(DBSource).filter_by(source=source).all()
1718 __all__.append('get_suites_source_in')
1721 def get_sources_from_name(source, version=None, dm_upload_allowed=None, session=None):
1723 Returns list of DBSource objects for given C{source} name and other parameters
1726 @param source: DBSource package name to search for
1728 @type source: str or None
1729 @param source: DBSource version name to search for or None if not applicable
1731 @type dm_upload_allowed: bool
1732 @param dm_upload_allowed: If None, no effect. If True or False, only
1733 return packages with that dm_upload_allowed setting
1735 @type session: Session
1736 @param session: Optional SQL session object (a temporary one will be
1737 generated if not supplied)
1740 @return: list of DBSource objects for the given name (may be empty)
1743 q = session.query(DBSource).filter_by(source=source)
1745 if version is not None:
1746 q = q.filter_by(version=version)
1748 if dm_upload_allowed is not None:
1749 q = q.filter_by(dm_upload_allowed=dm_upload_allowed)
1753 __all__.append('get_sources_from_name')
1756 def get_source_in_suite(source, suite, session=None):
1758 Returns list of DBSource objects for a combination of C{source} and C{suite}.
1760 - B{source} - source package name, eg. I{mailfilter}, I{bbdb}, I{glibc}
1761 - B{suite} - a suite name, eg. I{unstable}
1763 @type source: string
1764 @param source: source package name
1767 @param suite: the suite name
1770 @return: the version for I{source} in I{suite}
1774 q = session.query(SrcAssociation)
1775 q = q.join('source').filter_by(source=source)
1776 q = q.join('suite').filter_by(suite_name=suite)
1779 return q.one().source
1780 except NoResultFound:
1783 __all__.append('get_source_in_suite')
1785 ################################################################################
1787 class SrcAssociation(object):
1788 def __init__(self, *args, **kwargs):
1792 return '<SrcAssociation %s (%s, %s)>' % (self.sa_id, self.source, self.suite)
1794 __all__.append('SrcAssociation')
1796 ################################################################################
1798 class SrcUploader(object):
1799 def __init__(self, *args, **kwargs):
1803 return '<SrcUploader %s>' % self.uploader_id
1805 __all__.append('SrcUploader')
1807 ################################################################################
1809 SUITE_FIELDS = [ ('SuiteName', 'suite_name'),
1810 ('SuiteID', 'suite_id'),
1811 ('Version', 'version'),
1812 ('Origin', 'origin'),
1814 ('Description', 'description'),
1815 ('Untouchable', 'untouchable'),
1816 ('Announce', 'announce'),
1817 ('Codename', 'codename'),
1818 ('OverrideCodename', 'overridecodename'),
1819 ('ValidTime', 'validtime'),
1820 ('Priority', 'priority'),
1821 ('NotAutomatic', 'notautomatic'),
1822 ('CopyChanges', 'copychanges'),
1823 ('CopyDotDak', 'copydotdak'),
1824 ('CommentsDir', 'commentsdir'),
1825 ('OverrideSuite', 'overridesuite'),
1826 ('ChangelogBase', 'changelogbase')]
1829 class Suite(object):
1830 def __init__(self, *args, **kwargs):
1834 return '<Suite %s>' % self.suite_name
1836 def __eq__(self, val):
1837 if isinstance(val, str):
1838 return (self.suite_name == val)
1839 # This signals to use the normal comparison operator
1840 return NotImplemented
1842 def __ne__(self, val):
1843 if isinstance(val, str):
1844 return (self.suite_name != val)
1845 # This signals to use the normal comparison operator
1846 return NotImplemented
1850 for disp, field in SUITE_FIELDS:
1851 val = getattr(self, field, None)
1853 ret.append("%s: %s" % (disp, val))
1855 return "\n".join(ret)
1857 __all__.append('Suite')
1860 def get_suite_architecture(suite, architecture, session=None):
1862 Returns a SuiteArchitecture object given C{suite} and ${arch} or None if it
1866 @param suite: Suite name to search for
1868 @type architecture: str
1869 @param architecture: Architecture name to search for
1871 @type session: Session
1872 @param session: Optional SQL session object (a temporary one will be
1873 generated if not supplied)
1875 @rtype: SuiteArchitecture
1876 @return: the SuiteArchitecture object or None
1879 q = session.query(SuiteArchitecture)
1880 q = q.join(Architecture).filter_by(arch_string=architecture)
1881 q = q.join(Suite).filter_by(suite_name=suite)
1885 except NoResultFound:
1888 __all__.append('get_suite_architecture')
1891 def get_suite(suite, session=None):
1893 Returns Suite object for given C{suite name}.
1896 @param suite: The name of the suite
1898 @type session: Session
1899 @param session: Optional SQLA session object (a temporary one will be
1900 generated if not supplied)
1903 @return: Suite object for the requested suite name (None if not presenT)
1906 q = session.query(Suite).filter_by(suite_name=suite)
1910 except NoResultFound:
1913 __all__.append('get_suite')
1915 ################################################################################
1917 class SuiteArchitecture(object):
1918 def __init__(self, *args, **kwargs):
1922 return '<SuiteArchitecture (%s, %s)>' % (self.suite_id, self.arch_id)
1924 __all__.append('SuiteArchitecture')
1927 def get_suite_architectures(suite, skipsrc=False, skipall=False, session=None):
1929 Returns list of Architecture objects for given C{suite} name
1932 @param source: Suite name to search for
1934 @type skipsrc: boolean
1935 @param skipsrc: Whether to skip returning the 'source' architecture entry
1938 @type skipall: boolean
1939 @param skipall: Whether to skip returning the 'all' architecture entry
1942 @type session: Session
1943 @param session: Optional SQL session object (a temporary one will be
1944 generated if not supplied)
1947 @return: list of Architecture objects for the given name (may be empty)
1950 q = session.query(Architecture)
1951 q = q.join(SuiteArchitecture)
1952 q = q.join(Suite).filter_by(suite_name=suite)
1955 q = q.filter(Architecture.arch_string != 'source')
1958 q = q.filter(Architecture.arch_string != 'all')
1960 q = q.order_by('arch_string')
1964 __all__.append('get_suite_architectures')
1966 ################################################################################
1969 def __init__(self, *args, **kwargs):
1972 def __eq__(self, val):
1973 if isinstance(val, str):
1974 return (self.uid == val)
1975 # This signals to use the normal comparison operator
1976 return NotImplemented
1978 def __ne__(self, val):
1979 if isinstance(val, str):
1980 return (self.uid != val)
1981 # This signals to use the normal comparison operator
1982 return NotImplemented
1985 return '<Uid %s (%s)>' % (self.uid, self.name)
1987 __all__.append('Uid')
1989 def add_database_user(uidname, session=None):
1991 Adds a database user
1993 @type uidname: string
1994 @param uidname: The uid of the user to add
1996 @type session: SQLAlchemy
1997 @param session: Optional SQL session object (a temporary one will be
1998 generated if not supplied). If not passed, a commit will be performed at
1999 the end of the function, otherwise the caller is responsible for commiting.
2002 @return: the uid object for the given uidname
2005 privatetrans = False
2007 session = DBConn().session()
2010 session.execute("CREATE USER :uid", {'uid': uidname})
2016 __all__.append('add_database_user')
2018 def get_or_set_uid(uidname, session=None):
2020 Returns uid object for given uidname.
2022 If no matching uidname is found, a row is inserted.
2024 @type uidname: string
2025 @param uidname: The uid to add
2027 @type session: SQLAlchemy
2028 @param session: Optional SQL session object (a temporary one will be
2029 generated if not supplied). If not passed, a commit will be performed at
2030 the end of the function, otherwise the caller is responsible for commiting.
2033 @return: the uid object for the given uidname
2036 privatetrans = False
2038 session = DBConn().session()
2041 q = session.query(Uid).filter_by(uid=uidname)
2045 except NoResultFound:
2060 __all__.append('get_or_set_uid')
2063 def get_uid_from_fingerprint(fpr, session=None):
2064 q = session.query(Uid)
2065 q = q.join(Fingerprint).filter_by(fingerprint=fpr)
2069 except NoResultFound:
2072 __all__.append('get_uid_from_fingerprint')
2074 ################################################################################
2076 class DBConn(Singleton):
2078 database module init.
2080 def __init__(self, *args, **kwargs):
2081 super(DBConn, self).__init__(*args, **kwargs)
2083 def _startup(self, *args, **kwargs):
2085 if kwargs.has_key('debug'):
2089 def __setuptables(self):
2090 self.tbl_architecture = Table('architecture', self.db_meta, autoload=True)
2091 self.tbl_archive = Table('archive', self.db_meta, autoload=True)
2092 self.tbl_bin_associations = Table('bin_associations', self.db_meta, autoload=True)
2093 self.tbl_binaries = Table('binaries', self.db_meta, autoload=True)
2094 self.tbl_component = Table('component', self.db_meta, autoload=True)
2095 self.tbl_config = Table('config', self.db_meta, autoload=True)
2096 self.tbl_content_associations = Table('content_associations', self.db_meta, autoload=True)
2097 self.tbl_content_file_names = Table('content_file_names', self.db_meta, autoload=True)
2098 self.tbl_content_file_paths = Table('content_file_paths', self.db_meta, autoload=True)
2099 self.tbl_dsc_files = Table('dsc_files', self.db_meta, autoload=True)
2100 self.tbl_files = Table('files', self.db_meta, autoload=True)
2101 self.tbl_fingerprint = Table('fingerprint', self.db_meta, autoload=True)
2102 self.tbl_keyrings = Table('keyrings', self.db_meta, autoload=True)
2103 self.tbl_location = Table('location', self.db_meta, autoload=True)
2104 self.tbl_maintainer = Table('maintainer', self.db_meta, autoload=True)
2105 self.tbl_new_comments = Table('new_comments', self.db_meta, autoload=True)
2106 self.tbl_override = Table('override', self.db_meta, autoload=True)
2107 self.tbl_override_type = Table('override_type', self.db_meta, autoload=True)
2108 self.tbl_pending_content_associations = Table('pending_content_associations', self.db_meta, autoload=True)
2109 self.tbl_priority = Table('priority', self.db_meta, autoload=True)
2110 self.tbl_queue = Table('queue', self.db_meta, autoload=True)
2111 self.tbl_queue_build = Table('queue_build', self.db_meta, autoload=True)
2112 self.tbl_section = Table('section', self.db_meta, autoload=True)
2113 self.tbl_source = Table('source', self.db_meta, autoload=True)
2114 self.tbl_src_associations = Table('src_associations', self.db_meta, autoload=True)
2115 self.tbl_src_uploaders = Table('src_uploaders', self.db_meta, autoload=True)
2116 self.tbl_suite = Table('suite', self.db_meta, autoload=True)
2117 self.tbl_suite_architectures = Table('suite_architectures', self.db_meta, autoload=True)
2118 self.tbl_uid = Table('uid', self.db_meta, autoload=True)
2120 def __setupmappers(self):
2121 mapper(Architecture, self.tbl_architecture,
2122 properties = dict(arch_id = self.tbl_architecture.c.id))
2124 mapper(Archive, self.tbl_archive,
2125 properties = dict(archive_id = self.tbl_archive.c.id,
2126 archive_name = self.tbl_archive.c.name))
2128 mapper(BinAssociation, self.tbl_bin_associations,
2129 properties = dict(ba_id = self.tbl_bin_associations.c.id,
2130 suite_id = self.tbl_bin_associations.c.suite,
2131 suite = relation(Suite),
2132 binary_id = self.tbl_bin_associations.c.bin,
2133 binary = relation(DBBinary)))
2135 mapper(DBBinary, self.tbl_binaries,
2136 properties = dict(binary_id = self.tbl_binaries.c.id,
2137 package = self.tbl_binaries.c.package,
2138 version = self.tbl_binaries.c.version,
2139 maintainer_id = self.tbl_binaries.c.maintainer,
2140 maintainer = relation(Maintainer),
2141 source_id = self.tbl_binaries.c.source,
2142 source = relation(DBSource),
2143 arch_id = self.tbl_binaries.c.architecture,
2144 architecture = relation(Architecture),
2145 poolfile_id = self.tbl_binaries.c.file,
2146 poolfile = relation(PoolFile),
2147 binarytype = self.tbl_binaries.c.type,
2148 fingerprint_id = self.tbl_binaries.c.sig_fpr,
2149 fingerprint = relation(Fingerprint),
2150 install_date = self.tbl_binaries.c.install_date,
2151 binassociations = relation(BinAssociation,
2152 primaryjoin=(self.tbl_binaries.c.id==self.tbl_bin_associations.c.bin))))
2154 mapper(Component, self.tbl_component,
2155 properties = dict(component_id = self.tbl_component.c.id,
2156 component_name = self.tbl_component.c.name))
2158 mapper(DBConfig, self.tbl_config,
2159 properties = dict(config_id = self.tbl_config.c.id))
2161 mapper(ContentAssociation, self.tbl_content_associations,
2162 properties = dict(ca_id = self.tbl_content_associations.c.id,
2163 filename_id = self.tbl_content_associations.c.filename,
2164 filename = relation(ContentFilename),
2165 filepath_id = self.tbl_content_associations.c.filepath,
2166 filepath = relation(ContentFilepath),
2167 binary_id = self.tbl_content_associations.c.binary_pkg,
2168 binary = relation(DBBinary)))
2171 mapper(ContentFilename, self.tbl_content_file_names,
2172 properties = dict(cafilename_id = self.tbl_content_file_names.c.id,
2173 filename = self.tbl_content_file_names.c.file))
2175 mapper(ContentFilepath, self.tbl_content_file_paths,
2176 properties = dict(cafilepath_id = self.tbl_content_file_paths.c.id,
2177 filepath = self.tbl_content_file_paths.c.path))
2179 mapper(DSCFile, self.tbl_dsc_files,
2180 properties = dict(dscfile_id = self.tbl_dsc_files.c.id,
2181 source_id = self.tbl_dsc_files.c.source,
2182 source = relation(DBSource),
2183 poolfile_id = self.tbl_dsc_files.c.file,
2184 poolfile = relation(PoolFile)))
2186 mapper(PoolFile, self.tbl_files,
2187 properties = dict(file_id = self.tbl_files.c.id,
2188 filesize = self.tbl_files.c.size,
2189 location_id = self.tbl_files.c.location,
2190 location = relation(Location)))
2192 mapper(Fingerprint, self.tbl_fingerprint,
2193 properties = dict(fingerprint_id = self.tbl_fingerprint.c.id,
2194 uid_id = self.tbl_fingerprint.c.uid,
2195 uid = relation(Uid),
2196 keyring_id = self.tbl_fingerprint.c.keyring,
2197 keyring = relation(Keyring)))
2199 mapper(Keyring, self.tbl_keyrings,
2200 properties = dict(keyring_name = self.tbl_keyrings.c.name,
2201 keyring_id = self.tbl_keyrings.c.id))
2203 mapper(Location, self.tbl_location,
2204 properties = dict(location_id = self.tbl_location.c.id,
2205 component_id = self.tbl_location.c.component,
2206 component = relation(Component),
2207 archive_id = self.tbl_location.c.archive,
2208 archive = relation(Archive),
2209 archive_type = self.tbl_location.c.type))
2211 mapper(Maintainer, self.tbl_maintainer,
2212 properties = dict(maintainer_id = self.tbl_maintainer.c.id))
2214 mapper(NewComment, self.tbl_new_comments,
2215 properties = dict(comment_id = self.tbl_new_comments.c.id))
2217 mapper(Override, self.tbl_override,
2218 properties = dict(suite_id = self.tbl_override.c.suite,
2219 suite = relation(Suite),
2220 component_id = self.tbl_override.c.component,
2221 component = relation(Component),
2222 priority_id = self.tbl_override.c.priority,
2223 priority = relation(Priority),
2224 section_id = self.tbl_override.c.section,
2225 section = relation(Section),
2226 overridetype_id = self.tbl_override.c.type,
2227 overridetype = relation(OverrideType)))
2229 mapper(OverrideType, self.tbl_override_type,
2230 properties = dict(overridetype = self.tbl_override_type.c.type,
2231 overridetype_id = self.tbl_override_type.c.id))
2233 mapper(PendingContentAssociation, self.tbl_pending_content_associations,
2234 properties = dict(pca_id = self.tbl_pending_content_associations.c.id,
2235 filepath_id = self.tbl_pending_content_associations.c.filepath,
2236 filepath = relation(ContentFilepath),
2237 filename_id = self.tbl_pending_content_associations.c.filename,
2238 filename = relation(ContentFilename)))
2240 mapper(Priority, self.tbl_priority,
2241 properties = dict(priority_id = self.tbl_priority.c.id))
2243 mapper(Queue, self.tbl_queue,
2244 properties = dict(queue_id = self.tbl_queue.c.id))
2246 mapper(QueueBuild, self.tbl_queue_build,
2247 properties = dict(suite_id = self.tbl_queue_build.c.suite,
2248 queue_id = self.tbl_queue_build.c.queue,
2249 queue = relation(Queue, backref='queuebuild')))
2251 mapper(Section, self.tbl_section,
2252 properties = dict(section_id = self.tbl_section.c.id))
2254 mapper(DBSource, self.tbl_source,
2255 properties = dict(source_id = self.tbl_source.c.id,
2256 version = self.tbl_source.c.version,
2257 maintainer_id = self.tbl_source.c.maintainer,
2258 maintainer = relation(Maintainer,
2259 primaryjoin=(self.tbl_source.c.maintainer==self.tbl_maintainer.c.id)),
2260 poolfile_id = self.tbl_source.c.file,
2261 poolfile = relation(PoolFile),
2262 fingerprint_id = self.tbl_source.c.sig_fpr,
2263 fingerprint = relation(Fingerprint),
2264 changedby_id = self.tbl_source.c.changedby,
2265 changedby = relation(Maintainer,
2266 primaryjoin=(self.tbl_source.c.changedby==self.tbl_maintainer.c.id)),
2267 srcfiles = relation(DSCFile,
2268 primaryjoin=(self.tbl_source.c.id==self.tbl_dsc_files.c.source)),
2269 srcassociations = relation(SrcAssociation,
2270 primaryjoin=(self.tbl_source.c.id==self.tbl_src_associations.c.source))))
2272 mapper(SrcAssociation, self.tbl_src_associations,
2273 properties = dict(sa_id = self.tbl_src_associations.c.id,
2274 suite_id = self.tbl_src_associations.c.suite,
2275 suite = relation(Suite),
2276 source_id = self.tbl_src_associations.c.source,
2277 source = relation(DBSource)))
2279 mapper(SrcUploader, self.tbl_src_uploaders,
2280 properties = dict(uploader_id = self.tbl_src_uploaders.c.id,
2281 source_id = self.tbl_src_uploaders.c.source,
2282 source = relation(DBSource,
2283 primaryjoin=(self.tbl_src_uploaders.c.source==self.tbl_source.c.id)),
2284 maintainer_id = self.tbl_src_uploaders.c.maintainer,
2285 maintainer = relation(Maintainer,
2286 primaryjoin=(self.tbl_src_uploaders.c.maintainer==self.tbl_maintainer.c.id))))
2288 mapper(Suite, self.tbl_suite,
2289 properties = dict(suite_id = self.tbl_suite.c.id))
2291 mapper(SuiteArchitecture, self.tbl_suite_architectures,
2292 properties = dict(suite_id = self.tbl_suite_architectures.c.suite,
2293 suite = relation(Suite, backref='suitearchitectures'),
2294 arch_id = self.tbl_suite_architectures.c.architecture,
2295 architecture = relation(Architecture)))
2297 mapper(Uid, self.tbl_uid,
2298 properties = dict(uid_id = self.tbl_uid.c.id,
2299 fingerprint = relation(Fingerprint)))
2301 ## Connection functions
2302 def __createconn(self):
2303 from config import Config
2307 connstr = "postgres://%s" % cnf["DB::Host"]
2308 if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
2309 connstr += ":%s" % cnf["DB::Port"]
2310 connstr += "/%s" % cnf["DB::Name"]
2313 connstr = "postgres:///%s" % cnf["DB::Name"]
2314 if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
2315 connstr += "?port=%s" % cnf["DB::Port"]
2317 self.db_pg = create_engine(connstr, echo=self.debug)
2318 self.db_meta = MetaData()
2319 self.db_meta.bind = self.db_pg
2320 self.db_smaker = sessionmaker(bind=self.db_pg,
2324 self.__setuptables()
2325 self.__setupmappers()
2328 return self.db_smaker()
2330 __all__.append('DBConn')