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