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