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