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 DBBinary(object):
235 def __init__(self, *args, **kwargs):
239 return '<DBBinary %s (%s, %s)>' % (self.package, self.version, self.architecture)
241 __all__.append('DBBinary')
244 def get_suites_binary_in(package, session=None):
246 Returns list of Suite objects which given C{package} name is in
249 @param source: DBBinary package name to search for
252 @return: list of Suite objects for the given package
255 return session.query(Suite).join(BinAssociation).join(DBBinary).filter_by(package=package).all()
257 __all__.append('get_suites_binary_in')
260 def get_binary_from_id(id, session=None):
262 Returns DBBinary object for given C{id}
265 @param id: Id of the required binary
267 @type session: Session
268 @param session: Optional SQLA session object (a temporary one will be
269 generated if not supplied)
272 @return: DBBinary object for the given binary (None if not present)
275 q = session.query(DBBinary).filter_by(binary_id=id)
279 except NoResultFound:
282 __all__.append('get_binary_from_id')
285 def get_binaries_from_name(package, version=None, architecture=None, session=None):
287 Returns list of DBBinary objects for given C{package} name
290 @param package: DBBinary package name to search for
292 @type version: str or None
293 @param version: Version to search for (or None)
295 @type package: str, list or None
296 @param package: Architectures to limit to (or None if no limit)
298 @type session: Session
299 @param session: Optional SQL session object (a temporary one will be
300 generated if not supplied)
303 @return: list of DBBinary objects for the given name (may be empty)
306 q = session.query(DBBinary).filter_by(package=package)
308 if version is not None:
309 q = q.filter_by(version=version)
311 if architecture is not None:
312 if not isinstance(architecture, list):
313 architecture = [architecture]
314 q = q.join(Architecture).filter(Architecture.arch_string.in_(architecture))
320 __all__.append('get_binaries_from_name')
323 def get_binaries_from_source_id(source_id, session=None):
325 Returns list of DBBinary objects for given C{source_id}
328 @param source_id: source_id to search for
330 @type session: Session
331 @param session: Optional SQL session object (a temporary one will be
332 generated if not supplied)
335 @return: list of DBBinary objects for the given name (may be empty)
338 return session.query(DBBinary).filter_by(source_id=source_id).all()
340 __all__.append('get_binaries_from_source_id')
343 def get_binary_from_name_suite(package, suitename, session=None):
344 ### For dak examine-package
345 ### XXX: Doesn't use object API yet
347 sql = """SELECT DISTINCT(b.package), b.version, c.name, su.suite_name
348 FROM binaries b, files fi, location l, component c, bin_associations ba, suite su
349 WHERE b.package=:package
351 AND fi.location = l.id
352 AND l.component = c.id
355 AND su.suite_name=:suitename
356 ORDER BY b.version DESC"""
358 return session.execute(sql, {'package': package, 'suitename': suitename})
360 __all__.append('get_binary_from_name_suite')
363 def get_binary_components(package, suitename, arch, session=None):
364 # Check for packages that have moved from one component to another
365 query = """SELECT c.name FROM binaries b, bin_associations ba, suite s, location l, component c, architecture a, files f
366 WHERE b.package=:package AND s.suite_name=:suitename
367 AND (a.arch_string = :arch OR a.arch_string = 'all')
368 AND ba.bin = b.id AND ba.suite = s.id AND b.architecture = a.id
369 AND f.location = l.id
370 AND l.component = c.id
373 vals = {'package': package, 'suitename': suitename, 'arch': arch}
375 return session.execute(query, vals)
377 __all__.append('get_binary_components')
379 ################################################################################
381 class BinaryACL(object):
382 def __init__(self, *args, **kwargs):
386 return '<BinaryACL %s>' % self.binary_acl_id
388 __all__.append('BinaryACL')
390 ################################################################################
392 class BinaryACLMap(object):
393 def __init__(self, *args, **kwargs):
397 return '<BinaryACLMap %s>' % self.binary_acl_map_id
399 __all__.append('BinaryACLMap')
401 ################################################################################
403 class Component(object):
404 def __init__(self, *args, **kwargs):
407 def __eq__(self, val):
408 if isinstance(val, str):
409 return (self.component_name == val)
410 # This signals to use the normal comparison operator
411 return NotImplemented
413 def __ne__(self, val):
414 if isinstance(val, str):
415 return (self.component_name != val)
416 # This signals to use the normal comparison operator
417 return NotImplemented
420 return '<Component %s>' % self.component_name
423 __all__.append('Component')
426 def get_component(component, session=None):
428 Returns database id for given C{component}.
430 @type component: string
431 @param component: The name of the override type
434 @return: the database id for the given component
437 component = component.lower()
439 q = session.query(Component).filter_by(component_name=component)
443 except NoResultFound:
446 __all__.append('get_component')
448 ################################################################################
450 class DBConfig(object):
451 def __init__(self, *args, **kwargs):
455 return '<DBConfig %s>' % self.name
457 __all__.append('DBConfig')
459 ################################################################################
461 class ContentFilename(object):
462 def __init__(self, *args, **kwargs):
466 return '<ContentFilename %s>' % self.filename
468 __all__.append('ContentFilename')
471 def get_or_set_contents_file_id(filename, session=None):
473 Returns database id for given filename.
475 If no matching file is found, a row is inserted.
477 @type filename: string
478 @param filename: The filename
479 @type session: SQLAlchemy
480 @param session: Optional SQL session object (a temporary one will be
481 generated if not supplied). If not passed, a commit will be performed at
482 the end of the function, otherwise the caller is responsible for commiting.
485 @return: the database id for the given component
488 q = session.query(ContentFilename).filter_by(filename=filename)
491 ret = q.one().cafilename_id
492 except NoResultFound:
493 cf = ContentFilename()
494 cf.filename = filename
496 session.commit_or_flush()
497 ret = cf.cafilename_id
501 __all__.append('get_or_set_contents_file_id')
504 def get_contents(suite, overridetype, section=None, session=None):
506 Returns contents for a suite / overridetype combination, limiting
507 to a section if not None.
510 @param suite: Suite object
512 @type overridetype: OverrideType
513 @param overridetype: OverrideType object
515 @type section: Section
516 @param section: Optional section object to limit results to
518 @type session: SQLAlchemy
519 @param session: Optional SQL session object (a temporary one will be
520 generated if not supplied)
523 @return: ResultsProxy object set up to return tuples of (filename, section,
527 # find me all of the contents for a given suite
528 contents_q = """SELECT (p.path||'/'||n.file) AS fn,
532 FROM content_associations c join content_file_paths p ON (c.filepath=p.id)
533 JOIN content_file_names n ON (c.filename=n.id)
534 JOIN binaries b ON (b.id=c.binary_pkg)
535 JOIN override o ON (o.package=b.package)
536 JOIN section s ON (s.id=o.section)
537 WHERE o.suite = :suiteid AND o.type = :overridetypeid
538 AND b.type=:overridetypename"""
540 vals = {'suiteid': suite.suite_id,
541 'overridetypeid': overridetype.overridetype_id,
542 'overridetypename': overridetype.overridetype}
544 if section is not None:
545 contents_q += " AND s.id = :sectionid"
546 vals['sectionid'] = section.section_id
548 contents_q += " ORDER BY fn"
550 return session.execute(contents_q, vals)
552 __all__.append('get_contents')
554 ################################################################################
556 class ContentFilepath(object):
557 def __init__(self, *args, **kwargs):
561 return '<ContentFilepath %s>' % self.filepath
563 __all__.append('ContentFilepath')
566 def get_or_set_contents_path_id(filepath, session=None):
568 Returns database id for given path.
570 If no matching file is found, a row is inserted.
572 @type filename: string
573 @param filename: The filepath
574 @type session: SQLAlchemy
575 @param session: Optional SQL session object (a temporary one will be
576 generated if not supplied). If not passed, a commit will be performed at
577 the end of the function, otherwise the caller is responsible for commiting.
580 @return: the database id for the given path
583 q = session.query(ContentFilepath).filter_by(filepath=filepath)
586 ret = q.one().cafilepath_id
587 except NoResultFound:
588 cf = ContentFilepath()
589 cf.filepath = filepath
591 session.commit_or_flush()
592 ret = cf.cafilepath_id
596 __all__.append('get_or_set_contents_path_id')
598 ################################################################################
600 class ContentAssociation(object):
601 def __init__(self, *args, **kwargs):
605 return '<ContentAssociation %s>' % self.ca_id
607 __all__.append('ContentAssociation')
609 def insert_content_paths(binary_id, fullpaths, session=None):
611 Make sure given path is associated with given binary id
614 @param binary_id: the id of the binary
615 @type fullpaths: list
616 @param fullpaths: the list of paths of the file being associated with the binary
617 @type session: SQLAlchemy session
618 @param session: Optional SQLAlchemy session. If this is passed, the caller
619 is responsible for ensuring a transaction has begun and committing the
620 results or rolling back based on the result code. If not passed, a commit
621 will be performed at the end of the function, otherwise the caller is
622 responsible for commiting.
624 @return: True upon success
629 session = DBConn().session()
635 for fullpath in fullpaths:
636 # Get the necessary IDs ...
637 (path, file) = os.path.split(fullpath)
639 filepath_id = get_or_set_contents_path_id(path, session)
640 filename_id = get_or_set_contents_file_id(file, session)
642 pathcache[fullpath] = (filepath_id, filename_id)
644 for fullpath, dat in pathcache.items():
645 ca = ContentAssociation()
646 ca.binary_id = binary_id
647 ca.filepath_id = dat[0]
648 ca.filename_id = dat[1]
651 # Only commit if we set up the session ourself
661 traceback.print_exc()
663 # Only rollback if we set up the session ourself
670 __all__.append('insert_content_paths')
672 ################################################################################
674 class DSCFile(object):
675 def __init__(self, *args, **kwargs):
679 return '<DSCFile %s>' % self.dscfile_id
681 __all__.append('DSCFile')
684 def get_dscfiles(dscfile_id=None, source_id=None, poolfile_id=None, session=None):
686 Returns a list of DSCFiles which may be empty
688 @type dscfile_id: int (optional)
689 @param dscfile_id: the dscfile_id of the DSCFiles to find
691 @type source_id: int (optional)
692 @param source_id: the source id related to the DSCFiles to find
694 @type poolfile_id: int (optional)
695 @param poolfile_id: the poolfile id related to the DSCFiles to find
698 @return: Possibly empty list of DSCFiles
701 q = session.query(DSCFile)
703 if dscfile_id is not None:
704 q = q.filter_by(dscfile_id=dscfile_id)
706 if source_id is not None:
707 q = q.filter_by(source_id=source_id)
709 if poolfile_id is not None:
710 q = q.filter_by(poolfile_id=poolfile_id)
714 __all__.append('get_dscfiles')
716 ################################################################################
718 class PoolFile(object):
719 def __init__(self, *args, **kwargs):
723 return '<PoolFile %s>' % self.filename
725 __all__.append('PoolFile')
728 def check_poolfile(filename, filesize, md5sum, location_id, session=None):
731 (ValidFileFound [boolean or None], PoolFile object or None)
733 @type filename: string
734 @param filename: the filename of the file to check against the DB
737 @param filesize: the size of the file to check against the DB
740 @param md5sum: the md5sum of the file to check against the DB
742 @type location_id: int
743 @param location_id: the id of the location to look in
746 @return: Tuple of length 2.
747 If more than one file found with that name:
749 If valid pool file found: (True, PoolFile object)
750 If valid pool file not found:
751 (False, None) if no file found
752 (False, PoolFile object) if file found with size/md5sum mismatch
755 q = session.query(PoolFile).filter_by(filename=filename)
756 q = q.join(Location).filter_by(location_id=location_id)
766 if obj.md5sum != md5sum or obj.filesize != filesize:
774 __all__.append('check_poolfile')
777 def get_poolfile_by_id(file_id, session=None):
779 Returns a PoolFile objects or None for the given id
782 @param file_id: the id of the file to look for
784 @rtype: PoolFile or None
785 @return: either the PoolFile object or None
788 q = session.query(PoolFile).filter_by(file_id=file_id)
792 except NoResultFound:
795 __all__.append('get_poolfile_by_id')
799 def get_poolfile_by_name(filename, location_id=None, session=None):
801 Returns an array of PoolFile objects for the given filename and
802 (optionally) location_id
804 @type filename: string
805 @param filename: the filename of the file to check against the DB
807 @type location_id: int
808 @param location_id: the id of the location to look in (optional)
811 @return: array of PoolFile objects
814 q = session.query(PoolFile).filter_by(filename=filename)
816 if location_id is not None:
817 q = q.join(Location).filter_by(location_id=location_id)
821 __all__.append('get_poolfile_by_name')
824 def get_poolfile_like_name(filename, session=None):
826 Returns an array of PoolFile objects which are like the given name
828 @type filename: string
829 @param filename: the filename of the file to check against the DB
832 @return: array of PoolFile objects
835 # TODO: There must be a way of properly using bind parameters with %FOO%
836 q = session.query(PoolFile).filter(PoolFile.filename.like('%%%s%%' % filename))
840 __all__.append('get_poolfile_like_name')
842 ################################################################################
844 class Fingerprint(object):
845 def __init__(self, *args, **kwargs):
849 return '<Fingerprint %s>' % self.fingerprint
851 __all__.append('Fingerprint')
854 def get_fingerprint(fpr, session=None):
856 Returns Fingerprint object for given fpr.
859 @param fpr: The fpr to find / add
861 @type session: SQLAlchemy
862 @param session: Optional SQL session object (a temporary one will be
863 generated if not supplied).
866 @return: the Fingerprint object for the given fpr or None
869 q = session.query(Fingerprint).filter_by(fingerprint=fpr)
873 except NoResultFound:
878 __all__.append('get_fingerprint')
881 def get_or_set_fingerprint(fpr, session=None):
883 Returns Fingerprint object for given fpr.
885 If no matching fpr is found, a row is inserted.
888 @param fpr: The fpr to find / add
890 @type session: SQLAlchemy
891 @param session: Optional SQL session object (a temporary one will be
892 generated if not supplied). If not passed, a commit will be performed at
893 the end of the function, otherwise the caller is responsible for commiting.
894 A flush will be performed either way.
897 @return: the Fingerprint object for the given fpr
900 q = session.query(Fingerprint).filter_by(fingerprint=fpr)
904 except NoResultFound:
905 fingerprint = Fingerprint()
906 fingerprint.fingerprint = fpr
907 session.add(fingerprint)
908 session.commit_or_flush()
913 __all__.append('get_or_set_fingerprint')
915 ################################################################################
917 # Helper routine for Keyring class
918 def get_ldap_name(entry):
920 for k in ["cn", "mn", "sn"]:
922 if ret and ret[0] != "" and ret[0] != "-":
924 return " ".join(name)
926 ################################################################################
928 class Keyring(object):
929 gpg_invocation = "gpg --no-default-keyring --keyring %s" +\
930 " --with-colons --fingerprint --fingerprint"
935 def __init__(self, *args, **kwargs):
939 return '<Keyring %s>' % self.keyring_name
941 def de_escape_gpg_str(self, str):
942 esclist = re.split(r'(\\x..)', str)
943 for x in range(1,len(esclist),2):
944 esclist[x] = "%c" % (int(esclist[x][2:],16))
945 return "".join(esclist)
947 def load_keys(self, keyring):
950 if not self.keyring_id:
951 raise Exception('Must be initialized with database information')
953 k = os.popen(self.gpg_invocation % keyring, "r")
957 for line in k.xreadlines():
958 field = line.split(":")
959 if field[0] == "pub":
961 (name, addr) = email.Utils.parseaddr(field[9])
962 name = re.sub(r"\s*[(].*[)]", "", name)
963 if name == "" or addr == "" or "@" not in addr:
966 name = self.de_escape_gpg_str(name)
967 self.keys[key] = {"email": addr}
969 self.keys[key]["name"] = name
970 self.keys[key]["aliases"] = [name]
971 self.keys[key]["fingerprints"] = []
973 elif key and field[0] == "sub" and len(field) >= 12:
974 signingkey = ("s" in field[11])
975 elif key and field[0] == "uid":
976 (name, addr) = email.Utils.parseaddr(field[9])
977 if name and name not in self.keys[key]["aliases"]:
978 self.keys[key]["aliases"].append(name)
979 elif signingkey and field[0] == "fpr":
980 self.keys[key]["fingerprints"].append(field[9])
981 self.fpr_lookup[field[9]] = key
983 def import_users_from_ldap(self, session):
987 LDAPDn = cnf["Import-LDAP-Fingerprints::LDAPDn"]
988 LDAPServer = cnf["Import-LDAP-Fingerprints::LDAPServer"]
990 l = ldap.open(LDAPServer)
991 l.simple_bind_s("","")
992 Attrs = l.search_s(LDAPDn, ldap.SCOPE_ONELEVEL,
993 "(&(keyfingerprint=*)(gidnumber=%s))" % (cnf["Import-Users-From-Passwd::ValidGID"]),
994 ["uid", "keyfingerprint", "cn", "mn", "sn"])
1003 uid = entry["uid"][0]
1004 name = get_ldap_name(entry)
1005 fingerprints = entry["keyFingerPrint"]
1007 for f in fingerprints:
1008 key = self.fpr_lookup.get(f, None)
1009 if key not in self.keys:
1011 self.keys[key]["uid"] = uid
1015 keyid = get_or_set_uid(uid, session).uid_id
1016 byuid[keyid] = (uid, name)
1017 byname[uid] = (keyid, name)
1019 return (byname, byuid)
1021 def generate_users_from_keyring(self, format, session):
1025 for x in self.keys.keys():
1026 if self.keys[x]["email"] == "invalid-uid":
1028 self.keys[x]["uid"] = format % "invalid-uid"
1030 uid = format % self.keys[x]["email"]
1031 keyid = get_or_set_uid(uid, session).uid_id
1032 byuid[keyid] = (uid, self.keys[x]["name"])
1033 byname[uid] = (keyid, self.keys[x]["name"])
1034 self.keys[x]["uid"] = uid
1037 uid = format % "invalid-uid"
1038 keyid = get_or_set_uid(uid, session).uid_id
1039 byuid[keyid] = (uid, "ungeneratable user id")
1040 byname[uid] = (keyid, "ungeneratable user id")
1042 return (byname, byuid)
1044 __all__.append('Keyring')
1047 def get_keyring(keyring, session=None):
1049 If C{keyring} does not have an entry in the C{keyrings} table yet, return None
1050 If C{keyring} already has an entry, simply return the existing Keyring
1052 @type keyring: string
1053 @param keyring: the keyring name
1056 @return: the Keyring object for this keyring
1059 q = session.query(Keyring).filter_by(keyring_name=keyring)
1063 except NoResultFound:
1066 __all__.append('get_keyring')
1068 ################################################################################
1070 class KeyringACLMap(object):
1071 def __init__(self, *args, **kwargs):
1075 return '<KeyringACLMap %s>' % self.keyring_acl_map_id
1077 __all__.append('KeyringACLMap')
1079 ################################################################################
1081 class Location(object):
1082 def __init__(self, *args, **kwargs):
1086 return '<Location %s (%s)>' % (self.path, self.location_id)
1088 __all__.append('Location')
1091 def get_location(location, component=None, archive=None, session=None):
1093 Returns Location object for the given combination of location, component
1096 @type location: string
1097 @param location: the path of the location, e.g. I{/srv/ftp.debian.org/ftp/pool/}
1099 @type component: string
1100 @param component: the component name (if None, no restriction applied)
1102 @type archive: string
1103 @param archive_id: the archive name (if None, no restriction applied)
1105 @rtype: Location / None
1106 @return: Either a Location object or None if one can't be found
1109 q = session.query(Location).filter_by(path=location)
1111 if archive is not None:
1112 q = q.join(Archive).filter_by(archive_name=archive)
1114 if component is not None:
1115 q = q.join(Component).filter_by(component_name=component)
1119 except NoResultFound:
1122 __all__.append('get_location')
1124 ################################################################################
1126 class Maintainer(object):
1127 def __init__(self, *args, **kwargs):
1131 return '''<Maintainer '%s' (%s)>''' % (self.name, self.maintainer_id)
1133 def get_split_maintainer(self):
1134 if not hasattr(self, 'name') or self.name is None:
1135 return ('', '', '', '')
1137 return fix_maintainer(self.name.strip())
1139 __all__.append('Maintainer')
1142 def get_or_set_maintainer(name, session=None):
1144 Returns Maintainer object for given maintainer name.
1146 If no matching maintainer name is found, a row is inserted.
1149 @param name: The maintainer name to add
1151 @type session: SQLAlchemy
1152 @param session: Optional SQL session object (a temporary one will be
1153 generated if not supplied). If not passed, a commit will be performed at
1154 the end of the function, otherwise the caller is responsible for commiting.
1155 A flush will be performed either way.
1158 @return: the Maintainer object for the given maintainer
1161 q = session.query(Maintainer).filter_by(name=name)
1164 except NoResultFound:
1165 maintainer = Maintainer()
1166 maintainer.name = name
1167 session.add(maintainer)
1168 session.commit_or_flush()
1173 __all__.append('get_or_set_maintainer')
1176 def get_maintainer(maintainer_id, session=None):
1178 Return the name of the maintainer behind C{maintainer_id} or None if that
1179 maintainer_id is invalid.
1181 @type maintainer_id: int
1182 @param maintainer_id: the id of the maintainer
1185 @return: the Maintainer with this C{maintainer_id}
1188 return session.query(Maintainer).get(maintainer_id)
1190 __all__.append('get_maintainer')
1192 ################################################################################
1194 class NewComment(object):
1195 def __init__(self, *args, **kwargs):
1199 return '''<NewComment for '%s %s' (%s)>''' % (self.package, self.version, self.comment_id)
1201 __all__.append('NewComment')
1204 def has_new_comment(package, version, session=None):
1206 Returns true if the given combination of C{package}, C{version} has a comment.
1208 @type package: string
1209 @param package: name of the package
1211 @type version: string
1212 @param version: package version
1214 @type session: Session
1215 @param session: Optional SQLA session object (a temporary one will be
1216 generated if not supplied)
1222 q = session.query(NewComment)
1223 q = q.filter_by(package=package)
1224 q = q.filter_by(version=version)
1226 return bool(q.count() > 0)
1228 __all__.append('has_new_comment')
1231 def get_new_comments(package=None, version=None, comment_id=None, session=None):
1233 Returns (possibly empty) list of NewComment objects for the given
1236 @type package: string (optional)
1237 @param package: name of the package
1239 @type version: string (optional)
1240 @param version: package version
1242 @type comment_id: int (optional)
1243 @param comment_id: An id of a comment
1245 @type session: Session
1246 @param session: Optional SQLA session object (a temporary one will be
1247 generated if not supplied)
1250 @return: A (possibly empty) list of NewComment objects will be returned
1253 q = session.query(NewComment)
1254 if package is not None: q = q.filter_by(package=package)
1255 if version is not None: q = q.filter_by(version=version)
1256 if comment_id is not None: q = q.filter_by(comment_id=comment_id)
1260 __all__.append('get_new_comments')
1262 ################################################################################
1264 class Override(object):
1265 def __init__(self, *args, **kwargs):
1269 return '<Override %s (%s)>' % (self.package, self.suite_id)
1271 __all__.append('Override')
1274 def get_override(package, suite=None, component=None, overridetype=None, session=None):
1276 Returns Override object for the given parameters
1278 @type package: string
1279 @param package: The name of the package
1281 @type suite: string, list or None
1282 @param suite: The name of the suite (or suites if a list) to limit to. If
1283 None, don't limit. Defaults to None.
1285 @type component: string, list or None
1286 @param component: The name of the component (or components if a list) to
1287 limit to. If None, don't limit. Defaults to None.
1289 @type overridetype: string, list or None
1290 @param overridetype: The name of the overridetype (or overridetypes if a list) to
1291 limit to. If None, don't limit. Defaults to None.
1293 @type session: Session
1294 @param session: Optional SQLA session object (a temporary one will be
1295 generated if not supplied)
1298 @return: A (possibly empty) list of Override objects will be returned
1301 q = session.query(Override)
1302 q = q.filter_by(package=package)
1304 if suite is not None:
1305 if not isinstance(suite, list): suite = [suite]
1306 q = q.join(Suite).filter(Suite.suite_name.in_(suite))
1308 if component is not None:
1309 if not isinstance(component, list): component = [component]
1310 q = q.join(Component).filter(Component.component_name.in_(component))
1312 if overridetype is not None:
1313 if not isinstance(overridetype, list): overridetype = [overridetype]
1314 q = q.join(OverrideType).filter(OverrideType.overridetype.in_(overridetype))
1318 __all__.append('get_override')
1321 ################################################################################
1323 class OverrideType(object):
1324 def __init__(self, *args, **kwargs):
1328 return '<OverrideType %s>' % self.overridetype
1330 __all__.append('OverrideType')
1333 def get_override_type(override_type, session=None):
1335 Returns OverrideType object for given C{override type}.
1337 @type override_type: string
1338 @param override_type: The name of the override type
1340 @type session: Session
1341 @param session: Optional SQLA session object (a temporary one will be
1342 generated if not supplied)
1345 @return: the database id for the given override type
1348 q = session.query(OverrideType).filter_by(overridetype=override_type)
1352 except NoResultFound:
1355 __all__.append('get_override_type')
1357 ################################################################################
1359 class PendingContentAssociation(object):
1360 def __init__(self, *args, **kwargs):
1364 return '<PendingContentAssociation %s>' % self.pca_id
1366 __all__.append('PendingContentAssociation')
1368 def insert_pending_content_paths(package, fullpaths, session=None):
1370 Make sure given paths are temporarily associated with given
1374 @param package: the package to associate with should have been read in from the binary control file
1375 @type fullpaths: list
1376 @param fullpaths: the list of paths of the file being associated with the binary
1377 @type session: SQLAlchemy session
1378 @param session: Optional SQLAlchemy session. If this is passed, the caller
1379 is responsible for ensuring a transaction has begun and committing the
1380 results or rolling back based on the result code. If not passed, a commit
1381 will be performed at the end of the function
1383 @return: True upon success, False if there is a problem
1386 privatetrans = False
1389 session = DBConn().session()
1393 arch = get_architecture(package['Architecture'], session)
1394 arch_id = arch.arch_id
1396 # Remove any already existing recorded files for this package
1397 q = session.query(PendingContentAssociation)
1398 q = q.filter_by(package=package['Package'])
1399 q = q.filter_by(version=package['Version'])
1400 q = q.filter_by(architecture=arch_id)
1405 for fullpath in fullpaths:
1406 (path, file) = os.path.split(fullpath)
1408 if path.startswith( "./" ):
1411 filepath_id = get_or_set_contents_path_id(path, session)
1412 filename_id = get_or_set_contents_file_id(file, session)
1414 pathcache[fullpath] = (filepath_id, filename_id)
1416 for fullpath, dat in pathcache.items():
1417 pca = PendingContentAssociation()
1418 pca.package = package['Package']
1419 pca.version = package['Version']
1420 pca.filepath_id = dat[0]
1421 pca.filename_id = dat[1]
1422 pca.architecture = arch_id
1425 # Only commit if we set up the session ourself
1433 except Exception, e:
1434 traceback.print_exc()
1436 # Only rollback if we set up the session ourself
1443 __all__.append('insert_pending_content_paths')
1445 ################################################################################
1447 class Priority(object):
1448 def __init__(self, *args, **kwargs):
1451 def __eq__(self, val):
1452 if isinstance(val, str):
1453 return (self.priority == val)
1454 # This signals to use the normal comparison operator
1455 return NotImplemented
1457 def __ne__(self, val):
1458 if isinstance(val, str):
1459 return (self.priority != val)
1460 # This signals to use the normal comparison operator
1461 return NotImplemented
1464 return '<Priority %s (%s)>' % (self.priority, self.priority_id)
1466 __all__.append('Priority')
1469 def get_priority(priority, session=None):
1471 Returns Priority object for given C{priority name}.
1473 @type priority: string
1474 @param priority: The name of the priority
1476 @type session: Session
1477 @param session: Optional SQLA session object (a temporary one will be
1478 generated if not supplied)
1481 @return: Priority object for the given priority
1484 q = session.query(Priority).filter_by(priority=priority)
1488 except NoResultFound:
1491 __all__.append('get_priority')
1494 def get_priorities(session=None):
1496 Returns dictionary of priority names -> id mappings
1498 @type session: Session
1499 @param session: Optional SQL session object (a temporary one will be
1500 generated if not supplied)
1503 @return: dictionary of priority names -> id mappings
1507 q = session.query(Priority)
1509 ret[x.priority] = x.priority_id
1513 __all__.append('get_priorities')
1515 ################################################################################
1517 class Queue(object):
1518 def __init__(self, *args, **kwargs):
1522 return '<Queue %s>' % self.queue_name
1524 def autobuild_upload(self, changes, srcpath, session=None):
1526 Update queue_build database table used for incoming autobuild support.
1528 @type changes: Changes
1529 @param changes: changes object for the upload to process
1531 @type srcpath: string
1532 @param srcpath: path for the queue file entries/link destinations
1534 @type session: SQLAlchemy session
1535 @param session: Optional SQLAlchemy session. If this is passed, the
1536 caller is responsible for ensuring a transaction has begun and
1537 committing the results or rolling back based on the result code. If
1538 not passed, a commit will be performed at the end of the function,
1539 otherwise the caller is responsible for commiting.
1541 @rtype: NoneType or string
1542 @return: None if the operation failed, a string describing the error if not
1545 privatetrans = False
1547 session = DBConn().session()
1550 # TODO: Remove by moving queue config into the database
1553 for suitename in changes.changes["distribution"].keys():
1554 # TODO: Move into database as:
1555 # buildqueuedir TEXT DEFAULT NULL (i.e. NULL is no build)
1556 # buildqueuecopy BOOLEAN NOT NULL DEFAULT FALSE (i.e. default is symlink)
1557 # This also gets rid of the SecurityQueueBuild hack below
1558 if suitename not in conf.ValueList("Dinstall::QueueBuildSuites"):
1562 s = get_suite(suitename, session)
1564 return "INTERNAL ERROR: Could not find suite %s" % suitename
1566 # TODO: Get from database as above
1567 dest_dir = conf["Dir::QueueBuild"]
1569 # TODO: Move into database as above
1570 if conf.FindB("Dinstall::SecurityQueueBuild"):
1571 dest_dir = os.path.join(dest_dir, suitename)
1573 for file_entry in changes.files.keys():
1574 src = os.path.join(srcpath, file_entry)
1575 dest = os.path.join(dest_dir, file_entry)
1577 # TODO: Move into database as above
1578 if conf.FindB("Dinstall::SecurityQueueBuild"):
1579 # Copy it since the original won't be readable by www-data
1581 utils.copy(src, dest)
1583 # Create a symlink to it
1584 os.symlink(src, dest)
1587 qb.suite_id = s.suite_id
1588 qb.queue_id = self.queue_id
1594 # If the .orig tarballs are in the pool, create a symlink to
1595 # them (if one doesn't already exist)
1596 for dsc_file in changes.dsc_files.keys():
1597 # Skip all files except orig tarballs
1598 from daklib.regexes import re_is_orig_source
1599 if not re_is_orig_source.match(dsc_file):
1601 # Skip orig files not identified in the pool
1602 if not (changes.orig_files.has_key(dsc_file) and
1603 changes.orig_files[dsc_file].has_key("id")):
1605 orig_file_id = changes.orig_files[dsc_file]["id"]
1606 dest = os.path.join(dest_dir, dsc_file)
1608 # If it doesn't exist, create a symlink
1609 if not os.path.exists(dest):
1610 q = session.execute("SELECT l.path, f.filename FROM location l, files f WHERE f.id = :id and f.location = l.id",
1611 {'id': orig_file_id})
1614 return "[INTERNAL ERROR] Couldn't find id %s in files table." % (orig_file_id)
1616 src = os.path.join(res[0], res[1])
1617 os.symlink(src, dest)
1619 # Add it to the list of packages for later processing by apt-ftparchive
1621 qb.suite_id = s.suite_id
1622 qb.queue_id = self.queue_id
1627 # If it does, update things to ensure it's not removed prematurely
1629 qb = get_queue_build(dest, s.suite_id, session)
1641 __all__.append('Queue')
1644 def get_or_set_queue(queuename, session=None):
1646 Returns Queue object for given C{queue name}, creating it if it does not
1649 @type queuename: string
1650 @param queuename: The name of the queue
1652 @type session: Session
1653 @param session: Optional SQLA session object (a temporary one will be
1654 generated if not supplied)
1657 @return: Queue object for the given queue
1660 q = session.query(Queue).filter_by(queue_name=queuename)
1664 except NoResultFound:
1666 queue.queue_name = queuename
1668 session.commit_or_flush()
1673 __all__.append('get_or_set_queue')
1675 ################################################################################
1677 class QueueBuild(object):
1678 def __init__(self, *args, **kwargs):
1682 return '<QueueBuild %s (%s)>' % (self.filename, self.queue_id)
1684 __all__.append('QueueBuild')
1687 def get_queue_build(filename, suite, session=None):
1689 Returns QueueBuild object for given C{filename} and C{suite}.
1691 @type filename: string
1692 @param filename: The name of the file
1694 @type suiteid: int or str
1695 @param suiteid: Suite name or ID
1697 @type session: Session
1698 @param session: Optional SQLA session object (a temporary one will be
1699 generated if not supplied)
1702 @return: Queue object for the given queue
1705 if isinstance(suite, int):
1706 q = session.query(QueueBuild).filter_by(filename=filename).filter_by(suite_id=suite)
1708 q = session.query(QueueBuild).filter_by(filename=filename)
1709 q = q.join(Suite).filter_by(suite_name=suite)
1713 except NoResultFound:
1716 __all__.append('get_queue_build')
1718 ################################################################################
1720 class Section(object):
1721 def __init__(self, *args, **kwargs):
1724 def __eq__(self, val):
1725 if isinstance(val, str):
1726 return (self.section == val)
1727 # This signals to use the normal comparison operator
1728 return NotImplemented
1730 def __ne__(self, val):
1731 if isinstance(val, str):
1732 return (self.section != val)
1733 # This signals to use the normal comparison operator
1734 return NotImplemented
1737 return '<Section %s>' % self.section
1739 __all__.append('Section')
1742 def get_section(section, session=None):
1744 Returns Section object for given C{section name}.
1746 @type section: string
1747 @param section: The name of the section
1749 @type session: Session
1750 @param session: Optional SQLA session object (a temporary one will be
1751 generated if not supplied)
1754 @return: Section object for the given section name
1757 q = session.query(Section).filter_by(section=section)
1761 except NoResultFound:
1764 __all__.append('get_section')
1767 def get_sections(session=None):
1769 Returns dictionary of section names -> id mappings
1771 @type session: Session
1772 @param session: Optional SQL session object (a temporary one will be
1773 generated if not supplied)
1776 @return: dictionary of section names -> id mappings
1780 q = session.query(Section)
1782 ret[x.section] = x.section_id
1786 __all__.append('get_sections')
1788 ################################################################################
1790 class DBSource(object):
1791 def __init__(self, *args, **kwargs):
1795 return '<DBSource %s (%s)>' % (self.source, self.version)
1797 __all__.append('DBSource')
1800 def source_exists(source, source_version, suites = ["any"], session=None):
1802 Ensure that source exists somewhere in the archive for the binary
1803 upload being processed.
1804 1. exact match => 1.0-3
1805 2. bin-only NMU => 1.0-3+b1 , 1.0-3.1+b1
1807 @type package: string
1808 @param package: package source name
1810 @type source_version: string
1811 @param source_version: expected source version
1814 @param suites: list of suites to check in, default I{any}
1816 @type session: Session
1817 @param session: Optional SQLA session object (a temporary one will be
1818 generated if not supplied)
1821 @return: returns 1 if a source with expected version is found, otherwise 0
1828 for suite in suites:
1829 q = session.query(DBSource).filter_by(source=source)
1831 # source must exist in suite X, or in some other suite that's
1832 # mapped to X, recursively... silent-maps are counted too,
1833 # unreleased-maps aren't.
1834 maps = cnf.ValueList("SuiteMappings")[:]
1836 maps = [ m.split() for m in maps ]
1837 maps = [ (x[1], x[2]) for x in maps
1838 if x[0] == "map" or x[0] == "silent-map" ]
1841 if x[1] in s and x[0] not in s:
1844 q = q.join(SrcAssociation).join(Suite)
1845 q = q.filter(Suite.suite_name.in_(s))
1847 # Reduce the query results to a list of version numbers
1848 ql = [ j.version for j in q.all() ]
1851 if source_version in ql:
1855 from daklib.regexes import re_bin_only_nmu
1856 orig_source_version = re_bin_only_nmu.sub('', source_version)
1857 if orig_source_version in ql:
1860 # No source found so return not ok
1865 __all__.append('source_exists')
1868 def get_suites_source_in(source, session=None):
1870 Returns list of Suite objects which given C{source} name is in
1873 @param source: DBSource package name to search for
1876 @return: list of Suite objects for the given source
1879 return session.query(Suite).join(SrcAssociation).join(DBSource).filter_by(source=source).all()
1881 __all__.append('get_suites_source_in')
1884 def get_sources_from_name(source, version=None, dm_upload_allowed=None, session=None):
1886 Returns list of DBSource objects for given C{source} name and other parameters
1889 @param source: DBSource package name to search for
1891 @type source: str or None
1892 @param source: DBSource version name to search for or None if not applicable
1894 @type dm_upload_allowed: bool
1895 @param dm_upload_allowed: If None, no effect. If True or False, only
1896 return packages with that dm_upload_allowed setting
1898 @type session: Session
1899 @param session: Optional SQL session object (a temporary one will be
1900 generated if not supplied)
1903 @return: list of DBSource objects for the given name (may be empty)
1906 q = session.query(DBSource).filter_by(source=source)
1908 if version is not None:
1909 q = q.filter_by(version=version)
1911 if dm_upload_allowed is not None:
1912 q = q.filter_by(dm_upload_allowed=dm_upload_allowed)
1916 __all__.append('get_sources_from_name')
1919 def get_source_in_suite(source, suite, session=None):
1921 Returns list of DBSource objects for a combination of C{source} and C{suite}.
1923 - B{source} - source package name, eg. I{mailfilter}, I{bbdb}, I{glibc}
1924 - B{suite} - a suite name, eg. I{unstable}
1926 @type source: string
1927 @param source: source package name
1930 @param suite: the suite name
1933 @return: the version for I{source} in I{suite}
1937 q = session.query(SrcAssociation)
1938 q = q.join('source').filter_by(source=source)
1939 q = q.join('suite').filter_by(suite_name=suite)
1942 return q.one().source
1943 except NoResultFound:
1946 __all__.append('get_source_in_suite')
1948 ################################################################################
1950 class SourceACL(object):
1951 def __init__(self, *args, **kwargs):
1955 return '<SourceACL %s>' % self.source_acl_id
1957 __all__.append('SourceACL')
1959 ################################################################################
1961 class SrcAssociation(object):
1962 def __init__(self, *args, **kwargs):
1966 return '<SrcAssociation %s (%s, %s)>' % (self.sa_id, self.source, self.suite)
1968 __all__.append('SrcAssociation')
1970 ################################################################################
1972 class SrcFormat(object):
1973 def __init__(self, *args, **kwargs):
1977 return '<SrcFormat %s>' % (self.format_name)
1979 __all__.append('SrcFormat')
1981 ################################################################################
1983 class SrcUploader(object):
1984 def __init__(self, *args, **kwargs):
1988 return '<SrcUploader %s>' % self.uploader_id
1990 __all__.append('SrcUploader')
1992 ################################################################################
1994 SUITE_FIELDS = [ ('SuiteName', 'suite_name'),
1995 ('SuiteID', 'suite_id'),
1996 ('Version', 'version'),
1997 ('Origin', 'origin'),
1999 ('Description', 'description'),
2000 ('Untouchable', 'untouchable'),
2001 ('Announce', 'announce'),
2002 ('Codename', 'codename'),
2003 ('OverrideCodename', 'overridecodename'),
2004 ('ValidTime', 'validtime'),
2005 ('Priority', 'priority'),
2006 ('NotAutomatic', 'notautomatic'),
2007 ('CopyChanges', 'copychanges'),
2008 ('CopyDotDak', 'copydotdak'),
2009 ('CommentsDir', 'commentsdir'),
2010 ('OverrideSuite', 'overridesuite'),
2011 ('ChangelogBase', 'changelogbase')]
2014 class Suite(object):
2015 def __init__(self, *args, **kwargs):
2019 return '<Suite %s>' % self.suite_name
2021 def __eq__(self, val):
2022 if isinstance(val, str):
2023 return (self.suite_name == val)
2024 # This signals to use the normal comparison operator
2025 return NotImplemented
2027 def __ne__(self, val):
2028 if isinstance(val, str):
2029 return (self.suite_name != val)
2030 # This signals to use the normal comparison operator
2031 return NotImplemented
2035 for disp, field in SUITE_FIELDS:
2036 val = getattr(self, field, None)
2038 ret.append("%s: %s" % (disp, val))
2040 return "\n".join(ret)
2042 __all__.append('Suite')
2045 def get_suite_architecture(suite, architecture, session=None):
2047 Returns a SuiteArchitecture object given C{suite} and ${arch} or None if it
2051 @param suite: Suite name to search for
2053 @type architecture: str
2054 @param architecture: Architecture name to search for
2056 @type session: Session
2057 @param session: Optional SQL session object (a temporary one will be
2058 generated if not supplied)
2060 @rtype: SuiteArchitecture
2061 @return: the SuiteArchitecture object or None
2064 q = session.query(SuiteArchitecture)
2065 q = q.join(Architecture).filter_by(arch_string=architecture)
2066 q = q.join(Suite).filter_by(suite_name=suite)
2070 except NoResultFound:
2073 __all__.append('get_suite_architecture')
2076 def get_suite(suite, session=None):
2078 Returns Suite object for given C{suite name}.
2081 @param suite: The name of the suite
2083 @type session: Session
2084 @param session: Optional SQLA session object (a temporary one will be
2085 generated if not supplied)
2088 @return: Suite object for the requested suite name (None if not present)
2091 q = session.query(Suite).filter_by(suite_name=suite)
2095 except NoResultFound:
2098 __all__.append('get_suite')
2100 ################################################################################
2102 class SuiteArchitecture(object):
2103 def __init__(self, *args, **kwargs):
2107 return '<SuiteArchitecture (%s, %s)>' % (self.suite_id, self.arch_id)
2109 __all__.append('SuiteArchitecture')
2112 def get_suite_architectures(suite, skipsrc=False, skipall=False, session=None):
2114 Returns list of Architecture objects for given C{suite} name
2117 @param source: Suite name to search for
2119 @type skipsrc: boolean
2120 @param skipsrc: Whether to skip returning the 'source' architecture entry
2123 @type skipall: boolean
2124 @param skipall: Whether to skip returning the 'all' architecture entry
2127 @type session: Session
2128 @param session: Optional SQL session object (a temporary one will be
2129 generated if not supplied)
2132 @return: list of Architecture objects for the given name (may be empty)
2135 q = session.query(Architecture)
2136 q = q.join(SuiteArchitecture)
2137 q = q.join(Suite).filter_by(suite_name=suite)
2140 q = q.filter(Architecture.arch_string != 'source')
2143 q = q.filter(Architecture.arch_string != 'all')
2145 q = q.order_by('arch_string')
2149 __all__.append('get_suite_architectures')
2151 ################################################################################
2153 class SuiteSrcFormat(object):
2154 def __init__(self, *args, **kwargs):
2158 return '<SuiteSrcFormat (%s, %s)>' % (self.suite_id, self.src_format_id)
2160 __all__.append('SuiteSrcFormat')
2163 def get_suite_src_formats(suite, session=None):
2165 Returns list of allowed SrcFormat for C{suite}.
2168 @param suite: Suite name to search for
2170 @type session: Session
2171 @param session: Optional SQL session object (a temporary one will be
2172 generated if not supplied)
2175 @return: the list of allowed source formats for I{suite}
2178 q = session.query(SrcFormat)
2179 q = q.join(SuiteSrcFormat)
2180 q = q.join(Suite).filter_by(suite_name=suite)
2181 q = q.order_by('format_name')
2185 __all__.append('get_suite_src_formats')
2187 ################################################################################
2190 def __init__(self, *args, **kwargs):
2193 def __eq__(self, val):
2194 if isinstance(val, str):
2195 return (self.uid == val)
2196 # This signals to use the normal comparison operator
2197 return NotImplemented
2199 def __ne__(self, val):
2200 if isinstance(val, str):
2201 return (self.uid != val)
2202 # This signals to use the normal comparison operator
2203 return NotImplemented
2206 return '<Uid %s (%s)>' % (self.uid, self.name)
2208 __all__.append('Uid')
2211 def add_database_user(uidname, session=None):
2213 Adds a database user
2215 @type uidname: string
2216 @param uidname: The uid of the user to add
2218 @type session: SQLAlchemy
2219 @param session: Optional SQL session object (a temporary one will be
2220 generated if not supplied). If not passed, a commit will be performed at
2221 the end of the function, otherwise the caller is responsible for commiting.
2224 @return: the uid object for the given uidname
2227 session.execute("CREATE USER :uid", {'uid': uidname})
2228 session.commit_or_flush()
2230 __all__.append('add_database_user')
2233 def get_or_set_uid(uidname, session=None):
2235 Returns uid object for given uidname.
2237 If no matching uidname is found, a row is inserted.
2239 @type uidname: string
2240 @param uidname: The uid to add
2242 @type session: SQLAlchemy
2243 @param session: Optional SQL session object (a temporary one will be
2244 generated if not supplied). If not passed, a commit will be performed at
2245 the end of the function, otherwise the caller is responsible for commiting.
2248 @return: the uid object for the given uidname
2251 q = session.query(Uid).filter_by(uid=uidname)
2255 except NoResultFound:
2259 session.commit_or_flush()
2264 __all__.append('get_or_set_uid')
2267 def get_uid_from_fingerprint(fpr, session=None):
2268 q = session.query(Uid)
2269 q = q.join(Fingerprint).filter_by(fingerprint=fpr)
2273 except NoResultFound:
2276 __all__.append('get_uid_from_fingerprint')
2278 ################################################################################
2280 class UploadBlock(object):
2281 def __init__(self, *args, **kwargs):
2285 return '<UploadBlock %s (%s)>' % (self.source, self.upload_block_id)
2287 __all__.append('UploadBlock')
2289 ################################################################################
2291 class DBConn(Singleton):
2293 database module init.
2295 def __init__(self, *args, **kwargs):
2296 super(DBConn, self).__init__(*args, **kwargs)
2298 def _startup(self, *args, **kwargs):
2300 if kwargs.has_key('debug'):
2304 def __setuptables(self):
2305 self.tbl_architecture = Table('architecture', self.db_meta, autoload=True)
2306 self.tbl_archive = Table('archive', self.db_meta, autoload=True)
2307 self.tbl_bin_associations = Table('bin_associations', self.db_meta, autoload=True)
2308 self.tbl_binaries = Table('binaries', self.db_meta, autoload=True)
2309 self.tbl_binary_acl = Table('binary_acl', self.db_meta, autoload=True)
2310 self.tbl_binary_acl_map = Table('binary_acl_map', self.db_meta, autoload=True)
2311 self.tbl_component = Table('component', self.db_meta, autoload=True)
2312 self.tbl_config = Table('config', self.db_meta, autoload=True)
2313 self.tbl_content_associations = Table('content_associations', self.db_meta, autoload=True)
2314 self.tbl_content_file_names = Table('content_file_names', self.db_meta, autoload=True)
2315 self.tbl_content_file_paths = Table('content_file_paths', self.db_meta, autoload=True)
2316 self.tbl_dsc_files = Table('dsc_files', self.db_meta, autoload=True)
2317 self.tbl_files = Table('files', self.db_meta, autoload=True)
2318 self.tbl_fingerprint = Table('fingerprint', self.db_meta, autoload=True)
2319 self.tbl_keyrings = Table('keyrings', self.db_meta, autoload=True)
2320 self.tbl_keyring_acl_map = Table('keyring_acl_map', self.db_meta, autoload=True)
2321 self.tbl_location = Table('location', self.db_meta, autoload=True)
2322 self.tbl_maintainer = Table('maintainer', self.db_meta, autoload=True)
2323 self.tbl_new_comments = Table('new_comments', self.db_meta, autoload=True)
2324 self.tbl_override = Table('override', self.db_meta, autoload=True)
2325 self.tbl_override_type = Table('override_type', self.db_meta, autoload=True)
2326 self.tbl_pending_content_associations = Table('pending_content_associations', self.db_meta, autoload=True)
2327 self.tbl_priority = Table('priority', self.db_meta, autoload=True)
2328 self.tbl_queue = Table('queue', self.db_meta, autoload=True)
2329 self.tbl_queue_build = Table('queue_build', self.db_meta, autoload=True)
2330 self.tbl_section = Table('section', self.db_meta, autoload=True)
2331 self.tbl_source = Table('source', self.db_meta, autoload=True)
2332 self.tbl_source_acl = Table('source_acl', self.db_meta, autoload=True)
2333 self.tbl_src_associations = Table('src_associations', self.db_meta, autoload=True)
2334 self.tbl_src_format = Table('src_format', self.db_meta, autoload=True)
2335 self.tbl_src_uploaders = Table('src_uploaders', self.db_meta, autoload=True)
2336 self.tbl_suite = Table('suite', self.db_meta, autoload=True)
2337 self.tbl_suite_architectures = Table('suite_architectures', self.db_meta, autoload=True)
2338 self.tbl_suite_src_formats = Table('suite_src_formats', self.db_meta, autoload=True)
2339 self.tbl_uid = Table('uid', self.db_meta, autoload=True)
2340 self.tbl_upload_blocks = Table('upload_blocks', self.db_meta, autoload=True)
2342 def __setupmappers(self):
2343 mapper(Architecture, self.tbl_architecture,
2344 properties = dict(arch_id = self.tbl_architecture.c.id))
2346 mapper(Archive, self.tbl_archive,
2347 properties = dict(archive_id = self.tbl_archive.c.id,
2348 archive_name = self.tbl_archive.c.name))
2350 mapper(BinAssociation, self.tbl_bin_associations,
2351 properties = dict(ba_id = self.tbl_bin_associations.c.id,
2352 suite_id = self.tbl_bin_associations.c.suite,
2353 suite = relation(Suite),
2354 binary_id = self.tbl_bin_associations.c.bin,
2355 binary = relation(DBBinary)))
2357 mapper(DBBinary, self.tbl_binaries,
2358 properties = dict(binary_id = self.tbl_binaries.c.id,
2359 package = self.tbl_binaries.c.package,
2360 version = self.tbl_binaries.c.version,
2361 maintainer_id = self.tbl_binaries.c.maintainer,
2362 maintainer = relation(Maintainer),
2363 source_id = self.tbl_binaries.c.source,
2364 source = relation(DBSource),
2365 arch_id = self.tbl_binaries.c.architecture,
2366 architecture = relation(Architecture),
2367 poolfile_id = self.tbl_binaries.c.file,
2368 poolfile = relation(PoolFile),
2369 binarytype = self.tbl_binaries.c.type,
2370 fingerprint_id = self.tbl_binaries.c.sig_fpr,
2371 fingerprint = relation(Fingerprint),
2372 install_date = self.tbl_binaries.c.install_date,
2373 binassociations = relation(BinAssociation,
2374 primaryjoin=(self.tbl_binaries.c.id==self.tbl_bin_associations.c.bin))))
2376 mapper(BinaryACL, self.tbl_binary_acl,
2377 properties = dict(binary_acl_id = self.tbl_binary_acl.c.id))
2379 mapper(BinaryACLMap, self.tbl_binary_acl_map,
2380 properties = dict(binary_acl_map_id = self.tbl_binary_acl_map.c.id,
2381 fingerprint = relation(Fingerprint, backref="binary_acl_map"),
2382 architecture = relation(Architecture)))
2384 mapper(Component, self.tbl_component,
2385 properties = dict(component_id = self.tbl_component.c.id,
2386 component_name = self.tbl_component.c.name))
2388 mapper(DBConfig, self.tbl_config,
2389 properties = dict(config_id = self.tbl_config.c.id))
2391 mapper(ContentAssociation, self.tbl_content_associations,
2392 properties = dict(ca_id = self.tbl_content_associations.c.id,
2393 filename_id = self.tbl_content_associations.c.filename,
2394 filename = relation(ContentFilename),
2395 filepath_id = self.tbl_content_associations.c.filepath,
2396 filepath = relation(ContentFilepath),
2397 binary_id = self.tbl_content_associations.c.binary_pkg,
2398 binary = relation(DBBinary)))
2401 mapper(ContentFilename, self.tbl_content_file_names,
2402 properties = dict(cafilename_id = self.tbl_content_file_names.c.id,
2403 filename = self.tbl_content_file_names.c.file))
2405 mapper(ContentFilepath, self.tbl_content_file_paths,
2406 properties = dict(cafilepath_id = self.tbl_content_file_paths.c.id,
2407 filepath = self.tbl_content_file_paths.c.path))
2409 mapper(DSCFile, self.tbl_dsc_files,
2410 properties = dict(dscfile_id = self.tbl_dsc_files.c.id,
2411 source_id = self.tbl_dsc_files.c.source,
2412 source = relation(DBSource),
2413 poolfile_id = self.tbl_dsc_files.c.file,
2414 poolfile = relation(PoolFile)))
2416 mapper(PoolFile, self.tbl_files,
2417 properties = dict(file_id = self.tbl_files.c.id,
2418 filesize = self.tbl_files.c.size,
2419 location_id = self.tbl_files.c.location,
2420 location = relation(Location)))
2422 mapper(Fingerprint, self.tbl_fingerprint,
2423 properties = dict(fingerprint_id = self.tbl_fingerprint.c.id,
2424 uid_id = self.tbl_fingerprint.c.uid,
2425 uid = relation(Uid),
2426 keyring_id = self.tbl_fingerprint.c.keyring,
2427 keyring = relation(Keyring),
2428 source_acl = relation(SourceACL),
2429 binary_acl = relation(BinaryACL)))
2431 mapper(Keyring, self.tbl_keyrings,
2432 properties = dict(keyring_name = self.tbl_keyrings.c.name,
2433 keyring_id = self.tbl_keyrings.c.id))
2435 mapper(KeyringACLMap, self.tbl_keyring_acl_map,
2436 properties = dict(keyring_acl_map_id = self.tbl_keyring_acl_map.c.id,
2437 keyring = relation(Keyring, backref="keyring_acl_map"),
2438 architecture = relation(Architecture)))
2440 mapper(Location, self.tbl_location,
2441 properties = dict(location_id = self.tbl_location.c.id,
2442 component_id = self.tbl_location.c.component,
2443 component = relation(Component),
2444 archive_id = self.tbl_location.c.archive,
2445 archive = relation(Archive),
2446 archive_type = self.tbl_location.c.type))
2448 mapper(Maintainer, self.tbl_maintainer,
2449 properties = dict(maintainer_id = self.tbl_maintainer.c.id))
2451 mapper(NewComment, self.tbl_new_comments,
2452 properties = dict(comment_id = self.tbl_new_comments.c.id))
2454 mapper(Override, self.tbl_override,
2455 properties = dict(suite_id = self.tbl_override.c.suite,
2456 suite = relation(Suite),
2457 component_id = self.tbl_override.c.component,
2458 component = relation(Component),
2459 priority_id = self.tbl_override.c.priority,
2460 priority = relation(Priority),
2461 section_id = self.tbl_override.c.section,
2462 section = relation(Section),
2463 overridetype_id = self.tbl_override.c.type,
2464 overridetype = relation(OverrideType)))
2466 mapper(OverrideType, self.tbl_override_type,
2467 properties = dict(overridetype = self.tbl_override_type.c.type,
2468 overridetype_id = self.tbl_override_type.c.id))
2470 mapper(PendingContentAssociation, self.tbl_pending_content_associations,
2471 properties = dict(pca_id = self.tbl_pending_content_associations.c.id,
2472 filepath_id = self.tbl_pending_content_associations.c.filepath,
2473 filepath = relation(ContentFilepath),
2474 filename_id = self.tbl_pending_content_associations.c.filename,
2475 filename = relation(ContentFilename)))
2477 mapper(Priority, self.tbl_priority,
2478 properties = dict(priority_id = self.tbl_priority.c.id))
2480 mapper(Queue, self.tbl_queue,
2481 properties = dict(queue_id = self.tbl_queue.c.id))
2483 mapper(QueueBuild, self.tbl_queue_build,
2484 properties = dict(suite_id = self.tbl_queue_build.c.suite,
2485 queue_id = self.tbl_queue_build.c.queue,
2486 queue = relation(Queue, backref='queuebuild')))
2488 mapper(Section, self.tbl_section,
2489 properties = dict(section_id = self.tbl_section.c.id))
2491 mapper(DBSource, self.tbl_source,
2492 properties = dict(source_id = self.tbl_source.c.id,
2493 version = self.tbl_source.c.version,
2494 maintainer_id = self.tbl_source.c.maintainer,
2495 maintainer = relation(Maintainer,
2496 primaryjoin=(self.tbl_source.c.maintainer==self.tbl_maintainer.c.id)),
2497 poolfile_id = self.tbl_source.c.file,
2498 poolfile = relation(PoolFile),
2499 fingerprint_id = self.tbl_source.c.sig_fpr,
2500 fingerprint = relation(Fingerprint),
2501 changedby_id = self.tbl_source.c.changedby,
2502 changedby = relation(Maintainer,
2503 primaryjoin=(self.tbl_source.c.changedby==self.tbl_maintainer.c.id)),
2504 srcfiles = relation(DSCFile,
2505 primaryjoin=(self.tbl_source.c.id==self.tbl_dsc_files.c.source)),
2506 srcassociations = relation(SrcAssociation,
2507 primaryjoin=(self.tbl_source.c.id==self.tbl_src_associations.c.source)),
2508 srcuploaders = relation(SrcUploader)))
2510 mapper(SourceACL, self.tbl_source_acl,
2511 properties = dict(source_acl_id = self.tbl_source_acl.c.id))
2513 mapper(SrcAssociation, self.tbl_src_associations,
2514 properties = dict(sa_id = self.tbl_src_associations.c.id,
2515 suite_id = self.tbl_src_associations.c.suite,
2516 suite = relation(Suite),
2517 source_id = self.tbl_src_associations.c.source,
2518 source = relation(DBSource)))
2520 mapper(SrcFormat, self.tbl_src_format,
2521 properties = dict(src_format_id = self.tbl_src_format.c.id,
2522 format_name = self.tbl_src_format.c.format_name))
2524 mapper(SrcUploader, self.tbl_src_uploaders,
2525 properties = dict(uploader_id = self.tbl_src_uploaders.c.id,
2526 source_id = self.tbl_src_uploaders.c.source,
2527 source = relation(DBSource,
2528 primaryjoin=(self.tbl_src_uploaders.c.source==self.tbl_source.c.id)),
2529 maintainer_id = self.tbl_src_uploaders.c.maintainer,
2530 maintainer = relation(Maintainer,
2531 primaryjoin=(self.tbl_src_uploaders.c.maintainer==self.tbl_maintainer.c.id))))
2533 mapper(Suite, self.tbl_suite,
2534 properties = dict(suite_id = self.tbl_suite.c.id))
2536 mapper(SuiteArchitecture, self.tbl_suite_architectures,
2537 properties = dict(suite_id = self.tbl_suite_architectures.c.suite,
2538 suite = relation(Suite, backref='suitearchitectures'),
2539 arch_id = self.tbl_suite_architectures.c.architecture,
2540 architecture = relation(Architecture)))
2542 mapper(SuiteSrcFormat, self.tbl_suite_src_formats,
2543 properties = dict(suite_id = self.tbl_suite_src_formats.c.suite,
2544 suite = relation(Suite, backref='suitesrcformats'),
2545 src_format_id = self.tbl_suite_src_formats.c.src_format,
2546 src_format = relation(SrcFormat)))
2548 mapper(Uid, self.tbl_uid,
2549 properties = dict(uid_id = self.tbl_uid.c.id,
2550 fingerprint = relation(Fingerprint)))
2552 mapper(UploadBlock, self.tbl_upload_blocks,
2553 properties = dict(upload_block_id = self.tbl_upload_blocks.c.id,
2554 fingerprint = relation(Fingerprint, backref="uploadblocks"),
2555 uid = relation(Uid, backref="uploadblocks")))
2557 ## Connection functions
2558 def __createconn(self):
2559 from config import Config
2563 connstr = "postgres://%s" % cnf["DB::Host"]
2564 if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
2565 connstr += ":%s" % cnf["DB::Port"]
2566 connstr += "/%s" % cnf["DB::Name"]
2569 connstr = "postgres:///%s" % cnf["DB::Name"]
2570 if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
2571 connstr += "?port=%s" % cnf["DB::Port"]
2573 self.db_pg = create_engine(connstr, echo=self.debug)
2574 self.db_meta = MetaData()
2575 self.db_meta.bind = self.db_pg
2576 self.db_smaker = sessionmaker(bind=self.db_pg,
2580 self.__setuptables()
2581 self.__setupmappers()
2584 return self.db_smaker()
2586 __all__.append('DBConn')