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, 2010 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 ################################################################################
47 import simplejson as json
49 from datetime import datetime, timedelta
50 from errno import ENOENT
51 from tempfile import mkstemp, mkdtemp
53 from inspect import getargspec
56 from sqlalchemy import create_engine, Table, MetaData, Column, Integer
57 from sqlalchemy.orm import sessionmaker, mapper, relation, object_session, \
58 backref, MapperExtension, EXT_CONTINUE
59 from sqlalchemy import types as sqltypes
61 # Don't remove this, we re-export the exceptions to scripts which import us
62 from sqlalchemy.exc import *
63 from sqlalchemy.orm.exc import NoResultFound
65 # Only import Config until Queue stuff is changed to store its config
67 from config import Config
68 from textutils import fix_maintainer
69 from dak_exceptions import DBUpdateError, NoSourceFieldError
71 # suppress some deprecation warnings in squeeze related to sqlalchemy
73 warnings.filterwarnings('ignore', \
74 "The SQLAlchemy PostgreSQL dialect has been renamed from 'postgres' to 'postgresql'.*", \
76 # TODO: sqlalchemy needs some extra configuration to correctly reflect
77 # the ind_deb_contents_* indexes - we ignore the warnings at the moment
78 warnings.filterwarnings("ignore", 'Predicate of partial index', SAWarning)
81 ################################################################################
83 # Patch in support for the debversion field type so that it works during
87 # that is for sqlalchemy 0.6
88 UserDefinedType = sqltypes.UserDefinedType
90 # this one for sqlalchemy 0.5
91 UserDefinedType = sqltypes.TypeEngine
93 class DebVersion(UserDefinedType):
94 def get_col_spec(self):
97 def bind_processor(self, dialect):
100 # ' = None' is needed for sqlalchemy 0.5:
101 def result_processor(self, dialect, coltype = None):
104 sa_major_version = sqlalchemy.__version__[0:3]
105 if sa_major_version in ["0.5", "0.6"]:
106 from sqlalchemy.databases import postgres
107 postgres.ischema_names['debversion'] = DebVersion
109 raise Exception("dak only ported to SQLA versions 0.5 and 0.6. See daklib/dbconn.py")
111 ################################################################################
113 __all__ = ['IntegrityError', 'SQLAlchemyError', 'DebVersion']
115 ################################################################################
117 def session_wrapper(fn):
119 Wrapper around common ".., session=None):" handling. If the wrapped
120 function is called without passing 'session', we create a local one
121 and destroy it when the function ends.
123 Also attaches a commit_or_flush method to the session; if we created a
124 local session, this is a synonym for session.commit(), otherwise it is a
125 synonym for session.flush().
128 def wrapped(*args, **kwargs):
129 private_transaction = False
131 # Find the session object
132 session = kwargs.get('session')
135 if len(args) <= len(getargspec(fn)[0]) - 1:
136 # No session specified as last argument or in kwargs
137 private_transaction = True
138 session = kwargs['session'] = DBConn().session()
140 # Session is last argument in args
144 session = args[-1] = DBConn().session()
145 private_transaction = True
147 if private_transaction:
148 session.commit_or_flush = session.commit
150 session.commit_or_flush = session.flush
153 return fn(*args, **kwargs)
155 if private_transaction:
156 # We created a session; close it.
159 wrapped.__doc__ = fn.__doc__
160 wrapped.func_name = fn.func_name
164 __all__.append('session_wrapper')
166 ################################################################################
168 class ORMObject(object):
170 ORMObject is a base class for all ORM classes mapped by SQLalchemy. All
171 derived classes must implement the properties() method.
174 def properties(self):
176 This method should be implemented by all derived classes and returns a
177 list of the important properties. The properties 'created' and
178 'modified' will be added automatically. A suffix '_count' should be
179 added to properties that are lists or query objects. The most important
180 property name should be returned as the first element in the list
181 because it is used by repr().
187 Returns a JSON representation of the object based on the properties
188 returned from the properties() method.
191 # add created and modified
192 all_properties = self.properties() + ['created', 'modified']
193 for property in all_properties:
194 # check for list or query
195 if property[-6:] == '_count':
196 real_property = property[:-6]
197 if not hasattr(self, real_property):
199 value = getattr(self, real_property)
200 if hasattr(value, '__len__'):
203 elif hasattr(value, 'count'):
205 value = value.count()
207 raise KeyError('Do not understand property %s.' % property)
209 if not hasattr(self, property):
212 value = getattr(self, property)
216 elif isinstance(value, ORMObject):
217 # use repr() for ORMObject types
220 # we want a string for all other types because json cannot
223 data[property] = value
224 return json.dumps(data)
228 Returns the name of the class.
230 return type(self).__name__
234 Returns a short string representation of the object using the first
235 element from the properties() method.
237 primary_property = self.properties()[0]
238 value = getattr(self, primary_property)
239 return '<%s %s>' % (self.classname(), str(value))
243 Returns a human readable form of the object using the properties()
246 return '<%s %s>' % (self.classname(), self.json())
248 def not_null_constraints(self):
250 Returns a list of properties that must be not NULL. Derived classes
251 should override this method if needed.
255 validation_message = \
256 "Validation failed because property '%s' must not be empty in object\n%s"
260 This function validates the not NULL constraints as returned by
261 not_null_constraints(). It raises the DBUpdateError exception if
264 for property in self.not_null_constraints():
265 # TODO: It is a bit awkward that the mapper configuration allow
266 # directly setting the numeric _id columns. We should get rid of it
268 if hasattr(self, property + '_id') and \
269 getattr(self, property + '_id') is not None:
271 if not hasattr(self, property) or getattr(self, property) is None:
272 raise DBUpdateError(self.validation_message % \
273 (property, str(self)))
277 def get(cls, primary_key, session = None):
279 This is a support function that allows getting an object by its primary
282 Architecture.get(3[, session])
284 instead of the more verbose
286 session.query(Architecture).get(3)
288 return session.query(cls).get(primary_key)
290 __all__.append('ORMObject')
292 ################################################################################
294 class Validator(MapperExtension):
296 This class calls the validate() method for each instance for the
297 'before_update' and 'before_insert' events. A global object validator is
298 used for configuring the individual mappers.
301 def before_update(self, mapper, connection, instance):
305 def before_insert(self, mapper, connection, instance):
309 validator = Validator()
311 ################################################################################
313 class Architecture(ORMObject):
314 def __init__(self, arch_string = None, description = None):
315 self.arch_string = arch_string
316 self.description = description
318 def __eq__(self, val):
319 if isinstance(val, str):
320 return (self.arch_string== val)
321 # This signals to use the normal comparison operator
322 return NotImplemented
324 def __ne__(self, val):
325 if isinstance(val, str):
326 return (self.arch_string != val)
327 # This signals to use the normal comparison operator
328 return NotImplemented
330 def properties(self):
331 return ['arch_string', 'arch_id', 'suites_count']
333 def not_null_constraints(self):
334 return ['arch_string']
336 __all__.append('Architecture')
339 def get_architecture(architecture, session=None):
341 Returns database id for given C{architecture}.
343 @type architecture: string
344 @param architecture: The name of the architecture
346 @type session: Session
347 @param session: Optional SQLA session object (a temporary one will be
348 generated if not supplied)
351 @return: Architecture object for the given arch (None if not present)
354 q = session.query(Architecture).filter_by(arch_string=architecture)
358 except NoResultFound:
361 __all__.append('get_architecture')
363 # TODO: should be removed because the implementation is too trivial
365 def get_architecture_suites(architecture, session=None):
367 Returns list of Suite objects for given C{architecture} name
369 @type architecture: str
370 @param architecture: Architecture name to search for
372 @type session: Session
373 @param session: Optional SQL session object (a temporary one will be
374 generated if not supplied)
377 @return: list of Suite objects for the given name (may be empty)
380 return get_architecture(architecture, session).suites
382 __all__.append('get_architecture_suites')
384 ################################################################################
386 class Archive(object):
387 def __init__(self, *args, **kwargs):
391 return '<Archive %s>' % self.archive_name
393 __all__.append('Archive')
396 def get_archive(archive, session=None):
398 returns database id for given C{archive}.
400 @type archive: string
401 @param archive: the name of the arhive
403 @type session: Session
404 @param session: Optional SQLA session object (a temporary one will be
405 generated if not supplied)
408 @return: Archive object for the given name (None if not present)
411 archive = archive.lower()
413 q = session.query(Archive).filter_by(archive_name=archive)
417 except NoResultFound:
420 __all__.append('get_archive')
422 ################################################################################
424 class BinContents(object):
425 def __init__(self, *args, **kwargs):
429 return '<BinContents (%s, %s)>' % (self.binary, self.filename)
431 __all__.append('BinContents')
433 ################################################################################
435 class DBBinary(ORMObject):
436 def __init__(self, package = None, source = None, version = None, \
437 maintainer = None, architecture = None, poolfile = None, \
439 self.package = package
441 self.version = version
442 self.maintainer = maintainer
443 self.architecture = architecture
444 self.poolfile = poolfile
445 self.binarytype = binarytype
447 def properties(self):
448 return ['package', 'version', 'maintainer', 'source', 'architecture', \
449 'poolfile', 'binarytype', 'fingerprint', 'install_date', \
450 'suites_count', 'binary_id']
452 def not_null_constraints(self):
453 return ['package', 'version', 'maintainer', 'source', 'poolfile', \
456 __all__.append('DBBinary')
459 def get_suites_binary_in(package, session=None):
461 Returns list of Suite objects which given C{package} name is in
464 @param package: DBBinary package name to search for
467 @return: list of Suite objects for the given package
470 return session.query(Suite).filter(Suite.binaries.any(DBBinary.package == package)).all()
472 __all__.append('get_suites_binary_in')
475 def get_component_by_package_suite(package, suite_list, session=None):
476 ### For dak examine-package
478 return session.query(Component.component_name). \
479 join(Component.location, Location.files, PoolFile.binary). \
480 filter_by(package = package). \
481 join(DBBinary.suites).filter(Suite.suite_name.in_(suite_list)). \
484 __all__.append('get_component_by_package_suite')
487 def get_binary_components(package, suitename, arch, session=None):
488 # Check for packages that have moved from one component to another
489 query = """SELECT c.name FROM binaries b, bin_associations ba, suite s, location l, component c, architecture a, files f
490 WHERE b.package=:package AND s.suite_name=:suitename
491 AND (a.arch_string = :arch OR a.arch_string = 'all')
492 AND ba.bin = b.id AND ba.suite = s.id AND b.architecture = a.id
493 AND f.location = l.id
494 AND l.component = c.id
497 vals = {'package': package, 'suitename': suitename, 'arch': arch}
499 return session.execute(query, vals)
501 __all__.append('get_binary_components')
503 ################################################################################
505 class BinaryACL(object):
506 def __init__(self, *args, **kwargs):
510 return '<BinaryACL %s>' % self.binary_acl_id
512 __all__.append('BinaryACL')
514 ################################################################################
516 class BinaryACLMap(object):
517 def __init__(self, *args, **kwargs):
521 return '<BinaryACLMap %s>' % self.binary_acl_map_id
523 __all__.append('BinaryACLMap')
525 ################################################################################
530 ArchiveDir "%(archivepath)s";
531 OverrideDir "%(overridedir)s";
532 CacheDir "%(cachedir)s";
537 Packages::Compress ". bzip2 gzip";
538 Sources::Compress ". bzip2 gzip";
543 bindirectory "incoming"
548 BinOverride "override.sid.all3";
549 BinCacheDB "packages-accepted.db";
551 FileList "%(filelist)s";
554 Packages::Extensions ".deb .udeb";
557 bindirectory "incoming/"
560 BinOverride "override.sid.all3";
561 SrcOverride "override.sid.all3.src";
562 FileList "%(filelist)s";
566 class BuildQueue(object):
567 def __init__(self, *args, **kwargs):
571 return '<BuildQueue %s>' % self.queue_name
573 def write_metadata(self, starttime, force=False):
574 # Do we write out metafiles?
575 if not (force or self.generate_metadata):
578 session = DBConn().session().object_session(self)
580 fl_fd = fl_name = ac_fd = ac_name = None
582 arches = " ".join([ a.arch_string for a in session.query(Architecture).all() if a.arch_string != 'source' ])
583 startdir = os.getcwd()
586 # Grab files we want to include
587 newer = session.query(BuildQueueFile).filter_by(build_queue_id = self.queue_id).filter(BuildQueueFile.lastused + timedelta(seconds=self.stay_of_execution) > starttime).all()
588 # Write file list with newer files
589 (fl_fd, fl_name) = mkstemp()
591 os.write(fl_fd, '%s\n' % n.fullpath)
596 # Write minimal apt.conf
597 # TODO: Remove hardcoding from template
598 (ac_fd, ac_name) = mkstemp()
599 os.write(ac_fd, MINIMAL_APT_CONF % {'archivepath': self.path,
601 'cachedir': cnf["Dir::Cache"],
602 'overridedir': cnf["Dir::Override"],
606 # Run apt-ftparchive generate
607 os.chdir(os.path.dirname(ac_name))
608 os.system('apt-ftparchive -qq -o APT::FTPArchive::Contents=off generate %s' % os.path.basename(ac_name))
610 # Run apt-ftparchive release
611 # TODO: Eww - fix this
612 bname = os.path.basename(self.path)
616 # We have to remove the Release file otherwise it'll be included in the
619 os.unlink(os.path.join(bname, 'Release'))
623 os.system("""apt-ftparchive -qq -o APT::FTPArchive::Release::Origin="%s" -o APT::FTPArchive::Release::Label="%s" -o APT::FTPArchive::Release::Description="%s" -o APT::FTPArchive::Release::Architectures="%s" release %s > Release""" % (self.origin, self.label, self.releasedescription, arches, bname))
625 # Crude hack with open and append, but this whole section is and should be redone.
626 if self.notautomatic:
627 release=open("Release", "a")
628 release.write("NotAutomatic: yes")
633 keyring = "--secret-keyring \"%s\"" % cnf["Dinstall::SigningKeyring"]
634 if cnf.has_key("Dinstall::SigningPubKeyring"):
635 keyring += " --keyring \"%s\"" % cnf["Dinstall::SigningPubKeyring"]
637 os.system("gpg %s --no-options --batch --no-tty --armour --default-key %s --detach-sign -o Release.gpg Release""" % (keyring, self.signingkey))
639 # Move the files if we got this far
640 os.rename('Release', os.path.join(bname, 'Release'))
642 os.rename('Release.gpg', os.path.join(bname, 'Release.gpg'))
644 # Clean up any left behind files
671 def clean_and_update(self, starttime, Logger, dryrun=False):
672 """WARNING: This routine commits for you"""
673 session = DBConn().session().object_session(self)
675 if self.generate_metadata and not dryrun:
676 self.write_metadata(starttime)
678 # Grab files older than our execution time
679 older = session.query(BuildQueueFile).filter_by(build_queue_id = self.queue_id).filter(BuildQueueFile.lastused + timedelta(seconds=self.stay_of_execution) <= starttime).all()
685 Logger.log(["I: Would have removed %s from the queue" % o.fullpath])
687 Logger.log(["I: Removing %s from the queue" % o.fullpath])
688 os.unlink(o.fullpath)
691 # If it wasn't there, don't worry
692 if e.errno == ENOENT:
695 # TODO: Replace with proper logging call
696 Logger.log(["E: Could not remove %s" % o.fullpath])
703 for f in os.listdir(self.path):
704 if f.startswith('Packages') or f.startswith('Source') or f.startswith('Release') or f.startswith('advisory'):
708 r = session.query(BuildQueueFile).filter_by(build_queue_id = self.queue_id).filter_by(filename = f).one()
709 except NoResultFound:
710 fp = os.path.join(self.path, f)
712 Logger.log(["I: Would remove unused link %s" % fp])
714 Logger.log(["I: Removing unused link %s" % fp])
718 Logger.log(["E: Failed to unlink unreferenced file %s" % r.fullpath])
720 def add_file_from_pool(self, poolfile):
721 """Copies a file into the pool. Assumes that the PoolFile object is
722 attached to the same SQLAlchemy session as the Queue object is.
724 The caller is responsible for committing after calling this function."""
725 poolfile_basename = poolfile.filename[poolfile.filename.rindex(os.sep)+1:]
727 # Check if we have a file of this name or this ID already
728 for f in self.queuefiles:
729 if f.fileid is not None and f.fileid == poolfile.file_id or \
730 f.poolfile.filename == poolfile_basename:
731 # In this case, update the BuildQueueFile entry so we
732 # don't remove it too early
733 f.lastused = datetime.now()
734 DBConn().session().object_session(poolfile).add(f)
737 # Prepare BuildQueueFile object
738 qf = BuildQueueFile()
739 qf.build_queue_id = self.queue_id
740 qf.lastused = datetime.now()
741 qf.filename = poolfile_basename
743 targetpath = poolfile.fullpath
744 queuepath = os.path.join(self.path, poolfile_basename)
748 # We need to copy instead of symlink
750 utils.copy(targetpath, queuepath)
751 # NULL in the fileid field implies a copy
754 os.symlink(targetpath, queuepath)
755 qf.fileid = poolfile.file_id
759 # Get the same session as the PoolFile is using and add the qf to it
760 DBConn().session().object_session(poolfile).add(qf)
765 __all__.append('BuildQueue')
768 def get_build_queue(queuename, session=None):
770 Returns BuildQueue object for given C{queue name}, creating it if it does not
773 @type queuename: string
774 @param queuename: The name of the queue
776 @type session: Session
777 @param session: Optional SQLA session object (a temporary one will be
778 generated if not supplied)
781 @return: BuildQueue object for the given queue
784 q = session.query(BuildQueue).filter_by(queue_name=queuename)
788 except NoResultFound:
791 __all__.append('get_build_queue')
793 ################################################################################
795 class BuildQueueFile(object):
796 def __init__(self, *args, **kwargs):
800 return '<BuildQueueFile %s (%s)>' % (self.filename, self.build_queue_id)
804 return os.path.join(self.buildqueue.path, self.filename)
807 __all__.append('BuildQueueFile')
809 ################################################################################
811 class ChangePendingBinary(object):
812 def __init__(self, *args, **kwargs):
816 return '<ChangePendingBinary %s>' % self.change_pending_binary_id
818 __all__.append('ChangePendingBinary')
820 ################################################################################
822 class ChangePendingFile(object):
823 def __init__(self, *args, **kwargs):
827 return '<ChangePendingFile %s>' % self.change_pending_file_id
829 __all__.append('ChangePendingFile')
831 ################################################################################
833 class ChangePendingSource(object):
834 def __init__(self, *args, **kwargs):
838 return '<ChangePendingSource %s>' % self.change_pending_source_id
840 __all__.append('ChangePendingSource')
842 ################################################################################
844 class Component(ORMObject):
845 def __init__(self, component_name = None):
846 self.component_name = component_name
848 def __eq__(self, val):
849 if isinstance(val, str):
850 return (self.component_name == val)
851 # This signals to use the normal comparison operator
852 return NotImplemented
854 def __ne__(self, val):
855 if isinstance(val, str):
856 return (self.component_name != val)
857 # This signals to use the normal comparison operator
858 return NotImplemented
860 def properties(self):
861 return ['component_name', 'component_id', 'description', 'location', \
864 def not_null_constraints(self):
865 return ['component_name']
868 __all__.append('Component')
871 def get_component(component, session=None):
873 Returns database id for given C{component}.
875 @type component: string
876 @param component: The name of the override type
879 @return: the database id for the given component
882 component = component.lower()
884 q = session.query(Component).filter_by(component_name=component)
888 except NoResultFound:
891 __all__.append('get_component')
893 ################################################################################
895 class DBConfig(object):
896 def __init__(self, *args, **kwargs):
900 return '<DBConfig %s>' % self.name
902 __all__.append('DBConfig')
904 ################################################################################
907 def get_or_set_contents_file_id(filename, session=None):
909 Returns database id for given filename.
911 If no matching file is found, a row is inserted.
913 @type filename: string
914 @param filename: The filename
915 @type session: SQLAlchemy
916 @param session: Optional SQL session object (a temporary one will be
917 generated if not supplied). If not passed, a commit will be performed at
918 the end of the function, otherwise the caller is responsible for commiting.
921 @return: the database id for the given component
924 q = session.query(ContentFilename).filter_by(filename=filename)
927 ret = q.one().cafilename_id
928 except NoResultFound:
929 cf = ContentFilename()
930 cf.filename = filename
932 session.commit_or_flush()
933 ret = cf.cafilename_id
937 __all__.append('get_or_set_contents_file_id')
940 def get_contents(suite, overridetype, section=None, session=None):
942 Returns contents for a suite / overridetype combination, limiting
943 to a section if not None.
946 @param suite: Suite object
948 @type overridetype: OverrideType
949 @param overridetype: OverrideType object
951 @type section: Section
952 @param section: Optional section object to limit results to
954 @type session: SQLAlchemy
955 @param session: Optional SQL session object (a temporary one will be
956 generated if not supplied)
959 @return: ResultsProxy object set up to return tuples of (filename, section,
963 # find me all of the contents for a given suite
964 contents_q = """SELECT (p.path||'/'||n.file) AS fn,
968 FROM content_associations c join content_file_paths p ON (c.filepath=p.id)
969 JOIN content_file_names n ON (c.filename=n.id)
970 JOIN binaries b ON (b.id=c.binary_pkg)
971 JOIN override o ON (o.package=b.package)
972 JOIN section s ON (s.id=o.section)
973 WHERE o.suite = :suiteid AND o.type = :overridetypeid
974 AND b.type=:overridetypename"""
976 vals = {'suiteid': suite.suite_id,
977 'overridetypeid': overridetype.overridetype_id,
978 'overridetypename': overridetype.overridetype}
980 if section is not None:
981 contents_q += " AND s.id = :sectionid"
982 vals['sectionid'] = section.section_id
984 contents_q += " ORDER BY fn"
986 return session.execute(contents_q, vals)
988 __all__.append('get_contents')
990 ################################################################################
992 class ContentFilepath(object):
993 def __init__(self, *args, **kwargs):
997 return '<ContentFilepath %s>' % self.filepath
999 __all__.append('ContentFilepath')
1002 def get_or_set_contents_path_id(filepath, session=None):
1004 Returns database id for given path.
1006 If no matching file is found, a row is inserted.
1008 @type filepath: string
1009 @param filepath: The filepath
1011 @type session: SQLAlchemy
1012 @param session: Optional SQL session object (a temporary one will be
1013 generated if not supplied). If not passed, a commit will be performed at
1014 the end of the function, otherwise the caller is responsible for commiting.
1017 @return: the database id for the given path
1020 q = session.query(ContentFilepath).filter_by(filepath=filepath)
1023 ret = q.one().cafilepath_id
1024 except NoResultFound:
1025 cf = ContentFilepath()
1026 cf.filepath = filepath
1028 session.commit_or_flush()
1029 ret = cf.cafilepath_id
1033 __all__.append('get_or_set_contents_path_id')
1035 ################################################################################
1037 class ContentAssociation(object):
1038 def __init__(self, *args, **kwargs):
1042 return '<ContentAssociation %s>' % self.ca_id
1044 __all__.append('ContentAssociation')
1046 def insert_content_paths(binary_id, fullpaths, session=None):
1048 Make sure given path is associated with given binary id
1050 @type binary_id: int
1051 @param binary_id: the id of the binary
1052 @type fullpaths: list
1053 @param fullpaths: the list of paths of the file being associated with the binary
1054 @type session: SQLAlchemy session
1055 @param session: Optional SQLAlchemy session. If this is passed, the caller
1056 is responsible for ensuring a transaction has begun and committing the
1057 results or rolling back based on the result code. If not passed, a commit
1058 will be performed at the end of the function, otherwise the caller is
1059 responsible for commiting.
1061 @return: True upon success
1064 privatetrans = False
1066 session = DBConn().session()
1071 def generate_path_dicts():
1072 for fullpath in fullpaths:
1073 if fullpath.startswith( './' ):
1074 fullpath = fullpath[2:]
1076 yield {'filename':fullpath, 'id': binary_id }
1078 for d in generate_path_dicts():
1079 session.execute( "INSERT INTO bin_contents ( file, binary_id ) VALUES ( :filename, :id )",
1088 traceback.print_exc()
1090 # Only rollback if we set up the session ourself
1097 __all__.append('insert_content_paths')
1099 ################################################################################
1101 class DSCFile(object):
1102 def __init__(self, *args, **kwargs):
1106 return '<DSCFile %s>' % self.dscfile_id
1108 __all__.append('DSCFile')
1111 def get_dscfiles(dscfile_id=None, source_id=None, poolfile_id=None, session=None):
1113 Returns a list of DSCFiles which may be empty
1115 @type dscfile_id: int (optional)
1116 @param dscfile_id: the dscfile_id of the DSCFiles to find
1118 @type source_id: int (optional)
1119 @param source_id: the source id related to the DSCFiles to find
1121 @type poolfile_id: int (optional)
1122 @param poolfile_id: the poolfile id related to the DSCFiles to find
1125 @return: Possibly empty list of DSCFiles
1128 q = session.query(DSCFile)
1130 if dscfile_id is not None:
1131 q = q.filter_by(dscfile_id=dscfile_id)
1133 if source_id is not None:
1134 q = q.filter_by(source_id=source_id)
1136 if poolfile_id is not None:
1137 q = q.filter_by(poolfile_id=poolfile_id)
1141 __all__.append('get_dscfiles')
1143 ################################################################################
1145 class PoolFile(ORMObject):
1146 def __init__(self, filename = None, location = None, filesize = -1, \
1148 self.filename = filename
1149 self.location = location
1150 self.filesize = filesize
1151 self.md5sum = md5sum
1155 return os.path.join(self.location.path, self.filename)
1157 def is_valid(self, filesize = -1, md5sum = None):\
1158 return self.filesize == filesize and self.md5sum == md5sum
1160 def properties(self):
1161 return ['filename', 'file_id', 'filesize', 'md5sum', 'sha1sum', \
1162 'sha256sum', 'location', 'source', 'binary', 'last_used']
1164 def not_null_constraints(self):
1165 return ['filename', 'md5sum', 'location']
1167 __all__.append('PoolFile')
1170 def check_poolfile(filename, filesize, md5sum, location_id, session=None):
1173 (ValidFileFound [boolean], PoolFile object or None)
1175 @type filename: string
1176 @param filename: the filename of the file to check against the DB
1179 @param filesize: the size of the file to check against the DB
1181 @type md5sum: string
1182 @param md5sum: the md5sum of the file to check against the DB
1184 @type location_id: int
1185 @param location_id: the id of the location to look in
1188 @return: Tuple of length 2.
1189 - If valid pool file found: (C{True}, C{PoolFile object})
1190 - If valid pool file not found:
1191 - (C{False}, C{None}) if no file found
1192 - (C{False}, C{PoolFile object}) if file found with size/md5sum mismatch
1195 poolfile = session.query(Location).get(location_id). \
1196 files.filter_by(filename=filename).first()
1198 if poolfile and poolfile.is_valid(filesize = filesize, md5sum = md5sum):
1201 return (valid, poolfile)
1203 __all__.append('check_poolfile')
1205 # TODO: the implementation can trivially be inlined at the place where the
1206 # function is called
1208 def get_poolfile_by_id(file_id, session=None):
1210 Returns a PoolFile objects or None for the given id
1213 @param file_id: the id of the file to look for
1215 @rtype: PoolFile or None
1216 @return: either the PoolFile object or None
1219 return session.query(PoolFile).get(file_id)
1221 __all__.append('get_poolfile_by_id')
1224 def get_poolfile_like_name(filename, session=None):
1226 Returns an array of PoolFile objects which are like the given name
1228 @type filename: string
1229 @param filename: the filename of the file to check against the DB
1232 @return: array of PoolFile objects
1235 # TODO: There must be a way of properly using bind parameters with %FOO%
1236 q = session.query(PoolFile).filter(PoolFile.filename.like('%%/%s' % filename))
1240 __all__.append('get_poolfile_like_name')
1243 def add_poolfile(filename, datadict, location_id, session=None):
1245 Add a new file to the pool
1247 @type filename: string
1248 @param filename: filename
1250 @type datadict: dict
1251 @param datadict: dict with needed data
1253 @type location_id: int
1254 @param location_id: database id of the location
1257 @return: the PoolFile object created
1259 poolfile = PoolFile()
1260 poolfile.filename = filename
1261 poolfile.filesize = datadict["size"]
1262 poolfile.md5sum = datadict["md5sum"]
1263 poolfile.sha1sum = datadict["sha1sum"]
1264 poolfile.sha256sum = datadict["sha256sum"]
1265 poolfile.location_id = location_id
1267 session.add(poolfile)
1268 # Flush to get a file id (NB: This is not a commit)
1273 __all__.append('add_poolfile')
1275 ################################################################################
1277 class Fingerprint(ORMObject):
1278 def __init__(self, fingerprint = None):
1279 self.fingerprint = fingerprint
1281 def properties(self):
1282 return ['fingerprint', 'fingerprint_id', 'keyring', 'uid', \
1285 def not_null_constraints(self):
1286 return ['fingerprint']
1288 __all__.append('Fingerprint')
1291 def get_fingerprint(fpr, session=None):
1293 Returns Fingerprint object for given fpr.
1296 @param fpr: The fpr to find / add
1298 @type session: SQLAlchemy
1299 @param session: Optional SQL session object (a temporary one will be
1300 generated if not supplied).
1303 @return: the Fingerprint object for the given fpr or None
1306 q = session.query(Fingerprint).filter_by(fingerprint=fpr)
1310 except NoResultFound:
1315 __all__.append('get_fingerprint')
1318 def get_or_set_fingerprint(fpr, session=None):
1320 Returns Fingerprint object for given fpr.
1322 If no matching fpr is found, a row is inserted.
1325 @param fpr: The fpr to find / add
1327 @type session: SQLAlchemy
1328 @param session: Optional SQL session object (a temporary one will be
1329 generated if not supplied). If not passed, a commit will be performed at
1330 the end of the function, otherwise the caller is responsible for commiting.
1331 A flush will be performed either way.
1334 @return: the Fingerprint object for the given fpr
1337 q = session.query(Fingerprint).filter_by(fingerprint=fpr)
1341 except NoResultFound:
1342 fingerprint = Fingerprint()
1343 fingerprint.fingerprint = fpr
1344 session.add(fingerprint)
1345 session.commit_or_flush()
1350 __all__.append('get_or_set_fingerprint')
1352 ################################################################################
1354 # Helper routine for Keyring class
1355 def get_ldap_name(entry):
1357 for k in ["cn", "mn", "sn"]:
1359 if ret and ret[0] != "" and ret[0] != "-":
1361 return " ".join(name)
1363 ################################################################################
1365 class Keyring(object):
1366 gpg_invocation = "gpg --no-default-keyring --keyring %s" +\
1367 " --with-colons --fingerprint --fingerprint"
1372 def __init__(self, *args, **kwargs):
1376 return '<Keyring %s>' % self.keyring_name
1378 def de_escape_gpg_str(self, txt):
1379 esclist = re.split(r'(\\x..)', txt)
1380 for x in range(1,len(esclist),2):
1381 esclist[x] = "%c" % (int(esclist[x][2:],16))
1382 return "".join(esclist)
1384 def parse_address(self, uid):
1385 """parses uid and returns a tuple of real name and email address"""
1387 (name, address) = email.Utils.parseaddr(uid)
1388 name = re.sub(r"\s*[(].*[)]", "", name)
1389 name = self.de_escape_gpg_str(name)
1392 return (name, address)
1394 def load_keys(self, keyring):
1395 if not self.keyring_id:
1396 raise Exception('Must be initialized with database information')
1398 k = os.popen(self.gpg_invocation % keyring, "r")
1402 for line in k.xreadlines():
1403 field = line.split(":")
1404 if field[0] == "pub":
1407 (name, addr) = self.parse_address(field[9])
1409 self.keys[key]["email"] = addr
1410 self.keys[key]["name"] = name
1411 self.keys[key]["fingerprints"] = []
1413 elif key and field[0] == "sub" and len(field) >= 12:
1414 signingkey = ("s" in field[11])
1415 elif key and field[0] == "uid":
1416 (name, addr) = self.parse_address(field[9])
1417 if "email" not in self.keys[key] and "@" in addr:
1418 self.keys[key]["email"] = addr
1419 self.keys[key]["name"] = name
1420 elif signingkey and field[0] == "fpr":
1421 self.keys[key]["fingerprints"].append(field[9])
1422 self.fpr_lookup[field[9]] = key
1424 def import_users_from_ldap(self, session):
1428 LDAPDn = cnf["Import-LDAP-Fingerprints::LDAPDn"]
1429 LDAPServer = cnf["Import-LDAP-Fingerprints::LDAPServer"]
1431 l = ldap.open(LDAPServer)
1432 l.simple_bind_s("","")
1433 Attrs = l.search_s(LDAPDn, ldap.SCOPE_ONELEVEL,
1434 "(&(keyfingerprint=*)(gidnumber=%s))" % (cnf["Import-Users-From-Passwd::ValidGID"]),
1435 ["uid", "keyfingerprint", "cn", "mn", "sn"])
1437 ldap_fin_uid_id = {}
1444 uid = entry["uid"][0]
1445 name = get_ldap_name(entry)
1446 fingerprints = entry["keyFingerPrint"]
1448 for f in fingerprints:
1449 key = self.fpr_lookup.get(f, None)
1450 if key not in self.keys:
1452 self.keys[key]["uid"] = uid
1456 keyid = get_or_set_uid(uid, session).uid_id
1457 byuid[keyid] = (uid, name)
1458 byname[uid] = (keyid, name)
1460 return (byname, byuid)
1462 def generate_users_from_keyring(self, format, session):
1466 for x in self.keys.keys():
1467 if "email" not in self.keys[x]:
1469 self.keys[x]["uid"] = format % "invalid-uid"
1471 uid = format % self.keys[x]["email"]
1472 keyid = get_or_set_uid(uid, session).uid_id
1473 byuid[keyid] = (uid, self.keys[x]["name"])
1474 byname[uid] = (keyid, self.keys[x]["name"])
1475 self.keys[x]["uid"] = uid
1478 uid = format % "invalid-uid"
1479 keyid = get_or_set_uid(uid, session).uid_id
1480 byuid[keyid] = (uid, "ungeneratable user id")
1481 byname[uid] = (keyid, "ungeneratable user id")
1483 return (byname, byuid)
1485 __all__.append('Keyring')
1488 def get_keyring(keyring, session=None):
1490 If C{keyring} does not have an entry in the C{keyrings} table yet, return None
1491 If C{keyring} already has an entry, simply return the existing Keyring
1493 @type keyring: string
1494 @param keyring: the keyring name
1497 @return: the Keyring object for this keyring
1500 q = session.query(Keyring).filter_by(keyring_name=keyring)
1504 except NoResultFound:
1507 __all__.append('get_keyring')
1509 ################################################################################
1511 class KeyringACLMap(object):
1512 def __init__(self, *args, **kwargs):
1516 return '<KeyringACLMap %s>' % self.keyring_acl_map_id
1518 __all__.append('KeyringACLMap')
1520 ################################################################################
1522 class DBChange(object):
1523 def __init__(self, *args, **kwargs):
1527 return '<DBChange %s>' % self.changesname
1529 def clean_from_queue(self):
1530 session = DBConn().session().object_session(self)
1532 # Remove changes_pool_files entries
1535 # Remove changes_pending_files references
1538 # Clear out of queue
1539 self.in_queue = None
1540 self.approved_for_id = None
1542 __all__.append('DBChange')
1545 def get_dbchange(filename, session=None):
1547 returns DBChange object for given C{filename}.
1549 @type filename: string
1550 @param filename: the name of the file
1552 @type session: Session
1553 @param session: Optional SQLA session object (a temporary one will be
1554 generated if not supplied)
1557 @return: DBChange object for the given filename (C{None} if not present)
1560 q = session.query(DBChange).filter_by(changesname=filename)
1564 except NoResultFound:
1567 __all__.append('get_dbchange')
1569 ################################################################################
1571 # TODO: Why do we have a separate Location class? Can't it be fully integrated
1572 # into class Component?
1573 class Location(ORMObject):
1574 def __init__(self, path = None, component = None):
1576 self.component = component
1577 # the column 'type' should go away, see comment at mapper
1578 self.archive_type = 'pool'
1580 def properties(self):
1581 return ['path', 'archive_type', 'component', 'files_count']
1583 def not_null_constraints(self):
1584 return ['path', 'archive_type']
1586 __all__.append('Location')
1589 def get_location(location, component=None, archive=None, session=None):
1591 Returns Location object for the given combination of location, component
1594 @type location: string
1595 @param location: the path of the location, e.g. I{/srv/ftp-master.debian.org/ftp/pool/}
1597 @type component: string
1598 @param component: the component name (if None, no restriction applied)
1600 @type archive: string
1601 @param archive: the archive name (if None, no restriction applied)
1603 @rtype: Location / None
1604 @return: Either a Location object or None if one can't be found
1607 q = session.query(Location).filter_by(path=location)
1609 if archive is not None:
1610 q = q.join(Archive).filter_by(archive_name=archive)
1612 if component is not None:
1613 q = q.join(Component).filter_by(component_name=component)
1617 except NoResultFound:
1620 __all__.append('get_location')
1622 ################################################################################
1624 class Maintainer(ORMObject):
1625 def __init__(self, name = None):
1628 def properties(self):
1629 return ['name', 'maintainer_id']
1631 def not_null_constraints(self):
1634 def get_split_maintainer(self):
1635 if not hasattr(self, 'name') or self.name is None:
1636 return ('', '', '', '')
1638 return fix_maintainer(self.name.strip())
1640 __all__.append('Maintainer')
1643 def get_or_set_maintainer(name, session=None):
1645 Returns Maintainer object for given maintainer name.
1647 If no matching maintainer name is found, a row is inserted.
1650 @param name: The maintainer name to add
1652 @type session: SQLAlchemy
1653 @param session: Optional SQL session object (a temporary one will be
1654 generated if not supplied). If not passed, a commit will be performed at
1655 the end of the function, otherwise the caller is responsible for commiting.
1656 A flush will be performed either way.
1659 @return: the Maintainer object for the given maintainer
1662 q = session.query(Maintainer).filter_by(name=name)
1665 except NoResultFound:
1666 maintainer = Maintainer()
1667 maintainer.name = name
1668 session.add(maintainer)
1669 session.commit_or_flush()
1674 __all__.append('get_or_set_maintainer')
1677 def get_maintainer(maintainer_id, session=None):
1679 Return the name of the maintainer behind C{maintainer_id} or None if that
1680 maintainer_id is invalid.
1682 @type maintainer_id: int
1683 @param maintainer_id: the id of the maintainer
1686 @return: the Maintainer with this C{maintainer_id}
1689 return session.query(Maintainer).get(maintainer_id)
1691 __all__.append('get_maintainer')
1693 ################################################################################
1695 class NewComment(object):
1696 def __init__(self, *args, **kwargs):
1700 return '''<NewComment for '%s %s' (%s)>''' % (self.package, self.version, self.comment_id)
1702 __all__.append('NewComment')
1705 def has_new_comment(package, version, session=None):
1707 Returns true if the given combination of C{package}, C{version} has a comment.
1709 @type package: string
1710 @param package: name of the package
1712 @type version: string
1713 @param version: package version
1715 @type session: Session
1716 @param session: Optional SQLA session object (a temporary one will be
1717 generated if not supplied)
1723 q = session.query(NewComment)
1724 q = q.filter_by(package=package)
1725 q = q.filter_by(version=version)
1727 return bool(q.count() > 0)
1729 __all__.append('has_new_comment')
1732 def get_new_comments(package=None, version=None, comment_id=None, session=None):
1734 Returns (possibly empty) list of NewComment objects for the given
1737 @type package: string (optional)
1738 @param package: name of the package
1740 @type version: string (optional)
1741 @param version: package version
1743 @type comment_id: int (optional)
1744 @param comment_id: An id of a comment
1746 @type session: Session
1747 @param session: Optional SQLA session object (a temporary one will be
1748 generated if not supplied)
1751 @return: A (possibly empty) list of NewComment objects will be returned
1754 q = session.query(NewComment)
1755 if package is not None: q = q.filter_by(package=package)
1756 if version is not None: q = q.filter_by(version=version)
1757 if comment_id is not None: q = q.filter_by(comment_id=comment_id)
1761 __all__.append('get_new_comments')
1763 ################################################################################
1765 class Override(object):
1766 def __init__(self, *args, **kwargs):
1770 return '<Override %s (%s)>' % (self.package, self.suite_id)
1772 __all__.append('Override')
1775 def get_override(package, suite=None, component=None, overridetype=None, session=None):
1777 Returns Override object for the given parameters
1779 @type package: string
1780 @param package: The name of the package
1782 @type suite: string, list or None
1783 @param suite: The name of the suite (or suites if a list) to limit to. If
1784 None, don't limit. Defaults to None.
1786 @type component: string, list or None
1787 @param component: The name of the component (or components if a list) to
1788 limit to. If None, don't limit. Defaults to None.
1790 @type overridetype: string, list or None
1791 @param overridetype: The name of the overridetype (or overridetypes if a list) to
1792 limit to. If None, don't limit. Defaults to None.
1794 @type session: Session
1795 @param session: Optional SQLA session object (a temporary one will be
1796 generated if not supplied)
1799 @return: A (possibly empty) list of Override objects will be returned
1802 q = session.query(Override)
1803 q = q.filter_by(package=package)
1805 if suite is not None:
1806 if not isinstance(suite, list): suite = [suite]
1807 q = q.join(Suite).filter(Suite.suite_name.in_(suite))
1809 if component is not None:
1810 if not isinstance(component, list): component = [component]
1811 q = q.join(Component).filter(Component.component_name.in_(component))
1813 if overridetype is not None:
1814 if not isinstance(overridetype, list): overridetype = [overridetype]
1815 q = q.join(OverrideType).filter(OverrideType.overridetype.in_(overridetype))
1819 __all__.append('get_override')
1822 ################################################################################
1824 class OverrideType(object):
1825 def __init__(self, *args, **kwargs):
1829 return '<OverrideType %s>' % self.overridetype
1831 __all__.append('OverrideType')
1834 def get_override_type(override_type, session=None):
1836 Returns OverrideType object for given C{override type}.
1838 @type override_type: string
1839 @param override_type: The name of the override type
1841 @type session: Session
1842 @param session: Optional SQLA session object (a temporary one will be
1843 generated if not supplied)
1846 @return: the database id for the given override type
1849 q = session.query(OverrideType).filter_by(overridetype=override_type)
1853 except NoResultFound:
1856 __all__.append('get_override_type')
1858 ################################################################################
1860 class DebContents(object):
1861 def __init__(self, *args, **kwargs):
1865 return '<DebConetnts %s: %s>' % (self.package.package,self.file)
1867 __all__.append('DebContents')
1870 class UdebContents(object):
1871 def __init__(self, *args, **kwargs):
1875 return '<UdebConetnts %s: %s>' % (self.package.package,self.file)
1877 __all__.append('UdebContents')
1879 class PendingBinContents(object):
1880 def __init__(self, *args, **kwargs):
1884 return '<PendingBinContents %s>' % self.contents_id
1886 __all__.append('PendingBinContents')
1888 def insert_pending_content_paths(package,
1893 Make sure given paths are temporarily associated with given
1897 @param package: the package to associate with should have been read in from the binary control file
1898 @type fullpaths: list
1899 @param fullpaths: the list of paths of the file being associated with the binary
1900 @type session: SQLAlchemy session
1901 @param session: Optional SQLAlchemy session. If this is passed, the caller
1902 is responsible for ensuring a transaction has begun and committing the
1903 results or rolling back based on the result code. If not passed, a commit
1904 will be performed at the end of the function
1906 @return: True upon success, False if there is a problem
1909 privatetrans = False
1912 session = DBConn().session()
1916 arch = get_architecture(package['Architecture'], session)
1917 arch_id = arch.arch_id
1919 # Remove any already existing recorded files for this package
1920 q = session.query(PendingBinContents)
1921 q = q.filter_by(package=package['Package'])
1922 q = q.filter_by(version=package['Version'])
1923 q = q.filter_by(architecture=arch_id)
1926 for fullpath in fullpaths:
1928 if fullpath.startswith( "./" ):
1929 fullpath = fullpath[2:]
1931 pca = PendingBinContents()
1932 pca.package = package['Package']
1933 pca.version = package['Version']
1935 pca.architecture = arch_id
1938 pca.type = 8 # gross
1940 pca.type = 7 # also gross
1943 # Only commit if we set up the session ourself
1951 except Exception, e:
1952 traceback.print_exc()
1954 # Only rollback if we set up the session ourself
1961 __all__.append('insert_pending_content_paths')
1963 ################################################################################
1965 class PolicyQueue(object):
1966 def __init__(self, *args, **kwargs):
1970 return '<PolicyQueue %s>' % self.queue_name
1972 __all__.append('PolicyQueue')
1975 def get_policy_queue(queuename, session=None):
1977 Returns PolicyQueue object for given C{queue name}
1979 @type queuename: string
1980 @param queuename: The name of the queue
1982 @type session: Session
1983 @param session: Optional SQLA session object (a temporary one will be
1984 generated if not supplied)
1987 @return: PolicyQueue object for the given queue
1990 q = session.query(PolicyQueue).filter_by(queue_name=queuename)
1994 except NoResultFound:
1997 __all__.append('get_policy_queue')
2000 def get_policy_queue_from_path(pathname, session=None):
2002 Returns PolicyQueue object for given C{path name}
2004 @type queuename: string
2005 @param queuename: The path
2007 @type session: Session
2008 @param session: Optional SQLA session object (a temporary one will be
2009 generated if not supplied)
2012 @return: PolicyQueue object for the given queue
2015 q = session.query(PolicyQueue).filter_by(path=pathname)
2019 except NoResultFound:
2022 __all__.append('get_policy_queue_from_path')
2024 ################################################################################
2026 class Priority(object):
2027 def __init__(self, *args, **kwargs):
2030 def __eq__(self, val):
2031 if isinstance(val, str):
2032 return (self.priority == val)
2033 # This signals to use the normal comparison operator
2034 return NotImplemented
2036 def __ne__(self, val):
2037 if isinstance(val, str):
2038 return (self.priority != val)
2039 # This signals to use the normal comparison operator
2040 return NotImplemented
2043 return '<Priority %s (%s)>' % (self.priority, self.priority_id)
2045 __all__.append('Priority')
2048 def get_priority(priority, session=None):
2050 Returns Priority object for given C{priority name}.
2052 @type priority: string
2053 @param priority: The name of the priority
2055 @type session: Session
2056 @param session: Optional SQLA session object (a temporary one will be
2057 generated if not supplied)
2060 @return: Priority object for the given priority
2063 q = session.query(Priority).filter_by(priority=priority)
2067 except NoResultFound:
2070 __all__.append('get_priority')
2073 def get_priorities(session=None):
2075 Returns dictionary of priority names -> id mappings
2077 @type session: Session
2078 @param session: Optional SQL session object (a temporary one will be
2079 generated if not supplied)
2082 @return: dictionary of priority names -> id mappings
2086 q = session.query(Priority)
2088 ret[x.priority] = x.priority_id
2092 __all__.append('get_priorities')
2094 ################################################################################
2096 class Section(object):
2097 def __init__(self, *args, **kwargs):
2100 def __eq__(self, val):
2101 if isinstance(val, str):
2102 return (self.section == val)
2103 # This signals to use the normal comparison operator
2104 return NotImplemented
2106 def __ne__(self, val):
2107 if isinstance(val, str):
2108 return (self.section != val)
2109 # This signals to use the normal comparison operator
2110 return NotImplemented
2113 return '<Section %s>' % self.section
2115 __all__.append('Section')
2118 def get_section(section, session=None):
2120 Returns Section object for given C{section name}.
2122 @type section: string
2123 @param section: The name of the section
2125 @type session: Session
2126 @param session: Optional SQLA session object (a temporary one will be
2127 generated if not supplied)
2130 @return: Section object for the given section name
2133 q = session.query(Section).filter_by(section=section)
2137 except NoResultFound:
2140 __all__.append('get_section')
2143 def get_sections(session=None):
2145 Returns dictionary of section names -> id mappings
2147 @type session: Session
2148 @param session: Optional SQL session object (a temporary one will be
2149 generated if not supplied)
2152 @return: dictionary of section names -> id mappings
2156 q = session.query(Section)
2158 ret[x.section] = x.section_id
2162 __all__.append('get_sections')
2164 ################################################################################
2166 class DBSource(ORMObject):
2167 def __init__(self, source = None, version = None, maintainer = None, \
2168 changedby = None, poolfile = None, install_date = None):
2169 self.source = source
2170 self.version = version
2171 self.maintainer = maintainer
2172 self.changedby = changedby
2173 self.poolfile = poolfile
2174 self.install_date = install_date
2176 def properties(self):
2177 return ['source', 'source_id', 'maintainer', 'changedby', \
2178 'fingerprint', 'poolfile', 'version', 'suites_count', \
2179 'install_date', 'binaries_count']
2181 def not_null_constraints(self):
2182 return ['source', 'version', 'install_date', 'maintainer', \
2183 'changedby', 'poolfile', 'install_date']
2185 __all__.append('DBSource')
2188 def source_exists(source, source_version, suites = ["any"], session=None):
2190 Ensure that source exists somewhere in the archive for the binary
2191 upload being processed.
2192 1. exact match => 1.0-3
2193 2. bin-only NMU => 1.0-3+b1 , 1.0-3.1+b1
2195 @type source: string
2196 @param source: source name
2198 @type source_version: string
2199 @param source_version: expected source version
2202 @param suites: list of suites to check in, default I{any}
2204 @type session: Session
2205 @param session: Optional SQLA session object (a temporary one will be
2206 generated if not supplied)
2209 @return: returns 1 if a source with expected version is found, otherwise 0
2216 from daklib.regexes import re_bin_only_nmu
2217 orig_source_version = re_bin_only_nmu.sub('', source_version)
2219 for suite in suites:
2220 q = session.query(DBSource).filter_by(source=source). \
2221 filter(DBSource.version.in_([source_version, orig_source_version]))
2223 # source must exist in suite X, or in some other suite that's
2224 # mapped to X, recursively... silent-maps are counted too,
2225 # unreleased-maps aren't.
2226 maps = cnf.ValueList("SuiteMappings")[:]
2228 maps = [ m.split() for m in maps ]
2229 maps = [ (x[1], x[2]) for x in maps
2230 if x[0] == "map" or x[0] == "silent-map" ]
2233 if x[1] in s and x[0] not in s:
2236 q = q.filter(DBSource.suites.any(Suite.suite_name.in_(s)))
2241 # No source found so return not ok
2246 __all__.append('source_exists')
2249 def get_suites_source_in(source, session=None):
2251 Returns list of Suite objects which given C{source} name is in
2254 @param source: DBSource package name to search for
2257 @return: list of Suite objects for the given source
2260 return session.query(Suite).filter(Suite.sources.any(source=source)).all()
2262 __all__.append('get_suites_source_in')
2265 def get_sources_from_name(source, version=None, dm_upload_allowed=None, session=None):
2267 Returns list of DBSource objects for given C{source} name and other parameters
2270 @param source: DBSource package name to search for
2272 @type version: str or None
2273 @param version: DBSource version name to search for or None if not applicable
2275 @type dm_upload_allowed: bool
2276 @param dm_upload_allowed: If None, no effect. If True or False, only
2277 return packages with that dm_upload_allowed setting
2279 @type session: Session
2280 @param session: Optional SQL session object (a temporary one will be
2281 generated if not supplied)
2284 @return: list of DBSource objects for the given name (may be empty)
2287 q = session.query(DBSource).filter_by(source=source)
2289 if version is not None:
2290 q = q.filter_by(version=version)
2292 if dm_upload_allowed is not None:
2293 q = q.filter_by(dm_upload_allowed=dm_upload_allowed)
2297 __all__.append('get_sources_from_name')
2299 # FIXME: This function fails badly if it finds more than 1 source package and
2300 # its implementation is trivial enough to be inlined.
2302 def get_source_in_suite(source, suite, session=None):
2304 Returns a DBSource object for a combination of C{source} and C{suite}.
2306 - B{source} - source package name, eg. I{mailfilter}, I{bbdb}, I{glibc}
2307 - B{suite} - a suite name, eg. I{unstable}
2309 @type source: string
2310 @param source: source package name
2313 @param suite: the suite name
2316 @return: the version for I{source} in I{suite}
2320 q = get_suite(suite, session).get_sources(source)
2323 except NoResultFound:
2326 __all__.append('get_source_in_suite')
2328 ################################################################################
2331 def add_dsc_to_db(u, filename, session=None):
2332 entry = u.pkg.files[filename]
2336 source.source = u.pkg.dsc["source"]
2337 source.version = u.pkg.dsc["version"] # NB: not files[file]["version"], that has no epoch
2338 source.maintainer_id = get_or_set_maintainer(u.pkg.dsc["maintainer"], session).maintainer_id
2339 source.changedby_id = get_or_set_maintainer(u.pkg.changes["changed-by"], session).maintainer_id
2340 source.fingerprint_id = get_or_set_fingerprint(u.pkg.changes["fingerprint"], session).fingerprint_id
2341 source.install_date = datetime.now().date()
2343 dsc_component = entry["component"]
2344 dsc_location_id = entry["location id"]
2346 source.dm_upload_allowed = (u.pkg.dsc.get("dm-upload-allowed", '') == "yes")
2348 # Set up a new poolfile if necessary
2349 if not entry.has_key("files id") or not entry["files id"]:
2350 filename = entry["pool name"] + filename
2351 poolfile = add_poolfile(filename, entry, dsc_location_id, session)
2353 pfs.append(poolfile)
2354 entry["files id"] = poolfile.file_id
2356 source.poolfile_id = entry["files id"]
2359 suite_names = u.pkg.changes["distribution"].keys()
2360 source.suites = session.query(Suite). \
2361 filter(Suite.suite_name.in_(suite_names)).all()
2363 # Add the source files to the DB (files and dsc_files)
2365 dscfile.source_id = source.source_id
2366 dscfile.poolfile_id = entry["files id"]
2367 session.add(dscfile)
2369 for dsc_file, dentry in u.pkg.dsc_files.items():
2371 df.source_id = source.source_id
2373 # If the .orig tarball is already in the pool, it's
2374 # files id is stored in dsc_files by check_dsc().
2375 files_id = dentry.get("files id", None)
2377 # Find the entry in the files hash
2378 # TODO: Bail out here properly
2380 for f, e in u.pkg.files.items():
2385 if files_id is None:
2386 filename = dfentry["pool name"] + dsc_file
2388 (found, obj) = check_poolfile(filename, dentry["size"], dentry["md5sum"], dsc_location_id)
2389 # FIXME: needs to check for -1/-2 and or handle exception
2390 if found and obj is not None:
2391 files_id = obj.file_id
2394 # If still not found, add it
2395 if files_id is None:
2396 # HACK: Force sha1sum etc into dentry
2397 dentry["sha1sum"] = dfentry["sha1sum"]
2398 dentry["sha256sum"] = dfentry["sha256sum"]
2399 poolfile = add_poolfile(filename, dentry, dsc_location_id, session)
2400 pfs.append(poolfile)
2401 files_id = poolfile.file_id
2403 poolfile = get_poolfile_by_id(files_id, session)
2404 if poolfile is None:
2405 utils.fubar("INTERNAL ERROR. Found no poolfile with id %d" % files_id)
2406 pfs.append(poolfile)
2408 df.poolfile_id = files_id
2411 # Add the src_uploaders to the DB
2412 uploader_ids = [source.maintainer_id]
2413 if u.pkg.dsc.has_key("uploaders"):
2414 for up in u.pkg.dsc["uploaders"].replace(">, ", ">\t").split("\t"):
2416 uploader_ids.append(get_or_set_maintainer(up, session).maintainer_id)
2419 for up_id in uploader_ids:
2420 if added_ids.has_key(up_id):
2422 utils.warn("Already saw uploader %s for source %s" % (up_id, source.source))
2428 su.maintainer_id = up_id
2429 su.source_id = source.source_id
2434 return source, dsc_component, dsc_location_id, pfs
2436 __all__.append('add_dsc_to_db')
2439 def add_deb_to_db(u, filename, session=None):
2441 Contrary to what you might expect, this routine deals with both
2442 debs and udebs. That info is in 'dbtype', whilst 'type' is
2443 'deb' for both of them
2446 entry = u.pkg.files[filename]
2449 bin.package = entry["package"]
2450 bin.version = entry["version"]
2451 bin.maintainer_id = get_or_set_maintainer(entry["maintainer"], session).maintainer_id
2452 bin.fingerprint_id = get_or_set_fingerprint(u.pkg.changes["fingerprint"], session).fingerprint_id
2453 bin.arch_id = get_architecture(entry["architecture"], session).arch_id
2454 bin.binarytype = entry["dbtype"]
2457 filename = entry["pool name"] + filename
2458 fullpath = os.path.join(cnf["Dir::Pool"], filename)
2459 if not entry.get("location id", None):
2460 entry["location id"] = get_location(cnf["Dir::Pool"], entry["component"], session=session).location_id
2462 if entry.get("files id", None):
2463 poolfile = get_poolfile_by_id(bin.poolfile_id)
2464 bin.poolfile_id = entry["files id"]
2466 poolfile = add_poolfile(filename, entry, entry["location id"], session)
2467 bin.poolfile_id = entry["files id"] = poolfile.file_id
2470 bin_sources = get_sources_from_name(entry["source package"], entry["source version"], session=session)
2471 if len(bin_sources) != 1:
2472 raise NoSourceFieldError, "Unable to find a unique source id for %s (%s), %s, file %s, type %s, signed by %s" % \
2473 (bin.package, bin.version, entry["architecture"],
2474 filename, bin.binarytype, u.pkg.changes["fingerprint"])
2476 bin.source_id = bin_sources[0].source_id
2478 # Add and flush object so it has an ID
2481 suite_names = u.pkg.changes["distribution"].keys()
2482 bin.suites = session.query(Suite). \
2483 filter(Suite.suite_name.in_(suite_names)).all()
2487 # Deal with contents - disabled for now
2488 #contents = copy_temporary_contents(bin.package, bin.version, bin.architecture.arch_string, os.path.basename(filename), None, session)
2490 # print "REJECT\nCould not determine contents of package %s" % bin.package
2491 # session.rollback()
2492 # raise MissingContents, "No contents stored for package %s, and couldn't determine contents of %s" % (bin.package, filename)
2496 __all__.append('add_deb_to_db')
2498 ################################################################################
2500 class SourceACL(object):
2501 def __init__(self, *args, **kwargs):
2505 return '<SourceACL %s>' % self.source_acl_id
2507 __all__.append('SourceACL')
2509 ################################################################################
2511 class SrcFormat(object):
2512 def __init__(self, *args, **kwargs):
2516 return '<SrcFormat %s>' % (self.format_name)
2518 __all__.append('SrcFormat')
2520 ################################################################################
2522 class SrcUploader(object):
2523 def __init__(self, *args, **kwargs):
2527 return '<SrcUploader %s>' % self.uploader_id
2529 __all__.append('SrcUploader')
2531 ################################################################################
2533 SUITE_FIELDS = [ ('SuiteName', 'suite_name'),
2534 ('SuiteID', 'suite_id'),
2535 ('Version', 'version'),
2536 ('Origin', 'origin'),
2538 ('Description', 'description'),
2539 ('Untouchable', 'untouchable'),
2540 ('Announce', 'announce'),
2541 ('Codename', 'codename'),
2542 ('OverrideCodename', 'overridecodename'),
2543 ('ValidTime', 'validtime'),
2544 ('Priority', 'priority'),
2545 ('NotAutomatic', 'notautomatic'),
2546 ('CopyChanges', 'copychanges'),
2547 ('OverrideSuite', 'overridesuite')]
2549 # Why the heck don't we have any UNIQUE constraints in table suite?
2550 # TODO: Add UNIQUE constraints for appropriate columns.
2551 class Suite(ORMObject):
2552 def __init__(self, suite_name = None, version = None):
2553 self.suite_name = suite_name
2554 self.version = version
2556 def properties(self):
2557 return ['suite_name', 'version', 'sources_count', 'binaries_count']
2559 def not_null_constraints(self):
2560 return ['suite_name', 'version']
2562 def __eq__(self, val):
2563 if isinstance(val, str):
2564 return (self.suite_name == val)
2565 # This signals to use the normal comparison operator
2566 return NotImplemented
2568 def __ne__(self, val):
2569 if isinstance(val, str):
2570 return (self.suite_name != val)
2571 # This signals to use the normal comparison operator
2572 return NotImplemented
2576 for disp, field in SUITE_FIELDS:
2577 val = getattr(self, field, None)
2579 ret.append("%s: %s" % (disp, val))
2581 return "\n".join(ret)
2583 def get_architectures(self, skipsrc=False, skipall=False):
2585 Returns list of Architecture objects
2587 @type skipsrc: boolean
2588 @param skipsrc: Whether to skip returning the 'source' architecture entry
2591 @type skipall: boolean
2592 @param skipall: Whether to skip returning the 'all' architecture entry
2596 @return: list of Architecture objects for the given name (may be empty)
2599 q = object_session(self).query(Architecture).with_parent(self)
2601 q = q.filter(Architecture.arch_string != 'source')
2603 q = q.filter(Architecture.arch_string != 'all')
2604 return q.order_by(Architecture.arch_string).all()
2606 def get_sources(self, source):
2608 Returns a query object representing DBSource that is part of C{suite}.
2610 - B{source} - source package name, eg. I{mailfilter}, I{bbdb}, I{glibc}
2612 @type source: string
2613 @param source: source package name
2615 @rtype: sqlalchemy.orm.query.Query
2616 @return: a query of DBSource
2620 session = object_session(self)
2621 return session.query(DBSource).filter_by(source = source). \
2624 __all__.append('Suite')
2627 def get_suite(suite, session=None):
2629 Returns Suite object for given C{suite name}.
2632 @param suite: The name of the suite
2634 @type session: Session
2635 @param session: Optional SQLA session object (a temporary one will be
2636 generated if not supplied)
2639 @return: Suite object for the requested suite name (None if not present)
2642 q = session.query(Suite).filter_by(suite_name=suite)
2646 except NoResultFound:
2649 __all__.append('get_suite')
2651 ################################################################################
2653 # TODO: should be removed because the implementation is too trivial
2655 def get_suite_architectures(suite, skipsrc=False, skipall=False, session=None):
2657 Returns list of Architecture objects for given C{suite} name
2660 @param suite: Suite name to search for
2662 @type skipsrc: boolean
2663 @param skipsrc: Whether to skip returning the 'source' architecture entry
2666 @type skipall: boolean
2667 @param skipall: Whether to skip returning the 'all' architecture entry
2670 @type session: Session
2671 @param session: Optional SQL session object (a temporary one will be
2672 generated if not supplied)
2675 @return: list of Architecture objects for the given name (may be empty)
2678 return get_suite(suite, session).get_architectures(skipsrc, skipall)
2680 __all__.append('get_suite_architectures')
2682 ################################################################################
2684 class SuiteSrcFormat(object):
2685 def __init__(self, *args, **kwargs):
2689 return '<SuiteSrcFormat (%s, %s)>' % (self.suite_id, self.src_format_id)
2691 __all__.append('SuiteSrcFormat')
2694 def get_suite_src_formats(suite, session=None):
2696 Returns list of allowed SrcFormat for C{suite}.
2699 @param suite: Suite name to search for
2701 @type session: Session
2702 @param session: Optional SQL session object (a temporary one will be
2703 generated if not supplied)
2706 @return: the list of allowed source formats for I{suite}
2709 q = session.query(SrcFormat)
2710 q = q.join(SuiteSrcFormat)
2711 q = q.join(Suite).filter_by(suite_name=suite)
2712 q = q.order_by('format_name')
2716 __all__.append('get_suite_src_formats')
2718 ################################################################################
2720 class Uid(ORMObject):
2721 def __init__(self, uid = None, name = None):
2725 def __eq__(self, val):
2726 if isinstance(val, str):
2727 return (self.uid == val)
2728 # This signals to use the normal comparison operator
2729 return NotImplemented
2731 def __ne__(self, val):
2732 if isinstance(val, str):
2733 return (self.uid != val)
2734 # This signals to use the normal comparison operator
2735 return NotImplemented
2737 def properties(self):
2738 return ['uid', 'name', 'fingerprint']
2740 def not_null_constraints(self):
2743 __all__.append('Uid')
2746 def get_or_set_uid(uidname, session=None):
2748 Returns uid object for given uidname.
2750 If no matching uidname is found, a row is inserted.
2752 @type uidname: string
2753 @param uidname: The uid to add
2755 @type session: SQLAlchemy
2756 @param session: Optional SQL session object (a temporary one will be
2757 generated if not supplied). If not passed, a commit will be performed at
2758 the end of the function, otherwise the caller is responsible for commiting.
2761 @return: the uid object for the given uidname
2764 q = session.query(Uid).filter_by(uid=uidname)
2768 except NoResultFound:
2772 session.commit_or_flush()
2777 __all__.append('get_or_set_uid')
2780 def get_uid_from_fingerprint(fpr, session=None):
2781 q = session.query(Uid)
2782 q = q.join(Fingerprint).filter_by(fingerprint=fpr)
2786 except NoResultFound:
2789 __all__.append('get_uid_from_fingerprint')
2791 ################################################################################
2793 class UploadBlock(object):
2794 def __init__(self, *args, **kwargs):
2798 return '<UploadBlock %s (%s)>' % (self.source, self.upload_block_id)
2800 __all__.append('UploadBlock')
2802 ################################################################################
2804 class DBConn(object):
2806 database module init.
2810 def __init__(self, *args, **kwargs):
2811 self.__dict__ = self.__shared_state
2813 if not getattr(self, 'initialised', False):
2814 self.initialised = True
2815 self.debug = kwargs.has_key('debug')
2818 def __setuptables(self):
2819 tables_with_primary = (
2830 'changes_pending_binaries',
2831 'changes_pending_files',
2832 'changes_pending_source',
2842 'pending_bin_contents',
2854 # The following tables have primary keys but sqlalchemy
2855 # version 0.5 fails to reflect them correctly with database
2856 # versions before upgrade #41.
2858 #'build_queue_files',
2861 tables_no_primary = (
2863 'changes_pending_files_map',
2864 'changes_pending_source_files',
2865 'changes_pool_files',
2868 'suite_architectures',
2869 'suite_src_formats',
2870 'suite_build_queue_copy',
2872 # see the comment above
2874 'build_queue_files',
2878 'almost_obsolete_all_associations',
2879 'almost_obsolete_src_associations',
2880 'any_associations_source',
2881 'bin_assoc_by_arch',
2882 'bin_associations_binaries',
2883 'binaries_suite_arch',
2884 'binfiles_suite_component_arch',
2887 'newest_all_associations',
2888 'newest_any_associations',
2890 'newest_src_association',
2891 'obsolete_all_associations',
2892 'obsolete_any_associations',
2893 'obsolete_any_by_all_associations',
2894 'obsolete_src_associations',
2896 'src_associations_bin',
2897 'src_associations_src',
2898 'suite_arch_by_name',
2901 # Sqlalchemy version 0.5 fails to reflect the SERIAL type
2902 # correctly and that is why we have to use a workaround. It can
2903 # be removed as soon as we switch to version 0.6.
2904 for table_name in tables_with_primary:
2905 table = Table(table_name, self.db_meta, \
2906 Column('id', Integer, primary_key = True), \
2907 autoload=True, useexisting=True)
2908 setattr(self, 'tbl_%s' % table_name, table)
2910 for table_name in tables_no_primary:
2911 table = Table(table_name, self.db_meta, autoload=True)
2912 setattr(self, 'tbl_%s' % table_name, table)
2914 for view_name in views:
2915 view = Table(view_name, self.db_meta, autoload=True)
2916 setattr(self, 'view_%s' % view_name, view)
2918 def __setupmappers(self):
2919 mapper(Architecture, self.tbl_architecture,
2920 properties = dict(arch_id = self.tbl_architecture.c.id,
2921 suites = relation(Suite, secondary=self.tbl_suite_architectures,
2922 order_by='suite_name',
2923 backref=backref('architectures', order_by='arch_string'))),
2924 extension = validator)
2926 mapper(Archive, self.tbl_archive,
2927 properties = dict(archive_id = self.tbl_archive.c.id,
2928 archive_name = self.tbl_archive.c.name))
2930 mapper(PendingBinContents, self.tbl_pending_bin_contents,
2931 properties = dict(contents_id =self.tbl_pending_bin_contents.c.id,
2932 filename = self.tbl_pending_bin_contents.c.filename,
2933 package = self.tbl_pending_bin_contents.c.package,
2934 version = self.tbl_pending_bin_contents.c.version,
2935 arch = self.tbl_pending_bin_contents.c.arch,
2936 otype = self.tbl_pending_bin_contents.c.type))
2938 mapper(DebContents, self.tbl_deb_contents,
2939 properties = dict(binary_id=self.tbl_deb_contents.c.binary_id,
2940 package=self.tbl_deb_contents.c.package,
2941 suite=self.tbl_deb_contents.c.suite,
2942 arch=self.tbl_deb_contents.c.arch,
2943 section=self.tbl_deb_contents.c.section,
2944 filename=self.tbl_deb_contents.c.filename))
2946 mapper(UdebContents, self.tbl_udeb_contents,
2947 properties = dict(binary_id=self.tbl_udeb_contents.c.binary_id,
2948 package=self.tbl_udeb_contents.c.package,
2949 suite=self.tbl_udeb_contents.c.suite,
2950 arch=self.tbl_udeb_contents.c.arch,
2951 section=self.tbl_udeb_contents.c.section,
2952 filename=self.tbl_udeb_contents.c.filename))
2954 mapper(BuildQueue, self.tbl_build_queue,
2955 properties = dict(queue_id = self.tbl_build_queue.c.id))
2957 mapper(BuildQueueFile, self.tbl_build_queue_files,
2958 properties = dict(buildqueue = relation(BuildQueue, backref='queuefiles'),
2959 poolfile = relation(PoolFile, backref='buildqueueinstances')))
2961 mapper(DBBinary, self.tbl_binaries,
2962 properties = dict(binary_id = self.tbl_binaries.c.id,
2963 package = self.tbl_binaries.c.package,
2964 version = self.tbl_binaries.c.version,
2965 maintainer_id = self.tbl_binaries.c.maintainer,
2966 maintainer = relation(Maintainer),
2967 source_id = self.tbl_binaries.c.source,
2968 source = relation(DBSource, backref='binaries'),
2969 arch_id = self.tbl_binaries.c.architecture,
2970 architecture = relation(Architecture),
2971 poolfile_id = self.tbl_binaries.c.file,
2972 poolfile = relation(PoolFile, backref=backref('binary', uselist = False)),
2973 binarytype = self.tbl_binaries.c.type,
2974 fingerprint_id = self.tbl_binaries.c.sig_fpr,
2975 fingerprint = relation(Fingerprint),
2976 install_date = self.tbl_binaries.c.install_date,
2977 suites = relation(Suite, secondary=self.tbl_bin_associations,
2978 backref=backref('binaries', lazy='dynamic'))),
2979 extension = validator)
2981 mapper(BinaryACL, self.tbl_binary_acl,
2982 properties = dict(binary_acl_id = self.tbl_binary_acl.c.id))
2984 mapper(BinaryACLMap, self.tbl_binary_acl_map,
2985 properties = dict(binary_acl_map_id = self.tbl_binary_acl_map.c.id,
2986 fingerprint = relation(Fingerprint, backref="binary_acl_map"),
2987 architecture = relation(Architecture)))
2989 mapper(Component, self.tbl_component,
2990 properties = dict(component_id = self.tbl_component.c.id,
2991 component_name = self.tbl_component.c.name),
2992 extension = validator)
2994 mapper(DBConfig, self.tbl_config,
2995 properties = dict(config_id = self.tbl_config.c.id))
2997 mapper(DSCFile, self.tbl_dsc_files,
2998 properties = dict(dscfile_id = self.tbl_dsc_files.c.id,
2999 source_id = self.tbl_dsc_files.c.source,
3000 source = relation(DBSource),
3001 poolfile_id = self.tbl_dsc_files.c.file,
3002 poolfile = relation(PoolFile)))
3004 mapper(PoolFile, self.tbl_files,
3005 properties = dict(file_id = self.tbl_files.c.id,
3006 filesize = self.tbl_files.c.size,
3007 location_id = self.tbl_files.c.location,
3008 location = relation(Location,
3009 # using lazy='dynamic' in the back
3010 # reference because we have A LOT of
3011 # files in one location
3012 backref=backref('files', lazy='dynamic'))),
3013 extension = validator)
3015 mapper(Fingerprint, self.tbl_fingerprint,
3016 properties = dict(fingerprint_id = self.tbl_fingerprint.c.id,
3017 uid_id = self.tbl_fingerprint.c.uid,
3018 uid = relation(Uid),
3019 keyring_id = self.tbl_fingerprint.c.keyring,
3020 keyring = relation(Keyring),
3021 source_acl = relation(SourceACL),
3022 binary_acl = relation(BinaryACL)),
3023 extension = validator)
3025 mapper(Keyring, self.tbl_keyrings,
3026 properties = dict(keyring_name = self.tbl_keyrings.c.name,
3027 keyring_id = self.tbl_keyrings.c.id))
3029 mapper(DBChange, self.tbl_changes,
3030 properties = dict(change_id = self.tbl_changes.c.id,
3031 poolfiles = relation(PoolFile,
3032 secondary=self.tbl_changes_pool_files,
3033 backref="changeslinks"),
3034 seen = self.tbl_changes.c.seen,
3035 source = self.tbl_changes.c.source,
3036 binaries = self.tbl_changes.c.binaries,
3037 architecture = self.tbl_changes.c.architecture,
3038 distribution = self.tbl_changes.c.distribution,
3039 urgency = self.tbl_changes.c.urgency,
3040 maintainer = self.tbl_changes.c.maintainer,
3041 changedby = self.tbl_changes.c.changedby,
3042 date = self.tbl_changes.c.date,
3043 version = self.tbl_changes.c.version,
3044 files = relation(ChangePendingFile,
3045 secondary=self.tbl_changes_pending_files_map,
3046 backref="changesfile"),
3047 in_queue_id = self.tbl_changes.c.in_queue,
3048 in_queue = relation(PolicyQueue,
3049 primaryjoin=(self.tbl_changes.c.in_queue==self.tbl_policy_queue.c.id)),
3050 approved_for_id = self.tbl_changes.c.approved_for))
3052 mapper(ChangePendingBinary, self.tbl_changes_pending_binaries,
3053 properties = dict(change_pending_binary_id = self.tbl_changes_pending_binaries.c.id))
3055 mapper(ChangePendingFile, self.tbl_changes_pending_files,
3056 properties = dict(change_pending_file_id = self.tbl_changes_pending_files.c.id,
3057 filename = self.tbl_changes_pending_files.c.filename,
3058 size = self.tbl_changes_pending_files.c.size,
3059 md5sum = self.tbl_changes_pending_files.c.md5sum,
3060 sha1sum = self.tbl_changes_pending_files.c.sha1sum,
3061 sha256sum = self.tbl_changes_pending_files.c.sha256sum))
3063 mapper(ChangePendingSource, self.tbl_changes_pending_source,
3064 properties = dict(change_pending_source_id = self.tbl_changes_pending_source.c.id,
3065 change = relation(DBChange),
3066 maintainer = relation(Maintainer,
3067 primaryjoin=(self.tbl_changes_pending_source.c.maintainer_id==self.tbl_maintainer.c.id)),
3068 changedby = relation(Maintainer,
3069 primaryjoin=(self.tbl_changes_pending_source.c.changedby_id==self.tbl_maintainer.c.id)),
3070 fingerprint = relation(Fingerprint),
3071 source_files = relation(ChangePendingFile,
3072 secondary=self.tbl_changes_pending_source_files,
3073 backref="pending_sources")))
3076 mapper(KeyringACLMap, self.tbl_keyring_acl_map,
3077 properties = dict(keyring_acl_map_id = self.tbl_keyring_acl_map.c.id,
3078 keyring = relation(Keyring, backref="keyring_acl_map"),
3079 architecture = relation(Architecture)))
3081 mapper(Location, self.tbl_location,
3082 properties = dict(location_id = self.tbl_location.c.id,
3083 component_id = self.tbl_location.c.component,
3084 component = relation(Component, \
3085 backref=backref('location', uselist = False)),
3086 archive_id = self.tbl_location.c.archive,
3087 archive = relation(Archive),
3088 # FIXME: the 'type' column is old cruft and
3089 # should be removed in the future.
3090 archive_type = self.tbl_location.c.type),
3091 extension = validator)
3093 mapper(Maintainer, self.tbl_maintainer,
3094 properties = dict(maintainer_id = self.tbl_maintainer.c.id,
3095 maintains_sources = relation(DBSource, backref='maintainer',
3096 primaryjoin=(self.tbl_maintainer.c.id==self.tbl_source.c.maintainer)),
3097 changed_sources = relation(DBSource, backref='changedby',
3098 primaryjoin=(self.tbl_maintainer.c.id==self.tbl_source.c.changedby))),
3099 extension = validator)
3101 mapper(NewComment, self.tbl_new_comments,
3102 properties = dict(comment_id = self.tbl_new_comments.c.id))
3104 mapper(Override, self.tbl_override,
3105 properties = dict(suite_id = self.tbl_override.c.suite,
3106 suite = relation(Suite),
3107 package = self.tbl_override.c.package,
3108 component_id = self.tbl_override.c.component,
3109 component = relation(Component),
3110 priority_id = self.tbl_override.c.priority,
3111 priority = relation(Priority),
3112 section_id = self.tbl_override.c.section,
3113 section = relation(Section),
3114 overridetype_id = self.tbl_override.c.type,
3115 overridetype = relation(OverrideType)))
3117 mapper(OverrideType, self.tbl_override_type,
3118 properties = dict(overridetype = self.tbl_override_type.c.type,
3119 overridetype_id = self.tbl_override_type.c.id))
3121 mapper(PolicyQueue, self.tbl_policy_queue,
3122 properties = dict(policy_queue_id = self.tbl_policy_queue.c.id))
3124 mapper(Priority, self.tbl_priority,
3125 properties = dict(priority_id = self.tbl_priority.c.id))
3127 mapper(Section, self.tbl_section,
3128 properties = dict(section_id = self.tbl_section.c.id,
3129 section=self.tbl_section.c.section))
3131 mapper(DBSource, self.tbl_source,
3132 properties = dict(source_id = self.tbl_source.c.id,
3133 version = self.tbl_source.c.version,
3134 maintainer_id = self.tbl_source.c.maintainer,
3135 poolfile_id = self.tbl_source.c.file,
3136 poolfile = relation(PoolFile, backref=backref('source', uselist = False)),
3137 fingerprint_id = self.tbl_source.c.sig_fpr,
3138 fingerprint = relation(Fingerprint),
3139 changedby_id = self.tbl_source.c.changedby,
3140 srcfiles = relation(DSCFile,
3141 primaryjoin=(self.tbl_source.c.id==self.tbl_dsc_files.c.source)),
3142 suites = relation(Suite, secondary=self.tbl_src_associations,
3143 backref=backref('sources', lazy='dynamic')),
3144 srcuploaders = relation(SrcUploader)),
3145 extension = validator)
3147 mapper(SourceACL, self.tbl_source_acl,
3148 properties = dict(source_acl_id = self.tbl_source_acl.c.id))
3150 mapper(SrcFormat, self.tbl_src_format,
3151 properties = dict(src_format_id = self.tbl_src_format.c.id,
3152 format_name = self.tbl_src_format.c.format_name))
3154 mapper(SrcUploader, self.tbl_src_uploaders,
3155 properties = dict(uploader_id = self.tbl_src_uploaders.c.id,
3156 source_id = self.tbl_src_uploaders.c.source,
3157 source = relation(DBSource,
3158 primaryjoin=(self.tbl_src_uploaders.c.source==self.tbl_source.c.id)),
3159 maintainer_id = self.tbl_src_uploaders.c.maintainer,
3160 maintainer = relation(Maintainer,
3161 primaryjoin=(self.tbl_src_uploaders.c.maintainer==self.tbl_maintainer.c.id))))
3163 mapper(Suite, self.tbl_suite,
3164 properties = dict(suite_id = self.tbl_suite.c.id,
3165 policy_queue = relation(PolicyQueue),
3166 copy_queues = relation(BuildQueue,
3167 secondary=self.tbl_suite_build_queue_copy)),
3168 extension = validator)
3170 mapper(SuiteSrcFormat, self.tbl_suite_src_formats,
3171 properties = dict(suite_id = self.tbl_suite_src_formats.c.suite,
3172 suite = relation(Suite, backref='suitesrcformats'),
3173 src_format_id = self.tbl_suite_src_formats.c.src_format,
3174 src_format = relation(SrcFormat)))
3176 mapper(Uid, self.tbl_uid,
3177 properties = dict(uid_id = self.tbl_uid.c.id,
3178 fingerprint = relation(Fingerprint)),
3179 extension = validator)
3181 mapper(UploadBlock, self.tbl_upload_blocks,
3182 properties = dict(upload_block_id = self.tbl_upload_blocks.c.id,
3183 fingerprint = relation(Fingerprint, backref="uploadblocks"),
3184 uid = relation(Uid, backref="uploadblocks")))
3186 ## Connection functions
3187 def __createconn(self):
3188 from config import Config
3192 connstr = "postgres://%s" % cnf["DB::Host"]
3193 if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
3194 connstr += ":%s" % cnf["DB::Port"]
3195 connstr += "/%s" % cnf["DB::Name"]
3198 connstr = "postgres:///%s" % cnf["DB::Name"]
3199 if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
3200 connstr += "?port=%s" % cnf["DB::Port"]
3202 self.db_pg = create_engine(connstr, echo=self.debug)
3203 self.db_meta = MetaData()
3204 self.db_meta.bind = self.db_pg
3205 self.db_smaker = sessionmaker(bind=self.db_pg,
3209 self.__setuptables()
3210 self.__setupmappers()
3213 return self.db_smaker()
3215 __all__.append('DBConn')