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