]> git.decadent.org.uk Git - dak.git/blob - daklib/dbconn.py
aeffdcb328562582f4bea9c22b2788f436256e61
[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 @session_wrapper
1996 def get_suite_src_formats(suite, session=None):
1997     """
1998     Returns list of allowed SrcFormat for C{suite}.
1999
2000     @type suite: str
2001     @param suite: Suite name to search for
2002
2003     @type session: Session
2004     @param session: Optional SQL session object (a temporary one will be
2005     generated if not supplied)
2006
2007     @rtype: list
2008     @return: the list of allowed source formats for I{suite}
2009     """
2010
2011     q = session.query(SrcFormat)
2012     q = q.join(SuiteSrcFormat)
2013     q = q.join(Suite).filter_by(suite_name=suite)
2014     q = q.order_by('format_name')
2015
2016     return q.all()
2017
2018 __all__.append('get_suite_src_formats')
2019
2020 ################################################################################
2021
2022 class Uid(object):
2023     def __init__(self, *args, **kwargs):
2024         pass
2025
2026     def __eq__(self, val):
2027         if isinstance(val, str):
2028             return (self.uid == val)
2029         # This signals to use the normal comparison operator
2030         return NotImplemented
2031
2032     def __ne__(self, val):
2033         if isinstance(val, str):
2034             return (self.uid != val)
2035         # This signals to use the normal comparison operator
2036         return NotImplemented
2037
2038     def __repr__(self):
2039         return '<Uid %s (%s)>' % (self.uid, self.name)
2040
2041 __all__.append('Uid')
2042
2043 def add_database_user(uidname, session=None):
2044     """
2045     Adds a database user
2046
2047     @type uidname: string
2048     @param uidname: The uid of the user to add
2049
2050     @type session: SQLAlchemy
2051     @param session: Optional SQL session object (a temporary one will be
2052     generated if not supplied).  If not passed, a commit will be performed at
2053     the end of the function, otherwise the caller is responsible for commiting.
2054
2055     @rtype: Uid
2056     @return: the uid object for the given uidname
2057     """
2058
2059     privatetrans = False
2060     if session is None:
2061         session = DBConn().session()
2062         privatetrans = True
2063
2064     session.execute("CREATE USER :uid", {'uid': uidname})
2065
2066     if privatetrans:
2067         session.commit()
2068         session.close()
2069
2070 __all__.append('add_database_user')
2071
2072 def get_or_set_uid(uidname, session=None):
2073     """
2074     Returns uid object for given uidname.
2075
2076     If no matching uidname is found, a row is inserted.
2077
2078     @type uidname: string
2079     @param uidname: The uid to add
2080
2081     @type session: SQLAlchemy
2082     @param session: Optional SQL session object (a temporary one will be
2083     generated if not supplied).  If not passed, a commit will be performed at
2084     the end of the function, otherwise the caller is responsible for commiting.
2085
2086     @rtype: Uid
2087     @return: the uid object for the given uidname
2088     """
2089
2090     privatetrans = False
2091     if session is None:
2092         session = DBConn().session()
2093         privatetrans = True
2094
2095     q = session.query(Uid).filter_by(uid=uidname)
2096
2097     try:
2098         ret = q.one()
2099     except NoResultFound:
2100         uid = Uid()
2101         uid.uid = uidname
2102         session.add(uid)
2103         if privatetrans:
2104             session.commit()
2105         else:
2106             session.flush()
2107         ret = uid
2108
2109     if privatetrans:
2110         session.close()
2111
2112     return ret
2113
2114 __all__.append('get_or_set_uid')
2115
2116 @session_wrapper
2117 def get_uid_from_fingerprint(fpr, session=None):
2118     q = session.query(Uid)
2119     q = q.join(Fingerprint).filter_by(fingerprint=fpr)
2120
2121     try:
2122         return q.one()
2123     except NoResultFound:
2124         return None
2125
2126 __all__.append('get_uid_from_fingerprint')
2127
2128 ################################################################################
2129
2130 class DBConn(Singleton):
2131     """
2132     database module init.
2133     """
2134     def __init__(self, *args, **kwargs):
2135         super(DBConn, self).__init__(*args, **kwargs)
2136
2137     def _startup(self, *args, **kwargs):
2138         self.debug = False
2139         if kwargs.has_key('debug'):
2140             self.debug = True
2141         self.__createconn()
2142
2143     def __setuptables(self):
2144         self.tbl_architecture = Table('architecture', self.db_meta, autoload=True)
2145         self.tbl_archive = Table('archive', self.db_meta, autoload=True)
2146         self.tbl_bin_associations = Table('bin_associations', self.db_meta, autoload=True)
2147         self.tbl_binaries = Table('binaries', self.db_meta, autoload=True)
2148         self.tbl_component = Table('component', self.db_meta, autoload=True)
2149         self.tbl_config = Table('config', self.db_meta, autoload=True)
2150         self.tbl_content_associations = Table('content_associations', self.db_meta, autoload=True)
2151         self.tbl_content_file_names = Table('content_file_names', self.db_meta, autoload=True)
2152         self.tbl_content_file_paths = Table('content_file_paths', self.db_meta, autoload=True)
2153         self.tbl_dsc_files = Table('dsc_files', self.db_meta, autoload=True)
2154         self.tbl_files = Table('files', self.db_meta, autoload=True)
2155         self.tbl_fingerprint = Table('fingerprint', self.db_meta, autoload=True)
2156         self.tbl_keyrings = Table('keyrings', self.db_meta, autoload=True)
2157         self.tbl_location = Table('location', self.db_meta, autoload=True)
2158         self.tbl_maintainer = Table('maintainer', self.db_meta, autoload=True)
2159         self.tbl_new_comments = Table('new_comments', self.db_meta, autoload=True)
2160         self.tbl_override = Table('override', self.db_meta, autoload=True)
2161         self.tbl_override_type = Table('override_type', self.db_meta, autoload=True)
2162         self.tbl_pending_content_associations = Table('pending_content_associations', self.db_meta, autoload=True)
2163         self.tbl_priority = Table('priority', self.db_meta, autoload=True)
2164         self.tbl_queue = Table('queue', self.db_meta, autoload=True)
2165         self.tbl_queue_build = Table('queue_build', self.db_meta, autoload=True)
2166         self.tbl_section = Table('section', self.db_meta, autoload=True)
2167         self.tbl_source = Table('source', self.db_meta, autoload=True)
2168         self.tbl_src_associations = Table('src_associations', self.db_meta, autoload=True)
2169         self.tbl_src_format = Table('src_format', self.db_meta, autoload=True)
2170         self.tbl_src_uploaders = Table('src_uploaders', self.db_meta, autoload=True)
2171         self.tbl_suite = Table('suite', self.db_meta, autoload=True)
2172         self.tbl_suite_architectures = Table('suite_architectures', self.db_meta, autoload=True)
2173         self.tbl_suite_src_formats = Table('suite_src_formats', self.db_meta, autoload=True)
2174         self.tbl_uid = Table('uid', self.db_meta, autoload=True)
2175
2176     def __setupmappers(self):
2177         mapper(Architecture, self.tbl_architecture,
2178                properties = dict(arch_id = self.tbl_architecture.c.id))
2179
2180         mapper(Archive, self.tbl_archive,
2181                properties = dict(archive_id = self.tbl_archive.c.id,
2182                                  archive_name = self.tbl_archive.c.name))
2183
2184         mapper(BinAssociation, self.tbl_bin_associations,
2185                properties = dict(ba_id = self.tbl_bin_associations.c.id,
2186                                  suite_id = self.tbl_bin_associations.c.suite,
2187                                  suite = relation(Suite),
2188                                  binary_id = self.tbl_bin_associations.c.bin,
2189                                  binary = relation(DBBinary)))
2190
2191         mapper(DBBinary, self.tbl_binaries,
2192                properties = dict(binary_id = self.tbl_binaries.c.id,
2193                                  package = self.tbl_binaries.c.package,
2194                                  version = self.tbl_binaries.c.version,
2195                                  maintainer_id = self.tbl_binaries.c.maintainer,
2196                                  maintainer = relation(Maintainer),
2197                                  source_id = self.tbl_binaries.c.source,
2198                                  source = relation(DBSource),
2199                                  arch_id = self.tbl_binaries.c.architecture,
2200                                  architecture = relation(Architecture),
2201                                  poolfile_id = self.tbl_binaries.c.file,
2202                                  poolfile = relation(PoolFile),
2203                                  binarytype = self.tbl_binaries.c.type,
2204                                  fingerprint_id = self.tbl_binaries.c.sig_fpr,
2205                                  fingerprint = relation(Fingerprint),
2206                                  install_date = self.tbl_binaries.c.install_date,
2207                                  binassociations = relation(BinAssociation,
2208                                                             primaryjoin=(self.tbl_binaries.c.id==self.tbl_bin_associations.c.bin))))
2209
2210         mapper(Component, self.tbl_component,
2211                properties = dict(component_id = self.tbl_component.c.id,
2212                                  component_name = self.tbl_component.c.name))
2213
2214         mapper(DBConfig, self.tbl_config,
2215                properties = dict(config_id = self.tbl_config.c.id))
2216
2217         mapper(ContentAssociation, self.tbl_content_associations,
2218                properties = dict(ca_id = self.tbl_content_associations.c.id,
2219                                  filename_id = self.tbl_content_associations.c.filename,
2220                                  filename    = relation(ContentFilename),
2221                                  filepath_id = self.tbl_content_associations.c.filepath,
2222                                  filepath    = relation(ContentFilepath),
2223                                  binary_id   = self.tbl_content_associations.c.binary_pkg,
2224                                  binary      = relation(DBBinary)))
2225
2226
2227         mapper(ContentFilename, self.tbl_content_file_names,
2228                properties = dict(cafilename_id = self.tbl_content_file_names.c.id,
2229                                  filename = self.tbl_content_file_names.c.file))
2230
2231         mapper(ContentFilepath, self.tbl_content_file_paths,
2232                properties = dict(cafilepath_id = self.tbl_content_file_paths.c.id,
2233                                  filepath = self.tbl_content_file_paths.c.path))
2234
2235         mapper(DSCFile, self.tbl_dsc_files,
2236                properties = dict(dscfile_id = self.tbl_dsc_files.c.id,
2237                                  source_id = self.tbl_dsc_files.c.source,
2238                                  source = relation(DBSource),
2239                                  poolfile_id = self.tbl_dsc_files.c.file,
2240                                  poolfile = relation(PoolFile)))
2241
2242         mapper(PoolFile, self.tbl_files,
2243                properties = dict(file_id = self.tbl_files.c.id,
2244                                  filesize = self.tbl_files.c.size,
2245                                  location_id = self.tbl_files.c.location,
2246                                  location = relation(Location)))
2247
2248         mapper(Fingerprint, self.tbl_fingerprint,
2249                properties = dict(fingerprint_id = self.tbl_fingerprint.c.id,
2250                                  uid_id = self.tbl_fingerprint.c.uid,
2251                                  uid = relation(Uid),
2252                                  keyring_id = self.tbl_fingerprint.c.keyring,
2253                                  keyring = relation(Keyring)))
2254
2255         mapper(Keyring, self.tbl_keyrings,
2256                properties = dict(keyring_name = self.tbl_keyrings.c.name,
2257                                  keyring_id = self.tbl_keyrings.c.id))
2258
2259         mapper(Location, self.tbl_location,
2260                properties = dict(location_id = self.tbl_location.c.id,
2261                                  component_id = self.tbl_location.c.component,
2262                                  component = relation(Component),
2263                                  archive_id = self.tbl_location.c.archive,
2264                                  archive = relation(Archive),
2265                                  archive_type = self.tbl_location.c.type))
2266
2267         mapper(Maintainer, self.tbl_maintainer,
2268                properties = dict(maintainer_id = self.tbl_maintainer.c.id))
2269
2270         mapper(NewComment, self.tbl_new_comments,
2271                properties = dict(comment_id = self.tbl_new_comments.c.id))
2272
2273         mapper(Override, self.tbl_override,
2274                properties = dict(suite_id = self.tbl_override.c.suite,
2275                                  suite = relation(Suite),
2276                                  component_id = self.tbl_override.c.component,
2277                                  component = relation(Component),
2278                                  priority_id = self.tbl_override.c.priority,
2279                                  priority = relation(Priority),
2280                                  section_id = self.tbl_override.c.section,
2281                                  section = relation(Section),
2282                                  overridetype_id = self.tbl_override.c.type,
2283                                  overridetype = relation(OverrideType)))
2284
2285         mapper(OverrideType, self.tbl_override_type,
2286                properties = dict(overridetype = self.tbl_override_type.c.type,
2287                                  overridetype_id = self.tbl_override_type.c.id))
2288
2289         mapper(PendingContentAssociation, self.tbl_pending_content_associations,
2290                properties = dict(pca_id = self.tbl_pending_content_associations.c.id,
2291                                  filepath_id = self.tbl_pending_content_associations.c.filepath,
2292                                  filepath = relation(ContentFilepath),
2293                                  filename_id = self.tbl_pending_content_associations.c.filename,
2294                                  filename = relation(ContentFilename)))
2295
2296         mapper(Priority, self.tbl_priority,
2297                properties = dict(priority_id = self.tbl_priority.c.id))
2298
2299         mapper(Queue, self.tbl_queue,
2300                properties = dict(queue_id = self.tbl_queue.c.id))
2301
2302         mapper(QueueBuild, self.tbl_queue_build,
2303                properties = dict(suite_id = self.tbl_queue_build.c.suite,
2304                                  queue_id = self.tbl_queue_build.c.queue,
2305                                  queue = relation(Queue, backref='queuebuild')))
2306
2307         mapper(Section, self.tbl_section,
2308                properties = dict(section_id = self.tbl_section.c.id))
2309
2310         mapper(DBSource, self.tbl_source,
2311                properties = dict(source_id = self.tbl_source.c.id,
2312                                  version = self.tbl_source.c.version,
2313                                  maintainer_id = self.tbl_source.c.maintainer,
2314                                  maintainer = relation(Maintainer,
2315                                                        primaryjoin=(self.tbl_source.c.maintainer==self.tbl_maintainer.c.id)),
2316                                  poolfile_id = self.tbl_source.c.file,
2317                                  poolfile = relation(PoolFile),
2318                                  fingerprint_id = self.tbl_source.c.sig_fpr,
2319                                  fingerprint = relation(Fingerprint),
2320                                  changedby_id = self.tbl_source.c.changedby,
2321                                  changedby = relation(Maintainer,
2322                                                       primaryjoin=(self.tbl_source.c.changedby==self.tbl_maintainer.c.id)),
2323                                  srcfiles = relation(DSCFile,
2324                                                      primaryjoin=(self.tbl_source.c.id==self.tbl_dsc_files.c.source)),
2325                                  srcassociations = relation(SrcAssociation,
2326                                                             primaryjoin=(self.tbl_source.c.id==self.tbl_src_associations.c.source))))
2327
2328         mapper(SrcAssociation, self.tbl_src_associations,
2329                properties = dict(sa_id = self.tbl_src_associations.c.id,
2330                                  suite_id = self.tbl_src_associations.c.suite,
2331                                  suite = relation(Suite),
2332                                  source_id = self.tbl_src_associations.c.source,
2333                                  source = relation(DBSource)))
2334
2335         mapper(SrcFormat, self.tbl_src_format,
2336                properties = dict(src_format_id = self.tbl_src_format.c.id,
2337                                  format_name = self.tbl_src_format.c.format_name))
2338
2339         mapper(SrcUploader, self.tbl_src_uploaders,
2340                properties = dict(uploader_id = self.tbl_src_uploaders.c.id,
2341                                  source_id = self.tbl_src_uploaders.c.source,
2342                                  source = relation(DBSource,
2343                                                    primaryjoin=(self.tbl_src_uploaders.c.source==self.tbl_source.c.id)),
2344                                  maintainer_id = self.tbl_src_uploaders.c.maintainer,
2345                                  maintainer = relation(Maintainer,
2346                                                        primaryjoin=(self.tbl_src_uploaders.c.maintainer==self.tbl_maintainer.c.id))))
2347
2348         mapper(Suite, self.tbl_suite,
2349                properties = dict(suite_id = self.tbl_suite.c.id))
2350
2351         mapper(SuiteArchitecture, self.tbl_suite_architectures,
2352                properties = dict(suite_id = self.tbl_suite_architectures.c.suite,
2353                                  suite = relation(Suite, backref='suitearchitectures'),
2354                                  arch_id = self.tbl_suite_architectures.c.architecture,
2355                                  architecture = relation(Architecture)))
2356
2357         mapper(SuiteSrcFormat, self.tbl_suite_src_formats,
2358                properties = dict(suite_id = self.tbl_suite_src_formats.c.suite,
2359                                  suite = relation(Suite, backref='suitesrcformats'),
2360                                  src_format_id = self.tbl_suite_src_formats.c.src_format,
2361                                  src_format = relation(SrcFormat)))
2362
2363         mapper(Uid, self.tbl_uid,
2364                properties = dict(uid_id = self.tbl_uid.c.id,
2365                                  fingerprint = relation(Fingerprint)))
2366
2367     ## Connection functions
2368     def __createconn(self):
2369         from config import Config
2370         cnf = Config()
2371         if cnf["DB::Host"]:
2372             # TCP/IP
2373             connstr = "postgres://%s" % cnf["DB::Host"]
2374             if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
2375                 connstr += ":%s" % cnf["DB::Port"]
2376             connstr += "/%s" % cnf["DB::Name"]
2377         else:
2378             # Unix Socket
2379             connstr = "postgres:///%s" % cnf["DB::Name"]
2380             if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
2381                 connstr += "?port=%s" % cnf["DB::Port"]
2382
2383         self.db_pg   = create_engine(connstr, echo=self.debug)
2384         self.db_meta = MetaData()
2385         self.db_meta.bind = self.db_pg
2386         self.db_smaker = sessionmaker(bind=self.db_pg,
2387                                       autoflush=True,
2388                                       autocommit=False)
2389
2390         self.__setuptables()
2391         self.__setupmappers()
2392
2393     def session(self):
2394         return self.db_smaker()
2395
2396 __all__.append('DBConn')
2397
2398