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