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