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