]> git.decadent.org.uk Git - dak.git/blob - daklib/dbconn.py
merge from ftp-master
[dak.git] / daklib / dbconn.py
1 #!/usr/bin/python
2
3 """ DB access class
4
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
11 """
12
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.
17
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.
22
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
26
27 ################################################################################
28
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"
33
34 ################################################################################
35
36 import os
37 import re
38 import psycopg2
39 import traceback
40 from datetime import datetime, timedelta
41 from errno import ENOENT
42 from tempfile import mkstemp, mkdtemp
43
44 from inspect import getargspec
45
46 import sqlalchemy
47 from sqlalchemy import create_engine, Table, MetaData
48 from sqlalchemy.orm import sessionmaker, mapper, relation
49 from sqlalchemy import types as sqltypes
50
51 # Don't remove this, we re-export the exceptions to scripts which import us
52 from sqlalchemy.exc import *
53 from sqlalchemy.orm.exc import NoResultFound
54
55 # Only import Config until Queue stuff is changed to store its config
56 # in the database
57 from config import Config
58 from textutils import fix_maintainer
59
60 ################################################################################
61
62 # Patch in support for the debversion field type so that it works during
63 # reflection
64
65 class DebVersion(sqltypes.Text):
66     def get_col_spec(self):
67         return "DEBVERSION"
68
69 sa_major_version = sqlalchemy.__version__[0:3]
70 if sa_major_version == "0.5":
71     from sqlalchemy.databases import postgres
72     postgres.ischema_names['debversion'] = DebVersion
73 else:
74     raise Exception("dak isn't ported to SQLA versions != 0.5 yet.  See daklib/dbconn.py")
75
76 ################################################################################
77
78 __all__ = ['IntegrityError', 'SQLAlchemyError']
79
80 ################################################################################
81
82 def session_wrapper(fn):
83     """
84     Wrapper around common ".., session=None):" handling. If the wrapped
85     function is called without passing 'session', we create a local one
86     and destroy it when the function ends.
87
88     Also attaches a commit_or_flush method to the session; if we created a
89     local session, this is a synonym for session.commit(), otherwise it is a
90     synonym for session.flush().
91     """
92
93     def wrapped(*args, **kwargs):
94         private_transaction = False
95
96         # Find the session object
97         session = kwargs.get('session')
98
99         if session is None:
100             if len(args) <= len(getargspec(fn)[0]) - 1:
101                 # No session specified as last argument or in kwargs
102                 private_transaction = True
103                 session = kwargs['session'] = DBConn().session()
104             else:
105                 # Session is last argument in args
106                 session = args[-1]
107                 if session is None:
108                     args = list(args)
109                     session = args[-1] = DBConn().session()
110                     private_transaction = True
111
112         if private_transaction:
113             session.commit_or_flush = session.commit
114         else:
115             session.commit_or_flush = session.flush
116
117         try:
118             return fn(*args, **kwargs)
119         finally:
120             if private_transaction:
121                 # We created a session; close it.
122                 session.close()
123
124     wrapped.__doc__ = fn.__doc__
125     wrapped.func_name = fn.func_name
126
127     return wrapped
128
129 __all__.append('session_wrapper')
130
131 ################################################################################
132
133 class Architecture(object):
134     def __init__(self, *args, **kwargs):
135         pass
136
137     def __eq__(self, val):
138         if isinstance(val, str):
139             return (self.arch_string== val)
140         # This signals to use the normal comparison operator
141         return NotImplemented
142
143     def __ne__(self, val):
144         if isinstance(val, str):
145             return (self.arch_string != val)
146         # This signals to use the normal comparison operator
147         return NotImplemented
148
149     def __repr__(self):
150         return '<Architecture %s>' % self.arch_string
151
152 __all__.append('Architecture')
153
154 @session_wrapper
155 def get_architecture(architecture, session=None):
156     """
157     Returns database id for given C{architecture}.
158
159     @type architecture: string
160     @param architecture: The name of the architecture
161
162     @type session: Session
163     @param session: Optional SQLA session object (a temporary one will be
164     generated if not supplied)
165
166     @rtype: Architecture
167     @return: Architecture object for the given arch (None if not present)
168     """
169
170     q = session.query(Architecture).filter_by(arch_string=architecture)
171
172     try:
173         return q.one()
174     except NoResultFound:
175         return None
176
177 __all__.append('get_architecture')
178
179 @session_wrapper
180 def get_architecture_suites(architecture, session=None):
181     """
182     Returns list of Suite objects for given C{architecture} name
183
184     @type source: str
185     @param source: Architecture name to search for
186
187     @type session: Session
188     @param session: Optional SQL session object (a temporary one will be
189     generated if not supplied)
190
191     @rtype: list
192     @return: list of Suite objects for the given name (may be empty)
193     """
194
195     q = session.query(Suite)
196     q = q.join(SuiteArchitecture)
197     q = q.join(Architecture).filter_by(arch_string=architecture).order_by('suite_name')
198
199     ret = q.all()
200
201     return ret
202
203 __all__.append('get_architecture_suites')
204
205 ################################################################################
206
207 class Archive(object):
208     def __init__(self, *args, **kwargs):
209         pass
210
211     def __repr__(self):
212         return '<Archive %s>' % self.archive_name
213
214 __all__.append('Archive')
215
216 @session_wrapper
217 def get_archive(archive, session=None):
218     """
219     returns database id for given C{archive}.
220
221     @type archive: string
222     @param archive: the name of the arhive
223
224     @type session: Session
225     @param session: Optional SQLA session object (a temporary one will be
226     generated if not supplied)
227
228     @rtype: Archive
229     @return: Archive object for the given name (None if not present)
230
231     """
232     archive = archive.lower()
233
234     q = session.query(Archive).filter_by(archive_name=archive)
235
236     try:
237         return q.one()
238     except NoResultFound:
239         return None
240
241 __all__.append('get_archive')
242
243 ################################################################################
244
245 class BinAssociation(object):
246     def __init__(self, *args, **kwargs):
247         pass
248
249     def __repr__(self):
250         return '<BinAssociation %s (%s, %s)>' % (self.ba_id, self.binary, self.suite)
251
252 __all__.append('BinAssociation')
253
254 ################################################################################
255
256 class BinContents(object):
257     def __init__(self, *args, **kwargs):
258         pass
259
260     def __repr__(self):
261         return '<BinContents (%s, %s)>' % (self.binary, self.filename)
262
263 __all__.append('BinContents')
264
265 ################################################################################
266
267 class DBBinary(object):
268     def __init__(self, *args, **kwargs):
269         pass
270
271     def __repr__(self):
272         return '<DBBinary %s (%s, %s)>' % (self.package, self.version, self.architecture)
273
274 __all__.append('DBBinary')
275
276 @session_wrapper
277 def get_suites_binary_in(package, session=None):
278     """
279     Returns list of Suite objects which given C{package} name is in
280
281     @type source: str
282     @param source: DBBinary package name to search for
283
284     @rtype: list
285     @return: list of Suite objects for the given package
286     """
287
288     return session.query(Suite).join(BinAssociation).join(DBBinary).filter_by(package=package).all()
289
290 __all__.append('get_suites_binary_in')
291
292 @session_wrapper
293 def get_binary_from_id(binary_id, session=None):
294     """
295     Returns DBBinary object for given C{id}
296
297     @type binary_id: int
298     @param binary_id: Id of the required binary
299
300     @type session: Session
301     @param session: Optional SQLA session object (a temporary one will be
302     generated if not supplied)
303
304     @rtype: DBBinary
305     @return: DBBinary object for the given binary (None if not present)
306     """
307
308     q = session.query(DBBinary).filter_by(binary_id=binary_id)
309
310     try:
311         return q.one()
312     except NoResultFound:
313         return None
314
315 __all__.append('get_binary_from_id')
316
317 @session_wrapper
318 def get_binaries_from_name(package, version=None, architecture=None, session=None):
319     """
320     Returns list of DBBinary objects for given C{package} name
321
322     @type package: str
323     @param package: DBBinary package name to search for
324
325     @type version: str or None
326     @param version: Version to search for (or None)
327
328     @type package: str, list or None
329     @param package: Architectures to limit to (or None if no limit)
330
331     @type session: Session
332     @param session: Optional SQL session object (a temporary one will be
333     generated if not supplied)
334
335     @rtype: list
336     @return: list of DBBinary objects for the given name (may be empty)
337     """
338
339     q = session.query(DBBinary).filter_by(package=package)
340
341     if version is not None:
342         q = q.filter_by(version=version)
343
344     if architecture is not None:
345         if not isinstance(architecture, list):
346             architecture = [architecture]
347         q = q.join(Architecture).filter(Architecture.arch_string.in_(architecture))
348
349     ret = q.all()
350
351     return ret
352
353 __all__.append('get_binaries_from_name')
354
355 @session_wrapper
356 def get_binaries_from_source_id(source_id, session=None):
357     """
358     Returns list of DBBinary objects for given C{source_id}
359
360     @type source_id: int
361     @param source_id: source_id to search for
362
363     @type session: Session
364     @param session: Optional SQL session object (a temporary one will be
365     generated if not supplied)
366
367     @rtype: list
368     @return: list of DBBinary objects for the given name (may be empty)
369     """
370
371     return session.query(DBBinary).filter_by(source_id=source_id).all()
372
373 __all__.append('get_binaries_from_source_id')
374
375 @session_wrapper
376 def get_binary_from_name_suite(package, suitename, session=None):
377     ### For dak examine-package
378     ### XXX: Doesn't use object API yet
379
380     sql = """SELECT DISTINCT(b.package), b.version, c.name, su.suite_name
381              FROM binaries b, files fi, location l, component c, bin_associations ba, suite su
382              WHERE b.package='%(package)s'
383                AND b.file = fi.id
384                AND fi.location = l.id
385                AND l.component = c.id
386                AND ba.bin=b.id
387                AND ba.suite = su.id
388                AND su.suite_name %(suitename)s
389           ORDER BY b.version DESC"""
390
391     return session.execute(sql % {'package': package, 'suitename': suitename})
392
393 __all__.append('get_binary_from_name_suite')
394
395 @session_wrapper
396 def get_binary_components(package, suitename, arch, session=None):
397     # Check for packages that have moved from one component to another
398     query = """SELECT c.name FROM binaries b, bin_associations ba, suite s, location l, component c, architecture a, files f
399     WHERE b.package=:package AND s.suite_name=:suitename
400       AND (a.arch_string = :arch OR a.arch_string = 'all')
401       AND ba.bin = b.id AND ba.suite = s.id AND b.architecture = a.id
402       AND f.location = l.id
403       AND l.component = c.id
404       AND b.file = f.id"""
405
406     vals = {'package': package, 'suitename': suitename, 'arch': arch}
407
408     return session.execute(query, vals)
409
410 __all__.append('get_binary_components')
411
412 ################################################################################
413
414 class BinaryACL(object):
415     def __init__(self, *args, **kwargs):
416         pass
417
418     def __repr__(self):
419         return '<BinaryACL %s>' % self.binary_acl_id
420
421 __all__.append('BinaryACL')
422
423 ################################################################################
424
425 class BinaryACLMap(object):
426     def __init__(self, *args, **kwargs):
427         pass
428
429     def __repr__(self):
430         return '<BinaryACLMap %s>' % self.binary_acl_map_id
431
432 __all__.append('BinaryACLMap')
433
434 ################################################################################
435
436 MINIMAL_APT_CONF="""
437 Dir
438 {
439    ArchiveDir "%(archivepath)s";
440    OverrideDir "/srv/ftp.debian.org/scripts/override/";
441    CacheDir "/srv/ftp.debian.org/database/";
442 };
443
444 Default
445 {
446    Packages::Compress ". bzip2 gzip";
447    Sources::Compress ". bzip2 gzip";
448    DeLinkLimit 0;
449    FileMode 0664;
450 }
451
452 bindirectory "incoming"
453 {
454    Packages "Packages";
455    Contents " ";
456
457    BinOverride "override.sid.all3";
458    BinCacheDB "packages-accepted.db";
459
460    FileList "%(filelist)s";
461
462    PathPrefix "";
463    Packages::Extensions ".deb .udeb";
464 };
465
466 bindirectory "incoming/"
467 {
468    Sources "Sources";
469    BinOverride "override.sid.all3";
470    SrcOverride "override.sid.all3.src";
471    FileList "%(filelist)s";
472 };
473 """
474
475 class BuildQueue(object):
476     def __init__(self, *args, **kwargs):
477         pass
478
479     def __repr__(self):
480         return '<BuildQueue %s>' % self.queue_name
481
482     def write_metadata(self, starttime, force=False):
483         # Do we write out metafiles?
484         if not (force or self.generate_metadata):
485             return
486
487         session = DBConn().session().object_session(self)
488
489         fl_fd = fl_name = ac_fd = ac_name = None
490         tempdir = None
491         arches = " ".join([ a.arch_string for a in session.query(Architecture).all() if a.arch_string != 'source' ])
492         startdir = os.getcwd()
493
494         try:
495             # Grab files we want to include
496             newer = session.query(BuildQueueFile).filter_by(build_queue_id = self.queue_id).filter(BuildQueueFile.lastused + timedelta(seconds=self.stay_of_execution) > starttime).all()
497             # Write file list with newer files
498             (fl_fd, fl_name) = mkstemp()
499             for n in newer:
500                 os.write(fl_fd, '%s\n' % n.fullpath)
501             os.close(fl_fd)
502
503             # Write minimal apt.conf
504             # TODO: Remove hardcoding from template
505             (ac_fd, ac_name) = mkstemp()
506             os.write(ac_fd, MINIMAL_APT_CONF % {'archivepath': self.path,
507                                                 'filelist': fl_name})
508             os.close(ac_fd)
509
510             # Run apt-ftparchive generate
511             os.chdir(os.path.dirname(ac_name))
512             os.system('apt-ftparchive -qq -o APT::FTPArchive::Contents=off generate %s' % os.path.basename(ac_name))
513
514             # Run apt-ftparchive release
515             # TODO: Eww - fix this
516             bname = os.path.basename(self.path)
517             os.chdir(self.path)
518             os.chdir('..')
519
520             # We have to remove the Release file otherwise it'll be included in the
521             # new one
522             try:
523                 os.unlink(os.path.join(bname, 'Release'))
524             except OSError:
525                 pass
526
527             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))
528
529             # Sign if necessary
530             if self.signingkey:
531                 cnf = Config()
532                 keyring = "--secret-keyring \"%s\"" % cnf["Dinstall::SigningKeyring"]
533                 if cnf.has_key("Dinstall::SigningPubKeyring"):
534                     keyring += " --keyring \"%s\"" % cnf["Dinstall::SigningPubKeyring"]
535
536                 os.system("gpg %s --no-options --batch --no-tty --armour --default-key %s --detach-sign -o Release.gpg Release""" % (keyring, self.signingkey))
537
538             # Move the files if we got this far
539             os.rename('Release', os.path.join(bname, 'Release'))
540             if self.signingkey:
541                 os.rename('Release.gpg', os.path.join(bname, 'Release.gpg'))
542
543         # Clean up any left behind files
544         finally:
545             os.chdir(startdir)
546             if fl_fd:
547                 try:
548                     os.close(fl_fd)
549                 except OSError:
550                     pass
551
552             if fl_name:
553                 try:
554                     os.unlink(fl_name)
555                 except OSError:
556                     pass
557
558             if ac_fd:
559                 try:
560                     os.close(ac_fd)
561                 except OSError:
562                     pass
563
564             if ac_name:
565                 try:
566                     os.unlink(ac_name)
567                 except OSError:
568                     pass
569
570     def clean_and_update(self, starttime, Logger, dryrun=False):
571         """WARNING: This routine commits for you"""
572         session = DBConn().session().object_session(self)
573
574         if self.generate_metadata and not dryrun:
575             self.write_metadata(starttime)
576
577         # Grab files older than our execution time
578         older = session.query(BuildQueueFile).filter_by(build_queue_id = self.queue_id).filter(BuildQueueFile.lastused + timedelta(seconds=self.stay_of_execution) <= starttime).all()
579
580         for o in older:
581             killdb = False
582             try:
583                 if dryrun:
584                     Logger.log(["I: Would have removed %s from the queue" % o.fullpath])
585                 else:
586                     Logger.log(["I: Removing %s from the queue" % o.fullpath])
587                     os.unlink(o.fullpath)
588                     killdb = True
589             except OSError, e:
590                 # If it wasn't there, don't worry
591                 if e.errno == ENOENT:
592                     killdb = True
593                 else:
594                     # TODO: Replace with proper logging call
595                     Logger.log(["E: Could not remove %s" % o.fullpath])
596
597             if killdb:
598                 session.delete(o)
599
600         session.commit()
601
602         for f in os.listdir(self.path):
603             if f.startswith('Packages') or f.startswith('Source') or f.startswith('Release'):
604                 continue
605
606             try:
607                 r = session.query(BuildQueueFile).filter_by(build_queue_id = self.queue_id).filter_by(filename = f).one()
608             except NoResultFound:
609                 fp = os.path.join(self.path, f)
610                 if dryrun:
611                     Logger.log(["I: Would remove unused link %s" % fp])
612                 else:
613                     Logger.log(["I: Removing unused link %s" % fp])
614                     try:
615                         os.unlink(fp)
616                     except OSError:
617                         Logger.log(["E: Failed to unlink unreferenced file %s" % r.fullpath])
618
619     def add_file_from_pool(self, poolfile):
620         """Copies a file into the pool.  Assumes that the PoolFile object is
621         attached to the same SQLAlchemy session as the Queue object is.
622
623         The caller is responsible for committing after calling this function."""
624         poolfile_basename = poolfile.filename[poolfile.filename.rindex(os.sep)+1:]
625
626         # Check if we have a file of this name or this ID already
627         for f in self.queuefiles:
628             if f.fileid is not None and f.fileid == poolfile.file_id or \
629                f.poolfile.filename == poolfile_basename:
630                    # In this case, update the BuildQueueFile entry so we
631                    # don't remove it too early
632                    f.lastused = datetime.now()
633                    DBConn().session().object_session(poolfile).add(f)
634                    return f
635
636         # Prepare BuildQueueFile object
637         qf = BuildQueueFile()
638         qf.build_queue_id = self.queue_id
639         qf.lastused = datetime.now()
640         qf.filename = poolfile_basename
641
642         targetpath = poolfile.fullpath
643         queuepath = os.path.join(self.path, poolfile_basename)
644
645         try:
646             if self.copy_files:
647                 # We need to copy instead of symlink
648                 import utils
649                 utils.copy(targetpath, queuepath)
650                 # NULL in the fileid field implies a copy
651                 qf.fileid = None
652             else:
653                 os.symlink(targetpath, queuepath)
654                 qf.fileid = poolfile.file_id
655         except OSError:
656             return None
657
658         # Get the same session as the PoolFile is using and add the qf to it
659         DBConn().session().object_session(poolfile).add(qf)
660
661         return qf
662
663
664 __all__.append('BuildQueue')
665
666 @session_wrapper
667 def get_build_queue(queuename, session=None):
668     """
669     Returns BuildQueue object for given C{queue name}, creating it if it does not
670     exist.
671
672     @type queuename: string
673     @param queuename: The name of the queue
674
675     @type session: Session
676     @param session: Optional SQLA session object (a temporary one will be
677     generated if not supplied)
678
679     @rtype: BuildQueue
680     @return: BuildQueue object for the given queue
681     """
682
683     q = session.query(BuildQueue).filter_by(queue_name=queuename)
684
685     try:
686         return q.one()
687     except NoResultFound:
688         return None
689
690 __all__.append('get_build_queue')
691
692 ################################################################################
693
694 class BuildQueueFile(object):
695     def __init__(self, *args, **kwargs):
696         pass
697
698     def __repr__(self):
699         return '<BuildQueueFile %s (%s)>' % (self.filename, self.build_queue_id)
700
701     @property
702     def fullpath(self):
703         return os.path.join(self.buildqueue.path, self.filename)
704
705
706 __all__.append('BuildQueueFile')
707
708 ################################################################################
709
710 class ChangePendingBinary(object):
711     def __init__(self, *args, **kwargs):
712         pass
713
714     def __repr__(self):
715         return '<ChangePendingBinary %s>' % self.change_pending_binary_id
716
717 __all__.append('ChangePendingBinary')
718
719 ################################################################################
720
721 class ChangePendingFile(object):
722     def __init__(self, *args, **kwargs):
723         pass
724
725     def __repr__(self):
726         return '<ChangePendingFile %s>' % self.change_pending_file_id
727
728 __all__.append('ChangePendingFile')
729
730 ################################################################################
731
732 class ChangePendingSource(object):
733     def __init__(self, *args, **kwargs):
734         pass
735
736     def __repr__(self):
737         return '<ChangePendingSource %s>' % self.change_pending_source_id
738
739 __all__.append('ChangePendingSource')
740
741 ################################################################################
742
743 class Component(object):
744     def __init__(self, *args, **kwargs):
745         pass
746
747     def __eq__(self, val):
748         if isinstance(val, str):
749             return (self.component_name == val)
750         # This signals to use the normal comparison operator
751         return NotImplemented
752
753     def __ne__(self, val):
754         if isinstance(val, str):
755             return (self.component_name != val)
756         # This signals to use the normal comparison operator
757         return NotImplemented
758
759     def __repr__(self):
760         return '<Component %s>' % self.component_name
761
762
763 __all__.append('Component')
764
765 @session_wrapper
766 def get_component(component, session=None):
767     """
768     Returns database id for given C{component}.
769
770     @type component: string
771     @param component: The name of the override type
772
773     @rtype: int
774     @return: the database id for the given component
775
776     """
777     component = component.lower()
778
779     q = session.query(Component).filter_by(component_name=component)
780
781     try:
782         return q.one()
783     except NoResultFound:
784         return None
785
786 __all__.append('get_component')
787
788 ################################################################################
789
790 class DBConfig(object):
791     def __init__(self, *args, **kwargs):
792         pass
793
794     def __repr__(self):
795         return '<DBConfig %s>' % self.name
796
797 __all__.append('DBConfig')
798
799 ################################################################################
800
801 @session_wrapper
802 def get_or_set_contents_file_id(filename, session=None):
803     """
804     Returns database id for given filename.
805
806     If no matching file is found, a row is inserted.
807
808     @type filename: string
809     @param filename: The filename
810     @type session: SQLAlchemy
811     @param session: Optional SQL session object (a temporary one will be
812     generated if not supplied).  If not passed, a commit will be performed at
813     the end of the function, otherwise the caller is responsible for commiting.
814
815     @rtype: int
816     @return: the database id for the given component
817     """
818
819     q = session.query(ContentFilename).filter_by(filename=filename)
820
821     try:
822         ret = q.one().cafilename_id
823     except NoResultFound:
824         cf = ContentFilename()
825         cf.filename = filename
826         session.add(cf)
827         session.commit_or_flush()
828         ret = cf.cafilename_id
829
830     return ret
831
832 __all__.append('get_or_set_contents_file_id')
833
834 @session_wrapper
835 def get_contents(suite, overridetype, section=None, session=None):
836     """
837     Returns contents for a suite / overridetype combination, limiting
838     to a section if not None.
839
840     @type suite: Suite
841     @param suite: Suite object
842
843     @type overridetype: OverrideType
844     @param overridetype: OverrideType object
845
846     @type section: Section
847     @param section: Optional section object to limit results to
848
849     @type session: SQLAlchemy
850     @param session: Optional SQL session object (a temporary one will be
851     generated if not supplied)
852
853     @rtype: ResultsProxy
854     @return: ResultsProxy object set up to return tuples of (filename, section,
855     package, arch_id)
856     """
857
858     # find me all of the contents for a given suite
859     contents_q = """SELECT (p.path||'/'||n.file) AS fn,
860                             s.section,
861                             b.package,
862                             b.architecture
863                    FROM content_associations c join content_file_paths p ON (c.filepath=p.id)
864                    JOIN content_file_names n ON (c.filename=n.id)
865                    JOIN binaries b ON (b.id=c.binary_pkg)
866                    JOIN override o ON (o.package=b.package)
867                    JOIN section s ON (s.id=o.section)
868                    WHERE o.suite = :suiteid AND o.type = :overridetypeid
869                    AND b.type=:overridetypename"""
870
871     vals = {'suiteid': suite.suite_id,
872             'overridetypeid': overridetype.overridetype_id,
873             'overridetypename': overridetype.overridetype}
874
875     if section is not None:
876         contents_q += " AND s.id = :sectionid"
877         vals['sectionid'] = section.section_id
878
879     contents_q += " ORDER BY fn"
880
881     return session.execute(contents_q, vals)
882
883 __all__.append('get_contents')
884
885 ################################################################################
886
887 class ContentFilepath(object):
888     def __init__(self, *args, **kwargs):
889         pass
890
891     def __repr__(self):
892         return '<ContentFilepath %s>' % self.filepath
893
894 __all__.append('ContentFilepath')
895
896 @session_wrapper
897 def get_or_set_contents_path_id(filepath, session=None):
898     """
899     Returns database id for given path.
900
901     If no matching file is found, a row is inserted.
902
903     @type filename: string
904     @param filename: The filepath
905     @type session: SQLAlchemy
906     @param session: Optional SQL session object (a temporary one will be
907     generated if not supplied).  If not passed, a commit will be performed at
908     the end of the function, otherwise the caller is responsible for commiting.
909
910     @rtype: int
911     @return: the database id for the given path
912     """
913
914     q = session.query(ContentFilepath).filter_by(filepath=filepath)
915
916     try:
917         ret = q.one().cafilepath_id
918     except NoResultFound:
919         cf = ContentFilepath()
920         cf.filepath = filepath
921         session.add(cf)
922         session.commit_or_flush()
923         ret = cf.cafilepath_id
924
925     return ret
926
927 __all__.append('get_or_set_contents_path_id')
928
929 ################################################################################
930
931 class ContentAssociation(object):
932     def __init__(self, *args, **kwargs):
933         pass
934
935     def __repr__(self):
936         return '<ContentAssociation %s>' % self.ca_id
937
938 __all__.append('ContentAssociation')
939
940 def insert_content_paths(binary_id, fullpaths, session=None):
941     """
942     Make sure given path is associated with given binary id
943
944     @type binary_id: int
945     @param binary_id: the id of the binary
946     @type fullpaths: list
947     @param fullpaths: the list of paths of the file being associated with the binary
948     @type session: SQLAlchemy session
949     @param session: Optional SQLAlchemy session.  If this is passed, the caller
950     is responsible for ensuring a transaction has begun and committing the
951     results or rolling back based on the result code.  If not passed, a commit
952     will be performed at the end of the function, otherwise the caller is
953     responsible for commiting.
954
955     @return: True upon success
956     """
957
958     privatetrans = False
959     if session is None:
960         session = DBConn().session()
961         privatetrans = True
962
963     try:
964         # Insert paths
965         pathcache = {}
966
967         def generate_path_dicts():
968             for fullpath in fullpaths:
969                 if fullpath.startswith( './' ):
970                     fullpath = fullpath[2:]
971
972                 yield {'fulename':fullpath, 'id': binary_id }
973
974         session.execute( "INSERT INTO bin_contents ( file, binary_id ) VALUES ( :filename, :id )",
975                          generate_path_dicts() )
976
977         session.commit()
978         if privatetrans:
979             session.close()
980         return True
981
982     except:
983         traceback.print_exc()
984
985         # Only rollback if we set up the session ourself
986         if privatetrans:
987             session.rollback()
988             session.close()
989
990         return False
991
992 __all__.append('insert_content_paths')
993
994 ################################################################################
995
996 class DSCFile(object):
997     def __init__(self, *args, **kwargs):
998         pass
999
1000     def __repr__(self):
1001         return '<DSCFile %s>' % self.dscfile_id
1002
1003 __all__.append('DSCFile')
1004
1005 @session_wrapper
1006 def get_dscfiles(dscfile_id=None, source_id=None, poolfile_id=None, session=None):
1007     """
1008     Returns a list of DSCFiles which may be empty
1009
1010     @type dscfile_id: int (optional)
1011     @param dscfile_id: the dscfile_id of the DSCFiles to find
1012
1013     @type source_id: int (optional)
1014     @param source_id: the source id related to the DSCFiles to find
1015
1016     @type poolfile_id: int (optional)
1017     @param poolfile_id: the poolfile id related to the DSCFiles to find
1018
1019     @rtype: list
1020     @return: Possibly empty list of DSCFiles
1021     """
1022
1023     q = session.query(DSCFile)
1024
1025     if dscfile_id is not None:
1026         q = q.filter_by(dscfile_id=dscfile_id)
1027
1028     if source_id is not None:
1029         q = q.filter_by(source_id=source_id)
1030
1031     if poolfile_id is not None:
1032         q = q.filter_by(poolfile_id=poolfile_id)
1033
1034     return q.all()
1035
1036 __all__.append('get_dscfiles')
1037
1038 ################################################################################
1039
1040 class PoolFile(object):
1041     def __init__(self, *args, **kwargs):
1042         pass
1043
1044     def __repr__(self):
1045         return '<PoolFile %s>' % self.filename
1046
1047     @property
1048     def fullpath(self):
1049         return os.path.join(self.location.path, self.filename)
1050
1051 __all__.append('PoolFile')
1052
1053 @session_wrapper
1054 def check_poolfile(filename, filesize, md5sum, location_id, session=None):
1055     """
1056     Returns a tuple:
1057      (ValidFileFound [boolean or None], PoolFile object or None)
1058
1059     @type filename: string
1060     @param filename: the filename of the file to check against the DB
1061
1062     @type filesize: int
1063     @param filesize: the size of the file to check against the DB
1064
1065     @type md5sum: string
1066     @param md5sum: the md5sum of the file to check against the DB
1067
1068     @type location_id: int
1069     @param location_id: the id of the location to look in
1070
1071     @rtype: tuple
1072     @return: Tuple of length 2.
1073              If more than one file found with that name:
1074                     (None,  None)
1075              If valid pool file found: (True, PoolFile object)
1076              If valid pool file not found:
1077                     (False, None) if no file found
1078                     (False, PoolFile object) if file found with size/md5sum mismatch
1079     """
1080
1081     q = session.query(PoolFile).filter_by(filename=filename)
1082     q = q.join(Location).filter_by(location_id=location_id)
1083
1084     ret = None
1085
1086     if q.count() > 1:
1087         ret = (None, None)
1088     elif q.count() < 1:
1089         ret = (False, None)
1090     else:
1091         obj = q.one()
1092         if obj.md5sum != md5sum or obj.filesize != int(filesize):
1093             ret = (False, obj)
1094
1095     if ret is None:
1096         ret = (True, obj)
1097
1098     return ret
1099
1100 __all__.append('check_poolfile')
1101
1102 @session_wrapper
1103 def get_poolfile_by_id(file_id, session=None):
1104     """
1105     Returns a PoolFile objects or None for the given id
1106
1107     @type file_id: int
1108     @param file_id: the id of the file to look for
1109
1110     @rtype: PoolFile or None
1111     @return: either the PoolFile object or None
1112     """
1113
1114     q = session.query(PoolFile).filter_by(file_id=file_id)
1115
1116     try:
1117         return q.one()
1118     except NoResultFound:
1119         return None
1120
1121 __all__.append('get_poolfile_by_id')
1122
1123
1124 @session_wrapper
1125 def get_poolfile_by_name(filename, location_id=None, session=None):
1126     """
1127     Returns an array of PoolFile objects for the given filename and
1128     (optionally) location_id
1129
1130     @type filename: string
1131     @param filename: the filename of the file to check against the DB
1132
1133     @type location_id: int
1134     @param location_id: the id of the location to look in (optional)
1135
1136     @rtype: array
1137     @return: array of PoolFile objects
1138     """
1139
1140     q = session.query(PoolFile).filter_by(filename=filename)
1141
1142     if location_id is not None:
1143         q = q.join(Location).filter_by(location_id=location_id)
1144
1145     return q.all()
1146
1147 __all__.append('get_poolfile_by_name')
1148
1149 @session_wrapper
1150 def get_poolfile_like_name(filename, session=None):
1151     """
1152     Returns an array of PoolFile objects which are like the given name
1153
1154     @type filename: string
1155     @param filename: the filename of the file to check against the DB
1156
1157     @rtype: array
1158     @return: array of PoolFile objects
1159     """
1160
1161     # TODO: There must be a way of properly using bind parameters with %FOO%
1162     q = session.query(PoolFile).filter(PoolFile.filename.like('%%/%s' % filename))
1163
1164     return q.all()
1165
1166 __all__.append('get_poolfile_like_name')
1167
1168 @session_wrapper
1169 def add_poolfile(filename, datadict, location_id, session=None):
1170     """
1171     Add a new file to the pool
1172
1173     @type filename: string
1174     @param filename: filename
1175
1176     @type datadict: dict
1177     @param datadict: dict with needed data
1178
1179     @type location_id: int
1180     @param location_id: database id of the location
1181
1182     @rtype: PoolFile
1183     @return: the PoolFile object created
1184     """
1185     poolfile = PoolFile()
1186     poolfile.filename = filename
1187     poolfile.filesize = datadict["size"]
1188     poolfile.md5sum = datadict["md5sum"]
1189     poolfile.sha1sum = datadict["sha1sum"]
1190     poolfile.sha256sum = datadict["sha256sum"]
1191     poolfile.location_id = location_id
1192
1193     session.add(poolfile)
1194     # Flush to get a file id (NB: This is not a commit)
1195     session.flush()
1196
1197     return poolfile
1198
1199 __all__.append('add_poolfile')
1200
1201 ################################################################################
1202
1203 class Fingerprint(object):
1204     def __init__(self, *args, **kwargs):
1205         pass
1206
1207     def __repr__(self):
1208         return '<Fingerprint %s>' % self.fingerprint
1209
1210 __all__.append('Fingerprint')
1211
1212 @session_wrapper
1213 def get_fingerprint(fpr, session=None):
1214     """
1215     Returns Fingerprint object for given fpr.
1216
1217     @type fpr: string
1218     @param fpr: The fpr to find / add
1219
1220     @type session: SQLAlchemy
1221     @param session: Optional SQL session object (a temporary one will be
1222     generated if not supplied).
1223
1224     @rtype: Fingerprint
1225     @return: the Fingerprint object for the given fpr or None
1226     """
1227
1228     q = session.query(Fingerprint).filter_by(fingerprint=fpr)
1229
1230     try:
1231         ret = q.one()
1232     except NoResultFound:
1233         ret = None
1234
1235     return ret
1236
1237 __all__.append('get_fingerprint')
1238
1239 @session_wrapper
1240 def get_or_set_fingerprint(fpr, session=None):
1241     """
1242     Returns Fingerprint object for given fpr.
1243
1244     If no matching fpr is found, a row is inserted.
1245
1246     @type fpr: string
1247     @param fpr: The fpr to find / add
1248
1249     @type session: SQLAlchemy
1250     @param session: Optional SQL session object (a temporary one will be
1251     generated if not supplied).  If not passed, a commit will be performed at
1252     the end of the function, otherwise the caller is responsible for commiting.
1253     A flush will be performed either way.
1254
1255     @rtype: Fingerprint
1256     @return: the Fingerprint object for the given fpr
1257     """
1258
1259     q = session.query(Fingerprint).filter_by(fingerprint=fpr)
1260
1261     try:
1262         ret = q.one()
1263     except NoResultFound:
1264         fingerprint = Fingerprint()
1265         fingerprint.fingerprint = fpr
1266         session.add(fingerprint)
1267         session.commit_or_flush()
1268         ret = fingerprint
1269
1270     return ret
1271
1272 __all__.append('get_or_set_fingerprint')
1273
1274 ################################################################################
1275
1276 # Helper routine for Keyring class
1277 def get_ldap_name(entry):
1278     name = []
1279     for k in ["cn", "mn", "sn"]:
1280         ret = entry.get(k)
1281         if ret and ret[0] != "" and ret[0] != "-":
1282             name.append(ret[0])
1283     return " ".join(name)
1284
1285 ################################################################################
1286
1287 class Keyring(object):
1288     gpg_invocation = "gpg --no-default-keyring --keyring %s" +\
1289                      " --with-colons --fingerprint --fingerprint"
1290
1291     keys = {}
1292     fpr_lookup = {}
1293
1294     def __init__(self, *args, **kwargs):
1295         pass
1296
1297     def __repr__(self):
1298         return '<Keyring %s>' % self.keyring_name
1299
1300     def de_escape_gpg_str(self, txt):
1301         esclist = re.split(r'(\\x..)', txt)
1302         for x in range(1,len(esclist),2):
1303             esclist[x] = "%c" % (int(esclist[x][2:],16))
1304         return "".join(esclist)
1305
1306     def load_keys(self, keyring):
1307         import email.Utils
1308
1309         if not self.keyring_id:
1310             raise Exception('Must be initialized with database information')
1311
1312         k = os.popen(self.gpg_invocation % keyring, "r")
1313         key = None
1314         signingkey = False
1315
1316         for line in k.xreadlines():
1317             field = line.split(":")
1318             if field[0] == "pub":
1319                 key = field[4]
1320                 (name, addr) = email.Utils.parseaddr(field[9])
1321                 name = re.sub(r"\s*[(].*[)]", "", name)
1322                 if name == "" or addr == "" or "@" not in addr:
1323                     name = field[9]
1324                     addr = "invalid-uid"
1325                 name = self.de_escape_gpg_str(name)
1326                 self.keys[key] = {"email": addr}
1327                 if name != "":
1328                     self.keys[key]["name"] = name
1329                 self.keys[key]["aliases"] = [name]
1330                 self.keys[key]["fingerprints"] = []
1331                 signingkey = True
1332             elif key and field[0] == "sub" and len(field) >= 12:
1333                 signingkey = ("s" in field[11])
1334             elif key and field[0] == "uid":
1335                 (name, addr) = email.Utils.parseaddr(field[9])
1336                 if name and name not in self.keys[key]["aliases"]:
1337                     self.keys[key]["aliases"].append(name)
1338             elif signingkey and field[0] == "fpr":
1339                 self.keys[key]["fingerprints"].append(field[9])
1340                 self.fpr_lookup[field[9]] = key
1341
1342     def import_users_from_ldap(self, session):
1343         import ldap
1344         cnf = Config()
1345
1346         LDAPDn = cnf["Import-LDAP-Fingerprints::LDAPDn"]
1347         LDAPServer = cnf["Import-LDAP-Fingerprints::LDAPServer"]
1348
1349         l = ldap.open(LDAPServer)
1350         l.simple_bind_s("","")
1351         Attrs = l.search_s(LDAPDn, ldap.SCOPE_ONELEVEL,
1352                "(&(keyfingerprint=*)(gidnumber=%s))" % (cnf["Import-Users-From-Passwd::ValidGID"]),
1353                ["uid", "keyfingerprint", "cn", "mn", "sn"])
1354
1355         ldap_fin_uid_id = {}
1356
1357         byuid = {}
1358         byname = {}
1359
1360         for i in Attrs:
1361             entry = i[1]
1362             uid = entry["uid"][0]
1363             name = get_ldap_name(entry)
1364             fingerprints = entry["keyFingerPrint"]
1365             keyid = None
1366             for f in fingerprints:
1367                 key = self.fpr_lookup.get(f, None)
1368                 if key not in self.keys:
1369                     continue
1370                 self.keys[key]["uid"] = uid
1371
1372                 if keyid != None:
1373                     continue
1374                 keyid = get_or_set_uid(uid, session).uid_id
1375                 byuid[keyid] = (uid, name)
1376                 byname[uid] = (keyid, name)
1377
1378         return (byname, byuid)
1379
1380     def generate_users_from_keyring(self, format, session):
1381         byuid = {}
1382         byname = {}
1383         any_invalid = False
1384         for x in self.keys.keys():
1385             if self.keys[x]["email"] == "invalid-uid":
1386                 any_invalid = True
1387                 self.keys[x]["uid"] = format % "invalid-uid"
1388             else:
1389                 uid = format % self.keys[x]["email"]
1390                 keyid = get_or_set_uid(uid, session).uid_id
1391                 byuid[keyid] = (uid, self.keys[x]["name"])
1392                 byname[uid] = (keyid, self.keys[x]["name"])
1393                 self.keys[x]["uid"] = uid
1394
1395         if any_invalid:
1396             uid = format % "invalid-uid"
1397             keyid = get_or_set_uid(uid, session).uid_id
1398             byuid[keyid] = (uid, "ungeneratable user id")
1399             byname[uid] = (keyid, "ungeneratable user id")
1400
1401         return (byname, byuid)
1402
1403 __all__.append('Keyring')
1404
1405 @session_wrapper
1406 def get_keyring(keyring, session=None):
1407     """
1408     If C{keyring} does not have an entry in the C{keyrings} table yet, return None
1409     If C{keyring} already has an entry, simply return the existing Keyring
1410
1411     @type keyring: string
1412     @param keyring: the keyring name
1413
1414     @rtype: Keyring
1415     @return: the Keyring object for this keyring
1416     """
1417
1418     q = session.query(Keyring).filter_by(keyring_name=keyring)
1419
1420     try:
1421         return q.one()
1422     except NoResultFound:
1423         return None
1424
1425 __all__.append('get_keyring')
1426
1427 ################################################################################
1428
1429 class KeyringACLMap(object):
1430     def __init__(self, *args, **kwargs):
1431         pass
1432
1433     def __repr__(self):
1434         return '<KeyringACLMap %s>' % self.keyring_acl_map_id
1435
1436 __all__.append('KeyringACLMap')
1437
1438 ################################################################################
1439
1440 class DBChange(object):
1441     def __init__(self, *args, **kwargs):
1442         pass
1443
1444     def __repr__(self):
1445         return '<DBChange %s>' % self.changesname
1446
1447     def clean_from_queue(self):
1448         session = DBConn().session().object_session(self)
1449
1450         # Remove changes_pool_files entries
1451         self.poolfiles = []
1452
1453         # Remove changes_pending_files references
1454         self.files = []
1455
1456         # Clear out of queue
1457         self.in_queue = None
1458         self.approved_for_id = None
1459
1460 __all__.append('DBChange')
1461
1462 @session_wrapper
1463 def get_dbchange(filename, session=None):
1464     """
1465     returns DBChange object for given C{filename}.
1466
1467     @type archive: string
1468     @param archive: the name of the arhive
1469
1470     @type session: Session
1471     @param session: Optional SQLA session object (a temporary one will be
1472     generated if not supplied)
1473
1474     @rtype: Archive
1475     @return: Archive object for the given name (None if not present)
1476
1477     """
1478     q = session.query(DBChange).filter_by(changesname=filename)
1479
1480     try:
1481         return q.one()
1482     except NoResultFound:
1483         return None
1484
1485 __all__.append('get_dbchange')
1486
1487 ################################################################################
1488
1489 class Location(object):
1490     def __init__(self, *args, **kwargs):
1491         pass
1492
1493     def __repr__(self):
1494         return '<Location %s (%s)>' % (self.path, self.location_id)
1495
1496 __all__.append('Location')
1497
1498 @session_wrapper
1499 def get_location(location, component=None, archive=None, session=None):
1500     """
1501     Returns Location object for the given combination of location, component
1502     and archive
1503
1504     @type location: string
1505     @param location: the path of the location, e.g. I{/srv/ftp.debian.org/ftp/pool/}
1506
1507     @type component: string
1508     @param component: the component name (if None, no restriction applied)
1509
1510     @type archive: string
1511     @param archive_id: the archive name (if None, no restriction applied)
1512
1513     @rtype: Location / None
1514     @return: Either a Location object or None if one can't be found
1515     """
1516
1517     q = session.query(Location).filter_by(path=location)
1518
1519     if archive is not None:
1520         q = q.join(Archive).filter_by(archive_name=archive)
1521
1522     if component is not None:
1523         q = q.join(Component).filter_by(component_name=component)
1524
1525     try:
1526         return q.one()
1527     except NoResultFound:
1528         return None
1529
1530 __all__.append('get_location')
1531
1532 ################################################################################
1533
1534 class Maintainer(object):
1535     def __init__(self, *args, **kwargs):
1536         pass
1537
1538     def __repr__(self):
1539         return '''<Maintainer '%s' (%s)>''' % (self.name, self.maintainer_id)
1540
1541     def get_split_maintainer(self):
1542         if not hasattr(self, 'name') or self.name is None:
1543             return ('', '', '', '')
1544
1545         return fix_maintainer(self.name.strip())
1546
1547 __all__.append('Maintainer')
1548
1549 @session_wrapper
1550 def get_or_set_maintainer(name, session=None):
1551     """
1552     Returns Maintainer object for given maintainer name.
1553
1554     If no matching maintainer name is found, a row is inserted.
1555
1556     @type name: string
1557     @param name: The maintainer name to add
1558
1559     @type session: SQLAlchemy
1560     @param session: Optional SQL session object (a temporary one will be
1561     generated if not supplied).  If not passed, a commit will be performed at
1562     the end of the function, otherwise the caller is responsible for commiting.
1563     A flush will be performed either way.
1564
1565     @rtype: Maintainer
1566     @return: the Maintainer object for the given maintainer
1567     """
1568
1569     q = session.query(Maintainer).filter_by(name=name)
1570     try:
1571         ret = q.one()
1572     except NoResultFound:
1573         maintainer = Maintainer()
1574         maintainer.name = name
1575         session.add(maintainer)
1576         session.commit_or_flush()
1577         ret = maintainer
1578
1579     return ret
1580
1581 __all__.append('get_or_set_maintainer')
1582
1583 @session_wrapper
1584 def get_maintainer(maintainer_id, session=None):
1585     """
1586     Return the name of the maintainer behind C{maintainer_id} or None if that
1587     maintainer_id is invalid.
1588
1589     @type maintainer_id: int
1590     @param maintainer_id: the id of the maintainer
1591
1592     @rtype: Maintainer
1593     @return: the Maintainer with this C{maintainer_id}
1594     """
1595
1596     return session.query(Maintainer).get(maintainer_id)
1597
1598 __all__.append('get_maintainer')
1599
1600 ################################################################################
1601
1602 class NewComment(object):
1603     def __init__(self, *args, **kwargs):
1604         pass
1605
1606     def __repr__(self):
1607         return '''<NewComment for '%s %s' (%s)>''' % (self.package, self.version, self.comment_id)
1608
1609 __all__.append('NewComment')
1610
1611 @session_wrapper
1612 def has_new_comment(package, version, session=None):
1613     """
1614     Returns true if the given combination of C{package}, C{version} has a comment.
1615
1616     @type package: string
1617     @param package: name of the package
1618
1619     @type version: string
1620     @param version: package version
1621
1622     @type session: Session
1623     @param session: Optional SQLA session object (a temporary one will be
1624     generated if not supplied)
1625
1626     @rtype: boolean
1627     @return: true/false
1628     """
1629
1630     q = session.query(NewComment)
1631     q = q.filter_by(package=package)
1632     q = q.filter_by(version=version)
1633
1634     return bool(q.count() > 0)
1635
1636 __all__.append('has_new_comment')
1637
1638 @session_wrapper
1639 def get_new_comments(package=None, version=None, comment_id=None, session=None):
1640     """
1641     Returns (possibly empty) list of NewComment objects for the given
1642     parameters
1643
1644     @type package: string (optional)
1645     @param package: name of the package
1646
1647     @type version: string (optional)
1648     @param version: package version
1649
1650     @type comment_id: int (optional)
1651     @param comment_id: An id of a comment
1652
1653     @type session: Session
1654     @param session: Optional SQLA session object (a temporary one will be
1655     generated if not supplied)
1656
1657     @rtype: list
1658     @return: A (possibly empty) list of NewComment objects will be returned
1659     """
1660
1661     q = session.query(NewComment)
1662     if package is not None: q = q.filter_by(package=package)
1663     if version is not None: q = q.filter_by(version=version)
1664     if comment_id is not None: q = q.filter_by(comment_id=comment_id)
1665
1666     return q.all()
1667
1668 __all__.append('get_new_comments')
1669
1670 ################################################################################
1671
1672 class Override(object):
1673     def __init__(self, *args, **kwargs):
1674         pass
1675
1676     def __repr__(self):
1677         return '<Override %s (%s)>' % (self.package, self.suite_id)
1678
1679 __all__.append('Override')
1680
1681 @session_wrapper
1682 def get_override(package, suite=None, component=None, overridetype=None, session=None):
1683     """
1684     Returns Override object for the given parameters
1685
1686     @type package: string
1687     @param package: The name of the package
1688
1689     @type suite: string, list or None
1690     @param suite: The name of the suite (or suites if a list) to limit to.  If
1691                   None, don't limit.  Defaults to None.
1692
1693     @type component: string, list or None
1694     @param component: The name of the component (or components if a list) to
1695                       limit to.  If None, don't limit.  Defaults to None.
1696
1697     @type overridetype: string, list or None
1698     @param overridetype: The name of the overridetype (or overridetypes if a list) to
1699                          limit to.  If None, don't limit.  Defaults to None.
1700
1701     @type session: Session
1702     @param session: Optional SQLA session object (a temporary one will be
1703     generated if not supplied)
1704
1705     @rtype: list
1706     @return: A (possibly empty) list of Override objects will be returned
1707     """
1708
1709     q = session.query(Override)
1710     q = q.filter_by(package=package)
1711
1712     if suite is not None:
1713         if not isinstance(suite, list): suite = [suite]
1714         q = q.join(Suite).filter(Suite.suite_name.in_(suite))
1715
1716     if component is not None:
1717         if not isinstance(component, list): component = [component]
1718         q = q.join(Component).filter(Component.component_name.in_(component))
1719
1720     if overridetype is not None:
1721         if not isinstance(overridetype, list): overridetype = [overridetype]
1722         q = q.join(OverrideType).filter(OverrideType.overridetype.in_(overridetype))
1723
1724     return q.all()
1725
1726 __all__.append('get_override')
1727
1728
1729 ################################################################################
1730
1731 class OverrideType(object):
1732     def __init__(self, *args, **kwargs):
1733         pass
1734
1735     def __repr__(self):
1736         return '<OverrideType %s>' % self.overridetype
1737
1738 __all__.append('OverrideType')
1739
1740 @session_wrapper
1741 def get_override_type(override_type, session=None):
1742     """
1743     Returns OverrideType object for given C{override type}.
1744
1745     @type override_type: string
1746     @param override_type: The name of the override type
1747
1748     @type session: Session
1749     @param session: Optional SQLA session object (a temporary one will be
1750     generated if not supplied)
1751
1752     @rtype: int
1753     @return: the database id for the given override type
1754     """
1755
1756     q = session.query(OverrideType).filter_by(overridetype=override_type)
1757
1758     try:
1759         return q.one()
1760     except NoResultFound:
1761         return None
1762
1763 __all__.append('get_override_type')
1764
1765 ################################################################################
1766
1767 class DebContents(object):
1768     def __init__(self, *args, **kwargs):
1769         pass
1770
1771     def __repr__(self):
1772         return '<DebConetnts %s: %s>' % (self.package.package,self.file)
1773
1774 __all__.append('DebContents')
1775
1776
1777 class UdebContents(object):
1778     def __init__(self, *args, **kwargs):
1779         pass
1780
1781     def __repr__(self):
1782         return '<UdebConetnts %s: %s>' % (self.package.package,self.file)
1783
1784 __all__.append('UdebContents')
1785
1786 class PendingBinContents(object):
1787     def __init__(self, *args, **kwargs):
1788         pass
1789
1790     def __repr__(self):
1791         return '<PendingBinContents %s>' % self.contents_id
1792
1793 __all__.append('PendingBinContents')
1794
1795 def insert_pending_content_paths(package,
1796                                  is_udeb,
1797                                  fullpaths,
1798                                  session=None):
1799     """
1800     Make sure given paths are temporarily associated with given
1801     package
1802
1803     @type package: dict
1804     @param package: the package to associate with should have been read in from the binary control file
1805     @type fullpaths: list
1806     @param fullpaths: the list of paths of the file being associated with the binary
1807     @type session: SQLAlchemy session
1808     @param session: Optional SQLAlchemy session.  If this is passed, the caller
1809     is responsible for ensuring a transaction has begun and committing the
1810     results or rolling back based on the result code.  If not passed, a commit
1811     will be performed at the end of the function
1812
1813     @return: True upon success, False if there is a problem
1814     """
1815
1816     privatetrans = False
1817
1818     if session is None:
1819         session = DBConn().session()
1820         privatetrans = True
1821
1822     try:
1823         arch = get_architecture(package['Architecture'], session)
1824         arch_id = arch.arch_id
1825
1826         # Remove any already existing recorded files for this package
1827         q = session.query(PendingBinContents)
1828         q = q.filter_by(package=package['Package'])
1829         q = q.filter_by(version=package['Version'])
1830         q = q.filter_by(architecture=arch_id)
1831         q.delete()
1832
1833         for fullpath in fullpaths:
1834
1835             if fullpath.startswith( "./" ):
1836                 fullpath = fullpath[2:]
1837
1838             pca = PendingBinContents()
1839             pca.package = package['Package']
1840             pca.version = package['Version']
1841             pca.file = fullpath
1842             pca.architecture = arch_id
1843
1844             if isudeb:
1845                 pca.type = 8 # gross
1846             else:
1847                 pca.type = 7 # also gross
1848             session.add(pca)
1849
1850         # Only commit if we set up the session ourself
1851         if privatetrans:
1852             session.commit()
1853             session.close()
1854         else:
1855             session.flush()
1856
1857         return True
1858     except Exception, e:
1859         traceback.print_exc()
1860
1861         # Only rollback if we set up the session ourself
1862         if privatetrans:
1863             session.rollback()
1864             session.close()
1865
1866         return False
1867
1868 __all__.append('insert_pending_content_paths')
1869
1870 ################################################################################
1871
1872 class PolicyQueue(object):
1873     def __init__(self, *args, **kwargs):
1874         pass
1875
1876     def __repr__(self):
1877         return '<PolicyQueue %s>' % self.queue_name
1878
1879 __all__.append('PolicyQueue')
1880
1881 @session_wrapper
1882 def get_policy_queue(queuename, session=None):
1883     """
1884     Returns PolicyQueue object for given C{queue name}
1885
1886     @type queuename: string
1887     @param queuename: The name of the queue
1888
1889     @type session: Session
1890     @param session: Optional SQLA session object (a temporary one will be
1891     generated if not supplied)
1892
1893     @rtype: PolicyQueue
1894     @return: PolicyQueue object for the given queue
1895     """
1896
1897     q = session.query(PolicyQueue).filter_by(queue_name=queuename)
1898
1899     try:
1900         return q.one()
1901     except NoResultFound:
1902         return None
1903
1904 __all__.append('get_policy_queue')
1905
1906 ################################################################################
1907
1908 class Priority(object):
1909     def __init__(self, *args, **kwargs):
1910         pass
1911
1912     def __eq__(self, val):
1913         if isinstance(val, str):
1914             return (self.priority == val)
1915         # This signals to use the normal comparison operator
1916         return NotImplemented
1917
1918     def __ne__(self, val):
1919         if isinstance(val, str):
1920             return (self.priority != val)
1921         # This signals to use the normal comparison operator
1922         return NotImplemented
1923
1924     def __repr__(self):
1925         return '<Priority %s (%s)>' % (self.priority, self.priority_id)
1926
1927 __all__.append('Priority')
1928
1929 @session_wrapper
1930 def get_priority(priority, session=None):
1931     """
1932     Returns Priority object for given C{priority name}.
1933
1934     @type priority: string
1935     @param priority: The name of the priority
1936
1937     @type session: Session
1938     @param session: Optional SQLA session object (a temporary one will be
1939     generated if not supplied)
1940
1941     @rtype: Priority
1942     @return: Priority object for the given priority
1943     """
1944
1945     q = session.query(Priority).filter_by(priority=priority)
1946
1947     try:
1948         return q.one()
1949     except NoResultFound:
1950         return None
1951
1952 __all__.append('get_priority')
1953
1954 @session_wrapper
1955 def get_priorities(session=None):
1956     """
1957     Returns dictionary of priority names -> id mappings
1958
1959     @type session: Session
1960     @param session: Optional SQL session object (a temporary one will be
1961     generated if not supplied)
1962
1963     @rtype: dictionary
1964     @return: dictionary of priority names -> id mappings
1965     """
1966
1967     ret = {}
1968     q = session.query(Priority)
1969     for x in q.all():
1970         ret[x.priority] = x.priority_id
1971
1972     return ret
1973
1974 __all__.append('get_priorities')
1975
1976 ################################################################################
1977
1978 class Section(object):
1979     def __init__(self, *args, **kwargs):
1980         pass
1981
1982     def __eq__(self, val):
1983         if isinstance(val, str):
1984             return (self.section == val)
1985         # This signals to use the normal comparison operator
1986         return NotImplemented
1987
1988     def __ne__(self, val):
1989         if isinstance(val, str):
1990             return (self.section != val)
1991         # This signals to use the normal comparison operator
1992         return NotImplemented
1993
1994     def __repr__(self):
1995         return '<Section %s>' % self.section
1996
1997 __all__.append('Section')
1998
1999 @session_wrapper
2000 def get_section(section, session=None):
2001     """
2002     Returns Section object for given C{section name}.
2003
2004     @type section: string
2005     @param section: The name of the section
2006
2007     @type session: Session
2008     @param session: Optional SQLA session object (a temporary one will be
2009     generated if not supplied)
2010
2011     @rtype: Section
2012     @return: Section object for the given section name
2013     """
2014
2015     q = session.query(Section).filter_by(section=section)
2016
2017     try:
2018         return q.one()
2019     except NoResultFound:
2020         return None
2021
2022 __all__.append('get_section')
2023
2024 @session_wrapper
2025 def get_sections(session=None):
2026     """
2027     Returns dictionary of section names -> id mappings
2028
2029     @type session: Session
2030     @param session: Optional SQL session object (a temporary one will be
2031     generated if not supplied)
2032
2033     @rtype: dictionary
2034     @return: dictionary of section names -> id mappings
2035     """
2036
2037     ret = {}
2038     q = session.query(Section)
2039     for x in q.all():
2040         ret[x.section] = x.section_id
2041
2042     return ret
2043
2044 __all__.append('get_sections')
2045
2046 ################################################################################
2047
2048 class DBSource(object):
2049     def __init__(self, *args, **kwargs):
2050         pass
2051
2052     def __repr__(self):
2053         return '<DBSource %s (%s)>' % (self.source, self.version)
2054
2055 __all__.append('DBSource')
2056
2057 @session_wrapper
2058 def source_exists(source, source_version, suites = ["any"], session=None):
2059     """
2060     Ensure that source exists somewhere in the archive for the binary
2061     upload being processed.
2062       1. exact match     => 1.0-3
2063       2. bin-only NMU    => 1.0-3+b1 , 1.0-3.1+b1
2064
2065     @type package: string
2066     @param package: package source name
2067
2068     @type source_version: string
2069     @param source_version: expected source version
2070
2071     @type suites: list
2072     @param suites: list of suites to check in, default I{any}
2073
2074     @type session: Session
2075     @param session: Optional SQLA session object (a temporary one will be
2076     generated if not supplied)
2077
2078     @rtype: int
2079     @return: returns 1 if a source with expected version is found, otherwise 0
2080
2081     """
2082
2083     cnf = Config()
2084     ret = 1
2085
2086     for suite in suites:
2087         q = session.query(DBSource).filter_by(source=source)
2088         if suite != "any":
2089             # source must exist in suite X, or in some other suite that's
2090             # mapped to X, recursively... silent-maps are counted too,
2091             # unreleased-maps aren't.
2092             maps = cnf.ValueList("SuiteMappings")[:]
2093             maps.reverse()
2094             maps = [ m.split() for m in maps ]
2095             maps = [ (x[1], x[2]) for x in maps
2096                             if x[0] == "map" or x[0] == "silent-map" ]
2097             s = [suite]
2098             for x in maps:
2099                 if x[1] in s and x[0] not in s:
2100                     s.append(x[0])
2101
2102             q = q.join(SrcAssociation).join(Suite)
2103             q = q.filter(Suite.suite_name.in_(s))
2104
2105         # Reduce the query results to a list of version numbers
2106         ql = [ j.version for j in q.all() ]
2107
2108         # Try (1)
2109         if source_version in ql:
2110             continue
2111
2112         # Try (2)
2113         from daklib.regexes import re_bin_only_nmu
2114         orig_source_version = re_bin_only_nmu.sub('', source_version)
2115         if orig_source_version in ql:
2116             continue
2117
2118         # No source found so return not ok
2119         ret = 0
2120
2121     return ret
2122
2123 __all__.append('source_exists')
2124
2125 @session_wrapper
2126 def get_suites_source_in(source, session=None):
2127     """
2128     Returns list of Suite objects which given C{source} name is in
2129
2130     @type source: str
2131     @param source: DBSource package name to search for
2132
2133     @rtype: list
2134     @return: list of Suite objects for the given source
2135     """
2136
2137     return session.query(Suite).join(SrcAssociation).join(DBSource).filter_by(source=source).all()
2138
2139 __all__.append('get_suites_source_in')
2140
2141 @session_wrapper
2142 def get_sources_from_name(source, version=None, dm_upload_allowed=None, session=None):
2143     """
2144     Returns list of DBSource objects for given C{source} name and other parameters
2145
2146     @type source: str
2147     @param source: DBSource package name to search for
2148
2149     @type source: str or None
2150     @param source: DBSource version name to search for or None if not applicable
2151
2152     @type dm_upload_allowed: bool
2153     @param dm_upload_allowed: If None, no effect.  If True or False, only
2154     return packages with that dm_upload_allowed setting
2155
2156     @type session: Session
2157     @param session: Optional SQL session object (a temporary one will be
2158     generated if not supplied)
2159
2160     @rtype: list
2161     @return: list of DBSource objects for the given name (may be empty)
2162     """
2163
2164     q = session.query(DBSource).filter_by(source=source)
2165
2166     if version is not None:
2167         q = q.filter_by(version=version)
2168
2169     if dm_upload_allowed is not None:
2170         q = q.filter_by(dm_upload_allowed=dm_upload_allowed)
2171
2172     return q.all()
2173
2174 __all__.append('get_sources_from_name')
2175
2176 @session_wrapper
2177 def get_source_in_suite(source, suite, session=None):
2178     """
2179     Returns list of DBSource objects for a combination of C{source} and C{suite}.
2180
2181       - B{source} - source package name, eg. I{mailfilter}, I{bbdb}, I{glibc}
2182       - B{suite} - a suite name, eg. I{unstable}
2183
2184     @type source: string
2185     @param source: source package name
2186
2187     @type suite: string
2188     @param suite: the suite name
2189
2190     @rtype: string
2191     @return: the version for I{source} in I{suite}
2192
2193     """
2194
2195     q = session.query(SrcAssociation)
2196     q = q.join('source').filter_by(source=source)
2197     q = q.join('suite').filter_by(suite_name=suite)
2198
2199     try:
2200         return q.one().source
2201     except NoResultFound:
2202         return None
2203
2204 __all__.append('get_source_in_suite')
2205
2206 ################################################################################
2207
2208 @session_wrapper
2209 def add_dsc_to_db(u, filename, session=None):
2210     entry = u.pkg.files[filename]
2211     source = DBSource()
2212     pfs = []
2213
2214     source.source = u.pkg.dsc["source"]
2215     source.version = u.pkg.dsc["version"] # NB: not files[file]["version"], that has no epoch
2216     source.maintainer_id = get_or_set_maintainer(u.pkg.dsc["maintainer"], session).maintainer_id
2217     source.changedby_id = get_or_set_maintainer(u.pkg.changes["changed-by"], session).maintainer_id
2218     source.fingerprint_id = get_or_set_fingerprint(u.pkg.changes["fingerprint"], session).fingerprint_id
2219     source.install_date = datetime.now().date()
2220
2221     dsc_component = entry["component"]
2222     dsc_location_id = entry["location id"]
2223
2224     source.dm_upload_allowed = (u.pkg.dsc.get("dm-upload-allowed", '') == "yes")
2225
2226     # Set up a new poolfile if necessary
2227     if not entry.has_key("files id") or not entry["files id"]:
2228         filename = entry["pool name"] + filename
2229         poolfile = add_poolfile(filename, entry, dsc_location_id, session)
2230         session.flush()
2231         pfs.append(poolfile)
2232         entry["files id"] = poolfile.file_id
2233
2234     source.poolfile_id = entry["files id"]
2235     session.add(source)
2236     session.flush()
2237
2238     for suite_name in u.pkg.changes["distribution"].keys():
2239         sa = SrcAssociation()
2240         sa.source_id = source.source_id
2241         sa.suite_id = get_suite(suite_name).suite_id
2242         session.add(sa)
2243
2244     session.flush()
2245
2246     # Add the source files to the DB (files and dsc_files)
2247     dscfile = DSCFile()
2248     dscfile.source_id = source.source_id
2249     dscfile.poolfile_id = entry["files id"]
2250     session.add(dscfile)
2251
2252     for dsc_file, dentry in u.pkg.dsc_files.items():
2253         df = DSCFile()
2254         df.source_id = source.source_id
2255
2256         # If the .orig tarball is already in the pool, it's
2257         # files id is stored in dsc_files by check_dsc().
2258         files_id = dentry.get("files id", None)
2259
2260         # Find the entry in the files hash
2261         # TODO: Bail out here properly
2262         dfentry = None
2263         for f, e in u.pkg.files.items():
2264             if f == dsc_file:
2265                 dfentry = e
2266                 break
2267
2268         if files_id is None:
2269             filename = dfentry["pool name"] + dsc_file
2270
2271             (found, obj) = check_poolfile(filename, dentry["size"], dentry["md5sum"], dsc_location_id)
2272             # FIXME: needs to check for -1/-2 and or handle exception
2273             if found and obj is not None:
2274                 files_id = obj.file_id
2275                 pfs.append(obj)
2276
2277             # If still not found, add it
2278             if files_id is None:
2279                 # HACK: Force sha1sum etc into dentry
2280                 dentry["sha1sum"] = dfentry["sha1sum"]
2281                 dentry["sha256sum"] = dfentry["sha256sum"]
2282                 poolfile = add_poolfile(filename, dentry, dsc_location_id, session)
2283                 pfs.append(poolfile)
2284                 files_id = poolfile.file_id
2285         else:
2286             poolfile = get_poolfile_by_id(files_id, session)
2287             if poolfile is None:
2288                 utils.fubar("INTERNAL ERROR. Found no poolfile with id %d" % files_id)
2289             pfs.append(poolfile)
2290
2291         df.poolfile_id = files_id
2292         session.add(df)
2293
2294     session.flush()
2295
2296     # Add the src_uploaders to the DB
2297     uploader_ids = [source.maintainer_id]
2298     if u.pkg.dsc.has_key("uploaders"):
2299         for up in u.pkg.dsc["uploaders"].split(","):
2300             up = up.strip()
2301             uploader_ids.append(get_or_set_maintainer(up, session).maintainer_id)
2302
2303     added_ids = {}
2304     for up in uploader_ids:
2305         if added_ids.has_key(up):
2306             utils.warn("Already saw uploader %s for source %s" % (up, source.source))
2307             continue
2308
2309         added_ids[u]=1
2310
2311         su = SrcUploader()
2312         su.maintainer_id = up
2313         su.source_id = source.source_id
2314         session.add(su)
2315
2316     session.flush()
2317
2318     return source, dsc_component, dsc_location_id, pfs
2319
2320 __all__.append('add_dsc_to_db')
2321
2322 @session_wrapper
2323 def add_deb_to_db(u, filename, session=None):
2324     """
2325     Contrary to what you might expect, this routine deals with both
2326     debs and udebs.  That info is in 'dbtype', whilst 'type' is
2327     'deb' for both of them
2328     """
2329     cnf = Config()
2330     entry = u.pkg.files[filename]
2331
2332     bin = DBBinary()
2333     bin.package = entry["package"]
2334     bin.version = entry["version"]
2335     bin.maintainer_id = get_or_set_maintainer(entry["maintainer"], session).maintainer_id
2336     bin.fingerprint_id = get_or_set_fingerprint(u.pkg.changes["fingerprint"], session).fingerprint_id
2337     bin.arch_id = get_architecture(entry["architecture"], session).arch_id
2338     bin.binarytype = entry["dbtype"]
2339
2340     # Find poolfile id
2341     filename = entry["pool name"] + filename
2342     fullpath = os.path.join(cnf["Dir::Pool"], filename)
2343     if not entry.get("location id", None):
2344         entry["location id"] = get_location(cnf["Dir::Pool"], entry["component"], session=session).location_id
2345
2346     if entry.get("files id", None):
2347         poolfile = get_poolfile_by_id(bin.poolfile_id)
2348         bin.poolfile_id = entry["files id"]
2349     else:
2350         poolfile = add_poolfile(filename, entry, entry["location id"], session)
2351         bin.poolfile_id = entry["files id"] = poolfile.file_id
2352
2353     # Find source id
2354     bin_sources = get_sources_from_name(entry["source package"], entry["source version"], session=session)
2355     if len(bin_sources) != 1:
2356         raise NoSourceFieldError, "Unable to find a unique source id for %s (%s), %s, file %s, type %s, signed by %s" % \
2357                                   (bin.package, bin.version, bin.architecture.arch_string,
2358                                    filename, bin.binarytype, u.pkg.changes["fingerprint"])
2359
2360     bin.source_id = bin_sources[0].source_id
2361
2362     # Add and flush object so it has an ID
2363     session.add(bin)
2364     session.flush()
2365
2366     # Add BinAssociations
2367     for suite_name in u.pkg.changes["distribution"].keys():
2368         ba = BinAssociation()
2369         ba.binary_id = bin.binary_id
2370         ba.suite_id = get_suite(suite_name).suite_id
2371         session.add(ba)
2372
2373     session.flush()
2374
2375     # Deal with contents - disabled for now
2376     #contents = copy_temporary_contents(bin.package, bin.version, bin.architecture.arch_string, os.path.basename(filename), None, session)
2377     #if not contents:
2378     #    print "REJECT\nCould not determine contents of package %s" % bin.package
2379     #    session.rollback()
2380     #    raise MissingContents, "No contents stored for package %s, and couldn't determine contents of %s" % (bin.package, filename)
2381
2382     return poolfile
2383
2384 __all__.append('add_deb_to_db')
2385
2386 ################################################################################
2387
2388 class SourceACL(object):
2389     def __init__(self, *args, **kwargs):
2390         pass
2391
2392     def __repr__(self):
2393         return '<SourceACL %s>' % self.source_acl_id
2394
2395 __all__.append('SourceACL')
2396
2397 ################################################################################
2398
2399 class SrcAssociation(object):
2400     def __init__(self, *args, **kwargs):
2401         pass
2402
2403     def __repr__(self):
2404         return '<SrcAssociation %s (%s, %s)>' % (self.sa_id, self.source, self.suite)
2405
2406 __all__.append('SrcAssociation')
2407
2408 ################################################################################
2409
2410 class SrcFormat(object):
2411     def __init__(self, *args, **kwargs):
2412         pass
2413
2414     def __repr__(self):
2415         return '<SrcFormat %s>' % (self.format_name)
2416
2417 __all__.append('SrcFormat')
2418
2419 ################################################################################
2420
2421 class SrcUploader(object):
2422     def __init__(self, *args, **kwargs):
2423         pass
2424
2425     def __repr__(self):
2426         return '<SrcUploader %s>' % self.uploader_id
2427
2428 __all__.append('SrcUploader')
2429
2430 ################################################################################
2431
2432 SUITE_FIELDS = [ ('SuiteName', 'suite_name'),
2433                  ('SuiteID', 'suite_id'),
2434                  ('Version', 'version'),
2435                  ('Origin', 'origin'),
2436                  ('Label', 'label'),
2437                  ('Description', 'description'),
2438                  ('Untouchable', 'untouchable'),
2439                  ('Announce', 'announce'),
2440                  ('Codename', 'codename'),
2441                  ('OverrideCodename', 'overridecodename'),
2442                  ('ValidTime', 'validtime'),
2443                  ('Priority', 'priority'),
2444                  ('NotAutomatic', 'notautomatic'),
2445                  ('CopyChanges', 'copychanges'),
2446                  ('CopyDotDak', 'copydotdak'),
2447                  ('CommentsDir', 'commentsdir'),
2448                  ('OverrideSuite', 'overridesuite'),
2449                  ('ChangelogBase', 'changelogbase')]
2450
2451
2452 class Suite(object):
2453     def __init__(self, *args, **kwargs):
2454         pass
2455
2456     def __repr__(self):
2457         return '<Suite %s>' % self.suite_name
2458
2459     def __eq__(self, val):
2460         if isinstance(val, str):
2461             return (self.suite_name == val)
2462         # This signals to use the normal comparison operator
2463         return NotImplemented
2464
2465     def __ne__(self, val):
2466         if isinstance(val, str):
2467             return (self.suite_name != val)
2468         # This signals to use the normal comparison operator
2469         return NotImplemented
2470
2471     def details(self):
2472         ret = []
2473         for disp, field in SUITE_FIELDS:
2474             val = getattr(self, field, None)
2475             if val is not None:
2476                 ret.append("%s: %s" % (disp, val))
2477
2478         return "\n".join(ret)
2479
2480 __all__.append('Suite')
2481
2482 @session_wrapper
2483 def get_suite_architecture(suite, architecture, session=None):
2484     """
2485     Returns a SuiteArchitecture object given C{suite} and ${arch} or None if it
2486     doesn't exist
2487
2488     @type suite: str
2489     @param suite: Suite name to search for
2490
2491     @type architecture: str
2492     @param architecture: Architecture name to search for
2493
2494     @type session: Session
2495     @param session: Optional SQL session object (a temporary one will be
2496     generated if not supplied)
2497
2498     @rtype: SuiteArchitecture
2499     @return: the SuiteArchitecture object or None
2500     """
2501
2502     q = session.query(SuiteArchitecture)
2503     q = q.join(Architecture).filter_by(arch_string=architecture)
2504     q = q.join(Suite).filter_by(suite_name=suite)
2505
2506     try:
2507         return q.one()
2508     except NoResultFound:
2509         return None
2510
2511 __all__.append('get_suite_architecture')
2512
2513 @session_wrapper
2514 def get_suite(suite, session=None):
2515     """
2516     Returns Suite object for given C{suite name}.
2517
2518     @type suite: string
2519     @param suite: The name of the suite
2520
2521     @type session: Session
2522     @param session: Optional SQLA session object (a temporary one will be
2523     generated if not supplied)
2524
2525     @rtype: Suite
2526     @return: Suite object for the requested suite name (None if not present)
2527     """
2528
2529     q = session.query(Suite).filter_by(suite_name=suite)
2530
2531     try:
2532         return q.one()
2533     except NoResultFound:
2534         return None
2535
2536 __all__.append('get_suite')
2537
2538 ################################################################################
2539
2540 class SuiteArchitecture(object):
2541     def __init__(self, *args, **kwargs):
2542         pass
2543
2544     def __repr__(self):
2545         return '<SuiteArchitecture (%s, %s)>' % (self.suite_id, self.arch_id)
2546
2547 __all__.append('SuiteArchitecture')
2548
2549 @session_wrapper
2550 def get_suite_architectures(suite, skipsrc=False, skipall=False, session=None):
2551     """
2552     Returns list of Architecture objects for given C{suite} name
2553
2554     @type source: str
2555     @param source: Suite name to search for
2556
2557     @type skipsrc: boolean
2558     @param skipsrc: Whether to skip returning the 'source' architecture entry
2559     (Default False)
2560
2561     @type skipall: boolean
2562     @param skipall: Whether to skip returning the 'all' architecture entry
2563     (Default False)
2564
2565     @type session: Session
2566     @param session: Optional SQL session object (a temporary one will be
2567     generated if not supplied)
2568
2569     @rtype: list
2570     @return: list of Architecture objects for the given name (may be empty)
2571     """
2572
2573     q = session.query(Architecture)
2574     q = q.join(SuiteArchitecture)
2575     q = q.join(Suite).filter_by(suite_name=suite)
2576
2577     if skipsrc:
2578         q = q.filter(Architecture.arch_string != 'source')
2579
2580     if skipall:
2581         q = q.filter(Architecture.arch_string != 'all')
2582
2583     q = q.order_by('arch_string')
2584
2585     return q.all()
2586
2587 __all__.append('get_suite_architectures')
2588
2589 ################################################################################
2590
2591 class SuiteSrcFormat(object):
2592     def __init__(self, *args, **kwargs):
2593         pass
2594
2595     def __repr__(self):
2596         return '<SuiteSrcFormat (%s, %s)>' % (self.suite_id, self.src_format_id)
2597
2598 __all__.append('SuiteSrcFormat')
2599
2600 @session_wrapper
2601 def get_suite_src_formats(suite, session=None):
2602     """
2603     Returns list of allowed SrcFormat for C{suite}.
2604
2605     @type suite: str
2606     @param suite: Suite name to search for
2607
2608     @type session: Session
2609     @param session: Optional SQL session object (a temporary one will be
2610     generated if not supplied)
2611
2612     @rtype: list
2613     @return: the list of allowed source formats for I{suite}
2614     """
2615
2616     q = session.query(SrcFormat)
2617     q = q.join(SuiteSrcFormat)
2618     q = q.join(Suite).filter_by(suite_name=suite)
2619     q = q.order_by('format_name')
2620
2621     return q.all()
2622
2623 __all__.append('get_suite_src_formats')
2624
2625 ################################################################################
2626
2627 class Uid(object):
2628     def __init__(self, *args, **kwargs):
2629         pass
2630
2631     def __eq__(self, val):
2632         if isinstance(val, str):
2633             return (self.uid == val)
2634         # This signals to use the normal comparison operator
2635         return NotImplemented
2636
2637     def __ne__(self, val):
2638         if isinstance(val, str):
2639             return (self.uid != val)
2640         # This signals to use the normal comparison operator
2641         return NotImplemented
2642
2643     def __repr__(self):
2644         return '<Uid %s (%s)>' % (self.uid, self.name)
2645
2646 __all__.append('Uid')
2647
2648 @session_wrapper
2649 def add_database_user(uidname, session=None):
2650     """
2651     Adds a database user
2652
2653     @type uidname: string
2654     @param uidname: The uid of the user to add
2655
2656     @type session: SQLAlchemy
2657     @param session: Optional SQL session object (a temporary one will be
2658     generated if not supplied).  If not passed, a commit will be performed at
2659     the end of the function, otherwise the caller is responsible for commiting.
2660
2661     @rtype: Uid
2662     @return: the uid object for the given uidname
2663     """
2664
2665     session.execute("CREATE USER :uid", {'uid': uidname})
2666     session.commit_or_flush()
2667
2668 __all__.append('add_database_user')
2669
2670 @session_wrapper
2671 def get_or_set_uid(uidname, session=None):
2672     """
2673     Returns uid object for given uidname.
2674
2675     If no matching uidname is found, a row is inserted.
2676
2677     @type uidname: string
2678     @param uidname: The uid to add
2679
2680     @type session: SQLAlchemy
2681     @param session: Optional SQL session object (a temporary one will be
2682     generated if not supplied).  If not passed, a commit will be performed at
2683     the end of the function, otherwise the caller is responsible for commiting.
2684
2685     @rtype: Uid
2686     @return: the uid object for the given uidname
2687     """
2688
2689     q = session.query(Uid).filter_by(uid=uidname)
2690
2691     try:
2692         ret = q.one()
2693     except NoResultFound:
2694         uid = Uid()
2695         uid.uid = uidname
2696         session.add(uid)
2697         session.commit_or_flush()
2698         ret = uid
2699
2700     return ret
2701
2702 __all__.append('get_or_set_uid')
2703
2704 @session_wrapper
2705 def get_uid_from_fingerprint(fpr, session=None):
2706     q = session.query(Uid)
2707     q = q.join(Fingerprint).filter_by(fingerprint=fpr)
2708
2709     try:
2710         return q.one()
2711     except NoResultFound:
2712         return None
2713
2714 __all__.append('get_uid_from_fingerprint')
2715
2716 ################################################################################
2717
2718 class UploadBlock(object):
2719     def __init__(self, *args, **kwargs):
2720         pass
2721
2722     def __repr__(self):
2723         return '<UploadBlock %s (%s)>' % (self.source, self.upload_block_id)
2724
2725 __all__.append('UploadBlock')
2726
2727 ################################################################################
2728
2729 class DBConn(object):
2730     """
2731     database module init.
2732     """
2733     __shared_state = {}
2734
2735     def __init__(self, *args, **kwargs):
2736         self.__dict__ = self.__shared_state
2737
2738         if not getattr(self, 'initialised', False):
2739             self.initialised = True
2740             self.debug = kwargs.has_key('debug')
2741             self.__createconn()
2742
2743     def __setuptables(self):
2744         tables = (
2745             'architecture',
2746             'archive',
2747             'bin_associations',
2748             'binaries',
2749             'binary_acl',
2750             'binary_acl_map',
2751             'bin_contents'
2752             'build_queue',
2753             'build_queue_files',
2754             'component',
2755             'config',
2756             'changes_pending_binaries',
2757             'changes_pending_files',
2758             'changes_pending_files_map',
2759             'changes_pending_source',
2760             'changes_pending_source_files',
2761             'changes_pool_files',
2762             'deb_contents',
2763             'dsc_files',
2764             'files',
2765             'fingerprint',
2766             'keyrings',
2767             'changes',
2768             'keyring_acl_map',
2769             'location',
2770             'maintainer',
2771             'new_comments',
2772             'override',
2773             'override_type',
2774             'pending_bin_contents',
2775             'policy_queue',
2776             'priority',
2777             'section',
2778             'source',
2779             'source_acl',
2780             'src_associations',
2781             'src_format',
2782             'src_uploaders',
2783             'suite',
2784             'suite_architectures',
2785             'suite_src_formats',
2786             'suite_build_queue_copy',
2787             'udeb_contents',
2788             'uid',
2789             'upload_blocks',
2790         )
2791
2792         for table_name in tables:
2793             table = Table(table_name, self.db_meta, autoload=True)
2794             setattr(self, 'tbl_%s' % table_name, table)
2795
2796     def __setupmappers(self):
2797         mapper(Architecture, self.tbl_architecture,
2798                properties = dict(arch_id = self.tbl_architecture.c.id))
2799
2800         mapper(Archive, self.tbl_archive,
2801                properties = dict(archive_id = self.tbl_archive.c.id,
2802                                  archive_name = self.tbl_archive.c.name))
2803
2804         mapper(BinAssociation, self.tbl_bin_associations,
2805                properties = dict(ba_id = self.tbl_bin_associations.c.id,
2806                                  suite_id = self.tbl_bin_associations.c.suite,
2807                                  suite = relation(Suite),
2808                                  binary_id = self.tbl_bin_associations.c.bin,
2809                                  binary = relation(DBBinary)))
2810
2811         mapper(PendingBinContents, self.tbl_pending_bin_contents,
2812                properties = dict(contents_id =self.tbl_pending_bin_contents.c.id,
2813                                  filename = self.tbl_pending_bin_contents.c.filename,
2814                                  package = self.tbl_pending_bin_contents.c.package,
2815                                  version = self.tbl_pending_bin_contents.c.version,
2816                                  arch = self.tbl_pending_bin_contents.c.arch,
2817                                  otype = self.tbl_pending_bin_contents.c.type))
2818
2819         mapper(DebContents, self.tbl_deb_contents,
2820                properties = dict(binary_id=self.tbl_deb_contents.c.binary_id,
2821                                  package=self.tbl_deb_contents.c.package,
2822                                  component=self.tbl_deb_contents.c.component,
2823                                  arch=self.tbl_deb_contents.c.arch,
2824                                  section=self.tbl_deb_contents.c.section,
2825                                  filename=self.tbl_deb_contents.c.filename))
2826
2827         mapper(UdebContents, self.tbl_udeb_contents,
2828                properties = dict(binary_id=self.tbl_udeb_contents.c.binary_id,
2829                                  package=self.tbl_udeb_contents.c.package,
2830                                  component=self.tbl_udeb_contents.c.component,
2831                                  arch=self.tbl_udeb_contents.c.arch,
2832                                  section=self.tbl_udeb_contents.c.section,
2833                                  filename=self.tbl_udeb_contents.c.filename))
2834
2835         mapper(DBBinary, self.tbl_binaries,
2836                properties = dict(binary_id = self.tbl_binaries.c.id,
2837                                  package = self.tbl_binaries.c.package,
2838                                  version = self.tbl_binaries.c.version,
2839                                  maintainer_id = self.tbl_binaries.c.maintainer,
2840                                  maintainer = relation(Maintainer),
2841                                  source_id = self.tbl_binaries.c.source,
2842                                  source = relation(DBSource),
2843                                  arch_id = self.tbl_binaries.c.architecture,
2844                                  architecture = relation(Architecture),
2845                                  poolfile_id = self.tbl_binaries.c.file,
2846                                  poolfile = relation(PoolFile),
2847                                  binarytype = self.tbl_binaries.c.type,
2848                                  fingerprint_id = self.tbl_binaries.c.sig_fpr,
2849                                  fingerprint = relation(Fingerprint),
2850                                  install_date = self.tbl_binaries.c.install_date,
2851                                  binassociations = relation(BinAssociation,
2852                                                             primaryjoin=(self.tbl_binaries.c.id==self.tbl_bin_associations.c.bin))))
2853
2854         mapper(BinaryACL, self.tbl_binary_acl,
2855                properties = dict(binary_acl_id = self.tbl_binary_acl.c.id))
2856
2857         mapper(BinaryACLMap, self.tbl_binary_acl_map,
2858                properties = dict(binary_acl_map_id = self.tbl_binary_acl_map.c.id,
2859                                  fingerprint = relation(Fingerprint, backref="binary_acl_map"),
2860                                  architecture = relation(Architecture)))
2861
2862         mapper(Component, self.tbl_component,
2863                properties = dict(component_id = self.tbl_component.c.id,
2864                                  component_name = self.tbl_component.c.name))
2865
2866         mapper(DBConfig, self.tbl_config,
2867                properties = dict(config_id = self.tbl_config.c.id))
2868
2869         mapper(DSCFile, self.tbl_dsc_files,
2870                properties = dict(dscfile_id = self.tbl_dsc_files.c.id,
2871                                  source_id = self.tbl_dsc_files.c.source,
2872                                  source = relation(DBSource),
2873                                  poolfile_id = self.tbl_dsc_files.c.file,
2874                                  poolfile = relation(PoolFile)))
2875
2876         mapper(PoolFile, self.tbl_files,
2877                properties = dict(file_id = self.tbl_files.c.id,
2878                                  filesize = self.tbl_files.c.size,
2879                                  location_id = self.tbl_files.c.location,
2880                                  location = relation(Location)))
2881
2882         mapper(Fingerprint, self.tbl_fingerprint,
2883                properties = dict(fingerprint_id = self.tbl_fingerprint.c.id,
2884                                  uid_id = self.tbl_fingerprint.c.uid,
2885                                  uid = relation(Uid),
2886                                  keyring_id = self.tbl_fingerprint.c.keyring,
2887                                  keyring = relation(Keyring),
2888                                  source_acl = relation(SourceACL),
2889                                  binary_acl = relation(BinaryACL)))
2890
2891         mapper(Keyring, self.tbl_keyrings,
2892                properties = dict(keyring_name = self.tbl_keyrings.c.name,
2893                                  keyring_id = self.tbl_keyrings.c.id))
2894
2895         mapper(DBChange, self.tbl_changes,
2896                properties = dict(change_id = self.tbl_changes.c.id,
2897                                  poolfiles = relation(PoolFile,
2898                                                       secondary=self.tbl_changes_pool_files,
2899                                                       backref="changeslinks"),
2900                                  seen = self.tbl_changes.c.seen,
2901                                  source = self.tbl_changes.c.source,
2902                                  binaries = self.tbl_changes.c.binaries,
2903                                  architecture = self.tbl_changes.c.architecture,
2904                                  distribution = self.tbl_changes.c.distribution,
2905                                  urgency = self.tbl_changes.c.urgency,
2906                                  maintainer = self.tbl_changes.c.maintainer,
2907                                  changedby = self.tbl_changes.c.changedby,
2908                                  date = self.tbl_changes.c.date,
2909                                  version = self.tbl_changes.c.version,
2910                                  files = relation(ChangePendingFile,
2911                                                   secondary=self.tbl_changes_pending_files_map,
2912                                                   backref="changesfile"),
2913                                  in_queue_id = self.tbl_changes.c.in_queue,
2914                                  in_queue = relation(PolicyQueue,
2915                                                      primaryjoin=(self.tbl_changes.c.in_queue==self.tbl_policy_queue.c.id)),
2916                                  approved_for_id = self.tbl_changes.c.approved_for))
2917
2918         mapper(ChangePendingBinary, self.tbl_changes_pending_binaries,
2919                properties = dict(change_pending_binary_id = self.tbl_changes_pending_binaries.c.id))
2920
2921         mapper(ChangePendingFile, self.tbl_changes_pending_files,
2922                properties = dict(change_pending_file_id = self.tbl_changes_pending_files.c.id,
2923                                  filename = self.tbl_changes_pending_files.c.filename,
2924                                  size = self.tbl_changes_pending_files.c.size,
2925                                  md5sum = self.tbl_changes_pending_files.c.md5sum,
2926                                  sha1sum = self.tbl_changes_pending_files.c.sha1sum,
2927                                  sha256sum = self.tbl_changes_pending_files.c.sha256sum))
2928
2929         mapper(ChangePendingSource, self.tbl_changes_pending_source,
2930                properties = dict(change_pending_source_id = self.tbl_changes_pending_source.c.id,
2931                                  change = relation(DBChange),
2932                                  maintainer = relation(Maintainer,
2933                                                        primaryjoin=(self.tbl_changes_pending_source.c.maintainer_id==self.tbl_maintainer.c.id)),
2934                                  changedby = relation(Maintainer,
2935                                                       primaryjoin=(self.tbl_changes_pending_source.c.changedby_id==self.tbl_maintainer.c.id)),
2936                                  fingerprint = relation(Fingerprint),
2937                                  source_files = relation(ChangePendingFile,
2938                                                          secondary=self.tbl_changes_pending_source_files,
2939                                                          backref="pending_sources")))
2940                                  files = relation(KnownChangePendingFile, backref="changesfile")))
2941
2942         mapper(KnownChangePendingFile, self.tbl_changes_pending_files,
2943                properties = dict(known_change_pending_file_id = self.tbl_changes_pending_files.c.id))
2944
2945         mapper(KeyringACLMap, self.tbl_keyring_acl_map,
2946                properties = dict(keyring_acl_map_id = self.tbl_keyring_acl_map.c.id,
2947                                  keyring = relation(Keyring, backref="keyring_acl_map"),
2948                                  architecture = relation(Architecture)))
2949
2950         mapper(Location, self.tbl_location,
2951                properties = dict(location_id = self.tbl_location.c.id,
2952                                  component_id = self.tbl_location.c.component,
2953                                  component = relation(Component),
2954                                  archive_id = self.tbl_location.c.archive,
2955                                  archive = relation(Archive),
2956                                  archive_type = self.tbl_location.c.type))
2957
2958         mapper(Maintainer, self.tbl_maintainer,
2959                properties = dict(maintainer_id = self.tbl_maintainer.c.id))
2960
2961         mapper(NewComment, self.tbl_new_comments,
2962                properties = dict(comment_id = self.tbl_new_comments.c.id))
2963
2964         mapper(Override, self.tbl_override,
2965                properties = dict(suite_id = self.tbl_override.c.suite,
2966                                  suite = relation(Suite),
2967                                  package = self.tbl_override.c.package,
2968                                  component_id = self.tbl_override.c.component,
2969                                  component = relation(Component),
2970                                  priority_id = self.tbl_override.c.priority,
2971                                  priority = relation(Priority),
2972                                  section_id = self.tbl_override.c.section,
2973                                  section = relation(Section),
2974                                  overridetype_id = self.tbl_override.c.type,
2975                                  overridetype = relation(OverrideType)))
2976
2977         mapper(OverrideType, self.tbl_override_type,
2978                properties = dict(overridetype = self.tbl_override_type.c.type,
2979                                  overridetype_id = self.tbl_override_type.c.id))
2980
2981         mapper(PolicyQueue, self.tbl_policy_queue,
2982                properties = dict(policy_queue_id = self.tbl_policy_queue.c.id))
2983
2984         mapper(Priority, self.tbl_priority,
2985                properties = dict(priority_id = self.tbl_priority.c.id))
2986
2987         mapper(Section, self.tbl_section,
2988                properties = dict(section_id = self.tbl_section.c.id,
2989                                  section=self.tbl_section.c.section))
2990
2991         mapper(DBSource, self.tbl_source,
2992                properties = dict(source_id = self.tbl_source.c.id,
2993                                  version = self.tbl_source.c.version,
2994                                  maintainer_id = self.tbl_source.c.maintainer,
2995                                  maintainer = relation(Maintainer,
2996                                                        primaryjoin=(self.tbl_source.c.maintainer==self.tbl_maintainer.c.id)),
2997                                  poolfile_id = self.tbl_source.c.file,
2998                                  poolfile = relation(PoolFile),
2999                                  fingerprint_id = self.tbl_source.c.sig_fpr,
3000                                  fingerprint = relation(Fingerprint),
3001                                  changedby_id = self.tbl_source.c.changedby,
3002                                  changedby = relation(Maintainer,
3003                                                       primaryjoin=(self.tbl_source.c.changedby==self.tbl_maintainer.c.id)),
3004                                  srcfiles = relation(DSCFile,
3005                                                      primaryjoin=(self.tbl_source.c.id==self.tbl_dsc_files.c.source)),
3006                                  srcassociations = relation(SrcAssociation,
3007                                                             primaryjoin=(self.tbl_source.c.id==self.tbl_src_associations.c.source)),
3008                                  srcuploaders = relation(SrcUploader)))
3009
3010         mapper(SourceACL, self.tbl_source_acl,
3011                properties = dict(source_acl_id = self.tbl_source_acl.c.id))
3012
3013         mapper(SrcAssociation, self.tbl_src_associations,
3014                properties = dict(sa_id = self.tbl_src_associations.c.id,
3015                                  suite_id = self.tbl_src_associations.c.suite,
3016                                  suite = relation(Suite),
3017                                  source_id = self.tbl_src_associations.c.source,
3018                                  source = relation(DBSource)))
3019
3020         mapper(SrcFormat, self.tbl_src_format,
3021                properties = dict(src_format_id = self.tbl_src_format.c.id,
3022                                  format_name = self.tbl_src_format.c.format_name))
3023
3024         mapper(SrcUploader, self.tbl_src_uploaders,
3025                properties = dict(uploader_id = self.tbl_src_uploaders.c.id,
3026                                  source_id = self.tbl_src_uploaders.c.source,
3027                                  source = relation(DBSource,
3028                                                    primaryjoin=(self.tbl_src_uploaders.c.source==self.tbl_source.c.id)),
3029                                  maintainer_id = self.tbl_src_uploaders.c.maintainer,
3030                                  maintainer = relation(Maintainer,
3031                                                        primaryjoin=(self.tbl_src_uploaders.c.maintainer==self.tbl_maintainer.c.id))))
3032
3033         mapper(Suite, self.tbl_suite,
3034                properties = dict(suite_id = self.tbl_suite.c.id,
3035                                  policy_queue = relation(PolicyQueue),
3036                                  copy_queues = relation(BuildQueue, secondary=self.tbl_suite_build_queue_copy)))
3037
3038         mapper(SuiteArchitecture, self.tbl_suite_architectures,
3039                properties = dict(suite_id = self.tbl_suite_architectures.c.suite,
3040                                  suite = relation(Suite, backref='suitearchitectures'),
3041                                  arch_id = self.tbl_suite_architectures.c.architecture,
3042                                  architecture = relation(Architecture)))
3043
3044         mapper(SuiteSrcFormat, self.tbl_suite_src_formats,
3045                properties = dict(suite_id = self.tbl_suite_src_formats.c.suite,
3046                                  suite = relation(Suite, backref='suitesrcformats'),
3047                                  src_format_id = self.tbl_suite_src_formats.c.src_format,
3048                                  src_format = relation(SrcFormat)))
3049
3050         mapper(Uid, self.tbl_uid,
3051                properties = dict(uid_id = self.tbl_uid.c.id,
3052                                  fingerprint = relation(Fingerprint)))
3053
3054         mapper(UploadBlock, self.tbl_upload_blocks,
3055                properties = dict(upload_block_id = self.tbl_upload_blocks.c.id,
3056                                  fingerprint = relation(Fingerprint, backref="uploadblocks"),
3057                                  uid = relation(Uid, backref="uploadblocks")))
3058
3059     ## Connection functions
3060     def __createconn(self):
3061         from config import Config
3062         cnf = Config()
3063         if cnf["DB::Host"]:
3064             # TCP/IP
3065             connstr = "postgres://%s" % cnf["DB::Host"]
3066             if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
3067                 connstr += ":%s" % cnf["DB::Port"]
3068             connstr += "/%s" % cnf["DB::Name"]
3069         else:
3070             # Unix Socket
3071             connstr = "postgres:///%s" % cnf["DB::Name"]
3072             if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
3073                 connstr += "?port=%s" % cnf["DB::Port"]
3074
3075         self.db_pg   = create_engine(connstr, echo=self.debug)
3076         self.db_meta = MetaData()
3077         self.db_meta.bind = self.db_pg
3078         self.db_smaker = sessionmaker(bind=self.db_pg,
3079                                       autoflush=True,
3080                                       autocommit=False)
3081
3082         self.__setuptables()
3083         self.__setupmappers()
3084
3085     def session(self):
3086         return self.db_smaker()
3087
3088 __all__.append('DBConn')
3089
3090