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 sqlalchemy import create_engine, Table, MetaData, select
41 from sqlalchemy.orm import sessionmaker, mapper, relation
43 # Don't remove this, we re-export the exceptions to scripts which import us
44 from sqlalchemy.exc import *
46 # Only import Config until Queue stuff is changed to store its config
48 from config import Config
49 from singleton import Singleton
50 from textutils import fix_maintainer
52 ################################################################################
54 __all__ = ['IntegrityError', 'SQLAlchemyError']
56 ################################################################################
58 class Architecture(object):
59 def __init__(self, *args, **kwargs):
62 def __eq__(self, val):
63 if isinstance(val, str):
64 return (self.arch_string== val)
65 # This signals to use the normal comparison operator
68 def __ne__(self, val):
69 if isinstance(val, str):
70 return (self.arch_string != val)
71 # This signals to use the normal comparison operator
75 return '<Architecture %s>' % self.arch_string
77 __all__.append('Architecture')
79 def get_architecture(architecture, session=None):
81 Returns database id for given C{architecture}.
83 @type architecture: string
84 @param architecture: The name of the architecture
86 @type session: Session
87 @param session: Optional SQLA session object (a temporary one will be
88 generated if not supplied)
91 @return: Architecture object for the given arch (None if not present)
95 session = DBConn().session()
96 q = session.query(Architecture).filter_by(arch_string=architecture)
101 __all__.append('get_architecture')
103 def get_architecture_suites(architecture, session=None):
105 Returns list of Suite objects for given C{architecture} name
108 @param source: Architecture name to search for
110 @type session: Session
111 @param session: Optional SQL session object (a temporary one will be
112 generated if not supplied)
115 @return: list of Suite objects for the given name (may be empty)
119 session = DBConn().session()
121 q = session.query(Suite)
122 q = q.join(SuiteArchitecture)
123 q = q.join(Architecture).filter_by(arch_string=architecture).order_by('suite_name')
126 __all__.append('get_architecture_suites')
128 ################################################################################
130 class Archive(object):
131 def __init__(self, *args, **kwargs):
135 return '<Archive %s>' % self.name
137 __all__.append('Archive')
139 def get_archive(archive, session=None):
141 returns database id for given c{archive}.
143 @type archive: string
144 @param archive: the name of the arhive
146 @type session: Session
147 @param session: Optional SQLA session object (a temporary one will be
148 generated if not supplied)
151 @return: Archive object for the given name (None if not present)
154 archive = archive.lower()
156 session = DBConn().session()
157 q = session.query(Archive).filter_by(archive_name=archive)
162 __all__.append('get_archive')
164 ################################################################################
166 class BinAssociation(object):
167 def __init__(self, *args, **kwargs):
171 return '<BinAssociation %s (%s, %s)>' % (self.ba_id, self.binary, self.suite)
173 __all__.append('BinAssociation')
175 ################################################################################
177 class DBBinary(object):
178 def __init__(self, *args, **kwargs):
182 return '<DBBinary %s (%s, %s)>' % (self.package, self.version, self.architecture)
184 __all__.append('DBBinary')
186 def get_suites_binary_in(package, session=None):
188 Returns list of Suite objects which given C{package} name is in
191 @param source: DBBinary package name to search for
194 @return: list of Suite objects for the given package
198 session = DBConn().session()
200 return session.query(Suite).join(BinAssociation).join(DBBinary).filter_by(package=package).all()
202 __all__.append('get_suites_binary_in')
204 def get_binary_from_id(id, session=None):
206 Returns DBBinary object for given C{id}
209 @param id: Id of the required binary
211 @type session: Session
212 @param session: Optional SQLA session object (a temporary one will be
213 generated if not supplied)
216 @return: DBBinary object for the given binary (None if not present)
219 session = DBConn().session()
220 q = session.query(DBBinary).filter_by(binary_id=id)
225 __all__.append('get_binary_from_id')
227 def get_binaries_from_name(package, version=None, architecture=None, session=None):
229 Returns list of DBBinary objects for given C{package} name
232 @param package: DBBinary package name to search for
234 @type version: str or None
235 @param version: Version to search for (or None)
237 @type package: str, list or None
238 @param package: Architectures to limit to (or None if no limit)
240 @type session: Session
241 @param session: Optional SQL session object (a temporary one will be
242 generated if not supplied)
245 @return: list of DBBinary objects for the given name (may be empty)
248 session = DBConn().session()
250 q = session.query(DBBinary).filter_by(package=package)
252 if version is not None:
253 q = q.filter_by(version=version)
255 if architecture is not None:
256 if not isinstance(architecture, list):
257 architecture = [architecture]
258 q = q.join(Architecture).filter(Architecture.arch_string.in_(architecture))
262 __all__.append('get_binaries_from_name')
264 def get_binaries_from_source_id(source_id, session=None):
266 Returns list of DBBinary objects for given C{source_id}
269 @param source_id: source_id to search for
271 @type session: Session
272 @param session: Optional SQL session object (a temporary one will be
273 generated if not supplied)
276 @return: list of DBBinary objects for the given name (may be empty)
279 session = DBConn().session()
280 return session.query(DBBinary).filter_by(source_id=source_id).all()
282 __all__.append('get_binaries_from_source_id')
285 def get_binary_from_name_suite(package, suitename, session=None):
286 ### For dak examine-package
287 ### XXX: Doesn't use object API yet
289 session = DBConn().session()
291 sql = """SELECT DISTINCT(b.package), b.version, c.name, su.suite_name
292 FROM binaries b, files fi, location l, component c, bin_associations ba, suite su
293 WHERE b.package=:package
295 AND fi.location = l.id
296 AND l.component = c.id
299 AND su.suite_name=:suitename
300 ORDER BY b.version DESC"""
302 return session.execute(sql, {'package': package, 'suitename': suitename})
304 __all__.append('get_binary_from_name_suite')
306 def get_binary_components(package, suitename, arch, session=None):
307 # Check for packages that have moved from one component to another
308 query = """SELECT c.name FROM binaries b, bin_associations ba, suite s, location l, component c, architecture a, files f
309 WHERE b.package=:package AND s.suite_name=:suitename
310 AND (a.arch_string = :arch OR a.arch_string = 'all')
311 AND ba.bin = b.id AND ba.suite = s.id AND b.architecture = a.id
312 AND f.location = l.id
313 AND l.component = c.id
316 vals = {'package': package, 'suitename': suitename, 'arch': arch}
319 session = DBConn().session()
320 return session.execute(query, vals)
322 __all__.append('get_binary_components')
324 ################################################################################
326 class Component(object):
327 def __init__(self, *args, **kwargs):
330 def __eq__(self, val):
331 if isinstance(val, str):
332 return (self.component_name == val)
333 # This signals to use the normal comparison operator
334 return NotImplemented
336 def __ne__(self, val):
337 if isinstance(val, str):
338 return (self.component_name != val)
339 # This signals to use the normal comparison operator
340 return NotImplemented
343 return '<Component %s>' % self.component_name
346 __all__.append('Component')
348 def get_component(component, session=None):
350 Returns database id for given C{component}.
352 @type component: string
353 @param component: The name of the override type
356 @return: the database id for the given component
359 component = component.lower()
361 session = DBConn().session()
362 q = session.query(Component).filter_by(component_name=component)
367 __all__.append('get_component')
369 ################################################################################
371 class DBConfig(object):
372 def __init__(self, *args, **kwargs):
376 return '<DBConfig %s>' % self.name
378 __all__.append('DBConfig')
380 ################################################################################
382 class ContentFilename(object):
383 def __init__(self, *args, **kwargs):
387 return '<ContentFilename %s>' % self.filename
389 __all__.append('ContentFilename')
391 def get_or_set_contents_file_id(filename, session=None):
393 Returns database id for given filename.
395 If no matching file is found, a row is inserted.
397 @type filename: string
398 @param filename: The filename
399 @type session: SQLAlchemy
400 @param session: Optional SQL session object (a temporary one will be
401 generated if not supplied). If not passed, a commit will be performed at
402 the end of the function, otherwise the caller is responsible for commiting.
405 @return: the database id for the given component
409 session = DBConn().session()
413 q = session.query(ContentFilename).filter_by(filename=filename)
415 cf = ContentFilename()
416 cf.filename = filename
420 return cf.cafilename_id
422 return q.one().cafilename_id
425 traceback.print_exc()
428 __all__.append('get_or_set_contents_file_id')
430 def get_contents(suite, overridetype, section=None, session=None):
432 Returns contents for a suite / overridetype combination, limiting
433 to a section if not None.
436 @param suite: Suite object
438 @type overridetype: OverrideType
439 @param overridetype: OverrideType object
441 @type section: Section
442 @param section: Optional section object to limit results to
444 @type session: SQLAlchemy
445 @param session: Optional SQL session object (a temporary one will be
446 generated if not supplied)
449 @return: ResultsProxy object set up to return tuples of (filename, section,
454 session = DBConn().session()
456 # find me all of the contents for a given suite
457 contents_q = """SELECT (p.path||'/'||n.file) AS fn,
461 FROM content_associations c join content_file_paths p ON (c.filepath=p.id)
462 JOIN content_file_names n ON (c.filename=n.id)
463 JOIN binaries b ON (b.id=c.binary_pkg)
464 JOIN override o ON (o.package=b.package)
465 JOIN section s ON (s.id=o.section)
466 WHERE o.suite = :suiteid AND o.type = :overridetypeid
467 AND b.type=:overridetypename"""
469 vals = {'suiteid': suite.suite_id,
470 'overridetypeid': overridetype.overridetype_id,
471 'overridetypename': overridetype.overridetype}
473 if section is not None:
474 contents_q += " AND s.id = :sectionid"
475 vals['sectionid'] = section.section_id
477 contents_q += " ORDER BY fn"
479 return session.execute(contents_q, vals)
481 __all__.append('get_contents')
483 ################################################################################
485 class ContentFilepath(object):
486 def __init__(self, *args, **kwargs):
490 return '<ContentFilepath %s>' % self.filepath
492 __all__.append('ContentFilepath')
494 def get_or_set_contents_path_id(filepath, session):
496 Returns database id for given path.
498 If no matching file is found, a row is inserted.
500 @type filename: string
501 @param filename: The filepath
502 @type session: SQLAlchemy
503 @param session: Optional SQL session object (a temporary one will be
504 generated if not supplied). If not passed, a commit will be performed at
505 the end of the function, otherwise the caller is responsible for commiting.
508 @return: the database id for the given path
512 session = DBConn().session()
516 q = session.query(ContentFilepath).filter_by(filepath=filepath)
518 cf = ContentFilepath()
519 cf.filepath = filepath
523 return cf.cafilepath_id
525 return q.one().cafilepath_id
528 traceback.print_exc()
531 __all__.append('get_or_set_contents_path_id')
533 ################################################################################
535 class ContentAssociation(object):
536 def __init__(self, *args, **kwargs):
540 return '<ContentAssociation %s>' % self.ca_id
542 __all__.append('ContentAssociation')
544 def insert_content_paths(binary_id, fullpaths, session=None):
546 Make sure given path is associated with given binary id
549 @param binary_id: the id of the binary
550 @type fullpaths: list
551 @param fullpaths: the list of paths of the file being associated with the binary
552 @type session: SQLAlchemy session
553 @param session: Optional SQLAlchemy session. If this is passed, the caller
554 is responsible for ensuring a transaction has begun and committing the
555 results or rolling back based on the result code. If not passed, a commit
556 will be performed at the end of the function, otherwise the caller is
557 responsible for commiting.
559 @return: True upon success
565 session = DBConn().session()
569 for fullpath in fullpaths:
570 (path, file) = os.path.split(fullpath)
572 # Get the necessary IDs ...
573 ca = ContentAssociation()
574 ca.binary_id = binary_id
575 ca.filename_id = get_or_set_contents_file_id(file)
576 ca.filepath_id = get_or_set_contents_path_id(path)
579 # Only commit if we set up the session ourself
585 traceback.print_exc()
587 # Only rollback if we set up the session ourself
593 __all__.append('insert_content_paths')
595 ################################################################################
597 class DSCFile(object):
598 def __init__(self, *args, **kwargs):
602 return '<DSCFile %s>' % self.dscfile_id
604 __all__.append('DSCFile')
606 ################################################################################
608 class PoolFile(object):
609 def __init__(self, *args, **kwargs):
613 return '<PoolFile %s>' % self.filename
615 __all__.append('PoolFile')
617 def check_poolfile(filename, filesize, md5sum, location_id, session=None):
620 (ValidFileFound [boolean or None], PoolFile object or None)
622 @type filename: string
623 @param filename: the filename of the file to check against the DB
626 @param filesize: the size of the file to check against the DB
629 @param md5sum: the md5sum of the file to check against the DB
631 @type location_id: int
632 @param location_id: the id of the location to look in
635 @return: Tuple of length 2.
636 If more than one file found with that name:
638 If valid pool file found: (True, PoolFile object)
639 If valid pool file not found:
640 (False, None) if no file found
641 (False, PoolFile object) if file found with size/md5sum mismatch
645 session = DBConn().session()
647 q = session.query(PoolFile).filter_by(filename=filename)
648 q = q.join(Location).filter_by(location_id=location_id)
656 if obj.md5sum != md5sum or obj.filesize != filesize:
661 __all__.append('check_poolfile')
664 def get_poolfile_by_name(filename, location_id=None, session=None):
666 Returns an array of PoolFile objects for the given filename and
667 (optionally) location_id
669 @type filename: string
670 @param filename: the filename of the file to check against the DB
672 @type location_id: int
673 @param location_id: the id of the location to look in (optional)
676 @return: array of PoolFile objects
680 session = DBConn().session()
682 q = session.query(PoolFile).filter_by(filename=filename)
684 if location_id is not None:
685 q = q.join(Location).filter_by(location_id=location_id)
689 __all__.append('get_poolfile_by_name')
691 def get_poolfile_like_name(filename, session=None):
693 Returns an array of PoolFile objects which are like the given name
695 @type filename: string
696 @param filename: the filename of the file to check against the DB
699 @return: array of PoolFile objects
703 session = DBConn().session()
705 # TODO: There must be a way of properly using bind parameters with %FOO%
706 q = session.query(PoolFile).filter(PoolFile.filename.like('%%%s%%' % filename))
710 __all__.append('get_poolfile_like_name')
712 ################################################################################
714 class Fingerprint(object):
715 def __init__(self, *args, **kwargs):
719 return '<Fingerprint %s>' % self.fingerprint
721 __all__.append('Fingerprint')
723 ################################################################################
725 class Keyring(object):
726 def __init__(self, *args, **kwargs):
730 return '<Keyring %s>' % self.keyring_name
732 __all__.append('Keyring')
734 ################################################################################
736 class Location(object):
737 def __init__(self, *args, **kwargs):
741 return '<Location %s (%s)>' % (self.path, self.location_id)
743 __all__.append('Location')
745 def get_location(location, component=None, archive=None, session=None):
747 Returns Location object for the given combination of location, component
750 @type location: string
751 @param location: the path of the location, e.g. I{/srv/ftp.debian.org/ftp/pool/}
753 @type component: string
754 @param component: the component name (if None, no restriction applied)
756 @type archive: string
757 @param archive_id: the archive name (if None, no restriction applied)
759 @rtype: Location / None
760 @return: Either a Location object or None if one can't be found
764 session = DBConn().session()
766 q = session.query(Location).filter_by(path=location)
768 if archive is not None:
769 q = q.join(Archive).filter_by(archive_name=archive)
771 if component is not None:
772 q = q.join(Component).filter_by(component_name=component)
779 __all__.append('get_location')
781 ################################################################################
783 class Maintainer(object):
784 def __init__(self, *args, **kwargs):
788 return '''<Maintainer '%s' (%s)>''' % (self.name, self.maintainer_id)
790 def get_split_maintainer(self):
791 if not hasattr(self, 'name') or self.name is None:
792 return ('', '', '', '')
794 return fix_maintainer(self.name.strip())
796 __all__.append('Maintainer')
798 ################################################################################
800 class NewComment(object):
801 def __init__(self, *args, **kwargs):
805 return '''<NewComment for '%s %s' (%s)>''' % (self.package, self.version, self.comment_id)
807 __all__.append('NewComment')
809 def has_new_comment(package, version, session=None):
811 Returns true if the given combination of C{package}, C{version} has a comment.
813 @type package: string
814 @param package: name of the package
816 @type version: string
817 @param version: package version
819 @type session: Session
820 @param session: Optional SQLA session object (a temporary one will be
821 generated if not supplied)
828 session = DBConn().session()
830 q = session.query(NewComment)
831 q = q.filter_by(package=package)
832 q = q.filter_by(version=version)
835 __all__.append('has_new_comment')
837 def get_new_comments(package=None, version=None, comment_id=None, session=None):
839 Returns (possibly empty) list of NewComment objects for the given
842 @type package: string (optional)
843 @param package: name of the package
845 @type version: string (optional)
846 @param version: package version
848 @type comment_id: int (optional)
849 @param comment_id: An id of a comment
851 @type session: Session
852 @param session: Optional SQLA session object (a temporary one will be
853 generated if not supplied)
856 @return: A (possibly empty) list of NewComment objects will be returned
861 session = DBConn().session()
863 q = session.query(NewComment)
864 if package is not None: q = q.filter_by(package=package)
865 if version is not None: q = q.filter_by(version=version)
866 if comment_id is not None: q = q.filter_by(comment_id=comment_id)
870 __all__.append('get_new_comments')
872 ################################################################################
874 class Override(object):
875 def __init__(self, *args, **kwargs):
879 return '<Override %s (%s)>' % (self.package, self.suite_id)
881 __all__.append('Override')
883 def get_override(package, suite=None, component=None, overridetype=None, session=None):
885 Returns Override object for the given parameters
887 @type package: string
888 @param package: The name of the package
890 @type suite: string, list or None
891 @param suite: The name of the suite (or suites if a list) to limit to. If
892 None, don't limit. Defaults to None.
894 @type component: string, list or None
895 @param component: The name of the component (or components if a list) to
896 limit to. If None, don't limit. Defaults to None.
898 @type overridetype: string, list or None
899 @param overridetype: The name of the overridetype (or overridetypes if a list) to
900 limit to. If None, don't limit. Defaults to None.
902 @type session: Session
903 @param session: Optional SQLA session object (a temporary one will be
904 generated if not supplied)
907 @return: A (possibly empty) list of Override objects will be returned
911 session = DBConn().session()
913 q = session.query(Override)
914 q = q.filter_by(package=package)
916 if suite is not None:
917 if not isinstance(suite, list): suite = [suite]
918 q = q.join(Suite).filter(Suite.suite_name.in_(suite))
920 if component is not None:
921 if not isinstance(component, list): component = [component]
922 q = q.join(Component).filter(Component.component_name.in_(component))
924 if overridetype is not None:
925 if not isinstance(overridetype, list): overridetype = [overridetype]
926 q = q.join(OverrideType).filter(OverrideType.overridetype.in_(overridetype))
930 __all__.append('get_override')
933 ################################################################################
935 class OverrideType(object):
936 def __init__(self, *args, **kwargs):
940 return '<OverrideType %s>' % self.overridetype
942 __all__.append('OverrideType')
944 def get_override_type(override_type, session=None):
946 Returns OverrideType object for given C{override type}.
948 @type override_type: string
949 @param override_type: The name of the override type
951 @type session: Session
952 @param session: Optional SQLA session object (a temporary one will be
953 generated if not supplied)
956 @return: the database id for the given override type
960 session = DBConn().session()
961 q = session.query(OverrideType).filter_by(overridetype=override_type)
966 __all__.append('get_override_type')
968 ################################################################################
970 class PendingContentAssociation(object):
971 def __init__(self, *args, **kwargs):
975 return '<PendingContentAssociation %s>' % self.pca_id
977 __all__.append('PendingContentAssociation')
979 def insert_pending_content_paths(package, fullpaths, session=None):
981 Make sure given paths are temporarily associated with given
985 @param package: the package to associate with should have been read in from the binary control file
986 @type fullpaths: list
987 @param fullpaths: the list of paths of the file being associated with the binary
988 @type session: SQLAlchemy session
989 @param session: Optional SQLAlchemy session. If this is passed, the caller
990 is responsible for ensuring a transaction has begun and committing the
991 results or rolling back based on the result code. If not passed, a commit
992 will be performed at the end of the function
994 @return: True upon success, False if there is a problem
1000 session = DBConn().session()
1004 arch = get_architecture(package['Architecture'], session)
1005 arch_id = arch.arch_id
1007 # Remove any already existing recorded files for this package
1008 q = session.query(PendingContentAssociation)
1009 q = q.filter_by(package=package['Package'])
1010 q = q.filter_by(version=package['Version'])
1011 q = q.filter_by(architecture=arch_id)
1015 for fullpath in fullpaths:
1016 (path, file) = os.path.split(fullpath)
1018 if path.startswith( "./" ):
1021 pca = PendingContentAssociation()
1022 pca.package = package['Package']
1023 pca.version = package['Version']
1024 pca.filename_id = get_or_set_contents_file_id(file, session)
1025 pca.filepath_id = get_or_set_contents_path_id(path, session)
1026 pca.architecture = arch_id
1029 # Only commit if we set up the session ourself
1035 traceback.print_exc()
1037 # Only rollback if we set up the session ourself
1043 __all__.append('insert_pending_content_paths')
1045 ################################################################################
1047 class Priority(object):
1048 def __init__(self, *args, **kwargs):
1051 def __eq__(self, val):
1052 if isinstance(val, str):
1053 return (self.priority == val)
1054 # This signals to use the normal comparison operator
1055 return NotImplemented
1057 def __ne__(self, val):
1058 if isinstance(val, str):
1059 return (self.priority != val)
1060 # This signals to use the normal comparison operator
1061 return NotImplemented
1064 return '<Priority %s (%s)>' % (self.priority, self.priority_id)
1066 __all__.append('Priority')
1068 def get_priority(priority, session=None):
1070 Returns Priority object for given C{priority name}.
1072 @type priority: string
1073 @param priority: The name of the priority
1075 @type session: Session
1076 @param session: Optional SQLA session object (a temporary one will be
1077 generated if not supplied)
1080 @return: Priority object for the given priority
1084 session = DBConn().session()
1085 q = session.query(Priority).filter_by(priority=priority)
1090 __all__.append('get_priority')
1092 ################################################################################
1094 class Queue(object):
1095 def __init__(self, *args, **kwargs):
1099 return '<Queue %s>' % self.queue_name
1101 def autobuild_upload(self, changes, srcpath, session=None):
1103 Update queue_build database table used for incoming autobuild support.
1105 @type changes: Changes
1106 @param changes: changes object for the upload to process
1108 @type srcpath: string
1109 @param srcpath: path for the queue file entries/link destinations
1111 @type session: SQLAlchemy session
1112 @param session: Optional SQLAlchemy session. If this is passed, the
1113 caller is responsible for ensuring a transaction has begun and
1114 committing the results or rolling back based on the result code. If
1115 not passed, a commit will be performed at the end of the function,
1116 otherwise the caller is responsible for commiting.
1118 @rtype: NoneType or string
1119 @return: None if the operation failed, a string describing the error if not
1124 session = DBConn().session()
1127 # TODO: Remove by moving queue config into the database
1130 for suitename in changes.changes["distribution"].keys():
1131 # TODO: Move into database as:
1132 # buildqueuedir TEXT DEFAULT NULL (i.e. NULL is no build)
1133 # buildqueuecopy BOOLEAN NOT NULL DEFAULT FALSE (i.e. default is symlink)
1134 # This also gets rid of the SecurityQueueBuild hack below
1135 if suitename not in conf.ValueList("Dinstall::QueueBuildSuites"):
1139 s = get_suite(suitename, session)
1141 return "INTERNAL ERROR: Could not find suite %s" % suitename
1143 # TODO: Get from database as above
1144 dest_dir = conf["Dir::QueueBuild"]
1146 # TODO: Move into database as above
1147 if conf.FindB("Dinstall::SecurityQueueBuild"):
1148 dest_dir = os.path.join(dest_dir, suitename)
1150 for file_entry in changes.files.keys():
1151 src = os.path.join(srcpath, file_entry)
1152 dest = os.path.join(dest_dir, file_entry)
1154 # TODO: Move into database as above
1155 if Cnf.FindB("Dinstall::SecurityQueueBuild"):
1156 # Copy it since the original won't be readable by www-data
1157 utils.copy(src, dest)
1159 # Create a symlink to it
1160 os.symlink(src, dest)
1163 qb.suite_id = s.suite_id
1164 qb.queue_id = self.queue_id
1170 # If the .orig.tar.gz is in the pool, create a symlink to
1171 # it (if one doesn't already exist)
1172 if changes.orig_tar_id:
1173 # Determine the .orig.tar.gz file name
1174 for dsc_file in changes.dsc_files.keys():
1175 if dsc_file.endswith(".orig.tar.gz"):
1178 dest = os.path.join(dest_dir, filename)
1180 # If it doesn't exist, create a symlink
1181 if not os.path.exists(dest):
1182 q = session.execute("SELECT l.path, f.filename FROM location l, files f WHERE f.id = :id and f.location = l.id",
1183 {'id': changes.orig_tar_id})
1186 return "[INTERNAL ERROR] Couldn't find id %s in files table." % (changes.orig_tar_id)
1188 src = os.path.join(res[0], res[1])
1189 os.symlink(src, dest)
1191 # Add it to the list of packages for later processing by apt-ftparchive
1193 qb.suite_id = s.suite_id
1194 qb.queue_id = self.queue_id
1199 # If it does, update things to ensure it's not removed prematurely
1201 qb = get_queue_build(dest, suite_id, session)
1212 __all__.append('Queue')
1214 def get_queue(queuename, session=None):
1216 Returns Queue object for given C{queue name}.
1218 @type queuename: string
1219 @param queuename: The name of the queue
1221 @type session: Session
1222 @param session: Optional SQLA session object (a temporary one will be
1223 generated if not supplied)
1226 @return: Queue object for the given queue
1230 session = DBConn().session()
1231 q = session.query(Queue).filter_by(queue_name=queuename)
1236 __all__.append('get_queue')
1238 ################################################################################
1240 class QueueBuild(object):
1241 def __init__(self, *args, **kwargs):
1245 return '<QueueBuild %s (%s)>' % (self.filename, self.queue_id)
1247 __all__.append('QueueBuild')
1249 def get_queue_build(filename, suite_id, session=None):
1251 Returns QueueBuild object for given C{filename} and C{suite id}.
1253 @type filename: string
1254 @param filename: The name of the file
1257 @param suiteid: Suite ID
1259 @type session: Session
1260 @param session: Optional SQLA session object (a temporary one will be
1261 generated if not supplied)
1264 @return: Queue object for the given queue
1268 session = DBConn().session()
1269 q = session.query(QueueBuild).filter_by(filename=filename).filter_by(suite_id=suite_id)
1274 __all__.append('get_queue_build')
1276 ################################################################################
1278 class Section(object):
1279 def __init__(self, *args, **kwargs):
1282 def __eq__(self, val):
1283 if isinstance(val, str):
1284 return (self.section == val)
1285 # This signals to use the normal comparison operator
1286 return NotImplemented
1288 def __ne__(self, val):
1289 if isinstance(val, str):
1290 return (self.section != val)
1291 # This signals to use the normal comparison operator
1292 return NotImplemented
1295 return '<Section %s>' % self.section
1297 __all__.append('Section')
1299 def get_section(section, session=None):
1301 Returns Section object for given C{section name}.
1303 @type section: string
1304 @param section: The name of the section
1306 @type session: Session
1307 @param session: Optional SQLA session object (a temporary one will be
1308 generated if not supplied)
1311 @return: Section object for the given section name
1315 session = DBConn().session()
1316 q = session.query(Section).filter_by(section=section)
1321 __all__.append('get_section')
1323 ################################################################################
1325 class DBSource(object):
1326 def __init__(self, *args, **kwargs):
1330 return '<DBSource %s (%s)>' % (self.source, self.version)
1332 __all__.append('DBSource')
1334 def source_exists(source, source_version, suites = ["any"], session=None):
1336 Ensure that source exists somewhere in the archive for the binary
1337 upload being processed.
1338 1. exact match => 1.0-3
1339 2. bin-only NMU => 1.0-3+b1 , 1.0-3.1+b1
1341 @type package: string
1342 @param package: package source name
1344 @type source_version: string
1345 @param source_version: expected source version
1348 @param suites: list of suites to check in, default I{any}
1350 @type session: Session
1351 @param session: Optional SQLA session object (a temporary one will be
1352 generated if not supplied)
1355 @return: returns 1 if a source with expected version is found, otherwise 0
1360 session = DBConn().session()
1364 for suite in suites:
1365 q = session.query(DBSource).filter_by(source=source)
1367 # source must exist in suite X, or in some other suite that's
1368 # mapped to X, recursively... silent-maps are counted too,
1369 # unreleased-maps aren't.
1370 maps = cnf.ValueList("SuiteMappings")[:]
1372 maps = [ m.split() for m in maps ]
1373 maps = [ (x[1], x[2]) for x in maps
1374 if x[0] == "map" or x[0] == "silent-map" ]
1377 if x[1] in s and x[0] not in s:
1380 q = q.join(SrcAssociation).join(Suite)
1381 q = q.filter(Suite.suite_name.in_(s))
1383 # Reduce the query results to a list of version numbers
1384 ql = [ j.version for j in q.all() ]
1387 if source_version in ql:
1391 from daklib.regexes import re_bin_only_nmu
1392 orig_source_version = re_bin_only_nmu.sub('', source_version)
1393 if orig_source_version in ql:
1396 # No source found so return not ok
1402 __all__.append('source_exists')
1404 def get_suites_source_in(source, session=None):
1406 Returns list of Suite objects which given C{source} name is in
1409 @param source: DBSource package name to search for
1412 @return: list of Suite objects for the given source
1416 session = DBConn().session()
1418 return session.query(Suite).join(SrcAssociation).join(DBSource).filter_by(source=source).all()
1420 __all__.append('get_suites_source_in')
1422 def get_sources_from_name(source, version=None, dm_upload_allowed=None, session=None):
1424 Returns list of DBSource objects for given C{source} name and other parameters
1427 @param source: DBSource package name to search for
1429 @type source: str or None
1430 @param source: DBSource version name to search for or None if not applicable
1432 @type dm_upload_allowed: bool
1433 @param dm_upload_allowed: If None, no effect. If True or False, only
1434 return packages with that dm_upload_allowed setting
1436 @type session: Session
1437 @param session: Optional SQL session object (a temporary one will be
1438 generated if not supplied)
1441 @return: list of DBSource objects for the given name (may be empty)
1444 session = DBConn().session()
1446 q = session.query(DBSource).filter_by(source=source)
1448 if version is not None:
1449 q = q.filter_by(version=version)
1451 if dm_upload_allowed is not None:
1452 q = q.filter_by(dm_upload_allowed=dm_upload_allowed)
1456 __all__.append('get_sources_from_name')
1458 def get_source_in_suite(source, suite, session=None):
1460 Returns list of DBSource objects for a combination of C{source} and C{suite}.
1462 - B{source} - source package name, eg. I{mailfilter}, I{bbdb}, I{glibc}
1463 - B{suite} - a suite name, eg. I{unstable}
1465 @type source: string
1466 @param source: source package name
1469 @param suite: the suite name
1472 @return: the version for I{source} in I{suite}
1476 session = DBConn().session()
1477 q = session.query(SrcAssociation)
1478 q = q.join('source').filter_by(source=source)
1479 q = q.join('suite').filter_by(suite_name=suite)
1482 # ???: Maybe we should just return the SrcAssociation object instead
1483 return q.one().source
1485 __all__.append('get_source_in_suite')
1487 ################################################################################
1489 class SrcAssociation(object):
1490 def __init__(self, *args, **kwargs):
1494 return '<SrcAssociation %s (%s, %s)>' % (self.sa_id, self.source, self.suite)
1496 __all__.append('SrcAssociation')
1498 ################################################################################
1500 class SrcUploader(object):
1501 def __init__(self, *args, **kwargs):
1505 return '<SrcUploader %s>' % self.uploader_id
1507 __all__.append('SrcUploader')
1509 ################################################################################
1511 SUITE_FIELDS = [ ('SuiteName', 'suite_name'),
1512 ('SuiteID', 'suite_id'),
1513 ('Version', 'version'),
1514 ('Origin', 'origin'),
1516 ('Description', 'description'),
1517 ('Untouchable', 'untouchable'),
1518 ('Announce', 'announce'),
1519 ('Codename', 'codename'),
1520 ('OverrideCodename', 'overridecodename'),
1521 ('ValidTime', 'validtime'),
1522 ('Priority', 'priority'),
1523 ('NotAutomatic', 'notautomatic'),
1524 ('CopyChanges', 'copychanges'),
1525 ('CopyDotDak', 'copydotdak'),
1526 ('CommentsDir', 'commentsdir'),
1527 ('OverrideSuite', 'overridesuite'),
1528 ('ChangelogBase', 'changelogbase')]
1531 class Suite(object):
1532 def __init__(self, *args, **kwargs):
1536 return '<Suite %s>' % self.suite_name
1538 def __eq__(self, val):
1539 if isinstance(val, str):
1540 return (self.suite_name == val)
1541 # This signals to use the normal comparison operator
1542 return NotImplemented
1544 def __ne__(self, val):
1545 if isinstance(val, str):
1546 return (self.suite_name != val)
1547 # This signals to use the normal comparison operator
1548 return NotImplemented
1552 for disp, field in SUITE_FIELDS:
1553 val = getattr(self, field, None)
1555 ret.append("%s: %s" % (disp, val))
1557 return "\n".join(ret)
1559 __all__.append('Suite')
1561 def get_suite_architecture(suite, architecture, session=None):
1563 Returns a SuiteArchitecture object given C{suite} and ${arch} or None if it
1567 @param suite: Suite name to search for
1569 @type architecture: str
1570 @param architecture: Architecture name to search for
1572 @type session: Session
1573 @param session: Optional SQL session object (a temporary one will be
1574 generated if not supplied)
1576 @rtype: SuiteArchitecture
1577 @return: the SuiteArchitecture object or None
1581 session = DBConn().session()
1583 q = session.query(SuiteArchitecture)
1584 q = q.join(Architecture).filter_by(arch_string=architecture)
1585 q = q.join(Suite).filter_by(suite_name=suite)
1590 __all__.append('get_suite_architecture')
1592 def get_suite(suite, session=None):
1594 Returns Suite object for given C{suite name}.
1597 @param suite: The name of the suite
1599 @type session: Session
1600 @param session: Optional SQLA session object (a temporary one will be
1601 generated if not supplied)
1604 @return: Suite object for the requested suite name (None if not presenT)
1608 session = DBConn().session()
1609 q = session.query(Suite).filter_by(suite_name=suite)
1614 __all__.append('get_suite')
1616 ################################################################################
1618 class SuiteArchitecture(object):
1619 def __init__(self, *args, **kwargs):
1623 return '<SuiteArchitecture (%s, %s)>' % (self.suite_id, self.arch_id)
1625 __all__.append('SuiteArchitecture')
1627 def get_suite_architectures(suite, skipsrc=False, skipall=False, session=None):
1629 Returns list of Architecture objects for given C{suite} name
1632 @param source: Suite name to search for
1634 @type skipsrc: boolean
1635 @param skipsrc: Whether to skip returning the 'source' architecture entry
1638 @type skipall: boolean
1639 @param skipall: Whether to skip returning the 'all' architecture entry
1642 @type session: Session
1643 @param session: Optional SQL session object (a temporary one will be
1644 generated if not supplied)
1647 @return: list of Architecture objects for the given name (may be empty)
1651 session = DBConn().session()
1653 q = session.query(Architecture)
1654 q = q.join(SuiteArchitecture)
1655 q = q.join(Suite).filter_by(suite_name=suite)
1657 q = q.filter(Architecture.arch_string != 'source')
1659 q = q.filter(Architecture.arch_string != 'all')
1660 q = q.order_by('arch_string')
1663 __all__.append('get_suite_architectures')
1665 ################################################################################
1668 def __init__(self, *args, **kwargs):
1671 def __eq__(self, val):
1672 if isinstance(val, str):
1673 return (self.uid == val)
1674 # This signals to use the normal comparison operator
1675 return NotImplemented
1677 def __ne__(self, val):
1678 if isinstance(val, str):
1679 return (self.uid != val)
1680 # This signals to use the normal comparison operator
1681 return NotImplemented
1684 return '<Uid %s (%s)>' % (self.uid, self.name)
1686 __all__.append('Uid')
1688 def add_database_user(uidname, session=None):
1690 Adds a database user
1692 @type uidname: string
1693 @param uidname: The uid of the user to add
1695 @type session: SQLAlchemy
1696 @param session: Optional SQL session object (a temporary one will be
1697 generated if not supplied). If not passed, a commit will be performed at
1698 the end of the function, otherwise the caller is responsible for commiting.
1701 @return: the uid object for the given uidname
1703 privatetrans = False
1705 session = DBConn().session()
1709 session.execute("CREATE USER :uid", {'uid': uidname})
1713 traceback.print_exc()
1716 __all__.append('add_database_user')
1718 def get_or_set_uid(uidname, session=None):
1720 Returns uid object for given uidname.
1722 If no matching uidname is found, a row is inserted.
1724 @type uidname: string
1725 @param uidname: The uid to add
1727 @type session: SQLAlchemy
1728 @param session: Optional SQL session object (a temporary one will be
1729 generated if not supplied). If not passed, a commit will be performed at
1730 the end of the function, otherwise the caller is responsible for commiting.
1733 @return: the uid object for the given uidname
1735 privatetrans = False
1737 session = DBConn().session()
1741 q = session.query(Uid).filter_by(uid=uidname)
1753 traceback.print_exc()
1756 __all__.append('get_or_set_uid')
1759 def get_uid_from_fingerprint(fpr, session=None):
1761 session = DBConn().session()
1763 q = session.query(Uid)
1764 q = q.join(Fingerprint).filter_by(fingerprint=fpr)
1771 __all__.append('get_uid_from_fingerprint')
1773 ################################################################################
1775 class DBConn(Singleton):
1777 database module init.
1779 def __init__(self, *args, **kwargs):
1780 super(DBConn, self).__init__(*args, **kwargs)
1782 def _startup(self, *args, **kwargs):
1784 if kwargs.has_key('debug'):
1788 def __setuptables(self):
1789 self.tbl_architecture = Table('architecture', self.db_meta, autoload=True)
1790 self.tbl_archive = Table('archive', self.db_meta, autoload=True)
1791 self.tbl_bin_associations = Table('bin_associations', self.db_meta, autoload=True)
1792 self.tbl_binaries = Table('binaries', self.db_meta, autoload=True)
1793 self.tbl_component = Table('component', self.db_meta, autoload=True)
1794 self.tbl_config = Table('config', self.db_meta, autoload=True)
1795 self.tbl_content_associations = Table('content_associations', self.db_meta, autoload=True)
1796 self.tbl_content_file_names = Table('content_file_names', self.db_meta, autoload=True)
1797 self.tbl_content_file_paths = Table('content_file_paths', self.db_meta, autoload=True)
1798 self.tbl_dsc_files = Table('dsc_files', self.db_meta, autoload=True)
1799 self.tbl_files = Table('files', self.db_meta, autoload=True)
1800 self.tbl_fingerprint = Table('fingerprint', self.db_meta, autoload=True)
1801 self.tbl_keyrings = Table('keyrings', self.db_meta, autoload=True)
1802 self.tbl_location = Table('location', self.db_meta, autoload=True)
1803 self.tbl_maintainer = Table('maintainer', self.db_meta, autoload=True)
1804 self.tbl_new_comments = Table('new_comments', self.db_meta, autoload=True)
1805 self.tbl_override = Table('override', self.db_meta, autoload=True)
1806 self.tbl_override_type = Table('override_type', self.db_meta, autoload=True)
1807 self.tbl_pending_content_associations = Table('pending_content_associations', self.db_meta, autoload=True)
1808 self.tbl_priority = Table('priority', self.db_meta, autoload=True)
1809 self.tbl_queue = Table('queue', self.db_meta, autoload=True)
1810 self.tbl_queue_build = Table('queue_build', self.db_meta, autoload=True)
1811 self.tbl_section = Table('section', self.db_meta, autoload=True)
1812 self.tbl_source = Table('source', self.db_meta, autoload=True)
1813 self.tbl_src_associations = Table('src_associations', self.db_meta, autoload=True)
1814 self.tbl_src_uploaders = Table('src_uploaders', self.db_meta, autoload=True)
1815 self.tbl_suite = Table('suite', self.db_meta, autoload=True)
1816 self.tbl_suite_architectures = Table('suite_architectures', self.db_meta, autoload=True)
1817 self.tbl_uid = Table('uid', self.db_meta, autoload=True)
1819 def __setupmappers(self):
1820 mapper(Architecture, self.tbl_architecture,
1821 properties = dict(arch_id = self.tbl_architecture.c.id))
1823 mapper(Archive, self.tbl_archive,
1824 properties = dict(archive_id = self.tbl_archive.c.id,
1825 archive_name = self.tbl_archive.c.name))
1827 mapper(BinAssociation, self.tbl_bin_associations,
1828 properties = dict(ba_id = self.tbl_bin_associations.c.id,
1829 suite_id = self.tbl_bin_associations.c.suite,
1830 suite = relation(Suite),
1831 binary_id = self.tbl_bin_associations.c.bin,
1832 binary = relation(DBBinary)))
1834 mapper(DBBinary, self.tbl_binaries,
1835 properties = dict(binary_id = self.tbl_binaries.c.id,
1836 package = self.tbl_binaries.c.package,
1837 version = self.tbl_binaries.c.version,
1838 maintainer_id = self.tbl_binaries.c.maintainer,
1839 maintainer = relation(Maintainer),
1840 source_id = self.tbl_binaries.c.source,
1841 source = relation(DBSource),
1842 arch_id = self.tbl_binaries.c.architecture,
1843 architecture = relation(Architecture),
1844 poolfile_id = self.tbl_binaries.c.file,
1845 poolfile = relation(PoolFile),
1846 binarytype = self.tbl_binaries.c.type,
1847 fingerprint_id = self.tbl_binaries.c.sig_fpr,
1848 fingerprint = relation(Fingerprint),
1849 install_date = self.tbl_binaries.c.install_date,
1850 binassociations = relation(BinAssociation,
1851 primaryjoin=(self.tbl_binaries.c.id==self.tbl_bin_associations.c.bin))))
1853 mapper(Component, self.tbl_component,
1854 properties = dict(component_id = self.tbl_component.c.id,
1855 component_name = self.tbl_component.c.name))
1857 mapper(DBConfig, self.tbl_config,
1858 properties = dict(config_id = self.tbl_config.c.id))
1860 mapper(ContentAssociation, self.tbl_content_associations,
1861 properties = dict(ca_id = self.tbl_content_associations.c.id,
1862 filename_id = self.tbl_content_associations.c.filename,
1863 filename = relation(ContentFilename),
1864 filepath_id = self.tbl_content_associations.c.filepath,
1865 filepath = relation(ContentFilepath),
1866 binary_id = self.tbl_content_associations.c.binary_pkg,
1867 binary = relation(DBBinary)))
1870 mapper(ContentFilename, self.tbl_content_file_names,
1871 properties = dict(cafilename_id = self.tbl_content_file_names.c.id,
1872 filename = self.tbl_content_file_names.c.file))
1874 mapper(ContentFilepath, self.tbl_content_file_paths,
1875 properties = dict(cafilepath_id = self.tbl_content_file_paths.c.id,
1876 filepath = self.tbl_content_file_paths.c.path))
1878 mapper(DSCFile, self.tbl_dsc_files,
1879 properties = dict(dscfile_id = self.tbl_dsc_files.c.id,
1880 source_id = self.tbl_dsc_files.c.source,
1881 source = relation(DBSource),
1882 poolfile_id = self.tbl_dsc_files.c.file,
1883 poolfile = relation(PoolFile)))
1885 mapper(PoolFile, self.tbl_files,
1886 properties = dict(file_id = self.tbl_files.c.id,
1887 filesize = self.tbl_files.c.size,
1888 location_id = self.tbl_files.c.location,
1889 location = relation(Location)))
1891 mapper(Fingerprint, self.tbl_fingerprint,
1892 properties = dict(fingerprint_id = self.tbl_fingerprint.c.id,
1893 uid_id = self.tbl_fingerprint.c.uid,
1894 uid = relation(Uid),
1895 keyring_id = self.tbl_fingerprint.c.keyring,
1896 keyring = relation(Keyring)))
1898 mapper(Keyring, self.tbl_keyrings,
1899 properties = dict(keyring_name = self.tbl_keyrings.c.name,
1900 keyring_id = self.tbl_keyrings.c.id))
1902 mapper(Location, self.tbl_location,
1903 properties = dict(location_id = self.tbl_location.c.id,
1904 component_id = self.tbl_location.c.component,
1905 component = relation(Component),
1906 archive_id = self.tbl_location.c.archive,
1907 archive = relation(Archive),
1908 archive_type = self.tbl_location.c.type))
1910 mapper(Maintainer, self.tbl_maintainer,
1911 properties = dict(maintainer_id = self.tbl_maintainer.c.id))
1913 mapper(NewComment, self.tbl_new_comments,
1914 properties = dict(comment_id = self.tbl_new_comments.c.id))
1916 mapper(Override, self.tbl_override,
1917 properties = dict(suite_id = self.tbl_override.c.suite,
1918 suite = relation(Suite),
1919 component_id = self.tbl_override.c.component,
1920 component = relation(Component),
1921 priority_id = self.tbl_override.c.priority,
1922 priority = relation(Priority),
1923 section_id = self.tbl_override.c.section,
1924 section = relation(Section),
1925 overridetype_id = self.tbl_override.c.type,
1926 overridetype = relation(OverrideType)))
1928 mapper(OverrideType, self.tbl_override_type,
1929 properties = dict(overridetype = self.tbl_override_type.c.type,
1930 overridetype_id = self.tbl_override_type.c.id))
1932 mapper(PendingContentAssociation, self.tbl_pending_content_associations,
1933 properties = dict(pca_id = self.tbl_pending_content_associations.c.id,
1934 filepath_id = self.tbl_pending_content_associations.c.filepath,
1935 filepath = relation(ContentFilepath),
1936 filename_id = self.tbl_pending_content_associations.c.filename,
1937 filename = relation(ContentFilename)))
1939 mapper(Priority, self.tbl_priority,
1940 properties = dict(priority_id = self.tbl_priority.c.id))
1942 mapper(Queue, self.tbl_queue,
1943 properties = dict(queue_id = self.tbl_queue.c.id))
1945 mapper(QueueBuild, self.tbl_queue_build,
1946 properties = dict(suite_id = self.tbl_queue_build.c.suite,
1947 queue_id = self.tbl_queue_build.c.queue,
1948 queue = relation(Queue, backref='queuebuild')))
1950 mapper(Section, self.tbl_section,
1951 properties = dict(section_id = self.tbl_section.c.id))
1953 mapper(DBSource, self.tbl_source,
1954 properties = dict(source_id = self.tbl_source.c.id,
1955 version = self.tbl_source.c.version,
1956 maintainer_id = self.tbl_source.c.maintainer,
1957 maintainer = relation(Maintainer,
1958 primaryjoin=(self.tbl_source.c.maintainer==self.tbl_maintainer.c.id)),
1959 poolfile_id = self.tbl_source.c.file,
1960 poolfile = relation(PoolFile),
1961 fingerprint_id = self.tbl_source.c.sig_fpr,
1962 fingerprint = relation(Fingerprint),
1963 changedby_id = self.tbl_source.c.changedby,
1964 changedby = relation(Maintainer,
1965 primaryjoin=(self.tbl_source.c.changedby==self.tbl_maintainer.c.id)),
1966 srcfiles = relation(DSCFile,
1967 primaryjoin=(self.tbl_source.c.id==self.tbl_dsc_files.c.source)),
1968 srcassociations = relation(SrcAssociation,
1969 primaryjoin=(self.tbl_source.c.id==self.tbl_src_associations.c.source))))
1971 mapper(SrcAssociation, self.tbl_src_associations,
1972 properties = dict(sa_id = self.tbl_src_associations.c.id,
1973 suite_id = self.tbl_src_associations.c.suite,
1974 suite = relation(Suite),
1975 source_id = self.tbl_src_associations.c.source,
1976 source = relation(DBSource)))
1978 mapper(SrcUploader, self.tbl_src_uploaders,
1979 properties = dict(uploader_id = self.tbl_src_uploaders.c.id,
1980 source_id = self.tbl_src_uploaders.c.source,
1981 source = relation(DBSource,
1982 primaryjoin=(self.tbl_src_uploaders.c.source==self.tbl_source.c.id)),
1983 maintainer_id = self.tbl_src_uploaders.c.maintainer,
1984 maintainer = relation(Maintainer,
1985 primaryjoin=(self.tbl_src_uploaders.c.maintainer==self.tbl_maintainer.c.id))))
1987 mapper(Suite, self.tbl_suite,
1988 properties = dict(suite_id = self.tbl_suite.c.id))
1990 mapper(SuiteArchitecture, self.tbl_suite_architectures,
1991 properties = dict(suite_id = self.tbl_suite_architectures.c.suite,
1992 suite = relation(Suite, backref='suitearchitectures'),
1993 arch_id = self.tbl_suite_architectures.c.architecture,
1994 architecture = relation(Architecture)))
1996 mapper(Uid, self.tbl_uid,
1997 properties = dict(uid_id = self.tbl_uid.c.id,
1998 fingerprint = relation(Fingerprint)))
2000 ## Connection functions
2001 def __createconn(self):
2002 from config import Config
2006 connstr = "postgres://%s" % cnf["DB::Host"]
2007 if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
2008 connstr += ":%s" % cnf["DB::Port"]
2009 connstr += "/%s" % cnf["DB::Name"]
2012 connstr = "postgres:///%s" % cnf["DB::Name"]
2013 if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
2014 connstr += "?port=%s" % cnf["DB::Port"]
2016 self.db_pg = create_engine(connstr, echo=self.debug)
2017 self.db_meta = MetaData()
2018 self.db_meta.bind = self.db_pg
2019 self.db_smaker = sessionmaker(bind=self.db_pg,
2023 self.__setuptables()
2024 self.__setupmappers()
2027 return self.db_smaker()
2029 __all__.append('DBConn')