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