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