]> git.decadent.org.uk Git - dak.git/blob - daklib/dbconn.py
Merge branch 'master' into deb-src-3.0-sqla
[dak.git] / daklib / dbconn.py
1 #!/usr/bin/python
2
3 """ DB access class
4
5 @contact: Debian FTPMaster <ftpmaster@debian.org>
6 @copyright: 2000, 2001, 2002, 2003, 2004, 2006  James Troup <james@nocrew.org>
7 @copyright: 2008-2009  Mark Hymers <mhy@debian.org>
8 @copyright: 2009  Joerg Jaspert <joerg@debian.org>
9 @copyright: 2009  Mike O'Connor <stew@debian.org>
10 @license: GNU General Public License version 2 or later
11 """
12
13 # This program is free software; you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation; either version 2 of the License, or
16 # (at your option) any later version.
17
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21 # GNU General Public License for more details.
22
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
26
27 ################################################################################
28
29 # < mhy> I need a funny comment
30 # < sgran> two peanuts were walking down a dark street
31 # < sgran> one was a-salted
32 #  * mhy looks up the definition of "funny"
33
34 ################################################################################
35
36 import os
37 import psycopg2
38 import traceback
39
40 from inspect import getargspec
41
42 from sqlalchemy import create_engine, Table, MetaData, select
43 from sqlalchemy.orm import sessionmaker, mapper, relation
44
45 # Don't remove this, we re-export the exceptions to scripts which import us
46 from sqlalchemy.exc import *
47 from sqlalchemy.orm.exc import NoResultFound
48
49 # Only import Config until Queue stuff is changed to store its config
50 # in the database
51 from config import Config
52 from singleton import Singleton
53 from textutils import fix_maintainer
54
55 ################################################################################
56
57 __all__ = ['IntegrityError', 'SQLAlchemyError']
58
59 ################################################################################
60
61 def session_wrapper(fn):
62     def wrapped(*args, **kwargs):
63         private_transaction = False
64         session = kwargs.get('session')
65
66         # No session specified as last argument or in kwargs, create one.
67         if session is None and len(args) <= len(getargspec(fn)[0]) - 1:
68             private_transaction = True
69             kwargs['session'] = DBConn().session()
70
71         try:
72             return fn(*args, **kwargs)
73         finally:
74             if private_transaction:
75                 # We created a session; close it.
76                 kwargs['session'].close()
77
78     wrapped.__doc__ = fn.__doc__
79     wrapped.func_name = fn.func_name
80
81     return wrapped
82
83 ################################################################################
84
85 class Architecture(object):
86     def __init__(self, *args, **kwargs):
87         pass
88
89     def __eq__(self, val):
90         if isinstance(val, str):
91             return (self.arch_string== val)
92         # This signals to use the normal comparison operator
93         return NotImplemented
94
95     def __ne__(self, val):
96         if isinstance(val, str):
97             return (self.arch_string != val)
98         # This signals to use the normal comparison operator
99         return NotImplemented
100
101     def __repr__(self):
102         return '<Architecture %s>' % self.arch_string
103
104 __all__.append('Architecture')
105
106 @session_wrapper
107 def get_architecture(architecture, session=None):
108     """
109     Returns database id for given C{architecture}.
110
111     @type architecture: string
112     @param architecture: The name of the architecture
113
114     @type session: Session
115     @param session: Optional SQLA session object (a temporary one will be
116     generated if not supplied)
117
118     @rtype: Architecture
119     @return: Architecture object for the given arch (None if not present)
120     """
121
122     q = session.query(Architecture).filter_by(arch_string=architecture)
123
124     try:
125         return q.one()
126     except NoResultFound:
127         return None
128
129 __all__.append('get_architecture')
130
131 @session_wrapper
132 def get_architecture_suites(architecture, session=None):
133     """
134     Returns list of Suite objects for given C{architecture} name
135
136     @type source: str
137     @param source: Architecture name to search for
138
139     @type session: Session
140     @param session: Optional SQL session object (a temporary one will be
141     generated if not supplied)
142
143     @rtype: list
144     @return: list of Suite objects for the given name (may be empty)
145     """
146
147     q = session.query(Suite)
148     q = q.join(SuiteArchitecture)
149     q = q.join(Architecture).filter_by(arch_string=architecture).order_by('suite_name')
150
151     ret = q.all()
152
153     return ret
154
155 __all__.append('get_architecture_suites')
156
157 ################################################################################
158
159 class Archive(object):
160     def __init__(self, *args, **kwargs):
161         pass
162
163     def __repr__(self):
164         return '<Archive %s>' % self.archive_name
165
166 __all__.append('Archive')
167
168 @session_wrapper
169 def get_archive(archive, session=None):
170     """
171     returns database id for given c{archive}.
172
173     @type archive: string
174     @param archive: the name of the arhive
175
176     @type session: Session
177     @param session: Optional SQLA session object (a temporary one will be
178     generated if not supplied)
179
180     @rtype: Archive
181     @return: Archive object for the given name (None if not present)
182
183     """
184     archive = archive.lower()
185
186     q = session.query(Archive).filter_by(archive_name=archive)
187
188     try:
189         return q.one()
190     except NoResultFound:
191         return None
192
193 __all__.append('get_archive')
194
195 ################################################################################
196
197 class BinAssociation(object):
198     def __init__(self, *args, **kwargs):
199         pass
200
201     def __repr__(self):
202         return '<BinAssociation %s (%s, %s)>' % (self.ba_id, self.binary, self.suite)
203
204 __all__.append('BinAssociation')
205
206 ################################################################################
207
208 class DBBinary(object):
209     def __init__(self, *args, **kwargs):
210         pass
211
212     def __repr__(self):
213         return '<DBBinary %s (%s, %s)>' % (self.package, self.version, self.architecture)
214
215 __all__.append('DBBinary')
216
217 @session_wrapper
218 def get_suites_binary_in(package, session=None):
219     """
220     Returns list of Suite objects which given C{package} name is in
221
222     @type source: str
223     @param source: DBBinary package name to search for
224
225     @rtype: list
226     @return: list of Suite objects for the given package
227     """
228
229     return session.query(Suite).join(BinAssociation).join(DBBinary).filter_by(package=package).all()
230
231 __all__.append('get_suites_binary_in')
232
233 @session_wrapper
234 def get_binary_from_id(id, session=None):
235     """
236     Returns DBBinary object for given C{id}
237
238     @type id: int
239     @param id: Id of the required binary
240
241     @type session: Session
242     @param session: Optional SQLA session object (a temporary one will be
243     generated if not supplied)
244
245     @rtype: DBBinary
246     @return: DBBinary object for the given binary (None if not present)
247     """
248
249     q = session.query(DBBinary).filter_by(binary_id=id)
250
251     try:
252         return q.one()
253     except NoResultFound:
254         return None
255
256 __all__.append('get_binary_from_id')
257
258 @session_wrapper
259 def get_binaries_from_name(package, version=None, architecture=None, session=None):
260     """
261     Returns list of DBBinary objects for given C{package} name
262
263     @type package: str
264     @param package: DBBinary package name to search for
265
266     @type version: str or None
267     @param version: Version to search for (or None)
268
269     @type package: str, list or None
270     @param package: Architectures to limit to (or None if no limit)
271
272     @type session: Session
273     @param session: Optional SQL session object (a temporary one will be
274     generated if not supplied)
275
276     @rtype: list
277     @return: list of DBBinary objects for the given name (may be empty)
278     """
279
280     q = session.query(DBBinary).filter_by(package=package)
281
282     if version is not None:
283         q = q.filter_by(version=version)
284
285     if architecture is not None:
286         if not isinstance(architecture, list):
287             architecture = [architecture]
288         q = q.join(Architecture).filter(Architecture.arch_string.in_(architecture))
289
290     ret = q.all()
291
292     return ret
293
294 __all__.append('get_binaries_from_name')
295
296 @session_wrapper
297 def get_binaries_from_source_id(source_id, session=None):
298     """
299     Returns list of DBBinary objects for given C{source_id}
300
301     @type source_id: int
302     @param source_id: source_id to search for
303
304     @type session: Session
305     @param session: Optional SQL session object (a temporary one will be
306     generated if not supplied)
307
308     @rtype: list
309     @return: list of DBBinary objects for the given name (may be empty)
310     """
311
312     return session.query(DBBinary).filter_by(source_id=source_id).all()
313
314 __all__.append('get_binaries_from_source_id')
315
316 @session_wrapper
317 def get_binary_from_name_suite(package, suitename, session=None):
318     ### For dak examine-package
319     ### XXX: Doesn't use object API yet
320
321     sql = """SELECT DISTINCT(b.package), b.version, c.name, su.suite_name
322              FROM binaries b, files fi, location l, component c, bin_associations ba, suite su
323              WHERE b.package=:package
324                AND b.file = fi.id
325                AND fi.location = l.id
326                AND l.component = c.id
327                AND ba.bin=b.id
328                AND ba.suite = su.id
329                AND su.suite_name=:suitename
330           ORDER BY b.version DESC"""
331
332     return session.execute(sql, {'package': package, 'suitename': suitename})
333
334 __all__.append('get_binary_from_name_suite')
335
336 @session_wrapper
337 def get_binary_components(package, suitename, arch, session=None):
338     # Check for packages that have moved from one component to another
339     query = """SELECT c.name FROM binaries b, bin_associations ba, suite s, location l, component c, architecture a, files f
340     WHERE b.package=:package AND s.suite_name=:suitename
341       AND (a.arch_string = :arch OR a.arch_string = 'all')
342       AND ba.bin = b.id AND ba.suite = s.id AND b.architecture = a.id
343       AND f.location = l.id
344       AND l.component = c.id
345       AND b.file = f.id"""
346
347     vals = {'package': package, 'suitename': suitename, 'arch': arch}
348
349     return session.execute(query, vals)
350
351 __all__.append('get_binary_components')
352
353 ################################################################################
354
355 class Component(object):
356     def __init__(self, *args, **kwargs):
357         pass
358
359     def __eq__(self, val):
360         if isinstance(val, str):
361             return (self.component_name == val)
362         # This signals to use the normal comparison operator
363         return NotImplemented
364
365     def __ne__(self, val):
366         if isinstance(val, str):
367             return (self.component_name != val)
368         # This signals to use the normal comparison operator
369         return NotImplemented
370
371     def __repr__(self):
372         return '<Component %s>' % self.component_name
373
374
375 __all__.append('Component')
376
377 @session_wrapper
378 def get_component(component, session=None):
379     """
380     Returns database id for given C{component}.
381
382     @type component: string
383     @param component: The name of the override type
384
385     @rtype: int
386     @return: the database id for the given component
387
388     """
389     component = component.lower()
390
391     q = session.query(Component).filter_by(component_name=component)
392
393     try:
394         return q.one()
395     except NoResultFound:
396         return None
397
398 __all__.append('get_component')
399
400 ################################################################################
401
402 class DBConfig(object):
403     def __init__(self, *args, **kwargs):
404         pass
405
406     def __repr__(self):
407         return '<DBConfig %s>' % self.name
408
409 __all__.append('DBConfig')
410
411 ################################################################################
412
413 class ContentFilename(object):
414     def __init__(self, *args, **kwargs):
415         pass
416
417     def __repr__(self):
418         return '<ContentFilename %s>' % self.filename
419
420 __all__.append('ContentFilename')
421
422 def get_or_set_contents_file_id(filename, session=None):
423     """
424     Returns database id for given filename.
425
426     If no matching file is found, a row is inserted.
427
428     @type filename: string
429     @param filename: The filename
430     @type session: SQLAlchemy
431     @param session: Optional SQL session object (a temporary one will be
432     generated if not supplied).  If not passed, a commit will be performed at
433     the end of the function, otherwise the caller is responsible for commiting.
434
435     @rtype: int
436     @return: the database id for the given component
437     """
438     privatetrans = False
439     if session is None:
440         session = DBConn().session()
441         privatetrans = True
442
443     q = session.query(ContentFilename).filter_by(filename=filename)
444
445     try:
446         ret = q.one().cafilename_id
447     except NoResultFound:
448         cf = ContentFilename()
449         cf.filename = filename
450         session.add(cf)
451         if privatetrans:
452             session.commit()
453         else:
454             session.flush()
455         ret = cf.cafilename_id
456
457     if privatetrans:
458         session.close()
459
460     return ret
461
462 __all__.append('get_or_set_contents_file_id')
463
464 @session_wrapper
465 def get_contents(suite, overridetype, section=None, session=None):
466     """
467     Returns contents for a suite / overridetype combination, limiting
468     to a section if not None.
469
470     @type suite: Suite
471     @param suite: Suite object
472
473     @type overridetype: OverrideType
474     @param overridetype: OverrideType object
475
476     @type section: Section
477     @param section: Optional section object to limit results to
478
479     @type session: SQLAlchemy
480     @param session: Optional SQL session object (a temporary one will be
481     generated if not supplied)
482
483     @rtype: ResultsProxy
484     @return: ResultsProxy object set up to return tuples of (filename, section,
485     package, arch_id)
486     """
487
488     # find me all of the contents for a given suite
489     contents_q = """SELECT (p.path||'/'||n.file) AS fn,
490                             s.section,
491                             b.package,
492                             b.architecture
493                    FROM content_associations c join content_file_paths p ON (c.filepath=p.id)
494                    JOIN content_file_names n ON (c.filename=n.id)
495                    JOIN binaries b ON (b.id=c.binary_pkg)
496                    JOIN override o ON (o.package=b.package)
497                    JOIN section s ON (s.id=o.section)
498                    WHERE o.suite = :suiteid AND o.type = :overridetypeid
499                    AND b.type=:overridetypename"""
500
501     vals = {'suiteid': suite.suite_id,
502             'overridetypeid': overridetype.overridetype_id,
503             'overridetypename': overridetype.overridetype}
504
505     if section is not None:
506         contents_q += " AND s.id = :sectionid"
507         vals['sectionid'] = section.section_id
508
509     contents_q += " ORDER BY fn"
510
511     return session.execute(contents_q, vals)
512
513 __all__.append('get_contents')
514
515 ################################################################################
516
517 class ContentFilepath(object):
518     def __init__(self, *args, **kwargs):
519         pass
520
521     def __repr__(self):
522         return '<ContentFilepath %s>' % self.filepath
523
524 __all__.append('ContentFilepath')
525
526 def get_or_set_contents_path_id(filepath, session=None):
527     """
528     Returns database id for given path.
529
530     If no matching file is found, a row is inserted.
531
532     @type filename: string
533     @param filename: The filepath
534     @type session: SQLAlchemy
535     @param session: Optional SQL session object (a temporary one will be
536     generated if not supplied).  If not passed, a commit will be performed at
537     the end of the function, otherwise the caller is responsible for commiting.
538
539     @rtype: int
540     @return: the database id for the given path
541     """
542     privatetrans = False
543     if session is None:
544         session = DBConn().session()
545         privatetrans = True
546
547     q = session.query(ContentFilepath).filter_by(filepath=filepath)
548
549     try:
550         ret = q.one().cafilepath_id
551     except NoResultFound:
552         cf = ContentFilepath()
553         cf.filepath = filepath
554         session.add(cf)
555         if privatetrans:
556             session.commit()
557         else:
558             session.flush()
559         ret = cf.cafilepath_id
560
561     if privatetrans:
562         session.close()
563
564     return ret
565
566 __all__.append('get_or_set_contents_path_id')
567
568 ################################################################################
569
570 class ContentAssociation(object):
571     def __init__(self, *args, **kwargs):
572         pass
573
574     def __repr__(self):
575         return '<ContentAssociation %s>' % self.ca_id
576
577 __all__.append('ContentAssociation')
578
579 def insert_content_paths(binary_id, fullpaths, session=None):
580     """
581     Make sure given path is associated with given binary id
582
583     @type binary_id: int
584     @param binary_id: the id of the binary
585     @type fullpaths: list
586     @param fullpaths: the list of paths of the file being associated with the binary
587     @type session: SQLAlchemy session
588     @param session: Optional SQLAlchemy session.  If this is passed, the caller
589     is responsible for ensuring a transaction has begun and committing the
590     results or rolling back based on the result code.  If not passed, a commit
591     will be performed at the end of the function, otherwise the caller is
592     responsible for commiting.
593
594     @return: True upon success
595     """
596
597     privatetrans = False
598     if session is None:
599         session = DBConn().session()
600         privatetrans = True
601
602     try:
603         # Insert paths
604         pathcache = {}
605         for fullpath in fullpaths:
606             # Get the necessary IDs ...
607             (path, file) = os.path.split(fullpath)
608
609             filepath_id = get_or_set_contents_path_id(path, session)
610             filename_id = get_or_set_contents_file_id(file, session)
611
612             pathcache[fullpath] = (filepath_id, filename_id)
613
614         for fullpath, dat in pathcache.items():
615             ca = ContentAssociation()
616             ca.binary_id = binary_id
617             ca.filepath_id = dat[0]
618             ca.filename_id = dat[1]
619             session.add(ca)
620
621         # Only commit if we set up the session ourself
622         if privatetrans:
623             session.commit()
624             session.close()
625         else:
626             session.flush()
627
628         return True
629
630     except:
631         traceback.print_exc()
632
633         # Only rollback if we set up the session ourself
634         if privatetrans:
635             session.rollback()
636             session.close()
637
638         return False
639
640 __all__.append('insert_content_paths')
641
642 ################################################################################
643
644 class DSCFile(object):
645     def __init__(self, *args, **kwargs):
646         pass
647
648     def __repr__(self):
649         return '<DSCFile %s>' % self.dscfile_id
650
651 __all__.append('DSCFile')
652
653 @session_wrapper
654 def get_dscfiles(dscfile_id=None, source_id=None, poolfile_id=None, session=None):
655     """
656     Returns a list of DSCFiles which may be empty
657
658     @type dscfile_id: int (optional)
659     @param dscfile_id: the dscfile_id of the DSCFiles to find
660
661     @type source_id: int (optional)
662     @param source_id: the source id related to the DSCFiles to find
663
664     @type poolfile_id: int (optional)
665     @param poolfile_id: the poolfile id related to the DSCFiles to find
666
667     @rtype: list
668     @return: Possibly empty list of DSCFiles
669     """
670
671     q = session.query(DSCFile)
672
673     if dscfile_id is not None:
674         q = q.filter_by(dscfile_id=dscfile_id)
675
676     if source_id is not None:
677         q = q.filter_by(source_id=source_id)
678
679     if poolfile_id is not None:
680         q = q.filter_by(poolfile_id=poolfile_id)
681
682     return q.all()
683
684 __all__.append('get_dscfiles')
685
686 ################################################################################
687
688 class PoolFile(object):
689     def __init__(self, *args, **kwargs):
690         pass
691
692     def __repr__(self):
693         return '<PoolFile %s>' % self.filename
694
695 __all__.append('PoolFile')
696
697 @session_wrapper
698 def check_poolfile(filename, filesize, md5sum, location_id, session=None):
699     """
700     Returns a tuple:
701      (ValidFileFound [boolean or None], PoolFile object or None)
702
703     @type filename: string
704     @param filename: the filename of the file to check against the DB
705
706     @type filesize: int
707     @param filesize: the size of the file to check against the DB
708
709     @type md5sum: string
710     @param md5sum: the md5sum of the file to check against the DB
711
712     @type location_id: int
713     @param location_id: the id of the location to look in
714
715     @rtype: tuple
716     @return: Tuple of length 2.
717              If more than one file found with that name:
718                     (None,  None)
719              If valid pool file found: (True, PoolFile object)
720              If valid pool file not found:
721                     (False, None) if no file found
722                     (False, PoolFile object) if file found with size/md5sum mismatch
723     """
724
725     q = session.query(PoolFile).filter_by(filename=filename)
726     q = q.join(Location).filter_by(location_id=location_id)
727
728     ret = None
729
730     if q.count() > 1:
731         ret = (None, None)
732     elif q.count() < 1:
733         ret = (False, None)
734     else:
735         obj = q.one()
736         if obj.md5sum != md5sum or obj.filesize != filesize:
737             ret = (False, obj)
738
739     if ret is None:
740         ret = (True, obj)
741
742     return ret
743
744 __all__.append('check_poolfile')
745
746 @session_wrapper
747 def get_poolfile_by_id(file_id, session=None):
748     """
749     Returns a PoolFile objects or None for the given id
750
751     @type file_id: int
752     @param file_id: the id of the file to look for
753
754     @rtype: PoolFile or None
755     @return: either the PoolFile object or None
756     """
757
758     q = session.query(PoolFile).filter_by(file_id=file_id)
759
760     try:
761         return q.one()
762     except NoResultFound:
763         return None
764
765 __all__.append('get_poolfile_by_id')
766
767
768 @session_wrapper
769 def get_poolfile_by_name(filename, location_id=None, session=None):
770     """
771     Returns an array of PoolFile objects for the given filename and
772     (optionally) location_id
773
774     @type filename: string
775     @param filename: the filename of the file to check against the DB
776
777     @type location_id: int
778     @param location_id: the id of the location to look in (optional)
779
780     @rtype: array
781     @return: array of PoolFile objects
782     """
783
784     q = session.query(PoolFile).filter_by(filename=filename)
785
786     if location_id is not None:
787         q = q.join(Location).filter_by(location_id=location_id)
788
789     return q.all()
790
791 __all__.append('get_poolfile_by_name')
792
793 @session_wrapper
794 def get_poolfile_like_name(filename, session=None):
795     """
796     Returns an array of PoolFile objects which are like the given name
797
798     @type filename: string
799     @param filename: the filename of the file to check against the DB
800
801     @rtype: array
802     @return: array of PoolFile objects
803     """
804
805     # TODO: There must be a way of properly using bind parameters with %FOO%
806     q = session.query(PoolFile).filter(PoolFile.filename.like('%%%s%%' % filename))
807
808     return q.all()
809
810 __all__.append('get_poolfile_like_name')
811
812 ################################################################################
813
814 class Fingerprint(object):
815     def __init__(self, *args, **kwargs):
816         pass
817
818     def __repr__(self):
819         return '<Fingerprint %s>' % self.fingerprint
820
821 __all__.append('Fingerprint')
822
823 def get_or_set_fingerprint(fpr, session=None):
824     """
825     Returns Fingerprint object for given fpr.
826
827     If no matching fpr is found, a row is inserted.
828
829     @type fpr: string
830     @param fpr: The fpr to find / add
831
832     @type session: SQLAlchemy
833     @param session: Optional SQL session object (a temporary one will be
834     generated if not supplied).  If not passed, a commit will be performed at
835     the end of the function, otherwise the caller is responsible for commiting.
836     A flush will be performed either way.
837
838     @rtype: Fingerprint
839     @return: the Fingerprint object for the given fpr
840     """
841     privatetrans = False
842     if session is None:
843         session = DBConn().session()
844         privatetrans = True
845
846     q = session.query(Fingerprint).filter_by(fingerprint=fpr)
847
848     try:
849         ret = q.one()
850     except NoResultFound:
851         fingerprint = Fingerprint()
852         fingerprint.fingerprint = fpr
853         session.add(fingerprint)
854         if privatetrans:
855             session.commit()
856         else:
857             session.flush()
858         ret = fingerprint
859
860     if privatetrans:
861         session.close()
862
863     return ret
864
865 __all__.append('get_or_set_fingerprint')
866
867 ################################################################################
868
869 class Keyring(object):
870     def __init__(self, *args, **kwargs):
871         pass
872
873     def __repr__(self):
874         return '<Keyring %s>' % self.keyring_name
875
876 __all__.append('Keyring')
877
878 def get_or_set_keyring(keyring, session=None):
879     """
880     If C{keyring} does not have an entry in the C{keyrings} table yet, create one
881     and return the new Keyring
882     If C{keyring} already has an entry, simply return the existing Keyring
883
884     @type keyring: string
885     @param keyring: the keyring name
886
887     @rtype: Keyring
888     @return: the Keyring object for this keyring
889
890     """
891     privatetrans = False
892     if session is None:
893         session = DBConn().session()
894         privatetrans = True
895
896     try:
897         obj = session.query(Keyring).filter_by(keyring_name=keyring).first()
898
899         if obj is None:
900             obj = Keyring(keyring_name=keyring)
901             session.add(obj)
902             if privatetrans:
903                 session.commit()
904             else:
905                 session.flush()
906
907         return obj
908     finally:
909         if privatetrans:
910             session.close()
911
912 __all__.append('get_or_set_keyring')
913
914 ################################################################################
915
916 class Location(object):
917     def __init__(self, *args, **kwargs):
918         pass
919
920     def __repr__(self):
921         return '<Location %s (%s)>' % (self.path, self.location_id)
922
923 __all__.append('Location')
924
925 @session_wrapper
926 def get_location(location, component=None, archive=None, session=None):
927     """
928     Returns Location object for the given combination of location, component
929     and archive
930
931     @type location: string
932     @param location: the path of the location, e.g. I{/srv/ftp.debian.org/ftp/pool/}
933
934     @type component: string
935     @param component: the component name (if None, no restriction applied)
936
937     @type archive: string
938     @param archive_id: the archive name (if None, no restriction applied)
939
940     @rtype: Location / None
941     @return: Either a Location object or None if one can't be found
942     """
943
944     q = session.query(Location).filter_by(path=location)
945
946     if archive is not None:
947         q = q.join(Archive).filter_by(archive_name=archive)
948
949     if component is not None:
950         q = q.join(Component).filter_by(component_name=component)
951
952     try:
953         return q.one()
954     except NoResultFound:
955         return None
956
957 __all__.append('get_location')
958
959 ################################################################################
960
961 class Maintainer(object):
962     def __init__(self, *args, **kwargs):
963         pass
964
965     def __repr__(self):
966         return '''<Maintainer '%s' (%s)>''' % (self.name, self.maintainer_id)
967
968     def get_split_maintainer(self):
969         if not hasattr(self, 'name') or self.name is None:
970             return ('', '', '', '')
971
972         return fix_maintainer(self.name.strip())
973
974 __all__.append('Maintainer')
975
976 def get_or_set_maintainer(name, session=None):
977     """
978     Returns Maintainer object for given maintainer name.
979
980     If no matching maintainer name is found, a row is inserted.
981
982     @type name: string
983     @param name: The maintainer name to add
984
985     @type session: SQLAlchemy
986     @param session: Optional SQL session object (a temporary one will be
987     generated if not supplied).  If not passed, a commit will be performed at
988     the end of the function, otherwise the caller is responsible for commiting.
989     A flush will be performed either way.
990
991     @rtype: Maintainer
992     @return: the Maintainer object for the given maintainer
993     """
994     privatetrans = False
995     if session is None:
996         session = DBConn().session()
997         privatetrans = True
998
999     q = session.query(Maintainer).filter_by(name=name)
1000     try:
1001         ret = q.one()
1002     except NoResultFound:
1003         maintainer = Maintainer()
1004         maintainer.name = name
1005         session.add(maintainer)
1006         if privatetrans:
1007             session.commit()
1008         else:
1009             session.flush()
1010         ret = maintainer
1011
1012     if privatetrans:
1013         session.close()
1014
1015     return ret
1016
1017 __all__.append('get_or_set_maintainer')
1018
1019 def get_maintainer(maintainer_id, session=None):
1020     """
1021     Return the name of the maintainer behind C{maintainer_id} or None if that
1022     maintainer_id is invalid.
1023
1024     @type maintainer_id: int
1025     @param maintainer_id: the id of the maintainer
1026
1027     @rtype: Maintainer
1028     @return: the Maintainer with this C{maintainer_id}
1029     """
1030
1031     privatetrans = False
1032     if session is None:
1033         session = DBConn().session()
1034         privatetrans = True
1035
1036     try:
1037         return session.query(Maintainer).get(maintainer_id)
1038     finally:
1039         if privatetrans:
1040             session.close()
1041
1042 __all__.append('get_maintainer')
1043
1044 ################################################################################
1045
1046 class NewComment(object):
1047     def __init__(self, *args, **kwargs):
1048         pass
1049
1050     def __repr__(self):
1051         return '''<NewComment for '%s %s' (%s)>''' % (self.package, self.version, self.comment_id)
1052
1053 __all__.append('NewComment')
1054
1055 @session_wrapper
1056 def has_new_comment(package, version, session=None):
1057     """
1058     Returns true if the given combination of C{package}, C{version} has a comment.
1059
1060     @type package: string
1061     @param package: name of the package
1062
1063     @type version: string
1064     @param version: package version
1065
1066     @type session: Session
1067     @param session: Optional SQLA session object (a temporary one will be
1068     generated if not supplied)
1069
1070     @rtype: boolean
1071     @return: true/false
1072     """
1073
1074     q = session.query(NewComment)
1075     q = q.filter_by(package=package)
1076     q = q.filter_by(version=version)
1077
1078     return bool(q.count() > 0)
1079
1080 __all__.append('has_new_comment')
1081
1082 @session_wrapper
1083 def get_new_comments(package=None, version=None, comment_id=None, session=None):
1084     """
1085     Returns (possibly empty) list of NewComment objects for the given
1086     parameters
1087
1088     @type package: string (optional)
1089     @param package: name of the package
1090
1091     @type version: string (optional)
1092     @param version: package version
1093
1094     @type comment_id: int (optional)
1095     @param comment_id: An id of a comment
1096
1097     @type session: Session
1098     @param session: Optional SQLA session object (a temporary one will be
1099     generated if not supplied)
1100
1101     @rtype: list
1102     @return: A (possibly empty) list of NewComment objects will be returned
1103     """
1104
1105     q = session.query(NewComment)
1106     if package is not None: q = q.filter_by(package=package)
1107     if version is not None: q = q.filter_by(version=version)
1108     if comment_id is not None: q = q.filter_by(comment_id=comment_id)
1109
1110     return q.all()
1111
1112 __all__.append('get_new_comments')
1113
1114 ################################################################################
1115
1116 class Override(object):
1117     def __init__(self, *args, **kwargs):
1118         pass
1119
1120     def __repr__(self):
1121         return '<Override %s (%s)>' % (self.package, self.suite_id)
1122
1123 __all__.append('Override')
1124
1125 @session_wrapper
1126 def get_override(package, suite=None, component=None, overridetype=None, session=None):
1127     """
1128     Returns Override object for the given parameters
1129
1130     @type package: string
1131     @param package: The name of the package
1132
1133     @type suite: string, list or None
1134     @param suite: The name of the suite (or suites if a list) to limit to.  If
1135                   None, don't limit.  Defaults to None.
1136
1137     @type component: string, list or None
1138     @param component: The name of the component (or components if a list) to
1139                       limit to.  If None, don't limit.  Defaults to None.
1140
1141     @type overridetype: string, list or None
1142     @param overridetype: The name of the overridetype (or overridetypes if a list) to
1143                          limit to.  If None, don't limit.  Defaults to None.
1144
1145     @type session: Session
1146     @param session: Optional SQLA session object (a temporary one will be
1147     generated if not supplied)
1148
1149     @rtype: list
1150     @return: A (possibly empty) list of Override objects will be returned
1151     """
1152
1153     q = session.query(Override)
1154     q = q.filter_by(package=package)
1155
1156     if suite is not None:
1157         if not isinstance(suite, list): suite = [suite]
1158         q = q.join(Suite).filter(Suite.suite_name.in_(suite))
1159
1160     if component is not None:
1161         if not isinstance(component, list): component = [component]
1162         q = q.join(Component).filter(Component.component_name.in_(component))
1163
1164     if overridetype is not None:
1165         if not isinstance(overridetype, list): overridetype = [overridetype]
1166         q = q.join(OverrideType).filter(OverrideType.overridetype.in_(overridetype))
1167
1168     return q.all()
1169
1170 __all__.append('get_override')
1171
1172
1173 ################################################################################
1174
1175 class OverrideType(object):
1176     def __init__(self, *args, **kwargs):
1177         pass
1178
1179     def __repr__(self):
1180         return '<OverrideType %s>' % self.overridetype
1181
1182 __all__.append('OverrideType')
1183
1184 @session_wrapper
1185 def get_override_type(override_type, session=None):
1186     """
1187     Returns OverrideType object for given C{override type}.
1188
1189     @type override_type: string
1190     @param override_type: The name of the override type
1191
1192     @type session: Session
1193     @param session: Optional SQLA session object (a temporary one will be
1194     generated if not supplied)
1195
1196     @rtype: int
1197     @return: the database id for the given override type
1198     """
1199
1200     q = session.query(OverrideType).filter_by(overridetype=override_type)
1201
1202     try:
1203         return q.one()
1204     except NoResultFound:
1205         return None
1206
1207 __all__.append('get_override_type')
1208
1209 ################################################################################
1210
1211 class PendingContentAssociation(object):
1212     def __init__(self, *args, **kwargs):
1213         pass
1214
1215     def __repr__(self):
1216         return '<PendingContentAssociation %s>' % self.pca_id
1217
1218 __all__.append('PendingContentAssociation')
1219
1220 def insert_pending_content_paths(package, fullpaths, session=None):
1221     """
1222     Make sure given paths are temporarily associated with given
1223     package
1224
1225     @type package: dict
1226     @param package: the package to associate with should have been read in from the binary control file
1227     @type fullpaths: list
1228     @param fullpaths: the list of paths of the file being associated with the binary
1229     @type session: SQLAlchemy session
1230     @param session: Optional SQLAlchemy session.  If this is passed, the caller
1231     is responsible for ensuring a transaction has begun and committing the
1232     results or rolling back based on the result code.  If not passed, a commit
1233     will be performed at the end of the function
1234
1235     @return: True upon success, False if there is a problem
1236     """
1237
1238     privatetrans = False
1239
1240     if session is None:
1241         session = DBConn().session()
1242         privatetrans = True
1243
1244     try:
1245         arch = get_architecture(package['Architecture'], session)
1246         arch_id = arch.arch_id
1247
1248         # Remove any already existing recorded files for this package
1249         q = session.query(PendingContentAssociation)
1250         q = q.filter_by(package=package['Package'])
1251         q = q.filter_by(version=package['Version'])
1252         q = q.filter_by(architecture=arch_id)
1253         q.delete()
1254
1255         # Insert paths
1256         pathcache = {}
1257         for fullpath in fullpaths:
1258             (path, file) = os.path.split(fullpath)
1259
1260             if path.startswith( "./" ):
1261                 path = path[2:]
1262
1263             filepath_id = get_or_set_contents_path_id(path, session)
1264             filename_id = get_or_set_contents_file_id(file, session)
1265
1266             pathcache[fullpath] = (filepath_id, filename_id)
1267
1268         for fullpath, dat in pathcache.items():
1269             pca = PendingContentAssociation()
1270             pca.package = package['Package']
1271             pca.version = package['Version']
1272             pca.filepath_id = dat[0]
1273             pca.filename_id = dat[1]
1274             pca.architecture = arch_id
1275             session.add(pca)
1276
1277         # Only commit if we set up the session ourself
1278         if privatetrans:
1279             session.commit()
1280             session.close()
1281         else:
1282             session.flush()
1283
1284         return True
1285     except Exception, e:
1286         traceback.print_exc()
1287
1288         # Only rollback if we set up the session ourself
1289         if privatetrans:
1290             session.rollback()
1291             session.close()
1292
1293         return False
1294
1295 __all__.append('insert_pending_content_paths')
1296
1297 ################################################################################
1298
1299 class Priority(object):
1300     def __init__(self, *args, **kwargs):
1301         pass
1302
1303     def __eq__(self, val):
1304         if isinstance(val, str):
1305             return (self.priority == val)
1306         # This signals to use the normal comparison operator
1307         return NotImplemented
1308
1309     def __ne__(self, val):
1310         if isinstance(val, str):
1311             return (self.priority != val)
1312         # This signals to use the normal comparison operator
1313         return NotImplemented
1314
1315     def __repr__(self):
1316         return '<Priority %s (%s)>' % (self.priority, self.priority_id)
1317
1318 __all__.append('Priority')
1319
1320 @session_wrapper
1321 def get_priority(priority, session=None):
1322     """
1323     Returns Priority object for given C{priority name}.
1324
1325     @type priority: string
1326     @param priority: The name of the priority
1327
1328     @type session: Session
1329     @param session: Optional SQLA session object (a temporary one will be
1330     generated if not supplied)
1331
1332     @rtype: Priority
1333     @return: Priority object for the given priority
1334     """
1335
1336     q = session.query(Priority).filter_by(priority=priority)
1337
1338     try:
1339         return q.one()
1340     except NoResultFound:
1341         return None
1342
1343 __all__.append('get_priority')
1344
1345 @session_wrapper
1346 def get_priorities(session=None):
1347     """
1348     Returns dictionary of priority names -> id mappings
1349
1350     @type session: Session
1351     @param session: Optional SQL session object (a temporary one will be
1352     generated if not supplied)
1353
1354     @rtype: dictionary
1355     @return: dictionary of priority names -> id mappings
1356     """
1357
1358     ret = {}
1359     q = session.query(Priority)
1360     for x in q.all():
1361         ret[x.priority] = x.priority_id
1362
1363     return ret
1364
1365 __all__.append('get_priorities')
1366
1367 ################################################################################
1368
1369 class Queue(object):
1370     def __init__(self, *args, **kwargs):
1371         pass
1372
1373     def __repr__(self):
1374         return '<Queue %s>' % self.queue_name
1375
1376     def autobuild_upload(self, changes, srcpath, session=None):
1377         """
1378         Update queue_build database table used for incoming autobuild support.
1379
1380         @type changes: Changes
1381         @param changes: changes object for the upload to process
1382
1383         @type srcpath: string
1384         @param srcpath: path for the queue file entries/link destinations
1385
1386         @type session: SQLAlchemy session
1387         @param session: Optional SQLAlchemy session.  If this is passed, the
1388         caller is responsible for ensuring a transaction has begun and
1389         committing the results or rolling back based on the result code.  If
1390         not passed, a commit will be performed at the end of the function,
1391         otherwise the caller is responsible for commiting.
1392
1393         @rtype: NoneType or string
1394         @return: None if the operation failed, a string describing the error if not
1395         """
1396
1397         privatetrans = False
1398         if session is None:
1399             session = DBConn().session()
1400             privatetrans = True
1401
1402         # TODO: Remove by moving queue config into the database
1403         conf = Config()
1404
1405         for suitename in changes.changes["distribution"].keys():
1406             # TODO: Move into database as:
1407             #       buildqueuedir TEXT DEFAULT NULL (i.e. NULL is no build)
1408             #       buildqueuecopy BOOLEAN NOT NULL DEFAULT FALSE (i.e. default is symlink)
1409             #       This also gets rid of the SecurityQueueBuild hack below
1410             if suitename not in conf.ValueList("Dinstall::QueueBuildSuites"):
1411                 continue
1412
1413             # Find suite object
1414             s = get_suite(suitename, session)
1415             if s is None:
1416                 return "INTERNAL ERROR: Could not find suite %s" % suitename
1417
1418             # TODO: Get from database as above
1419             dest_dir = conf["Dir::QueueBuild"]
1420
1421             # TODO: Move into database as above
1422             if conf.FindB("Dinstall::SecurityQueueBuild"):
1423                 dest_dir = os.path.join(dest_dir, suitename)
1424
1425             for file_entry in changes.files.keys():
1426                 src = os.path.join(srcpath, file_entry)
1427                 dest = os.path.join(dest_dir, file_entry)
1428
1429                 # TODO: Move into database as above
1430                 if conf.FindB("Dinstall::SecurityQueueBuild"):
1431                     # Copy it since the original won't be readable by www-data
1432                     import utils
1433                     utils.copy(src, dest)
1434                 else:
1435                     # Create a symlink to it
1436                     os.symlink(src, dest)
1437
1438                 qb = QueueBuild()
1439                 qb.suite_id = s.suite_id
1440                 qb.queue_id = self.queue_id
1441                 qb.filename = dest
1442                 qb.in_queue = True
1443
1444                 session.add(qb)
1445
1446             # If the .orig tarballs are in the pool, create a symlink to
1447             # them (if one doesn't already exist)
1448             for dsc_file in changes.dsc_files.keys():
1449                 # Skip all files except orig tarballs
1450                 if not re_is_orig_source.match(dsc_file):
1451                     continue
1452                 # Skip orig files not identified in the pool
1453                 if not (changes.orig_files.has_key(dsc_file) and
1454                         changes.orig_files[dsc_file].has_key("id")):
1455                     continue
1456                 orig_file_id = changes.orig_files[dsc_file]["id"]
1457                 dest = os.path.join(dest_dir, dsc_file)
1458
1459                 # If it doesn't exist, create a symlink
1460                 if not os.path.exists(dest):
1461                     q = session.execute("SELECT l.path, f.filename FROM location l, files f WHERE f.id = :id and f.location = l.id",
1462                                         {'id': orig_file_id})
1463                     res = q.fetchone()
1464                     if not res:
1465                         return "[INTERNAL ERROR] Couldn't find id %s in files table." % (orig_file_id)
1466
1467                     src = os.path.join(res[0], res[1])
1468                     os.symlink(src, dest)
1469
1470                     # Add it to the list of packages for later processing by apt-ftparchive
1471                     qb = QueueBuild()
1472                     qb.suite_id = s.suite_id
1473                     qb.queue_id = self.queue_id
1474                     qb.filename = dest
1475                     qb.in_queue = True
1476                     session.add(qb)
1477
1478                 # If it does, update things to ensure it's not removed prematurely
1479                 else:
1480                     qb = get_queue_build(dest, s.suite_id, session)
1481                     if qb is None:
1482                         qb.in_queue = True
1483                         qb.last_used = None
1484                         session.add(qb)
1485
1486         if privatetrans:
1487             session.commit()
1488             session.close()
1489
1490         return None
1491
1492 __all__.append('Queue')
1493
1494 @session_wrapper
1495 def get_queue(queuename, session=None):
1496     """
1497     Returns Queue object for given C{queue name}.
1498
1499     @type queuename: string
1500     @param queuename: The name of the queue
1501
1502     @type session: Session
1503     @param session: Optional SQLA session object (a temporary one will be
1504     generated if not supplied)
1505
1506     @rtype: Queue
1507     @return: Queue object for the given queue
1508     """
1509
1510     q = session.query(Queue).filter_by(queue_name=queuename)
1511
1512     try:
1513         return q.one()
1514     except NoResultFound:
1515         return None
1516
1517 __all__.append('get_queue')
1518
1519 ################################################################################
1520
1521 class QueueBuild(object):
1522     def __init__(self, *args, **kwargs):
1523         pass
1524
1525     def __repr__(self):
1526         return '<QueueBuild %s (%s)>' % (self.filename, self.queue_id)
1527
1528 __all__.append('QueueBuild')
1529
1530 @session_wrapper
1531 def get_queue_build(filename, suite, session=None):
1532     """
1533     Returns QueueBuild object for given C{filename} and C{suite}.
1534
1535     @type filename: string
1536     @param filename: The name of the file
1537
1538     @type suiteid: int or str
1539     @param suiteid: Suite name or ID
1540
1541     @type session: Session
1542     @param session: Optional SQLA session object (a temporary one will be
1543     generated if not supplied)
1544
1545     @rtype: Queue
1546     @return: Queue object for the given queue
1547     """
1548
1549     if isinstance(suite, int):
1550         q = session.query(QueueBuild).filter_by(filename=filename).filter_by(suite_id=suite)
1551     else:
1552         q = session.query(QueueBuild).filter_by(filename=filename)
1553         q = q.join(Suite).filter_by(suite_name=suite)
1554
1555     try:
1556         return q.one()
1557     except NoResultFound:
1558         return None
1559
1560 __all__.append('get_queue_build')
1561
1562 ################################################################################
1563
1564 class Section(object):
1565     def __init__(self, *args, **kwargs):
1566         pass
1567
1568     def __eq__(self, val):
1569         if isinstance(val, str):
1570             return (self.section == val)
1571         # This signals to use the normal comparison operator
1572         return NotImplemented
1573
1574     def __ne__(self, val):
1575         if isinstance(val, str):
1576             return (self.section != val)
1577         # This signals to use the normal comparison operator
1578         return NotImplemented
1579
1580     def __repr__(self):
1581         return '<Section %s>' % self.section
1582
1583 __all__.append('Section')
1584
1585 @session_wrapper
1586 def get_section(section, session=None):
1587     """
1588     Returns Section object for given C{section name}.
1589
1590     @type section: string
1591     @param section: The name of the section
1592
1593     @type session: Session
1594     @param session: Optional SQLA session object (a temporary one will be
1595     generated if not supplied)
1596
1597     @rtype: Section
1598     @return: Section object for the given section name
1599     """
1600
1601     q = session.query(Section).filter_by(section=section)
1602
1603     try:
1604         return q.one()
1605     except NoResultFound:
1606         return None
1607
1608 __all__.append('get_section')
1609
1610 @session_wrapper
1611 def get_sections(session=None):
1612     """
1613     Returns dictionary of section names -> id mappings
1614
1615     @type session: Session
1616     @param session: Optional SQL session object (a temporary one will be
1617     generated if not supplied)
1618
1619     @rtype: dictionary
1620     @return: dictionary of section names -> id mappings
1621     """
1622
1623     ret = {}
1624     q = session.query(Section)
1625     for x in q.all():
1626         ret[x.section] = x.section_id
1627
1628     return ret
1629
1630 __all__.append('get_sections')
1631
1632 ################################################################################
1633
1634 class DBSource(object):
1635     def __init__(self, *args, **kwargs):
1636         pass
1637
1638     def __repr__(self):
1639         return '<DBSource %s (%s)>' % (self.source, self.version)
1640
1641 __all__.append('DBSource')
1642
1643 @session_wrapper
1644 def source_exists(source, source_version, suites = ["any"], session=None):
1645     """
1646     Ensure that source exists somewhere in the archive for the binary
1647     upload being processed.
1648       1. exact match     => 1.0-3
1649       2. bin-only NMU    => 1.0-3+b1 , 1.0-3.1+b1
1650
1651     @type package: string
1652     @param package: package source name
1653
1654     @type source_version: string
1655     @param source_version: expected source version
1656
1657     @type suites: list
1658     @param suites: list of suites to check in, default I{any}
1659
1660     @type session: Session
1661     @param session: Optional SQLA session object (a temporary one will be
1662     generated if not supplied)
1663
1664     @rtype: int
1665     @return: returns 1 if a source with expected version is found, otherwise 0
1666
1667     """
1668
1669     cnf = Config()
1670     ret = 1
1671
1672     for suite in suites:
1673         q = session.query(DBSource).filter_by(source=source)
1674         if suite != "any":
1675             # source must exist in suite X, or in some other suite that's
1676             # mapped to X, recursively... silent-maps are counted too,
1677             # unreleased-maps aren't.
1678             maps = cnf.ValueList("SuiteMappings")[:]
1679             maps.reverse()
1680             maps = [ m.split() for m in maps ]
1681             maps = [ (x[1], x[2]) for x in maps
1682                             if x[0] == "map" or x[0] == "silent-map" ]
1683             s = [suite]
1684             for x in maps:
1685                 if x[1] in s and x[0] not in s:
1686                     s.append(x[0])
1687
1688             q = q.join(SrcAssociation).join(Suite)
1689             q = q.filter(Suite.suite_name.in_(s))
1690
1691         # Reduce the query results to a list of version numbers
1692         ql = [ j.version for j in q.all() ]
1693
1694         # Try (1)
1695         if source_version in ql:
1696             continue
1697
1698         # Try (2)
1699         from daklib.regexes import re_bin_only_nmu
1700         orig_source_version = re_bin_only_nmu.sub('', source_version)
1701         if orig_source_version in ql:
1702             continue
1703
1704         # No source found so return not ok
1705         ret = 0
1706
1707     return ret
1708
1709 __all__.append('source_exists')
1710
1711 @session_wrapper
1712 def get_suites_source_in(source, session=None):
1713     """
1714     Returns list of Suite objects which given C{source} name is in
1715
1716     @type source: str
1717     @param source: DBSource package name to search for
1718
1719     @rtype: list
1720     @return: list of Suite objects for the given source
1721     """
1722
1723     return session.query(Suite).join(SrcAssociation).join(DBSource).filter_by(source=source).all()
1724
1725 __all__.append('get_suites_source_in')
1726
1727 @session_wrapper
1728 def get_sources_from_name(source, version=None, dm_upload_allowed=None, session=None):
1729     """
1730     Returns list of DBSource objects for given C{source} name and other parameters
1731
1732     @type source: str
1733     @param source: DBSource package name to search for
1734
1735     @type source: str or None
1736     @param source: DBSource version name to search for or None if not applicable
1737
1738     @type dm_upload_allowed: bool
1739     @param dm_upload_allowed: If None, no effect.  If True or False, only
1740     return packages with that dm_upload_allowed setting
1741
1742     @type session: Session
1743     @param session: Optional SQL session object (a temporary one will be
1744     generated if not supplied)
1745
1746     @rtype: list
1747     @return: list of DBSource objects for the given name (may be empty)
1748     """
1749
1750     q = session.query(DBSource).filter_by(source=source)
1751
1752     if version is not None:
1753         q = q.filter_by(version=version)
1754
1755     if dm_upload_allowed is not None:
1756         q = q.filter_by(dm_upload_allowed=dm_upload_allowed)
1757
1758     return q.all()
1759
1760 __all__.append('get_sources_from_name')
1761
1762 @session_wrapper
1763 def get_source_in_suite(source, suite, session=None):
1764     """
1765     Returns list of DBSource objects for a combination of C{source} and C{suite}.
1766
1767       - B{source} - source package name, eg. I{mailfilter}, I{bbdb}, I{glibc}
1768       - B{suite} - a suite name, eg. I{unstable}
1769
1770     @type source: string
1771     @param source: source package name
1772
1773     @type suite: string
1774     @param suite: the suite name
1775
1776     @rtype: string
1777     @return: the version for I{source} in I{suite}
1778
1779     """
1780
1781     q = session.query(SrcAssociation)
1782     q = q.join('source').filter_by(source=source)
1783     q = q.join('suite').filter_by(suite_name=suite)
1784
1785     try:
1786         return q.one().source
1787     except NoResultFound:
1788         return None
1789
1790 __all__.append('get_source_in_suite')
1791
1792 ################################################################################
1793
1794 class SrcAssociation(object):
1795     def __init__(self, *args, **kwargs):
1796         pass
1797
1798     def __repr__(self):
1799         return '<SrcAssociation %s (%s, %s)>' % (self.sa_id, self.source, self.suite)
1800
1801 __all__.append('SrcAssociation')
1802
1803 ################################################################################
1804
1805 class SrcFormat(object):
1806     def __init__(self, *args, **kwargs):
1807         pass
1808
1809     def __repr__(self):
1810         return '<SrcFormat %s>' % (self.format_name)
1811
1812 __all__.append('SrcFormat')
1813
1814 ################################################################################
1815
1816 class SrcUploader(object):
1817     def __init__(self, *args, **kwargs):
1818         pass
1819
1820     def __repr__(self):
1821         return '<SrcUploader %s>' % self.uploader_id
1822
1823 __all__.append('SrcUploader')
1824
1825 ################################################################################
1826
1827 SUITE_FIELDS = [ ('SuiteName', 'suite_name'),
1828                  ('SuiteID', 'suite_id'),
1829                  ('Version', 'version'),
1830                  ('Origin', 'origin'),
1831                  ('Label', 'label'),
1832                  ('Description', 'description'),
1833                  ('Untouchable', 'untouchable'),
1834                  ('Announce', 'announce'),
1835                  ('Codename', 'codename'),
1836                  ('OverrideCodename', 'overridecodename'),
1837                  ('ValidTime', 'validtime'),
1838                  ('Priority', 'priority'),
1839                  ('NotAutomatic', 'notautomatic'),
1840                  ('CopyChanges', 'copychanges'),
1841                  ('CopyDotDak', 'copydotdak'),
1842                  ('CommentsDir', 'commentsdir'),
1843                  ('OverrideSuite', 'overridesuite'),
1844                  ('ChangelogBase', 'changelogbase')]
1845
1846
1847 class Suite(object):
1848     def __init__(self, *args, **kwargs):
1849         pass
1850
1851     def __repr__(self):
1852         return '<Suite %s>' % self.suite_name
1853
1854     def __eq__(self, val):
1855         if isinstance(val, str):
1856             return (self.suite_name == val)
1857         # This signals to use the normal comparison operator
1858         return NotImplemented
1859
1860     def __ne__(self, val):
1861         if isinstance(val, str):
1862             return (self.suite_name != val)
1863         # This signals to use the normal comparison operator
1864         return NotImplemented
1865
1866     def details(self):
1867         ret = []
1868         for disp, field in SUITE_FIELDS:
1869             val = getattr(self, field, None)
1870             if val is not None:
1871                 ret.append("%s: %s" % (disp, val))
1872
1873         return "\n".join(ret)
1874
1875 __all__.append('Suite')
1876
1877 @session_wrapper
1878 def get_suite_architecture(suite, architecture, session=None):
1879     """
1880     Returns a SuiteArchitecture object given C{suite} and ${arch} or None if it
1881     doesn't exist
1882
1883     @type suite: str
1884     @param suite: Suite name to search for
1885
1886     @type architecture: str
1887     @param architecture: Architecture name to search for
1888
1889     @type session: Session
1890     @param session: Optional SQL session object (a temporary one will be
1891     generated if not supplied)
1892
1893     @rtype: SuiteArchitecture
1894     @return: the SuiteArchitecture object or None
1895     """
1896
1897     q = session.query(SuiteArchitecture)
1898     q = q.join(Architecture).filter_by(arch_string=architecture)
1899     q = q.join(Suite).filter_by(suite_name=suite)
1900
1901     try:
1902         return q.one()
1903     except NoResultFound:
1904         return None
1905
1906 __all__.append('get_suite_architecture')
1907
1908 @session_wrapper
1909 def get_suite(suite, session=None):
1910     """
1911     Returns Suite object for given C{suite name}.
1912
1913     @type suite: string
1914     @param suite: The name of the suite
1915
1916     @type session: Session
1917     @param session: Optional SQLA session object (a temporary one will be
1918     generated if not supplied)
1919
1920     @rtype: Suite
1921     @return: Suite object for the requested suite name (None if not presenT)
1922     """
1923
1924     q = session.query(Suite).filter_by(suite_name=suite)
1925
1926     try:
1927         return q.one()
1928     except NoResultFound:
1929         return None
1930
1931 __all__.append('get_suite')
1932
1933 ################################################################################
1934
1935 class SuiteArchitecture(object):
1936     def __init__(self, *args, **kwargs):
1937         pass
1938
1939     def __repr__(self):
1940         return '<SuiteArchitecture (%s, %s)>' % (self.suite_id, self.arch_id)
1941
1942 __all__.append('SuiteArchitecture')
1943
1944 @session_wrapper
1945 def get_suite_architectures(suite, skipsrc=False, skipall=False, session=None):
1946     """
1947     Returns list of Architecture objects for given C{suite} name
1948
1949     @type source: str
1950     @param source: Suite name to search for
1951
1952     @type skipsrc: boolean
1953     @param skipsrc: Whether to skip returning the 'source' architecture entry
1954     (Default False)
1955
1956     @type skipall: boolean
1957     @param skipall: Whether to skip returning the 'all' architecture entry
1958     (Default False)
1959
1960     @type session: Session
1961     @param session: Optional SQL session object (a temporary one will be
1962     generated if not supplied)
1963
1964     @rtype: list
1965     @return: list of Architecture objects for the given name (may be empty)
1966     """
1967
1968     q = session.query(Architecture)
1969     q = q.join(SuiteArchitecture)
1970     q = q.join(Suite).filter_by(suite_name=suite)
1971
1972     if skipsrc:
1973         q = q.filter(Architecture.arch_string != 'source')
1974
1975     if skipall:
1976         q = q.filter(Architecture.arch_string != 'all')
1977
1978     q = q.order_by('arch_string')
1979
1980     return q.all()
1981
1982 __all__.append('get_suite_architectures')
1983
1984 ################################################################################
1985
1986 class SuiteSrcFormat(object):
1987     def __init__(self, *args, **kwargs):
1988         pass
1989
1990     def __repr__(self):
1991         return '<SuiteSrcFormat (%s, %s)>' % (self.suite_id, self.src_format_id)
1992
1993 __all__.append('SuiteSrcFormat')
1994
1995 def get_suite_src_formats(suite, session=None):
1996     """
1997     Returns list of allowed SrcFormat for C{suite}.
1998
1999     @type suite: str
2000     @param suite: Suite name to search for
2001
2002     @type session: Session
2003     @param session: Optional SQL session object (a temporary one will be
2004     generated if not supplied)
2005
2006     @rtype: list
2007     @return: the list of allowed source formats for I{suite}
2008     """
2009
2010     privatetrans = False
2011     if session is None:
2012         session = DBConn().session()
2013         privatetrans = True
2014
2015     q = session.query(SrcFormat)
2016     q = q.join(SuiteSrcFormat)
2017     q = q.join(Suite).filter_by(suite_name=suite)
2018     q = q.order_by('format_name')
2019
2020     ret = q.all()
2021
2022     if privatetrans:
2023         session.close()
2024
2025     return ret
2026
2027 __all__.append('get_suite_src_formats')
2028
2029 ################################################################################
2030
2031 class Uid(object):
2032     def __init__(self, *args, **kwargs):
2033         pass
2034
2035     def __eq__(self, val):
2036         if isinstance(val, str):
2037             return (self.uid == val)
2038         # This signals to use the normal comparison operator
2039         return NotImplemented
2040
2041     def __ne__(self, val):
2042         if isinstance(val, str):
2043             return (self.uid != val)
2044         # This signals to use the normal comparison operator
2045         return NotImplemented
2046
2047     def __repr__(self):
2048         return '<Uid %s (%s)>' % (self.uid, self.name)
2049
2050 __all__.append('Uid')
2051
2052 def add_database_user(uidname, session=None):
2053     """
2054     Adds a database user
2055
2056     @type uidname: string
2057     @param uidname: The uid of the user to add
2058
2059     @type session: SQLAlchemy
2060     @param session: Optional SQL session object (a temporary one will be
2061     generated if not supplied).  If not passed, a commit will be performed at
2062     the end of the function, otherwise the caller is responsible for commiting.
2063
2064     @rtype: Uid
2065     @return: the uid object for the given uidname
2066     """
2067
2068     privatetrans = False
2069     if session is None:
2070         session = DBConn().session()
2071         privatetrans = True
2072
2073     session.execute("CREATE USER :uid", {'uid': uidname})
2074
2075     if privatetrans:
2076         session.commit()
2077         session.close()
2078
2079 __all__.append('add_database_user')
2080
2081 def get_or_set_uid(uidname, session=None):
2082     """
2083     Returns uid object for given uidname.
2084
2085     If no matching uidname is found, a row is inserted.
2086
2087     @type uidname: string
2088     @param uidname: The uid to add
2089
2090     @type session: SQLAlchemy
2091     @param session: Optional SQL session object (a temporary one will be
2092     generated if not supplied).  If not passed, a commit will be performed at
2093     the end of the function, otherwise the caller is responsible for commiting.
2094
2095     @rtype: Uid
2096     @return: the uid object for the given uidname
2097     """
2098
2099     privatetrans = False
2100     if session is None:
2101         session = DBConn().session()
2102         privatetrans = True
2103
2104     q = session.query(Uid).filter_by(uid=uidname)
2105
2106     try:
2107         ret = q.one()
2108     except NoResultFound:
2109         uid = Uid()
2110         uid.uid = uidname
2111         session.add(uid)
2112         if privatetrans:
2113             session.commit()
2114         else:
2115             session.flush()
2116         ret = uid
2117
2118     if privatetrans:
2119         session.close()
2120
2121     return ret
2122
2123 __all__.append('get_or_set_uid')
2124
2125 @session_wrapper
2126 def get_uid_from_fingerprint(fpr, session=None):
2127     q = session.query(Uid)
2128     q = q.join(Fingerprint).filter_by(fingerprint=fpr)
2129
2130     try:
2131         return q.one()
2132     except NoResultFound:
2133         return None
2134
2135 __all__.append('get_uid_from_fingerprint')
2136
2137 ################################################################################
2138
2139 class DBConn(Singleton):
2140     """
2141     database module init.
2142     """
2143     def __init__(self, *args, **kwargs):
2144         super(DBConn, self).__init__(*args, **kwargs)
2145
2146     def _startup(self, *args, **kwargs):
2147         self.debug = False
2148         if kwargs.has_key('debug'):
2149             self.debug = True
2150         self.__createconn()
2151
2152     def __setuptables(self):
2153         self.tbl_architecture = Table('architecture', self.db_meta, autoload=True)
2154         self.tbl_archive = Table('archive', self.db_meta, autoload=True)
2155         self.tbl_bin_associations = Table('bin_associations', self.db_meta, autoload=True)
2156         self.tbl_binaries = Table('binaries', self.db_meta, autoload=True)
2157         self.tbl_component = Table('component', self.db_meta, autoload=True)
2158         self.tbl_config = Table('config', self.db_meta, autoload=True)
2159         self.tbl_content_associations = Table('content_associations', self.db_meta, autoload=True)
2160         self.tbl_content_file_names = Table('content_file_names', self.db_meta, autoload=True)
2161         self.tbl_content_file_paths = Table('content_file_paths', self.db_meta, autoload=True)
2162         self.tbl_dsc_files = Table('dsc_files', self.db_meta, autoload=True)
2163         self.tbl_files = Table('files', self.db_meta, autoload=True)
2164         self.tbl_fingerprint = Table('fingerprint', self.db_meta, autoload=True)
2165         self.tbl_keyrings = Table('keyrings', self.db_meta, autoload=True)
2166         self.tbl_location = Table('location', self.db_meta, autoload=True)
2167         self.tbl_maintainer = Table('maintainer', self.db_meta, autoload=True)
2168         self.tbl_new_comments = Table('new_comments', self.db_meta, autoload=True)
2169         self.tbl_override = Table('override', self.db_meta, autoload=True)
2170         self.tbl_override_type = Table('override_type', self.db_meta, autoload=True)
2171         self.tbl_pending_content_associations = Table('pending_content_associations', self.db_meta, autoload=True)
2172         self.tbl_priority = Table('priority', self.db_meta, autoload=True)
2173         self.tbl_queue = Table('queue', self.db_meta, autoload=True)
2174         self.tbl_queue_build = Table('queue_build', self.db_meta, autoload=True)
2175         self.tbl_section = Table('section', self.db_meta, autoload=True)
2176         self.tbl_source = Table('source', self.db_meta, autoload=True)
2177         self.tbl_src_associations = Table('src_associations', self.db_meta, autoload=True)
2178         self.tbl_src_format = Table('src_format', self.db_meta, autoload=True)
2179         self.tbl_src_uploaders = Table('src_uploaders', self.db_meta, autoload=True)
2180         self.tbl_suite = Table('suite', self.db_meta, autoload=True)
2181         self.tbl_suite_architectures = Table('suite_architectures', self.db_meta, autoload=True)
2182         self.tbl_suite_src_formats = Table('suite_src_formats', self.db_meta, autoload=True)
2183         self.tbl_uid = Table('uid', self.db_meta, autoload=True)
2184
2185     def __setupmappers(self):
2186         mapper(Architecture, self.tbl_architecture,
2187                properties = dict(arch_id = self.tbl_architecture.c.id))
2188
2189         mapper(Archive, self.tbl_archive,
2190                properties = dict(archive_id = self.tbl_archive.c.id,
2191                                  archive_name = self.tbl_archive.c.name))
2192
2193         mapper(BinAssociation, self.tbl_bin_associations,
2194                properties = dict(ba_id = self.tbl_bin_associations.c.id,
2195                                  suite_id = self.tbl_bin_associations.c.suite,
2196                                  suite = relation(Suite),
2197                                  binary_id = self.tbl_bin_associations.c.bin,
2198                                  binary = relation(DBBinary)))
2199
2200         mapper(DBBinary, self.tbl_binaries,
2201                properties = dict(binary_id = self.tbl_binaries.c.id,
2202                                  package = self.tbl_binaries.c.package,
2203                                  version = self.tbl_binaries.c.version,
2204                                  maintainer_id = self.tbl_binaries.c.maintainer,
2205                                  maintainer = relation(Maintainer),
2206                                  source_id = self.tbl_binaries.c.source,
2207                                  source = relation(DBSource),
2208                                  arch_id = self.tbl_binaries.c.architecture,
2209                                  architecture = relation(Architecture),
2210                                  poolfile_id = self.tbl_binaries.c.file,
2211                                  poolfile = relation(PoolFile),
2212                                  binarytype = self.tbl_binaries.c.type,
2213                                  fingerprint_id = self.tbl_binaries.c.sig_fpr,
2214                                  fingerprint = relation(Fingerprint),
2215                                  install_date = self.tbl_binaries.c.install_date,
2216                                  binassociations = relation(BinAssociation,
2217                                                             primaryjoin=(self.tbl_binaries.c.id==self.tbl_bin_associations.c.bin))))
2218
2219         mapper(Component, self.tbl_component,
2220                properties = dict(component_id = self.tbl_component.c.id,
2221                                  component_name = self.tbl_component.c.name))
2222
2223         mapper(DBConfig, self.tbl_config,
2224                properties = dict(config_id = self.tbl_config.c.id))
2225
2226         mapper(ContentAssociation, self.tbl_content_associations,
2227                properties = dict(ca_id = self.tbl_content_associations.c.id,
2228                                  filename_id = self.tbl_content_associations.c.filename,
2229                                  filename    = relation(ContentFilename),
2230                                  filepath_id = self.tbl_content_associations.c.filepath,
2231                                  filepath    = relation(ContentFilepath),
2232                                  binary_id   = self.tbl_content_associations.c.binary_pkg,
2233                                  binary      = relation(DBBinary)))
2234
2235
2236         mapper(ContentFilename, self.tbl_content_file_names,
2237                properties = dict(cafilename_id = self.tbl_content_file_names.c.id,
2238                                  filename = self.tbl_content_file_names.c.file))
2239
2240         mapper(ContentFilepath, self.tbl_content_file_paths,
2241                properties = dict(cafilepath_id = self.tbl_content_file_paths.c.id,
2242                                  filepath = self.tbl_content_file_paths.c.path))
2243
2244         mapper(DSCFile, self.tbl_dsc_files,
2245                properties = dict(dscfile_id = self.tbl_dsc_files.c.id,
2246                                  source_id = self.tbl_dsc_files.c.source,
2247                                  source = relation(DBSource),
2248                                  poolfile_id = self.tbl_dsc_files.c.file,
2249                                  poolfile = relation(PoolFile)))
2250
2251         mapper(PoolFile, self.tbl_files,
2252                properties = dict(file_id = self.tbl_files.c.id,
2253                                  filesize = self.tbl_files.c.size,
2254                                  location_id = self.tbl_files.c.location,
2255                                  location = relation(Location)))
2256
2257         mapper(Fingerprint, self.tbl_fingerprint,
2258                properties = dict(fingerprint_id = self.tbl_fingerprint.c.id,
2259                                  uid_id = self.tbl_fingerprint.c.uid,
2260                                  uid = relation(Uid),
2261                                  keyring_id = self.tbl_fingerprint.c.keyring,
2262                                  keyring = relation(Keyring)))
2263
2264         mapper(Keyring, self.tbl_keyrings,
2265                properties = dict(keyring_name = self.tbl_keyrings.c.name,
2266                                  keyring_id = self.tbl_keyrings.c.id))
2267
2268         mapper(Location, self.tbl_location,
2269                properties = dict(location_id = self.tbl_location.c.id,
2270                                  component_id = self.tbl_location.c.component,
2271                                  component = relation(Component),
2272                                  archive_id = self.tbl_location.c.archive,
2273                                  archive = relation(Archive),
2274                                  archive_type = self.tbl_location.c.type))
2275
2276         mapper(Maintainer, self.tbl_maintainer,
2277                properties = dict(maintainer_id = self.tbl_maintainer.c.id))
2278
2279         mapper(NewComment, self.tbl_new_comments,
2280                properties = dict(comment_id = self.tbl_new_comments.c.id))
2281
2282         mapper(Override, self.tbl_override,
2283                properties = dict(suite_id = self.tbl_override.c.suite,
2284                                  suite = relation(Suite),
2285                                  component_id = self.tbl_override.c.component,
2286                                  component = relation(Component),
2287                                  priority_id = self.tbl_override.c.priority,
2288                                  priority = relation(Priority),
2289                                  section_id = self.tbl_override.c.section,
2290                                  section = relation(Section),
2291                                  overridetype_id = self.tbl_override.c.type,
2292                                  overridetype = relation(OverrideType)))
2293
2294         mapper(OverrideType, self.tbl_override_type,
2295                properties = dict(overridetype = self.tbl_override_type.c.type,
2296                                  overridetype_id = self.tbl_override_type.c.id))
2297
2298         mapper(PendingContentAssociation, self.tbl_pending_content_associations,
2299                properties = dict(pca_id = self.tbl_pending_content_associations.c.id,
2300                                  filepath_id = self.tbl_pending_content_associations.c.filepath,
2301                                  filepath = relation(ContentFilepath),
2302                                  filename_id = self.tbl_pending_content_associations.c.filename,
2303                                  filename = relation(ContentFilename)))
2304
2305         mapper(Priority, self.tbl_priority,
2306                properties = dict(priority_id = self.tbl_priority.c.id))
2307
2308         mapper(Queue, self.tbl_queue,
2309                properties = dict(queue_id = self.tbl_queue.c.id))
2310
2311         mapper(QueueBuild, self.tbl_queue_build,
2312                properties = dict(suite_id = self.tbl_queue_build.c.suite,
2313                                  queue_id = self.tbl_queue_build.c.queue,
2314                                  queue = relation(Queue, backref='queuebuild')))
2315
2316         mapper(Section, self.tbl_section,
2317                properties = dict(section_id = self.tbl_section.c.id))
2318
2319         mapper(DBSource, self.tbl_source,
2320                properties = dict(source_id = self.tbl_source.c.id,
2321                                  version = self.tbl_source.c.version,
2322                                  maintainer_id = self.tbl_source.c.maintainer,
2323                                  maintainer = relation(Maintainer,
2324                                                        primaryjoin=(self.tbl_source.c.maintainer==self.tbl_maintainer.c.id)),
2325                                  poolfile_id = self.tbl_source.c.file,
2326                                  poolfile = relation(PoolFile),
2327                                  fingerprint_id = self.tbl_source.c.sig_fpr,
2328                                  fingerprint = relation(Fingerprint),
2329                                  changedby_id = self.tbl_source.c.changedby,
2330                                  changedby = relation(Maintainer,
2331                                                       primaryjoin=(self.tbl_source.c.changedby==self.tbl_maintainer.c.id)),
2332                                  srcfiles = relation(DSCFile,
2333                                                      primaryjoin=(self.tbl_source.c.id==self.tbl_dsc_files.c.source)),
2334                                  srcassociations = relation(SrcAssociation,
2335                                                             primaryjoin=(self.tbl_source.c.id==self.tbl_src_associations.c.source))))
2336
2337         mapper(SrcAssociation, self.tbl_src_associations,
2338                properties = dict(sa_id = self.tbl_src_associations.c.id,
2339                                  suite_id = self.tbl_src_associations.c.suite,
2340                                  suite = relation(Suite),
2341                                  source_id = self.tbl_src_associations.c.source,
2342                                  source = relation(DBSource)))
2343
2344         mapper(SrcFormat, self.tbl_src_format,
2345                properties = dict(src_format_id = self.tbl_src_format.c.id,
2346                                  format_name = self.tbl_src_format.c.format_name))
2347
2348         mapper(SrcUploader, self.tbl_src_uploaders,
2349                properties = dict(uploader_id = self.tbl_src_uploaders.c.id,
2350                                  source_id = self.tbl_src_uploaders.c.source,
2351                                  source = relation(DBSource,
2352                                                    primaryjoin=(self.tbl_src_uploaders.c.source==self.tbl_source.c.id)),
2353                                  maintainer_id = self.tbl_src_uploaders.c.maintainer,
2354                                  maintainer = relation(Maintainer,
2355                                                        primaryjoin=(self.tbl_src_uploaders.c.maintainer==self.tbl_maintainer.c.id))))
2356
2357         mapper(Suite, self.tbl_suite,
2358                properties = dict(suite_id = self.tbl_suite.c.id))
2359
2360         mapper(SuiteArchitecture, self.tbl_suite_architectures,
2361                properties = dict(suite_id = self.tbl_suite_architectures.c.suite,
2362                                  suite = relation(Suite, backref='suitearchitectures'),
2363                                  arch_id = self.tbl_suite_architectures.c.architecture,
2364                                  architecture = relation(Architecture)))
2365
2366         mapper(SuiteSrcFormat, self.tbl_suite_src_formats,
2367                properties = dict(suite_id = self.tbl_suite_src_formats.c.suite,
2368                                  suite = relation(Suite, backref='suitesrcformats'),
2369                                  src_format_id = self.tbl_suite_src_formats.c.src_format,
2370                                  src_format = relation(SrcFormat)))
2371
2372         mapper(Uid, self.tbl_uid,
2373                properties = dict(uid_id = self.tbl_uid.c.id,
2374                                  fingerprint = relation(Fingerprint)))
2375
2376     ## Connection functions
2377     def __createconn(self):
2378         from config import Config
2379         cnf = Config()
2380         if cnf["DB::Host"]:
2381             # TCP/IP
2382             connstr = "postgres://%s" % cnf["DB::Host"]
2383             if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
2384                 connstr += ":%s" % cnf["DB::Port"]
2385             connstr += "/%s" % cnf["DB::Name"]
2386         else:
2387             # Unix Socket
2388             connstr = "postgres:///%s" % cnf["DB::Name"]
2389             if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
2390                 connstr += "?port=%s" % cnf["DB::Port"]
2391
2392         self.db_pg   = create_engine(connstr, echo=self.debug)
2393         self.db_meta = MetaData()
2394         self.db_meta.bind = self.db_pg
2395         self.db_smaker = sessionmaker(bind=self.db_pg,
2396                                       autoflush=True,
2397                                       autocommit=False)
2398
2399         self.__setuptables()
2400         self.__setupmappers()
2401
2402     def session(self):
2403         return self.db_smaker()
2404
2405 __all__.append('DBConn')
2406
2407