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 ################################################################################
41 from inspect import getargspec
43 from sqlalchemy import create_engine, Table, MetaData
44 from sqlalchemy.orm import sessionmaker, mapper, relation
46 # Don't remove this, we re-export the exceptions to scripts which import us
47 from sqlalchemy.exc import *
48 from sqlalchemy.orm.exc import NoResultFound
50 # Only import Config until Queue stuff is changed to store its config
52 from config import Config
53 from singleton import Singleton
54 from textutils import fix_maintainer
56 ################################################################################
58 __all__ = ['IntegrityError', 'SQLAlchemyError']
60 ################################################################################
62 def session_wrapper(fn):
64 Wrapper around common ".., session=None):" handling. If the wrapped
65 function is called without passing 'session', we create a local one
66 and destroy it when the function ends.
68 Also attaches a commit_or_flush method to the session; if we created a
69 local session, this is a synonym for session.commit(), otherwise it is a
70 synonym for session.flush().
73 def wrapped(*args, **kwargs):
74 private_transaction = False
76 # Find the session object
77 session = kwargs.get('session')
80 if len(args) <= len(getargspec(fn)[0]) - 1:
81 # No session specified as last argument or in kwargs
82 private_transaction = True
83 session = kwargs['session'] = DBConn().session()
85 # Session is last argument in args
89 session = args[-1] = DBConn().session()
90 private_transaction = True
92 if private_transaction:
93 session.commit_or_flush = session.commit
95 session.commit_or_flush = session.flush
98 return fn(*args, **kwargs)
100 if private_transaction:
101 # We created a session; close it.
104 wrapped.__doc__ = fn.__doc__
105 wrapped.func_name = fn.func_name
109 ################################################################################
111 class Architecture(object):
112 def __init__(self, *args, **kwargs):
115 def __eq__(self, val):
116 if isinstance(val, str):
117 return (self.arch_string== val)
118 # This signals to use the normal comparison operator
119 return NotImplemented
121 def __ne__(self, val):
122 if isinstance(val, str):
123 return (self.arch_string != val)
124 # This signals to use the normal comparison operator
125 return NotImplemented
128 return '<Architecture %s>' % self.arch_string
130 __all__.append('Architecture')
133 def get_architecture(architecture, session=None):
135 Returns database id for given C{architecture}.
137 @type architecture: string
138 @param architecture: The name of the architecture
140 @type session: Session
141 @param session: Optional SQLA session object (a temporary one will be
142 generated if not supplied)
145 @return: Architecture object for the given arch (None if not present)
148 q = session.query(Architecture).filter_by(arch_string=architecture)
152 except NoResultFound:
155 __all__.append('get_architecture')
158 def get_architecture_suites(architecture, session=None):
160 Returns list of Suite objects for given C{architecture} name
163 @param source: Architecture name to search for
165 @type session: Session
166 @param session: Optional SQL session object (a temporary one will be
167 generated if not supplied)
170 @return: list of Suite objects for the given name (may be empty)
173 q = session.query(Suite)
174 q = q.join(SuiteArchitecture)
175 q = q.join(Architecture).filter_by(arch_string=architecture).order_by('suite_name')
181 __all__.append('get_architecture_suites')
183 ################################################################################
185 class Archive(object):
186 def __init__(self, *args, **kwargs):
190 return '<Archive %s>' % self.archive_name
192 __all__.append('Archive')
195 def get_archive(archive, session=None):
197 returns database id for given C{archive}.
199 @type archive: string
200 @param archive: the name of the arhive
202 @type session: Session
203 @param session: Optional SQLA session object (a temporary one will be
204 generated if not supplied)
207 @return: Archive object for the given name (None if not present)
210 archive = archive.lower()
212 q = session.query(Archive).filter_by(archive_name=archive)
216 except NoResultFound:
219 __all__.append('get_archive')
221 ################################################################################
223 class BinAssociation(object):
224 def __init__(self, *args, **kwargs):
228 return '<BinAssociation %s (%s, %s)>' % (self.ba_id, self.binary, self.suite)
230 __all__.append('BinAssociation')
232 ################################################################################
234 class BinContents(object):
235 def __init__(self, *args, **kwargs):
239 return '<BinContents (%s, %s)>' % (self.binary, self.filename)
241 __all__.append('BinContents')
243 ################################################################################
245 class DBBinary(object):
246 def __init__(self, *args, **kwargs):
250 return '<DBBinary %s (%s, %s)>' % (self.package, self.version, self.architecture)
252 __all__.append('DBBinary')
255 def get_suites_binary_in(package, session=None):
257 Returns list of Suite objects which given C{package} name is in
260 @param source: DBBinary package name to search for
263 @return: list of Suite objects for the given package
266 return session.query(Suite).join(BinAssociation).join(DBBinary).filter_by(package=package).all()
268 __all__.append('get_suites_binary_in')
271 def get_binary_from_id(id, session=None):
273 Returns DBBinary object for given C{id}
276 @param id: Id of the required binary
278 @type session: Session
279 @param session: Optional SQLA session object (a temporary one will be
280 generated if not supplied)
283 @return: DBBinary object for the given binary (None if not present)
286 q = session.query(DBBinary).filter_by(binary_id=id)
290 except NoResultFound:
293 __all__.append('get_binary_from_id')
296 def get_binaries_from_name(package, version=None, architecture=None, session=None):
298 Returns list of DBBinary objects for given C{package} name
301 @param package: DBBinary package name to search for
303 @type version: str or None
304 @param version: Version to search for (or None)
306 @type package: str, list or None
307 @param package: Architectures to limit to (or None if no limit)
309 @type session: Session
310 @param session: Optional SQL session object (a temporary one will be
311 generated if not supplied)
314 @return: list of DBBinary objects for the given name (may be empty)
317 q = session.query(DBBinary).filter_by(package=package)
319 if version is not None:
320 q = q.filter_by(version=version)
322 if architecture is not None:
323 if not isinstance(architecture, list):
324 architecture = [architecture]
325 q = q.join(Architecture).filter(Architecture.arch_string.in_(architecture))
331 __all__.append('get_binaries_from_name')
334 def get_binaries_from_source_id(source_id, session=None):
336 Returns list of DBBinary objects for given C{source_id}
339 @param source_id: source_id to search for
341 @type session: Session
342 @param session: Optional SQL session object (a temporary one will be
343 generated if not supplied)
346 @return: list of DBBinary objects for the given name (may be empty)
349 return session.query(DBBinary).filter_by(source_id=source_id).all()
351 __all__.append('get_binaries_from_source_id')
354 def get_binary_from_name_suite(package, suitename, session=None):
355 ### For dak examine-package
356 ### XXX: Doesn't use object API yet
358 sql = """SELECT DISTINCT(b.package), b.version, c.name, su.suite_name
359 FROM binaries b, files fi, location l, component c, bin_associations ba, suite su
360 WHERE b.package=:package
362 AND fi.location = l.id
363 AND l.component = c.id
366 AND su.suite_name=:suitename
367 ORDER BY b.version DESC"""
369 return session.execute(sql, {'package': package, 'suitename': suitename})
371 __all__.append('get_binary_from_name_suite')
374 def get_binary_components(package, suitename, arch, session=None):
375 # Check for packages that have moved from one component to another
376 query = """SELECT c.name FROM binaries b, bin_associations ba, suite s, location l, component c, architecture a, files f
377 WHERE b.package=:package AND s.suite_name=:suitename
378 AND (a.arch_string = :arch OR a.arch_string = 'all')
379 AND ba.bin = b.id AND ba.suite = s.id AND b.architecture = a.id
380 AND f.location = l.id
381 AND l.component = c.id
384 vals = {'package': package, 'suitename': suitename, 'arch': arch}
386 return session.execute(query, vals)
388 __all__.append('get_binary_components')
390 ################################################################################
392 class BinaryACL(object):
393 def __init__(self, *args, **kwargs):
397 return '<BinaryACL %s>' % self.binary_acl_id
399 __all__.append('BinaryACL')
401 ################################################################################
403 class BinaryACLMap(object):
404 def __init__(self, *args, **kwargs):
408 return '<BinaryACLMap %s>' % self.binary_acl_map_id
410 __all__.append('BinaryACLMap')
412 ################################################################################
414 class Component(object):
415 def __init__(self, *args, **kwargs):
418 def __eq__(self, val):
419 if isinstance(val, str):
420 return (self.component_name == val)
421 # This signals to use the normal comparison operator
422 return NotImplemented
424 def __ne__(self, val):
425 if isinstance(val, str):
426 return (self.component_name != val)
427 # This signals to use the normal comparison operator
428 return NotImplemented
431 return '<Component %s>' % self.component_name
434 __all__.append('Component')
437 def get_component(component, session=None):
439 Returns database id for given C{component}.
441 @type component: string
442 @param component: The name of the override type
445 @return: the database id for the given component
448 component = component.lower()
450 q = session.query(Component).filter_by(component_name=component)
454 except NoResultFound:
457 __all__.append('get_component')
459 ################################################################################
461 class DBConfig(object):
462 def __init__(self, *args, **kwargs):
466 return '<DBConfig %s>' % self.name
468 __all__.append('DBConfig')
470 ################################################################################
473 def get_or_set_contents_file_id(filename, session=None):
475 Returns database id for given filename.
477 If no matching file is found, a row is inserted.
479 @type filename: string
480 @param filename: The filename
481 @type session: SQLAlchemy
482 @param session: Optional SQL session object (a temporary one will be
483 generated if not supplied). If not passed, a commit will be performed at
484 the end of the function, otherwise the caller is responsible for commiting.
487 @return: the database id for the given component
490 q = session.query(ContentFilename).filter_by(filename=filename)
493 ret = q.one().cafilename_id
494 except NoResultFound:
495 cf = ContentFilename()
496 cf.filename = filename
498 session.commit_or_flush()
499 ret = cf.cafilename_id
503 __all__.append('get_or_set_contents_file_id')
506 def get_contents(suite, overridetype, section=None, session=None):
508 Returns contents for a suite / overridetype combination, limiting
509 to a section if not None.
512 @param suite: Suite object
514 @type overridetype: OverrideType
515 @param overridetype: OverrideType object
517 @type section: Section
518 @param section: Optional section object to limit results to
520 @type session: SQLAlchemy
521 @param session: Optional SQL session object (a temporary one will be
522 generated if not supplied)
525 @return: ResultsProxy object set up to return tuples of (filename, section,
529 # find me all of the contents for a given suite
530 contents_q = """SELECT (p.path||'/'||n.file) AS fn,
534 FROM content_associations c join content_file_paths p ON (c.filepath=p.id)
535 JOIN content_file_names n ON (c.filename=n.id)
536 JOIN binaries b ON (b.id=c.binary_pkg)
537 JOIN override o ON (o.package=b.package)
538 JOIN section s ON (s.id=o.section)
539 WHERE o.suite = :suiteid AND o.type = :overridetypeid
540 AND b.type=:overridetypename"""
542 vals = {'suiteid': suite.suite_id,
543 'overridetypeid': overridetype.overridetype_id,
544 'overridetypename': overridetype.overridetype}
546 if section is not None:
547 contents_q += " AND s.id = :sectionid"
548 vals['sectionid'] = section.section_id
550 contents_q += " ORDER BY fn"
552 return session.execute(contents_q, vals)
554 __all__.append('get_contents')
556 ################################################################################
558 class ContentFilepath(object):
559 def __init__(self, *args, **kwargs):
563 return '<ContentFilepath %s>' % self.filepath
565 __all__.append('ContentFilepath')
568 def get_or_set_contents_path_id(filepath, session=None):
570 Returns database id for given path.
572 If no matching file is found, a row is inserted.
574 @type filename: string
575 @param filename: The filepath
576 @type session: SQLAlchemy
577 @param session: Optional SQL session object (a temporary one will be
578 generated if not supplied). If not passed, a commit will be performed at
579 the end of the function, otherwise the caller is responsible for commiting.
582 @return: the database id for the given path
585 q = session.query(ContentFilepath).filter_by(filepath=filepath)
588 ret = q.one().cafilepath_id
589 except NoResultFound:
590 cf = ContentFilepath()
591 cf.filepath = filepath
593 session.commit_or_flush()
594 ret = cf.cafilepath_id
598 __all__.append('get_or_set_contents_path_id')
600 ################################################################################
602 class ContentAssociation(object):
603 def __init__(self, *args, **kwargs):
607 return '<ContentAssociation %s>' % self.ca_id
609 __all__.append('ContentAssociation')
611 def insert_content_paths(binary_id, fullpaths, session=None):
613 Make sure given path is associated with given binary id
616 @param binary_id: the id of the binary
617 @type fullpaths: list
618 @param fullpaths: the list of paths of the file being associated with the binary
619 @type session: SQLAlchemy session
620 @param session: Optional SQLAlchemy session. If this is passed, the caller
621 is responsible for ensuring a transaction has begun and committing the
622 results or rolling back based on the result code. If not passed, a commit
623 will be performed at the end of the function, otherwise the caller is
624 responsible for commiting.
626 @return: True upon success
631 session = DBConn().session()
637 for fullpath in fullpaths:
638 if fullpath.startswith( './' ):
639 fullpath = fullpath[2:]
641 session.execute( "INSERT INTO bin_contents ( file, binary_id ) VALUES ( :filename, :id )", { 'filename': fullpath, 'id': binary_id} )
649 traceback.print_exc()
651 # Only rollback if we set up the session ourself
658 __all__.append('insert_content_paths')
660 ################################################################################
662 class DSCFile(object):
663 def __init__(self, *args, **kwargs):
667 return '<DSCFile %s>' % self.dscfile_id
669 __all__.append('DSCFile')
672 def get_dscfiles(dscfile_id=None, source_id=None, poolfile_id=None, session=None):
674 Returns a list of DSCFiles which may be empty
676 @type dscfile_id: int (optional)
677 @param dscfile_id: the dscfile_id of the DSCFiles to find
679 @type source_id: int (optional)
680 @param source_id: the source id related to the DSCFiles to find
682 @type poolfile_id: int (optional)
683 @param poolfile_id: the poolfile id related to the DSCFiles to find
686 @return: Possibly empty list of DSCFiles
689 q = session.query(DSCFile)
691 if dscfile_id is not None:
692 q = q.filter_by(dscfile_id=dscfile_id)
694 if source_id is not None:
695 q = q.filter_by(source_id=source_id)
697 if poolfile_id is not None:
698 q = q.filter_by(poolfile_id=poolfile_id)
702 __all__.append('get_dscfiles')
704 ################################################################################
706 class PoolFile(object):
707 def __init__(self, *args, **kwargs):
711 return '<PoolFile %s>' % self.filename
713 __all__.append('PoolFile')
716 def check_poolfile(filename, filesize, md5sum, location_id, session=None):
719 (ValidFileFound [boolean or None], PoolFile object or None)
721 @type filename: string
722 @param filename: the filename of the file to check against the DB
725 @param filesize: the size of the file to check against the DB
728 @param md5sum: the md5sum of the file to check against the DB
730 @type location_id: int
731 @param location_id: the id of the location to look in
734 @return: Tuple of length 2.
735 If more than one file found with that name:
737 If valid pool file found: (True, PoolFile object)
738 If valid pool file not found:
739 (False, None) if no file found
740 (False, PoolFile object) if file found with size/md5sum mismatch
743 q = session.query(PoolFile).filter_by(filename=filename)
744 q = q.join(Location).filter_by(location_id=location_id)
754 if obj.md5sum != md5sum or obj.filesize != filesize:
762 __all__.append('check_poolfile')
765 def get_poolfile_by_id(file_id, session=None):
767 Returns a PoolFile objects or None for the given id
770 @param file_id: the id of the file to look for
772 @rtype: PoolFile or None
773 @return: either the PoolFile object or None
776 q = session.query(PoolFile).filter_by(file_id=file_id)
780 except NoResultFound:
783 __all__.append('get_poolfile_by_id')
787 def get_poolfile_by_name(filename, location_id=None, session=None):
789 Returns an array of PoolFile objects for the given filename and
790 (optionally) location_id
792 @type filename: string
793 @param filename: the filename of the file to check against the DB
795 @type location_id: int
796 @param location_id: the id of the location to look in (optional)
799 @return: array of PoolFile objects
802 q = session.query(PoolFile).filter_by(filename=filename)
804 if location_id is not None:
805 q = q.join(Location).filter_by(location_id=location_id)
809 __all__.append('get_poolfile_by_name')
812 def get_poolfile_like_name(filename, session=None):
814 Returns an array of PoolFile objects which are like the given name
816 @type filename: string
817 @param filename: the filename of the file to check against the DB
820 @return: array of PoolFile objects
823 # TODO: There must be a way of properly using bind parameters with %FOO%
824 q = session.query(PoolFile).filter(PoolFile.filename.like('%%%s%%' % filename))
828 __all__.append('get_poolfile_like_name')
830 ################################################################################
832 class Fingerprint(object):
833 def __init__(self, *args, **kwargs):
837 return '<Fingerprint %s>' % self.fingerprint
839 __all__.append('Fingerprint')
842 def get_fingerprint(fpr, session=None):
844 Returns Fingerprint object for given fpr.
847 @param fpr: The fpr to find / add
849 @type session: SQLAlchemy
850 @param session: Optional SQL session object (a temporary one will be
851 generated if not supplied).
854 @return: the Fingerprint object for the given fpr or None
857 q = session.query(Fingerprint).filter_by(fingerprint=fpr)
861 except NoResultFound:
866 __all__.append('get_fingerprint')
869 def get_or_set_fingerprint(fpr, session=None):
871 Returns Fingerprint object for given fpr.
873 If no matching fpr is found, a row is inserted.
876 @param fpr: The fpr to find / add
878 @type session: SQLAlchemy
879 @param session: Optional SQL session object (a temporary one will be
880 generated if not supplied). If not passed, a commit will be performed at
881 the end of the function, otherwise the caller is responsible for commiting.
882 A flush will be performed either way.
885 @return: the Fingerprint object for the given fpr
888 q = session.query(Fingerprint).filter_by(fingerprint=fpr)
892 except NoResultFound:
893 fingerprint = Fingerprint()
894 fingerprint.fingerprint = fpr
895 session.add(fingerprint)
896 session.commit_or_flush()
901 __all__.append('get_or_set_fingerprint')
903 ################################################################################
905 # Helper routine for Keyring class
906 def get_ldap_name(entry):
908 for k in ["cn", "mn", "sn"]:
910 if ret and ret[0] != "" and ret[0] != "-":
912 return " ".join(name)
914 ################################################################################
916 class Keyring(object):
917 gpg_invocation = "gpg --no-default-keyring --keyring %s" +\
918 " --with-colons --fingerprint --fingerprint"
923 def __init__(self, *args, **kwargs):
927 return '<Keyring %s>' % self.keyring_name
929 def de_escape_gpg_str(self, str):
930 esclist = re.split(r'(\\x..)', str)
931 for x in range(1,len(esclist),2):
932 esclist[x] = "%c" % (int(esclist[x][2:],16))
933 return "".join(esclist)
935 def load_keys(self, keyring):
938 if not self.keyring_id:
939 raise Exception('Must be initialized with database information')
941 k = os.popen(self.gpg_invocation % keyring, "r")
945 for line in k.xreadlines():
946 field = line.split(":")
947 if field[0] == "pub":
949 (name, addr) = email.Utils.parseaddr(field[9])
950 name = re.sub(r"\s*[(].*[)]", "", name)
951 if name == "" or addr == "" or "@" not in addr:
954 name = self.de_escape_gpg_str(name)
955 self.keys[key] = {"email": addr}
957 self.keys[key]["name"] = name
958 self.keys[key]["aliases"] = [name]
959 self.keys[key]["fingerprints"] = []
961 elif key and field[0] == "sub" and len(field) >= 12:
962 signingkey = ("s" in field[11])
963 elif key and field[0] == "uid":
964 (name, addr) = email.Utils.parseaddr(field[9])
965 if name and name not in self.keys[key]["aliases"]:
966 self.keys[key]["aliases"].append(name)
967 elif signingkey and field[0] == "fpr":
968 self.keys[key]["fingerprints"].append(field[9])
969 self.fpr_lookup[field[9]] = key
971 def import_users_from_ldap(self, session):
975 LDAPDn = cnf["Import-LDAP-Fingerprints::LDAPDn"]
976 LDAPServer = cnf["Import-LDAP-Fingerprints::LDAPServer"]
978 l = ldap.open(LDAPServer)
979 l.simple_bind_s("","")
980 Attrs = l.search_s(LDAPDn, ldap.SCOPE_ONELEVEL,
981 "(&(keyfingerprint=*)(gidnumber=%s))" % (cnf["Import-Users-From-Passwd::ValidGID"]),
982 ["uid", "keyfingerprint", "cn", "mn", "sn"])
991 uid = entry["uid"][0]
992 name = get_ldap_name(entry)
993 fingerprints = entry["keyFingerPrint"]
995 for f in fingerprints:
996 key = self.fpr_lookup.get(f, None)
997 if key not in self.keys:
999 self.keys[key]["uid"] = uid
1003 keyid = get_or_set_uid(uid, session).uid_id
1004 byuid[keyid] = (uid, name)
1005 byname[uid] = (keyid, name)
1007 return (byname, byuid)
1009 def generate_users_from_keyring(self, format, session):
1013 for x in self.keys.keys():
1014 if self.keys[x]["email"] == "invalid-uid":
1016 self.keys[x]["uid"] = format % "invalid-uid"
1018 uid = format % self.keys[x]["email"]
1019 keyid = get_or_set_uid(uid, session).uid_id
1020 byuid[keyid] = (uid, self.keys[x]["name"])
1021 byname[uid] = (keyid, self.keys[x]["name"])
1022 self.keys[x]["uid"] = uid
1025 uid = format % "invalid-uid"
1026 keyid = get_or_set_uid(uid, session).uid_id
1027 byuid[keyid] = (uid, "ungeneratable user id")
1028 byname[uid] = (keyid, "ungeneratable user id")
1030 return (byname, byuid)
1032 __all__.append('Keyring')
1035 def get_keyring(keyring, session=None):
1037 If C{keyring} does not have an entry in the C{keyrings} table yet, return None
1038 If C{keyring} already has an entry, simply return the existing Keyring
1040 @type keyring: string
1041 @param keyring: the keyring name
1044 @return: the Keyring object for this keyring
1047 q = session.query(Keyring).filter_by(keyring_name=keyring)
1051 except NoResultFound:
1054 __all__.append('get_keyring')
1056 ################################################################################
1058 class KeyringACLMap(object):
1059 def __init__(self, *args, **kwargs):
1063 return '<KeyringACLMap %s>' % self.keyring_acl_map_id
1065 __all__.append('KeyringACLMap')
1067 ################################################################################
1069 class KnownChange(object):
1070 def __init__(self, *args, **kwargs):
1074 return '<KnownChange %s>' % self.changesname
1076 __all__.append('KnownChange')
1079 def get_knownchange(filename, session=None):
1081 returns knownchange object for given C{filename}.
1083 @type archive: string
1084 @param archive: the name of the arhive
1086 @type session: Session
1087 @param session: Optional SQLA session object (a temporary one will be
1088 generated if not supplied)
1091 @return: Archive object for the given name (None if not present)
1094 q = session.query(KnownChange).filter_by(changesname=filename)
1098 except NoResultFound:
1101 __all__.append('get_knownchange')
1103 ################################################################################
1104 class Location(object):
1105 def __init__(self, *args, **kwargs):
1109 return '<Location %s (%s)>' % (self.path, self.location_id)
1111 __all__.append('Location')
1114 def get_location(location, component=None, archive=None, session=None):
1116 Returns Location object for the given combination of location, component
1119 @type location: string
1120 @param location: the path of the location, e.g. I{/srv/ftp.debian.org/ftp/pool/}
1122 @type component: string
1123 @param component: the component name (if None, no restriction applied)
1125 @type archive: string
1126 @param archive_id: the archive name (if None, no restriction applied)
1128 @rtype: Location / None
1129 @return: Either a Location object or None if one can't be found
1132 q = session.query(Location).filter_by(path=location)
1134 if archive is not None:
1135 q = q.join(Archive).filter_by(archive_name=archive)
1137 if component is not None:
1138 q = q.join(Component).filter_by(component_name=component)
1142 except NoResultFound:
1145 __all__.append('get_location')
1147 ################################################################################
1149 class Maintainer(object):
1150 def __init__(self, *args, **kwargs):
1154 return '''<Maintainer '%s' (%s)>''' % (self.name, self.maintainer_id)
1156 def get_split_maintainer(self):
1157 if not hasattr(self, 'name') or self.name is None:
1158 return ('', '', '', '')
1160 return fix_maintainer(self.name.strip())
1162 __all__.append('Maintainer')
1165 def get_or_set_maintainer(name, session=None):
1167 Returns Maintainer object for given maintainer name.
1169 If no matching maintainer name is found, a row is inserted.
1172 @param name: The maintainer name to add
1174 @type session: SQLAlchemy
1175 @param session: Optional SQL session object (a temporary one will be
1176 generated if not supplied). If not passed, a commit will be performed at
1177 the end of the function, otherwise the caller is responsible for commiting.
1178 A flush will be performed either way.
1181 @return: the Maintainer object for the given maintainer
1184 q = session.query(Maintainer).filter_by(name=name)
1187 except NoResultFound:
1188 maintainer = Maintainer()
1189 maintainer.name = name
1190 session.add(maintainer)
1191 session.commit_or_flush()
1196 __all__.append('get_or_set_maintainer')
1199 def get_maintainer(maintainer_id, session=None):
1201 Return the name of the maintainer behind C{maintainer_id} or None if that
1202 maintainer_id is invalid.
1204 @type maintainer_id: int
1205 @param maintainer_id: the id of the maintainer
1208 @return: the Maintainer with this C{maintainer_id}
1211 return session.query(Maintainer).get(maintainer_id)
1213 __all__.append('get_maintainer')
1215 ################################################################################
1217 class NewComment(object):
1218 def __init__(self, *args, **kwargs):
1222 return '''<NewComment for '%s %s' (%s)>''' % (self.package, self.version, self.comment_id)
1224 __all__.append('NewComment')
1227 def has_new_comment(package, version, session=None):
1229 Returns true if the given combination of C{package}, C{version} has a comment.
1231 @type package: string
1232 @param package: name of the package
1234 @type version: string
1235 @param version: package version
1237 @type session: Session
1238 @param session: Optional SQLA session object (a temporary one will be
1239 generated if not supplied)
1245 q = session.query(NewComment)
1246 q = q.filter_by(package=package)
1247 q = q.filter_by(version=version)
1249 return bool(q.count() > 0)
1251 __all__.append('has_new_comment')
1254 def get_new_comments(package=None, version=None, comment_id=None, session=None):
1256 Returns (possibly empty) list of NewComment objects for the given
1259 @type package: string (optional)
1260 @param package: name of the package
1262 @type version: string (optional)
1263 @param version: package version
1265 @type comment_id: int (optional)
1266 @param comment_id: An id of a comment
1268 @type session: Session
1269 @param session: Optional SQLA session object (a temporary one will be
1270 generated if not supplied)
1273 @return: A (possibly empty) list of NewComment objects will be returned
1276 q = session.query(NewComment)
1277 if package is not None: q = q.filter_by(package=package)
1278 if version is not None: q = q.filter_by(version=version)
1279 if comment_id is not None: q = q.filter_by(comment_id=comment_id)
1283 __all__.append('get_new_comments')
1285 ################################################################################
1287 class Override(object):
1288 def __init__(self, *args, **kwargs):
1292 return '<Override %s (%s)>' % (self.package, self.suite_id)
1294 __all__.append('Override')
1297 def get_override(package, suite=None, component=None, overridetype=None, session=None):
1299 Returns Override object for the given parameters
1301 @type package: string
1302 @param package: The name of the package
1304 @type suite: string, list or None
1305 @param suite: The name of the suite (or suites if a list) to limit to. If
1306 None, don't limit. Defaults to None.
1308 @type component: string, list or None
1309 @param component: The name of the component (or components if a list) to
1310 limit to. If None, don't limit. Defaults to None.
1312 @type overridetype: string, list or None
1313 @param overridetype: The name of the overridetype (or overridetypes if a list) to
1314 limit to. If None, don't limit. Defaults to None.
1316 @type session: Session
1317 @param session: Optional SQLA session object (a temporary one will be
1318 generated if not supplied)
1321 @return: A (possibly empty) list of Override objects will be returned
1324 q = session.query(Override)
1325 q = q.filter_by(package=package)
1327 if suite is not None:
1328 if not isinstance(suite, list): suite = [suite]
1329 q = q.join(Suite).filter(Suite.suite_name.in_(suite))
1331 if component is not None:
1332 if not isinstance(component, list): component = [component]
1333 q = q.join(Component).filter(Component.component_name.in_(component))
1335 if overridetype is not None:
1336 if not isinstance(overridetype, list): overridetype = [overridetype]
1337 q = q.join(OverrideType).filter(OverrideType.overridetype.in_(overridetype))
1341 __all__.append('get_override')
1344 ################################################################################
1346 class OverrideType(object):
1347 def __init__(self, *args, **kwargs):
1351 return '<OverrideType %s>' % self.overridetype
1353 __all__.append('OverrideType')
1356 def get_override_type(override_type, session=None):
1358 Returns OverrideType object for given C{override type}.
1360 @type override_type: string
1361 @param override_type: The name of the override type
1363 @type session: Session
1364 @param session: Optional SQLA session object (a temporary one will be
1365 generated if not supplied)
1368 @return: the database id for the given override type
1371 q = session.query(OverrideType).filter_by(overridetype=override_type)
1375 except NoResultFound:
1378 __all__.append('get_override_type')
1380 ################################################################################
1382 class PendingContentAssociation(object):
1383 def __init__(self, *args, **kwargs):
1387 return '<PendingContentAssociation %s>' % self.pca_id
1389 __all__.append('PendingContentAssociation')
1391 def insert_pending_content_paths(package, fullpaths, session=None):
1393 Make sure given paths are temporarily associated with given
1397 @param package: the package to associate with should have been read in from the binary control file
1398 @type fullpaths: list
1399 @param fullpaths: the list of paths of the file being associated with the binary
1400 @type session: SQLAlchemy session
1401 @param session: Optional SQLAlchemy session. If this is passed, the caller
1402 is responsible for ensuring a transaction has begun and committing the
1403 results or rolling back based on the result code. If not passed, a commit
1404 will be performed at the end of the function
1406 @return: True upon success, False if there is a problem
1409 privatetrans = False
1412 session = DBConn().session()
1416 arch = get_architecture(package['Architecture'], session)
1417 arch_id = arch.arch_id
1419 # Remove any already existing recorded files for this package
1420 q = session.query(PendingContentAssociation)
1421 q = q.filter_by(package=package['Package'])
1422 q = q.filter_by(version=package['Version'])
1423 q = q.filter_by(architecture=arch_id)
1428 for fullpath in fullpaths:
1429 (path, file) = os.path.split(fullpath)
1431 if path.startswith( "./" ):
1434 filepath_id = get_or_set_contents_path_id(path, session)
1435 filename_id = get_or_set_contents_file_id(file, session)
1437 pathcache[fullpath] = (filepath_id, filename_id)
1439 for fullpath, dat in pathcache.items():
1440 pca = PendingContentAssociation()
1441 pca.package = package['Package']
1442 pca.version = package['Version']
1443 pca.filepath_id = dat[0]
1444 pca.filename_id = dat[1]
1445 pca.architecture = arch_id
1448 # Only commit if we set up the session ourself
1456 except Exception, e:
1457 traceback.print_exc()
1459 # Only rollback if we set up the session ourself
1466 __all__.append('insert_pending_content_paths')
1468 ################################################################################
1470 class Priority(object):
1471 def __init__(self, *args, **kwargs):
1474 def __eq__(self, val):
1475 if isinstance(val, str):
1476 return (self.priority == val)
1477 # This signals to use the normal comparison operator
1478 return NotImplemented
1480 def __ne__(self, val):
1481 if isinstance(val, str):
1482 return (self.priority != val)
1483 # This signals to use the normal comparison operator
1484 return NotImplemented
1487 return '<Priority %s (%s)>' % (self.priority, self.priority_id)
1489 __all__.append('Priority')
1492 def get_priority(priority, session=None):
1494 Returns Priority object for given C{priority name}.
1496 @type priority: string
1497 @param priority: The name of the priority
1499 @type session: Session
1500 @param session: Optional SQLA session object (a temporary one will be
1501 generated if not supplied)
1504 @return: Priority object for the given priority
1507 q = session.query(Priority).filter_by(priority=priority)
1511 except NoResultFound:
1514 __all__.append('get_priority')
1517 def get_priorities(session=None):
1519 Returns dictionary of priority names -> id mappings
1521 @type session: Session
1522 @param session: Optional SQL session object (a temporary one will be
1523 generated if not supplied)
1526 @return: dictionary of priority names -> id mappings
1530 q = session.query(Priority)
1532 ret[x.priority] = x.priority_id
1536 __all__.append('get_priorities')
1538 ################################################################################
1540 class Queue(object):
1541 def __init__(self, *args, **kwargs):
1545 return '<Queue %s>' % self.queue_name
1547 def autobuild_upload(self, changes, srcpath, session=None):
1549 Update queue_build database table used for incoming autobuild support.
1551 @type changes: Changes
1552 @param changes: changes object for the upload to process
1554 @type srcpath: string
1555 @param srcpath: path for the queue file entries/link destinations
1557 @type session: SQLAlchemy session
1558 @param session: Optional SQLAlchemy session. If this is passed, the
1559 caller is responsible for ensuring a transaction has begun and
1560 committing the results or rolling back based on the result code. If
1561 not passed, a commit will be performed at the end of the function,
1562 otherwise the caller is responsible for commiting.
1564 @rtype: NoneType or string
1565 @return: None if the operation failed, a string describing the error if not
1568 privatetrans = False
1570 session = DBConn().session()
1573 # TODO: Remove by moving queue config into the database
1576 for suitename in changes.changes["distribution"].keys():
1577 # TODO: Move into database as:
1578 # buildqueuedir TEXT DEFAULT NULL (i.e. NULL is no build)
1579 # buildqueuecopy BOOLEAN NOT NULL DEFAULT FALSE (i.e. default is symlink)
1580 # This also gets rid of the SecurityQueueBuild hack below
1581 if suitename not in conf.ValueList("Dinstall::QueueBuildSuites"):
1585 s = get_suite(suitename, session)
1587 return "INTERNAL ERROR: Could not find suite %s" % suitename
1589 # TODO: Get from database as above
1590 dest_dir = conf["Dir::QueueBuild"]
1592 # TODO: Move into database as above
1593 if conf.FindB("Dinstall::SecurityQueueBuild"):
1594 dest_dir = os.path.join(dest_dir, suitename)
1596 for file_entry in changes.files.keys():
1597 src = os.path.join(srcpath, file_entry)
1598 dest = os.path.join(dest_dir, file_entry)
1600 # TODO: Move into database as above
1601 if conf.FindB("Dinstall::SecurityQueueBuild"):
1602 # Copy it since the original won't be readable by www-data
1604 utils.copy(src, dest)
1606 # Create a symlink to it
1607 os.symlink(src, dest)
1610 qb.suite_id = s.suite_id
1611 qb.queue_id = self.queue_id
1617 # If the .orig tarballs are in the pool, create a symlink to
1618 # them (if one doesn't already exist)
1619 for dsc_file in changes.dsc_files.keys():
1620 # Skip all files except orig tarballs
1621 from daklib.regexes import re_is_orig_source
1622 if not re_is_orig_source.match(dsc_file):
1624 # Skip orig files not identified in the pool
1625 if not (changes.orig_files.has_key(dsc_file) and
1626 changes.orig_files[dsc_file].has_key("id")):
1628 orig_file_id = changes.orig_files[dsc_file]["id"]
1629 dest = os.path.join(dest_dir, dsc_file)
1631 # If it doesn't exist, create a symlink
1632 if not os.path.exists(dest):
1633 q = session.execute("SELECT l.path, f.filename FROM location l, files f WHERE f.id = :id and f.location = l.id",
1634 {'id': orig_file_id})
1637 return "[INTERNAL ERROR] Couldn't find id %s in files table." % (orig_file_id)
1639 src = os.path.join(res[0], res[1])
1640 os.symlink(src, dest)
1642 # Add it to the list of packages for later processing by apt-ftparchive
1644 qb.suite_id = s.suite_id
1645 qb.queue_id = self.queue_id
1650 # If it does, update things to ensure it's not removed prematurely
1652 qb = get_queue_build(dest, s.suite_id, session)
1664 __all__.append('Queue')
1667 def get_or_set_queue(queuename, session=None):
1669 Returns Queue object for given C{queue name}, creating it if it does not
1672 @type queuename: string
1673 @param queuename: The name of the queue
1675 @type session: Session
1676 @param session: Optional SQLA session object (a temporary one will be
1677 generated if not supplied)
1680 @return: Queue object for the given queue
1683 q = session.query(Queue).filter_by(queue_name=queuename)
1687 except NoResultFound:
1689 queue.queue_name = queuename
1691 session.commit_or_flush()
1696 __all__.append('get_or_set_queue')
1698 ################################################################################
1700 class QueueBuild(object):
1701 def __init__(self, *args, **kwargs):
1705 return '<QueueBuild %s (%s)>' % (self.filename, self.queue_id)
1707 __all__.append('QueueBuild')
1710 def get_queue_build(filename, suite, session=None):
1712 Returns QueueBuild object for given C{filename} and C{suite}.
1714 @type filename: string
1715 @param filename: The name of the file
1717 @type suiteid: int or str
1718 @param suiteid: Suite name or ID
1720 @type session: Session
1721 @param session: Optional SQLA session object (a temporary one will be
1722 generated if not supplied)
1725 @return: Queue object for the given queue
1728 if isinstance(suite, int):
1729 q = session.query(QueueBuild).filter_by(filename=filename).filter_by(suite_id=suite)
1731 q = session.query(QueueBuild).filter_by(filename=filename)
1732 q = q.join(Suite).filter_by(suite_name=suite)
1736 except NoResultFound:
1739 __all__.append('get_queue_build')
1741 ################################################################################
1743 class Section(object):
1744 def __init__(self, *args, **kwargs):
1747 def __eq__(self, val):
1748 if isinstance(val, str):
1749 return (self.section == val)
1750 # This signals to use the normal comparison operator
1751 return NotImplemented
1753 def __ne__(self, val):
1754 if isinstance(val, str):
1755 return (self.section != val)
1756 # This signals to use the normal comparison operator
1757 return NotImplemented
1760 return '<Section %s>' % self.section
1762 __all__.append('Section')
1765 def get_section(section, session=None):
1767 Returns Section object for given C{section name}.
1769 @type section: string
1770 @param section: The name of the section
1772 @type session: Session
1773 @param session: Optional SQLA session object (a temporary one will be
1774 generated if not supplied)
1777 @return: Section object for the given section name
1780 q = session.query(Section).filter_by(section=section)
1784 except NoResultFound:
1787 __all__.append('get_section')
1790 def get_sections(session=None):
1792 Returns dictionary of section names -> id mappings
1794 @type session: Session
1795 @param session: Optional SQL session object (a temporary one will be
1796 generated if not supplied)
1799 @return: dictionary of section names -> id mappings
1803 q = session.query(Section)
1805 ret[x.section] = x.section_id
1809 __all__.append('get_sections')
1811 ################################################################################
1813 class DBSource(object):
1814 def __init__(self, *args, **kwargs):
1818 return '<DBSource %s (%s)>' % (self.source, self.version)
1820 __all__.append('DBSource')
1823 def source_exists(source, source_version, suites = ["any"], session=None):
1825 Ensure that source exists somewhere in the archive for the binary
1826 upload being processed.
1827 1. exact match => 1.0-3
1828 2. bin-only NMU => 1.0-3+b1 , 1.0-3.1+b1
1830 @type package: string
1831 @param package: package source name
1833 @type source_version: string
1834 @param source_version: expected source version
1837 @param suites: list of suites to check in, default I{any}
1839 @type session: Session
1840 @param session: Optional SQLA session object (a temporary one will be
1841 generated if not supplied)
1844 @return: returns 1 if a source with expected version is found, otherwise 0
1851 for suite in suites:
1852 q = session.query(DBSource).filter_by(source=source)
1854 # source must exist in suite X, or in some other suite that's
1855 # mapped to X, recursively... silent-maps are counted too,
1856 # unreleased-maps aren't.
1857 maps = cnf.ValueList("SuiteMappings")[:]
1859 maps = [ m.split() for m in maps ]
1860 maps = [ (x[1], x[2]) for x in maps
1861 if x[0] == "map" or x[0] == "silent-map" ]
1864 if x[1] in s and x[0] not in s:
1867 q = q.join(SrcAssociation).join(Suite)
1868 q = q.filter(Suite.suite_name.in_(s))
1870 # Reduce the query results to a list of version numbers
1871 ql = [ j.version for j in q.all() ]
1874 if source_version in ql:
1878 from daklib.regexes import re_bin_only_nmu
1879 orig_source_version = re_bin_only_nmu.sub('', source_version)
1880 if orig_source_version in ql:
1883 # No source found so return not ok
1888 __all__.append('source_exists')
1891 def get_suites_source_in(source, session=None):
1893 Returns list of Suite objects which given C{source} name is in
1896 @param source: DBSource package name to search for
1899 @return: list of Suite objects for the given source
1902 return session.query(Suite).join(SrcAssociation).join(DBSource).filter_by(source=source).all()
1904 __all__.append('get_suites_source_in')
1907 def get_sources_from_name(source, version=None, dm_upload_allowed=None, session=None):
1909 Returns list of DBSource objects for given C{source} name and other parameters
1912 @param source: DBSource package name to search for
1914 @type source: str or None
1915 @param source: DBSource version name to search for or None if not applicable
1917 @type dm_upload_allowed: bool
1918 @param dm_upload_allowed: If None, no effect. If True or False, only
1919 return packages with that dm_upload_allowed setting
1921 @type session: Session
1922 @param session: Optional SQL session object (a temporary one will be
1923 generated if not supplied)
1926 @return: list of DBSource objects for the given name (may be empty)
1929 q = session.query(DBSource).filter_by(source=source)
1931 if version is not None:
1932 q = q.filter_by(version=version)
1934 if dm_upload_allowed is not None:
1935 q = q.filter_by(dm_upload_allowed=dm_upload_allowed)
1939 __all__.append('get_sources_from_name')
1942 def get_source_in_suite(source, suite, session=None):
1944 Returns list of DBSource objects for a combination of C{source} and C{suite}.
1946 - B{source} - source package name, eg. I{mailfilter}, I{bbdb}, I{glibc}
1947 - B{suite} - a suite name, eg. I{unstable}
1949 @type source: string
1950 @param source: source package name
1953 @param suite: the suite name
1956 @return: the version for I{source} in I{suite}
1960 q = session.query(SrcAssociation)
1961 q = q.join('source').filter_by(source=source)
1962 q = q.join('suite').filter_by(suite_name=suite)
1965 return q.one().source
1966 except NoResultFound:
1969 __all__.append('get_source_in_suite')
1971 ################################################################################
1973 class SourceACL(object):
1974 def __init__(self, *args, **kwargs):
1978 return '<SourceACL %s>' % self.source_acl_id
1980 __all__.append('SourceACL')
1982 ################################################################################
1984 class SrcAssociation(object):
1985 def __init__(self, *args, **kwargs):
1989 return '<SrcAssociation %s (%s, %s)>' % (self.sa_id, self.source, self.suite)
1991 __all__.append('SrcAssociation')
1993 ################################################################################
1995 class SrcFormat(object):
1996 def __init__(self, *args, **kwargs):
2000 return '<SrcFormat %s>' % (self.format_name)
2002 __all__.append('SrcFormat')
2004 ################################################################################
2006 class SrcUploader(object):
2007 def __init__(self, *args, **kwargs):
2011 return '<SrcUploader %s>' % self.uploader_id
2013 __all__.append('SrcUploader')
2015 ################################################################################
2017 SUITE_FIELDS = [ ('SuiteName', 'suite_name'),
2018 ('SuiteID', 'suite_id'),
2019 ('Version', 'version'),
2020 ('Origin', 'origin'),
2022 ('Description', 'description'),
2023 ('Untouchable', 'untouchable'),
2024 ('Announce', 'announce'),
2025 ('Codename', 'codename'),
2026 ('OverrideCodename', 'overridecodename'),
2027 ('ValidTime', 'validtime'),
2028 ('Priority', 'priority'),
2029 ('NotAutomatic', 'notautomatic'),
2030 ('CopyChanges', 'copychanges'),
2031 ('CopyDotDak', 'copydotdak'),
2032 ('CommentsDir', 'commentsdir'),
2033 ('OverrideSuite', 'overridesuite'),
2034 ('ChangelogBase', 'changelogbase')]
2037 class Suite(object):
2038 def __init__(self, *args, **kwargs):
2042 return '<Suite %s>' % self.suite_name
2044 def __eq__(self, val):
2045 if isinstance(val, str):
2046 return (self.suite_name == val)
2047 # This signals to use the normal comparison operator
2048 return NotImplemented
2050 def __ne__(self, val):
2051 if isinstance(val, str):
2052 return (self.suite_name != val)
2053 # This signals to use the normal comparison operator
2054 return NotImplemented
2058 for disp, field in SUITE_FIELDS:
2059 val = getattr(self, field, None)
2061 ret.append("%s: %s" % (disp, val))
2063 return "\n".join(ret)
2065 __all__.append('Suite')
2068 def get_suite_architecture(suite, architecture, session=None):
2070 Returns a SuiteArchitecture object given C{suite} and ${arch} or None if it
2074 @param suite: Suite name to search for
2076 @type architecture: str
2077 @param architecture: Architecture name to search for
2079 @type session: Session
2080 @param session: Optional SQL session object (a temporary one will be
2081 generated if not supplied)
2083 @rtype: SuiteArchitecture
2084 @return: the SuiteArchitecture object or None
2087 q = session.query(SuiteArchitecture)
2088 q = q.join(Architecture).filter_by(arch_string=architecture)
2089 q = q.join(Suite).filter_by(suite_name=suite)
2093 except NoResultFound:
2096 __all__.append('get_suite_architecture')
2099 def get_suite(suite, session=None):
2101 Returns Suite object for given C{suite name}.
2104 @param suite: The name of the suite
2106 @type session: Session
2107 @param session: Optional SQLA session object (a temporary one will be
2108 generated if not supplied)
2111 @return: Suite object for the requested suite name (None if not present)
2114 q = session.query(Suite).filter_by(suite_name=suite)
2118 except NoResultFound:
2121 __all__.append('get_suite')
2123 ################################################################################
2125 class SuiteArchitecture(object):
2126 def __init__(self, *args, **kwargs):
2130 return '<SuiteArchitecture (%s, %s)>' % (self.suite_id, self.arch_id)
2132 __all__.append('SuiteArchitecture')
2135 def get_suite_architectures(suite, skipsrc=False, skipall=False, session=None):
2137 Returns list of Architecture objects for given C{suite} name
2140 @param source: Suite name to search for
2142 @type skipsrc: boolean
2143 @param skipsrc: Whether to skip returning the 'source' architecture entry
2146 @type skipall: boolean
2147 @param skipall: Whether to skip returning the 'all' architecture entry
2150 @type session: Session
2151 @param session: Optional SQL session object (a temporary one will be
2152 generated if not supplied)
2155 @return: list of Architecture objects for the given name (may be empty)
2158 q = session.query(Architecture)
2159 q = q.join(SuiteArchitecture)
2160 q = q.join(Suite).filter_by(suite_name=suite)
2163 q = q.filter(Architecture.arch_string != 'source')
2166 q = q.filter(Architecture.arch_string != 'all')
2168 q = q.order_by('arch_string')
2172 __all__.append('get_suite_architectures')
2174 ################################################################################
2176 class SuiteSrcFormat(object):
2177 def __init__(self, *args, **kwargs):
2181 return '<SuiteSrcFormat (%s, %s)>' % (self.suite_id, self.src_format_id)
2183 __all__.append('SuiteSrcFormat')
2186 def get_suite_src_formats(suite, session=None):
2188 Returns list of allowed SrcFormat for C{suite}.
2191 @param suite: Suite name to search for
2193 @type session: Session
2194 @param session: Optional SQL session object (a temporary one will be
2195 generated if not supplied)
2198 @return: the list of allowed source formats for I{suite}
2201 q = session.query(SrcFormat)
2202 q = q.join(SuiteSrcFormat)
2203 q = q.join(Suite).filter_by(suite_name=suite)
2204 q = q.order_by('format_name')
2208 __all__.append('get_suite_src_formats')
2210 ################################################################################
2213 def __init__(self, *args, **kwargs):
2216 def __eq__(self, val):
2217 if isinstance(val, str):
2218 return (self.uid == val)
2219 # This signals to use the normal comparison operator
2220 return NotImplemented
2222 def __ne__(self, val):
2223 if isinstance(val, str):
2224 return (self.uid != val)
2225 # This signals to use the normal comparison operator
2226 return NotImplemented
2229 return '<Uid %s (%s)>' % (self.uid, self.name)
2231 __all__.append('Uid')
2234 def add_database_user(uidname, session=None):
2236 Adds a database user
2238 @type uidname: string
2239 @param uidname: The uid of the user to add
2241 @type session: SQLAlchemy
2242 @param session: Optional SQL session object (a temporary one will be
2243 generated if not supplied). If not passed, a commit will be performed at
2244 the end of the function, otherwise the caller is responsible for commiting.
2247 @return: the uid object for the given uidname
2250 session.execute("CREATE USER :uid", {'uid': uidname})
2251 session.commit_or_flush()
2253 __all__.append('add_database_user')
2256 def get_or_set_uid(uidname, session=None):
2258 Returns uid object for given uidname.
2260 If no matching uidname is found, a row is inserted.
2262 @type uidname: string
2263 @param uidname: The uid to add
2265 @type session: SQLAlchemy
2266 @param session: Optional SQL session object (a temporary one will be
2267 generated if not supplied). If not passed, a commit will be performed at
2268 the end of the function, otherwise the caller is responsible for commiting.
2271 @return: the uid object for the given uidname
2274 q = session.query(Uid).filter_by(uid=uidname)
2278 except NoResultFound:
2282 session.commit_or_flush()
2287 __all__.append('get_or_set_uid')
2290 def get_uid_from_fingerprint(fpr, session=None):
2291 q = session.query(Uid)
2292 q = q.join(Fingerprint).filter_by(fingerprint=fpr)
2296 except NoResultFound:
2299 __all__.append('get_uid_from_fingerprint')
2301 ################################################################################
2303 class UploadBlock(object):
2304 def __init__(self, *args, **kwargs):
2308 return '<UploadBlock %s (%s)>' % (self.source, self.upload_block_id)
2310 __all__.append('UploadBlock')
2312 ################################################################################
2314 class DBConn(Singleton):
2316 database module init.
2318 def __init__(self, *args, **kwargs):
2319 super(DBConn, self).__init__(*args, **kwargs)
2321 def _startup(self, *args, **kwargs):
2323 if kwargs.has_key('debug'):
2327 def __setuptables(self):
2328 self.tbl_architecture = Table('architecture', self.db_meta, autoload=True)
2329 self.tbl_archive = Table('archive', self.db_meta, autoload=True)
2330 self.tbl_bin_associations = Table('bin_associations', self.db_meta, autoload=True)
2331 self.tbl_binaries = Table('binaries', self.db_meta, autoload=True)
2332 self.tbl_binary_acl = Table('binary_acl', self.db_meta, autoload=True)
2333 self.tbl_binary_acl_map = Table('binary_acl_map', self.db_meta, autoload=True)
2334 self.tbl_component = Table('component', self.db_meta, autoload=True)
2335 self.tbl_config = Table('config', self.db_meta, autoload=True)
2336 self.tbl_content_associations = Table('content_associations', self.db_meta, autoload=True)
2337 self.tbl_content_file_names = Table('content_file_names', self.db_meta, autoload=True)
2338 self.tbl_content_file_paths = Table('content_file_paths', self.db_meta, autoload=True)
2339 self.tbl_dsc_files = Table('dsc_files', self.db_meta, autoload=True)
2340 self.tbl_files = Table('files', self.db_meta, autoload=True)
2341 self.tbl_fingerprint = Table('fingerprint', self.db_meta, autoload=True)
2342 self.tbl_keyrings = Table('keyrings', self.db_meta, autoload=True)
2343 self.tbl_known_changes = Table('known_changes', self.db_meta, autoload=True)
2344 self.tbl_keyring_acl_map = Table('keyring_acl_map', self.db_meta, autoload=True)
2345 self.tbl_location = Table('location', self.db_meta, autoload=True)
2346 self.tbl_maintainer = Table('maintainer', self.db_meta, autoload=True)
2347 self.tbl_new_comments = Table('new_comments', self.db_meta, autoload=True)
2348 self.tbl_override = Table('override', self.db_meta, autoload=True)
2349 self.tbl_override_type = Table('override_type', self.db_meta, autoload=True)
2350 self.tbl_pending_content_associations = Table('pending_content_associations', self.db_meta, autoload=True)
2351 self.tbl_priority = Table('priority', self.db_meta, autoload=True)
2352 self.tbl_queue = Table('queue', self.db_meta, autoload=True)
2353 self.tbl_queue_build = Table('queue_build', self.db_meta, autoload=True)
2354 self.tbl_section = Table('section', self.db_meta, autoload=True)
2355 self.tbl_source = Table('source', self.db_meta, autoload=True)
2356 self.tbl_source_acl = Table('source_acl', self.db_meta, autoload=True)
2357 self.tbl_src_associations = Table('src_associations', self.db_meta, autoload=True)
2358 self.tbl_src_format = Table('src_format', self.db_meta, autoload=True)
2359 self.tbl_src_uploaders = Table('src_uploaders', self.db_meta, autoload=True)
2360 self.tbl_suite = Table('suite', self.db_meta, autoload=True)
2361 self.tbl_suite_architectures = Table('suite_architectures', self.db_meta, autoload=True)
2362 self.tbl_suite_src_formats = Table('suite_src_formats', self.db_meta, autoload=True)
2363 self.tbl_uid = Table('uid', self.db_meta, autoload=True)
2364 self.tbl_upload_blocks = Table('upload_blocks', self.db_meta, autoload=True)
2366 def __setupmappers(self):
2367 mapper(Architecture, self.tbl_architecture,
2368 properties = dict(arch_id = self.tbl_architecture.c.id))
2370 mapper(Archive, self.tbl_archive,
2371 properties = dict(archive_id = self.tbl_archive.c.id,
2372 archive_name = self.tbl_archive.c.name))
2374 mapper(BinAssociation, self.tbl_bin_associations,
2375 properties = dict(ba_id = self.tbl_bin_associations.c.id,
2376 suite_id = self.tbl_bin_associations.c.suite,
2377 suite = relation(Suite),
2378 binary_id = self.tbl_bin_associations.c.bin,
2379 binary = relation(DBBinary)))
2382 mapper(DBBinary, self.tbl_binaries,
2383 properties = dict(binary_id = self.tbl_binaries.c.id,
2384 package = self.tbl_binaries.c.package,
2385 version = self.tbl_binaries.c.version,
2386 maintainer_id = self.tbl_binaries.c.maintainer,
2387 maintainer = relation(Maintainer),
2388 source_id = self.tbl_binaries.c.source,
2389 source = relation(DBSource),
2390 arch_id = self.tbl_binaries.c.architecture,
2391 architecture = relation(Architecture),
2392 poolfile_id = self.tbl_binaries.c.file,
2393 poolfile = relation(PoolFile),
2394 binarytype = self.tbl_binaries.c.type,
2395 fingerprint_id = self.tbl_binaries.c.sig_fpr,
2396 fingerprint = relation(Fingerprint),
2397 install_date = self.tbl_binaries.c.install_date,
2398 binassociations = relation(BinAssociation,
2399 primaryjoin=(self.tbl_binaries.c.id==self.tbl_bin_associations.c.bin))))
2401 mapper(BinaryACL, self.tbl_binary_acl,
2402 properties = dict(binary_acl_id = self.tbl_binary_acl.c.id))
2404 mapper(BinaryACLMap, self.tbl_binary_acl_map,
2405 properties = dict(binary_acl_map_id = self.tbl_binary_acl_map.c.id,
2406 fingerprint = relation(Fingerprint, backref="binary_acl_map"),
2407 architecture = relation(Architecture)))
2409 mapper(Component, self.tbl_component,
2410 properties = dict(component_id = self.tbl_component.c.id,
2411 component_name = self.tbl_component.c.name))
2413 mapper(DBConfig, self.tbl_config,
2414 properties = dict(config_id = self.tbl_config.c.id))
2416 mapper(DSCFile, self.tbl_dsc_files,
2417 properties = dict(dscfile_id = self.tbl_dsc_files.c.id,
2418 source_id = self.tbl_dsc_files.c.source,
2419 source = relation(DBSource),
2420 poolfile_id = self.tbl_dsc_files.c.file,
2421 poolfile = relation(PoolFile)))
2423 mapper(PoolFile, self.tbl_files,
2424 properties = dict(file_id = self.tbl_files.c.id,
2425 filesize = self.tbl_files.c.size,
2426 location_id = self.tbl_files.c.location,
2427 location = relation(Location)))
2429 mapper(Fingerprint, self.tbl_fingerprint,
2430 properties = dict(fingerprint_id = self.tbl_fingerprint.c.id,
2431 uid_id = self.tbl_fingerprint.c.uid,
2432 uid = relation(Uid),
2433 keyring_id = self.tbl_fingerprint.c.keyring,
2434 keyring = relation(Keyring),
2435 source_acl = relation(SourceACL),
2436 binary_acl = relation(BinaryACL)))
2438 mapper(Keyring, self.tbl_keyrings,
2439 properties = dict(keyring_name = self.tbl_keyrings.c.name,
2440 keyring_id = self.tbl_keyrings.c.id))
2442 mapper(KnownChange, self.tbl_known_changes,
2443 properties = dict(known_change_id = self.tbl_known_changes.c.id))
2445 mapper(KeyringACLMap, self.tbl_keyring_acl_map,
2446 properties = dict(keyring_acl_map_id = self.tbl_keyring_acl_map.c.id,
2447 keyring = relation(Keyring, backref="keyring_acl_map"),
2448 architecture = relation(Architecture)))
2450 mapper(Location, self.tbl_location,
2451 properties = dict(location_id = self.tbl_location.c.id,
2452 component_id = self.tbl_location.c.component,
2453 component = relation(Component),
2454 archive_id = self.tbl_location.c.archive,
2455 archive = relation(Archive),
2456 archive_type = self.tbl_location.c.type))
2458 mapper(Maintainer, self.tbl_maintainer,
2459 properties = dict(maintainer_id = self.tbl_maintainer.c.id))
2461 mapper(NewComment, self.tbl_new_comments,
2462 properties = dict(comment_id = self.tbl_new_comments.c.id))
2464 mapper(Override, self.tbl_override,
2465 properties = dict(suite_id = self.tbl_override.c.suite,
2466 suite = relation(Suite),
2467 component_id = self.tbl_override.c.component,
2468 component = relation(Component),
2469 priority_id = self.tbl_override.c.priority,
2470 priority = relation(Priority),
2471 section_id = self.tbl_override.c.section,
2472 section = relation(Section),
2473 overridetype_id = self.tbl_override.c.type,
2474 overridetype = relation(OverrideType)))
2476 mapper(OverrideType, self.tbl_override_type,
2477 properties = dict(overridetype = self.tbl_override_type.c.type,
2478 overridetype_id = self.tbl_override_type.c.id))
2480 mapper(Priority, self.tbl_priority,
2481 properties = dict(priority_id = self.tbl_priority.c.id))
2483 mapper(Queue, self.tbl_queue,
2484 properties = dict(queue_id = self.tbl_queue.c.id))
2486 mapper(QueueBuild, self.tbl_queue_build,
2487 properties = dict(suite_id = self.tbl_queue_build.c.suite,
2488 queue_id = self.tbl_queue_build.c.queue,
2489 queue = relation(Queue, backref='queuebuild')))
2491 mapper(Section, self.tbl_section,
2492 properties = dict(section_id = self.tbl_section.c.id))
2494 mapper(DBSource, self.tbl_source,
2495 properties = dict(source_id = self.tbl_source.c.id,
2496 version = self.tbl_source.c.version,
2497 maintainer_id = self.tbl_source.c.maintainer,
2498 maintainer = relation(Maintainer,
2499 primaryjoin=(self.tbl_source.c.maintainer==self.tbl_maintainer.c.id)),
2500 poolfile_id = self.tbl_source.c.file,
2501 poolfile = relation(PoolFile),
2502 fingerprint_id = self.tbl_source.c.sig_fpr,
2503 fingerprint = relation(Fingerprint),
2504 changedby_id = self.tbl_source.c.changedby,
2505 changedby = relation(Maintainer,
2506 primaryjoin=(self.tbl_source.c.changedby==self.tbl_maintainer.c.id)),
2507 srcfiles = relation(DSCFile,
2508 primaryjoin=(self.tbl_source.c.id==self.tbl_dsc_files.c.source)),
2509 srcassociations = relation(SrcAssociation,
2510 primaryjoin=(self.tbl_source.c.id==self.tbl_src_associations.c.source)),
2511 srcuploaders = relation(SrcUploader)))
2513 mapper(SourceACL, self.tbl_source_acl,
2514 properties = dict(source_acl_id = self.tbl_source_acl.c.id))
2516 mapper(SrcAssociation, self.tbl_src_associations,
2517 properties = dict(sa_id = self.tbl_src_associations.c.id,
2518 suite_id = self.tbl_src_associations.c.suite,
2519 suite = relation(Suite),
2520 source_id = self.tbl_src_associations.c.source,
2521 source = relation(DBSource)))
2523 mapper(SrcFormat, self.tbl_src_format,
2524 properties = dict(src_format_id = self.tbl_src_format.c.id,
2525 format_name = self.tbl_src_format.c.format_name))
2527 mapper(SrcUploader, self.tbl_src_uploaders,
2528 properties = dict(uploader_id = self.tbl_src_uploaders.c.id,
2529 source_id = self.tbl_src_uploaders.c.source,
2530 source = relation(DBSource,
2531 primaryjoin=(self.tbl_src_uploaders.c.source==self.tbl_source.c.id)),
2532 maintainer_id = self.tbl_src_uploaders.c.maintainer,
2533 maintainer = relation(Maintainer,
2534 primaryjoin=(self.tbl_src_uploaders.c.maintainer==self.tbl_maintainer.c.id))))
2536 mapper(Suite, self.tbl_suite,
2537 properties = dict(suite_id = self.tbl_suite.c.id))
2539 mapper(SuiteArchitecture, self.tbl_suite_architectures,
2540 properties = dict(suite_id = self.tbl_suite_architectures.c.suite,
2541 suite = relation(Suite, backref='suitearchitectures'),
2542 arch_id = self.tbl_suite_architectures.c.architecture,
2543 architecture = relation(Architecture)))
2545 mapper(SuiteSrcFormat, self.tbl_suite_src_formats,
2546 properties = dict(suite_id = self.tbl_suite_src_formats.c.suite,
2547 suite = relation(Suite, backref='suitesrcformats'),
2548 src_format_id = self.tbl_suite_src_formats.c.src_format,
2549 src_format = relation(SrcFormat)))
2551 mapper(Uid, self.tbl_uid,
2552 properties = dict(uid_id = self.tbl_uid.c.id,
2553 fingerprint = relation(Fingerprint)))
2555 mapper(UploadBlock, self.tbl_upload_blocks,
2556 properties = dict(upload_block_id = self.tbl_upload_blocks.c.id,
2557 fingerprint = relation(Fingerprint, backref="uploadblocks"),
2558 uid = relation(Uid, backref="uploadblocks")))
2560 ## Connection functions
2561 def __createconn(self):
2562 from config import Config
2566 connstr = "postgres://%s" % cnf["DB::Host"]
2567 if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
2568 connstr += ":%s" % cnf["DB::Port"]
2569 connstr += "/%s" % cnf["DB::Name"]
2572 connstr = "postgres:///%s" % cnf["DB::Name"]
2573 if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
2574 connstr += "?port=%s" % cnf["DB::Port"]
2576 self.db_pg = create_engine(connstr, echo=self.debug)
2577 self.db_meta = MetaData()
2578 self.db_meta.bind = self.db_pg
2579 self.db_smaker = sessionmaker(bind=self.db_pg,
2583 self.__setuptables()
2584 self.__setupmappers()
2587 return self.db_smaker()
2589 __all__.append('DBConn')