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