]> git.decadent.org.uk Git - dak.git/blob - daklib/dbconn.py
fix errors found by epydoc
[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         pathcache = {}
971
972         def generate_path_dicts():
973             for fullpath in fullpaths:
974                 if fullpath.startswith( './' ):
975                     fullpath = fullpath[2:]
976
977                 yield {'fulename':fullpath, 'id': binary_id }
978
979         session.execute( "INSERT INTO bin_contents ( file, binary_id ) VALUES ( :filename, :id )",
980                          generate_path_dicts() )
981
982         session.commit()
983         if privatetrans:
984             session.close()
985         return True
986
987     except:
988         traceback.print_exc()
989
990         # Only rollback if we set up the session ourself
991         if privatetrans:
992             session.rollback()
993             session.close()
994
995         return False
996
997 __all__.append('insert_content_paths')
998
999 ################################################################################
1000
1001 class DSCFile(object):
1002     def __init__(self, *args, **kwargs):
1003         pass
1004
1005     def __repr__(self):
1006         return '<DSCFile %s>' % self.dscfile_id
1007
1008 __all__.append('DSCFile')
1009
1010 @session_wrapper
1011 def get_dscfiles(dscfile_id=None, source_id=None, poolfile_id=None, session=None):
1012     """
1013     Returns a list of DSCFiles which may be empty
1014
1015     @type dscfile_id: int (optional)
1016     @param dscfile_id: the dscfile_id of the DSCFiles to find
1017
1018     @type source_id: int (optional)
1019     @param source_id: the source id related to the DSCFiles to find
1020
1021     @type poolfile_id: int (optional)
1022     @param poolfile_id: the poolfile id related to the DSCFiles to find
1023
1024     @rtype: list
1025     @return: Possibly empty list of DSCFiles
1026     """
1027
1028     q = session.query(DSCFile)
1029
1030     if dscfile_id is not None:
1031         q = q.filter_by(dscfile_id=dscfile_id)
1032
1033     if source_id is not None:
1034         q = q.filter_by(source_id=source_id)
1035
1036     if poolfile_id is not None:
1037         q = q.filter_by(poolfile_id=poolfile_id)
1038
1039     return q.all()
1040
1041 __all__.append('get_dscfiles')
1042
1043 ################################################################################
1044
1045 class PoolFile(object):
1046     def __init__(self, *args, **kwargs):
1047         pass
1048
1049     def __repr__(self):
1050         return '<PoolFile %s>' % self.filename
1051
1052     @property
1053     def fullpath(self):
1054         return os.path.join(self.location.path, self.filename)
1055
1056 __all__.append('PoolFile')
1057
1058 @session_wrapper
1059 def check_poolfile(filename, filesize, md5sum, location_id, session=None):
1060     """
1061     Returns a tuple:
1062     (ValidFileFound [boolean or None], PoolFile object or None)
1063
1064     @type filename: string
1065     @param filename: the filename of the file to check against the DB
1066
1067     @type filesize: int
1068     @param filesize: the size of the file to check against the DB
1069
1070     @type md5sum: string
1071     @param md5sum: the md5sum of the file to check against the DB
1072
1073     @type location_id: int
1074     @param location_id: the id of the location to look in
1075
1076     @rtype: tuple
1077     @return: Tuple of length 2.
1078                  - If more than one file found with that name: (C{None},  C{None})
1079                  - If valid pool file found: (C{True}, C{PoolFile object})
1080                  - If valid pool file not found:
1081                      - (C{False}, C{None}) if no file found
1082                      - (C{False}, C{PoolFile object}) if file found with size/md5sum mismatch
1083     """
1084
1085     q = session.query(PoolFile).filter_by(filename=filename)
1086     q = q.join(Location).filter_by(location_id=location_id)
1087
1088     ret = None
1089
1090     if q.count() > 1:
1091         ret = (None, None)
1092     elif q.count() < 1:
1093         ret = (False, None)
1094     else:
1095         obj = q.one()
1096         if obj.md5sum != md5sum or obj.filesize != int(filesize):
1097             ret = (False, obj)
1098
1099     if ret is None:
1100         ret = (True, obj)
1101
1102     return ret
1103
1104 __all__.append('check_poolfile')
1105
1106 @session_wrapper
1107 def get_poolfile_by_id(file_id, session=None):
1108     """
1109     Returns a PoolFile objects or None for the given id
1110
1111     @type file_id: int
1112     @param file_id: the id of the file to look for
1113
1114     @rtype: PoolFile or None
1115     @return: either the PoolFile object or None
1116     """
1117
1118     q = session.query(PoolFile).filter_by(file_id=file_id)
1119
1120     try:
1121         return q.one()
1122     except NoResultFound:
1123         return None
1124
1125 __all__.append('get_poolfile_by_id')
1126
1127
1128 @session_wrapper
1129 def get_poolfile_by_name(filename, location_id=None, session=None):
1130     """
1131     Returns an array of PoolFile objects for the given filename and
1132     (optionally) location_id
1133
1134     @type filename: string
1135     @param filename: the filename of the file to check against the DB
1136
1137     @type location_id: int
1138     @param location_id: the id of the location to look in (optional)
1139
1140     @rtype: array
1141     @return: array of PoolFile objects
1142     """
1143
1144     q = session.query(PoolFile).filter_by(filename=filename)
1145
1146     if location_id is not None:
1147         q = q.join(Location).filter_by(location_id=location_id)
1148
1149     return q.all()
1150
1151 __all__.append('get_poolfile_by_name')
1152
1153 @session_wrapper
1154 def get_poolfile_like_name(filename, session=None):
1155     """
1156     Returns an array of PoolFile objects which are like the given name
1157
1158     @type filename: string
1159     @param filename: the filename of the file to check against the DB
1160
1161     @rtype: array
1162     @return: array of PoolFile objects
1163     """
1164
1165     # TODO: There must be a way of properly using bind parameters with %FOO%
1166     q = session.query(PoolFile).filter(PoolFile.filename.like('%%/%s' % filename))
1167
1168     return q.all()
1169
1170 __all__.append('get_poolfile_like_name')
1171
1172 @session_wrapper
1173 def add_poolfile(filename, datadict, location_id, session=None):
1174     """
1175     Add a new file to the pool
1176
1177     @type filename: string
1178     @param filename: filename
1179
1180     @type datadict: dict
1181     @param datadict: dict with needed data
1182
1183     @type location_id: int
1184     @param location_id: database id of the location
1185
1186     @rtype: PoolFile
1187     @return: the PoolFile object created
1188     """
1189     poolfile = PoolFile()
1190     poolfile.filename = filename
1191     poolfile.filesize = datadict["size"]
1192     poolfile.md5sum = datadict["md5sum"]
1193     poolfile.sha1sum = datadict["sha1sum"]
1194     poolfile.sha256sum = datadict["sha256sum"]
1195     poolfile.location_id = location_id
1196
1197     session.add(poolfile)
1198     # Flush to get a file id (NB: This is not a commit)
1199     session.flush()
1200
1201     return poolfile
1202
1203 __all__.append('add_poolfile')
1204
1205 ################################################################################
1206
1207 class Fingerprint(object):
1208     def __init__(self, *args, **kwargs):
1209         pass
1210
1211     def __repr__(self):
1212         return '<Fingerprint %s>' % self.fingerprint
1213
1214 __all__.append('Fingerprint')
1215
1216 @session_wrapper
1217 def get_fingerprint(fpr, session=None):
1218     """
1219     Returns Fingerprint object for given fpr.
1220
1221     @type fpr: string
1222     @param fpr: The fpr to find / add
1223
1224     @type session: SQLAlchemy
1225     @param session: Optional SQL session object (a temporary one will be
1226     generated if not supplied).
1227
1228     @rtype: Fingerprint
1229     @return: the Fingerprint object for the given fpr or None
1230     """
1231
1232     q = session.query(Fingerprint).filter_by(fingerprint=fpr)
1233
1234     try:
1235         ret = q.one()
1236     except NoResultFound:
1237         ret = None
1238
1239     return ret
1240
1241 __all__.append('get_fingerprint')
1242
1243 @session_wrapper
1244 def get_or_set_fingerprint(fpr, session=None):
1245     """
1246     Returns Fingerprint object for given fpr.
1247
1248     If no matching fpr is found, a row is inserted.
1249
1250     @type fpr: string
1251     @param fpr: The fpr to find / add
1252
1253     @type session: SQLAlchemy
1254     @param session: Optional SQL session object (a temporary one will be
1255     generated if not supplied).  If not passed, a commit will be performed at
1256     the end of the function, otherwise the caller is responsible for commiting.
1257     A flush will be performed either way.
1258
1259     @rtype: Fingerprint
1260     @return: the Fingerprint object for the given fpr
1261     """
1262
1263     q = session.query(Fingerprint).filter_by(fingerprint=fpr)
1264
1265     try:
1266         ret = q.one()
1267     except NoResultFound:
1268         fingerprint = Fingerprint()
1269         fingerprint.fingerprint = fpr
1270         session.add(fingerprint)
1271         session.commit_or_flush()
1272         ret = fingerprint
1273
1274     return ret
1275
1276 __all__.append('get_or_set_fingerprint')
1277
1278 ################################################################################
1279
1280 # Helper routine for Keyring class
1281 def get_ldap_name(entry):
1282     name = []
1283     for k in ["cn", "mn", "sn"]:
1284         ret = entry.get(k)
1285         if ret and ret[0] != "" and ret[0] != "-":
1286             name.append(ret[0])
1287     return " ".join(name)
1288
1289 ################################################################################
1290
1291 class Keyring(object):
1292     gpg_invocation = "gpg --no-default-keyring --keyring %s" +\
1293                      " --with-colons --fingerprint --fingerprint"
1294
1295     keys = {}
1296     fpr_lookup = {}
1297
1298     def __init__(self, *args, **kwargs):
1299         pass
1300
1301     def __repr__(self):
1302         return '<Keyring %s>' % self.keyring_name
1303
1304     def de_escape_gpg_str(self, txt):
1305         esclist = re.split(r'(\\x..)', txt)
1306         for x in range(1,len(esclist),2):
1307             esclist[x] = "%c" % (int(esclist[x][2:],16))
1308         return "".join(esclist)
1309
1310     def load_keys(self, keyring):
1311         import email.Utils
1312
1313         if not self.keyring_id:
1314             raise Exception('Must be initialized with database information')
1315
1316         k = os.popen(self.gpg_invocation % keyring, "r")
1317         key = None
1318         signingkey = False
1319
1320         for line in k.xreadlines():
1321             field = line.split(":")
1322             if field[0] == "pub":
1323                 key = field[4]
1324                 (name, addr) = email.Utils.parseaddr(field[9])
1325                 name = re.sub(r"\s*[(].*[)]", "", name)
1326                 if name == "" or addr == "" or "@" not in addr:
1327                     name = field[9]
1328                     addr = "invalid-uid"
1329                 name = self.de_escape_gpg_str(name)
1330                 self.keys[key] = {"email": addr}
1331                 if name != "":
1332                     self.keys[key]["name"] = name
1333                 self.keys[key]["aliases"] = [name]
1334                 self.keys[key]["fingerprints"] = []
1335                 signingkey = True
1336             elif key and field[0] == "sub" and len(field) >= 12:
1337                 signingkey = ("s" in field[11])
1338             elif key and field[0] == "uid":
1339                 (name, addr) = email.Utils.parseaddr(field[9])
1340                 if name and name not in self.keys[key]["aliases"]:
1341                     self.keys[key]["aliases"].append(name)
1342             elif signingkey and field[0] == "fpr":
1343                 self.keys[key]["fingerprints"].append(field[9])
1344                 self.fpr_lookup[field[9]] = key
1345
1346     def import_users_from_ldap(self, session):
1347         import ldap
1348         cnf = Config()
1349
1350         LDAPDn = cnf["Import-LDAP-Fingerprints::LDAPDn"]
1351         LDAPServer = cnf["Import-LDAP-Fingerprints::LDAPServer"]
1352
1353         l = ldap.open(LDAPServer)
1354         l.simple_bind_s("","")
1355         Attrs = l.search_s(LDAPDn, ldap.SCOPE_ONELEVEL,
1356                "(&(keyfingerprint=*)(gidnumber=%s))" % (cnf["Import-Users-From-Passwd::ValidGID"]),
1357                ["uid", "keyfingerprint", "cn", "mn", "sn"])
1358
1359         ldap_fin_uid_id = {}
1360
1361         byuid = {}
1362         byname = {}
1363
1364         for i in Attrs:
1365             entry = i[1]
1366             uid = entry["uid"][0]
1367             name = get_ldap_name(entry)
1368             fingerprints = entry["keyFingerPrint"]
1369             keyid = None
1370             for f in fingerprints:
1371                 key = self.fpr_lookup.get(f, None)
1372                 if key not in self.keys:
1373                     continue
1374                 self.keys[key]["uid"] = uid
1375
1376                 if keyid != None:
1377                     continue
1378                 keyid = get_or_set_uid(uid, session).uid_id
1379                 byuid[keyid] = (uid, name)
1380                 byname[uid] = (keyid, name)
1381
1382         return (byname, byuid)
1383
1384     def generate_users_from_keyring(self, format, session):
1385         byuid = {}
1386         byname = {}
1387         any_invalid = False
1388         for x in self.keys.keys():
1389             if self.keys[x]["email"] == "invalid-uid":
1390                 any_invalid = True
1391                 self.keys[x]["uid"] = format % "invalid-uid"
1392             else:
1393                 uid = format % self.keys[x]["email"]
1394                 keyid = get_or_set_uid(uid, session).uid_id
1395                 byuid[keyid] = (uid, self.keys[x]["name"])
1396                 byname[uid] = (keyid, self.keys[x]["name"])
1397                 self.keys[x]["uid"] = uid
1398
1399         if any_invalid:
1400             uid = format % "invalid-uid"
1401             keyid = get_or_set_uid(uid, session).uid_id
1402             byuid[keyid] = (uid, "ungeneratable user id")
1403             byname[uid] = (keyid, "ungeneratable user id")
1404
1405         return (byname, byuid)
1406
1407 __all__.append('Keyring')
1408
1409 @session_wrapper
1410 def get_keyring(keyring, session=None):
1411     """
1412     If C{keyring} does not have an entry in the C{keyrings} table yet, return None
1413     If C{keyring} already has an entry, simply return the existing Keyring
1414
1415     @type keyring: string
1416     @param keyring: the keyring name
1417
1418     @rtype: Keyring
1419     @return: the Keyring object for this keyring
1420     """
1421
1422     q = session.query(Keyring).filter_by(keyring_name=keyring)
1423
1424     try:
1425         return q.one()
1426     except NoResultFound:
1427         return None
1428
1429 __all__.append('get_keyring')
1430
1431 ################################################################################
1432
1433 class KeyringACLMap(object):
1434     def __init__(self, *args, **kwargs):
1435         pass
1436
1437     def __repr__(self):
1438         return '<KeyringACLMap %s>' % self.keyring_acl_map_id
1439
1440 __all__.append('KeyringACLMap')
1441
1442 ################################################################################
1443
1444 class DBChange(object):
1445     def __init__(self, *args, **kwargs):
1446         pass
1447
1448     def __repr__(self):
1449         return '<DBChange %s>' % self.changesname
1450
1451     def clean_from_queue(self):
1452         session = DBConn().session().object_session(self)
1453
1454         # Remove changes_pool_files entries
1455         self.poolfiles = []
1456
1457         # Remove changes_pending_files references
1458         self.files = []
1459
1460         # Clear out of queue
1461         self.in_queue = None
1462         self.approved_for_id = None
1463
1464 __all__.append('DBChange')
1465
1466 @session_wrapper
1467 def get_dbchange(filename, session=None):
1468     """
1469     returns DBChange object for given C{filename}.
1470
1471     @type filename: string
1472     @param filename: the name of the file
1473
1474     @type session: Session
1475     @param session: Optional SQLA session object (a temporary one will be
1476     generated if not supplied)
1477
1478     @rtype: DBChange
1479     @return:  DBChange object for the given filename (C{None} if not present)
1480
1481     """
1482     q = session.query(DBChange).filter_by(changesname=filename)
1483
1484     try:
1485         return q.one()
1486     except NoResultFound:
1487         return None
1488
1489 __all__.append('get_dbchange')
1490
1491 ################################################################################
1492
1493 class Location(object):
1494     def __init__(self, *args, **kwargs):
1495         pass
1496
1497     def __repr__(self):
1498         return '<Location %s (%s)>' % (self.path, self.location_id)
1499
1500 __all__.append('Location')
1501
1502 @session_wrapper
1503 def get_location(location, component=None, archive=None, session=None):
1504     """
1505     Returns Location object for the given combination of location, component
1506     and archive
1507
1508     @type location: string
1509     @param location: the path of the location, e.g. I{/srv/ftp.debian.org/ftp/pool/}
1510
1511     @type component: string
1512     @param component: the component name (if None, no restriction applied)
1513
1514     @type archive: string
1515     @param archive: the archive name (if None, no restriction applied)
1516
1517     @rtype: Location / None
1518     @return: Either a Location object or None if one can't be found
1519     """
1520
1521     q = session.query(Location).filter_by(path=location)
1522
1523     if archive is not None:
1524         q = q.join(Archive).filter_by(archive_name=archive)
1525
1526     if component is not None:
1527         q = q.join(Component).filter_by(component_name=component)
1528
1529     try:
1530         return q.one()
1531     except NoResultFound:
1532         return None
1533
1534 __all__.append('get_location')
1535
1536 ################################################################################
1537
1538 class Maintainer(object):
1539     def __init__(self, *args, **kwargs):
1540         pass
1541
1542     def __repr__(self):
1543         return '''<Maintainer '%s' (%s)>''' % (self.name, self.maintainer_id)
1544
1545     def get_split_maintainer(self):
1546         if not hasattr(self, 'name') or self.name is None:
1547             return ('', '', '', '')
1548
1549         return fix_maintainer(self.name.strip())
1550
1551 __all__.append('Maintainer')
1552
1553 @session_wrapper
1554 def get_or_set_maintainer(name, session=None):
1555     """
1556     Returns Maintainer object for given maintainer name.
1557
1558     If no matching maintainer name is found, a row is inserted.
1559
1560     @type name: string
1561     @param name: The maintainer name to add
1562
1563     @type session: SQLAlchemy
1564     @param session: Optional SQL session object (a temporary one will be
1565     generated if not supplied).  If not passed, a commit will be performed at
1566     the end of the function, otherwise the caller is responsible for commiting.
1567     A flush will be performed either way.
1568
1569     @rtype: Maintainer
1570     @return: the Maintainer object for the given maintainer
1571     """
1572
1573     q = session.query(Maintainer).filter_by(name=name)
1574     try:
1575         ret = q.one()
1576     except NoResultFound:
1577         maintainer = Maintainer()
1578         maintainer.name = name
1579         session.add(maintainer)
1580         session.commit_or_flush()
1581         ret = maintainer
1582
1583     return ret
1584
1585 __all__.append('get_or_set_maintainer')
1586
1587 @session_wrapper
1588 def get_maintainer(maintainer_id, session=None):
1589     """
1590     Return the name of the maintainer behind C{maintainer_id} or None if that
1591     maintainer_id is invalid.
1592
1593     @type maintainer_id: int
1594     @param maintainer_id: the id of the maintainer
1595
1596     @rtype: Maintainer
1597     @return: the Maintainer with this C{maintainer_id}
1598     """
1599
1600     return session.query(Maintainer).get(maintainer_id)
1601
1602 __all__.append('get_maintainer')
1603
1604 ################################################################################
1605
1606 class NewComment(object):
1607     def __init__(self, *args, **kwargs):
1608         pass
1609
1610     def __repr__(self):
1611         return '''<NewComment for '%s %s' (%s)>''' % (self.package, self.version, self.comment_id)
1612
1613 __all__.append('NewComment')
1614
1615 @session_wrapper
1616 def has_new_comment(package, version, session=None):
1617     """
1618     Returns true if the given combination of C{package}, C{version} has a comment.
1619
1620     @type package: string
1621     @param package: name of the package
1622
1623     @type version: string
1624     @param version: package version
1625
1626     @type session: Session
1627     @param session: Optional SQLA session object (a temporary one will be
1628     generated if not supplied)
1629
1630     @rtype: boolean
1631     @return: true/false
1632     """
1633
1634     q = session.query(NewComment)
1635     q = q.filter_by(package=package)
1636     q = q.filter_by(version=version)
1637
1638     return bool(q.count() > 0)
1639
1640 __all__.append('has_new_comment')
1641
1642 @session_wrapper
1643 def get_new_comments(package=None, version=None, comment_id=None, session=None):
1644     """
1645     Returns (possibly empty) list of NewComment objects for the given
1646     parameters
1647
1648     @type package: string (optional)
1649     @param package: name of the package
1650
1651     @type version: string (optional)
1652     @param version: package version
1653
1654     @type comment_id: int (optional)
1655     @param comment_id: An id of a comment
1656
1657     @type session: Session
1658     @param session: Optional SQLA session object (a temporary one will be
1659     generated if not supplied)
1660
1661     @rtype: list
1662     @return: A (possibly empty) list of NewComment objects will be returned
1663     """
1664
1665     q = session.query(NewComment)
1666     if package is not None: q = q.filter_by(package=package)
1667     if version is not None: q = q.filter_by(version=version)
1668     if comment_id is not None: q = q.filter_by(comment_id=comment_id)
1669
1670     return q.all()
1671
1672 __all__.append('get_new_comments')
1673
1674 ################################################################################
1675
1676 class Override(object):
1677     def __init__(self, *args, **kwargs):
1678         pass
1679
1680     def __repr__(self):
1681         return '<Override %s (%s)>' % (self.package, self.suite_id)
1682
1683 __all__.append('Override')
1684
1685 @session_wrapper
1686 def get_override(package, suite=None, component=None, overridetype=None, session=None):
1687     """
1688     Returns Override object for the given parameters
1689
1690     @type package: string
1691     @param package: The name of the package
1692
1693     @type suite: string, list or None
1694     @param suite: The name of the suite (or suites if a list) to limit to.  If
1695                   None, don't limit.  Defaults to None.
1696
1697     @type component: string, list or None
1698     @param component: The name of the component (or components if a list) to
1699                       limit to.  If None, don't limit.  Defaults to None.
1700
1701     @type overridetype: string, list or None
1702     @param overridetype: The name of the overridetype (or overridetypes if a list) to
1703                          limit to.  If None, don't limit.  Defaults to None.
1704
1705     @type session: Session
1706     @param session: Optional SQLA session object (a temporary one will be
1707     generated if not supplied)
1708
1709     @rtype: list
1710     @return: A (possibly empty) list of Override objects will be returned
1711     """
1712
1713     q = session.query(Override)
1714     q = q.filter_by(package=package)
1715
1716     if suite is not None:
1717         if not isinstance(suite, list): suite = [suite]
1718         q = q.join(Suite).filter(Suite.suite_name.in_(suite))
1719
1720     if component is not None:
1721         if not isinstance(component, list): component = [component]
1722         q = q.join(Component).filter(Component.component_name.in_(component))
1723
1724     if overridetype is not None:
1725         if not isinstance(overridetype, list): overridetype = [overridetype]
1726         q = q.join(OverrideType).filter(OverrideType.overridetype.in_(overridetype))
1727
1728     return q.all()
1729
1730 __all__.append('get_override')
1731
1732
1733 ################################################################################
1734
1735 class OverrideType(object):
1736     def __init__(self, *args, **kwargs):
1737         pass
1738
1739     def __repr__(self):
1740         return '<OverrideType %s>' % self.overridetype
1741
1742 __all__.append('OverrideType')
1743
1744 @session_wrapper
1745 def get_override_type(override_type, session=None):
1746     """
1747     Returns OverrideType object for given C{override type}.
1748
1749     @type override_type: string
1750     @param override_type: The name of the override type
1751
1752     @type session: Session
1753     @param session: Optional SQLA session object (a temporary one will be
1754     generated if not supplied)
1755
1756     @rtype: int
1757     @return: the database id for the given override type
1758     """
1759
1760     q = session.query(OverrideType).filter_by(overridetype=override_type)
1761
1762     try:
1763         return q.one()
1764     except NoResultFound:
1765         return None
1766
1767 __all__.append('get_override_type')
1768
1769 ################################################################################
1770
1771 class DebContents(object):
1772     def __init__(self, *args, **kwargs):
1773         pass
1774
1775     def __repr__(self):
1776         return '<DebConetnts %s: %s>' % (self.package.package,self.file)
1777
1778 __all__.append('DebContents')
1779
1780
1781 class UdebContents(object):
1782     def __init__(self, *args, **kwargs):
1783         pass
1784
1785     def __repr__(self):
1786         return '<UdebConetnts %s: %s>' % (self.package.package,self.file)
1787
1788 __all__.append('UdebContents')
1789
1790 class PendingBinContents(object):
1791     def __init__(self, *args, **kwargs):
1792         pass
1793
1794     def __repr__(self):
1795         return '<PendingBinContents %s>' % self.contents_id
1796
1797 __all__.append('PendingBinContents')
1798
1799 def insert_pending_content_paths(package,
1800                                  is_udeb,
1801                                  fullpaths,
1802                                  session=None):
1803     """
1804     Make sure given paths are temporarily associated with given
1805     package
1806
1807     @type package: dict
1808     @param package: the package to associate with should have been read in from the binary control file
1809     @type fullpaths: list
1810     @param fullpaths: the list of paths of the file being associated with the binary
1811     @type session: SQLAlchemy session
1812     @param session: Optional SQLAlchemy session.  If this is passed, the caller
1813     is responsible for ensuring a transaction has begun and committing the
1814     results or rolling back based on the result code.  If not passed, a commit
1815     will be performed at the end of the function
1816
1817     @return: True upon success, False if there is a problem
1818     """
1819
1820     privatetrans = False
1821
1822     if session is None:
1823         session = DBConn().session()
1824         privatetrans = True
1825
1826     try:
1827         arch = get_architecture(package['Architecture'], session)
1828         arch_id = arch.arch_id
1829
1830         # Remove any already existing recorded files for this package
1831         q = session.query(PendingBinContents)
1832         q = q.filter_by(package=package['Package'])
1833         q = q.filter_by(version=package['Version'])
1834         q = q.filter_by(architecture=arch_id)
1835         q.delete()
1836
1837         for fullpath in fullpaths:
1838
1839             if fullpath.startswith( "./" ):
1840                 fullpath = fullpath[2:]
1841
1842             pca = PendingBinContents()
1843             pca.package = package['Package']
1844             pca.version = package['Version']
1845             pca.file = fullpath
1846             pca.architecture = arch_id
1847
1848             if isudeb:
1849                 pca.type = 8 # gross
1850             else:
1851                 pca.type = 7 # also gross
1852             session.add(pca)
1853
1854         # Only commit if we set up the session ourself
1855         if privatetrans:
1856             session.commit()
1857             session.close()
1858         else:
1859             session.flush()
1860
1861         return True
1862     except Exception, e:
1863         traceback.print_exc()
1864
1865         # Only rollback if we set up the session ourself
1866         if privatetrans:
1867             session.rollback()
1868             session.close()
1869
1870         return False
1871
1872 __all__.append('insert_pending_content_paths')
1873
1874 ################################################################################
1875
1876 class PolicyQueue(object):
1877     def __init__(self, *args, **kwargs):
1878         pass
1879
1880     def __repr__(self):
1881         return '<PolicyQueue %s>' % self.queue_name
1882
1883 __all__.append('PolicyQueue')
1884
1885 @session_wrapper
1886 def get_policy_queue(queuename, session=None):
1887     """
1888     Returns PolicyQueue object for given C{queue name}
1889
1890     @type queuename: string
1891     @param queuename: The name of the queue
1892
1893     @type session: Session
1894     @param session: Optional SQLA session object (a temporary one will be
1895     generated if not supplied)
1896
1897     @rtype: PolicyQueue
1898     @return: PolicyQueue object for the given queue
1899     """
1900
1901     q = session.query(PolicyQueue).filter_by(queue_name=queuename)
1902
1903     try:
1904         return q.one()
1905     except NoResultFound:
1906         return None
1907
1908 __all__.append('get_policy_queue')
1909
1910 ################################################################################
1911
1912 class Priority(object):
1913     def __init__(self, *args, **kwargs):
1914         pass
1915
1916     def __eq__(self, val):
1917         if isinstance(val, str):
1918             return (self.priority == val)
1919         # This signals to use the normal comparison operator
1920         return NotImplemented
1921
1922     def __ne__(self, val):
1923         if isinstance(val, str):
1924             return (self.priority != val)
1925         # This signals to use the normal comparison operator
1926         return NotImplemented
1927
1928     def __repr__(self):
1929         return '<Priority %s (%s)>' % (self.priority, self.priority_id)
1930
1931 __all__.append('Priority')
1932
1933 @session_wrapper
1934 def get_priority(priority, session=None):
1935     """
1936     Returns Priority object for given C{priority name}.
1937
1938     @type priority: string
1939     @param priority: The name of the priority
1940
1941     @type session: Session
1942     @param session: Optional SQLA session object (a temporary one will be
1943     generated if not supplied)
1944
1945     @rtype: Priority
1946     @return: Priority object for the given priority
1947     """
1948
1949     q = session.query(Priority).filter_by(priority=priority)
1950
1951     try:
1952         return q.one()
1953     except NoResultFound:
1954         return None
1955
1956 __all__.append('get_priority')
1957
1958 @session_wrapper
1959 def get_priorities(session=None):
1960     """
1961     Returns dictionary of priority names -> id mappings
1962
1963     @type session: Session
1964     @param session: Optional SQL session object (a temporary one will be
1965     generated if not supplied)
1966
1967     @rtype: dictionary
1968     @return: dictionary of priority names -> id mappings
1969     """
1970
1971     ret = {}
1972     q = session.query(Priority)
1973     for x in q.all():
1974         ret[x.priority] = x.priority_id
1975
1976     return ret
1977
1978 __all__.append('get_priorities')
1979
1980 ################################################################################
1981
1982 class Section(object):
1983     def __init__(self, *args, **kwargs):
1984         pass
1985
1986     def __eq__(self, val):
1987         if isinstance(val, str):
1988             return (self.section == val)
1989         # This signals to use the normal comparison operator
1990         return NotImplemented
1991
1992     def __ne__(self, val):
1993         if isinstance(val, str):
1994             return (self.section != val)
1995         # This signals to use the normal comparison operator
1996         return NotImplemented
1997
1998     def __repr__(self):
1999         return '<Section %s>' % self.section
2000
2001 __all__.append('Section')
2002
2003 @session_wrapper
2004 def get_section(section, session=None):
2005     """
2006     Returns Section object for given C{section name}.
2007
2008     @type section: string
2009     @param section: The name of the section
2010
2011     @type session: Session
2012     @param session: Optional SQLA session object (a temporary one will be
2013     generated if not supplied)
2014
2015     @rtype: Section
2016     @return: Section object for the given section name
2017     """
2018
2019     q = session.query(Section).filter_by(section=section)
2020
2021     try:
2022         return q.one()
2023     except NoResultFound:
2024         return None
2025
2026 __all__.append('get_section')
2027
2028 @session_wrapper
2029 def get_sections(session=None):
2030     """
2031     Returns dictionary of section names -> id mappings
2032
2033     @type session: Session
2034     @param session: Optional SQL session object (a temporary one will be
2035     generated if not supplied)
2036
2037     @rtype: dictionary
2038     @return: dictionary of section names -> id mappings
2039     """
2040
2041     ret = {}
2042     q = session.query(Section)
2043     for x in q.all():
2044         ret[x.section] = x.section_id
2045
2046     return ret
2047
2048 __all__.append('get_sections')
2049
2050 ################################################################################
2051
2052 class DBSource(object):
2053     def __init__(self, *args, **kwargs):
2054         pass
2055
2056     def __repr__(self):
2057         return '<DBSource %s (%s)>' % (self.source, self.version)
2058
2059 __all__.append('DBSource')
2060
2061 @session_wrapper
2062 def source_exists(source, source_version, suites = ["any"], session=None):
2063     """
2064     Ensure that source exists somewhere in the archive for the binary
2065     upload being processed.
2066       1. exact match     => 1.0-3
2067       2. bin-only NMU    => 1.0-3+b1 , 1.0-3.1+b1
2068
2069     @type source: string
2070     @param source: source name
2071
2072     @type source_version: string
2073     @param source_version: expected source version
2074
2075     @type suites: list
2076     @param suites: list of suites to check in, default I{any}
2077
2078     @type session: Session
2079     @param session: Optional SQLA session object (a temporary one will be
2080     generated if not supplied)
2081
2082     @rtype: int
2083     @return: returns 1 if a source with expected version is found, otherwise 0
2084
2085     """
2086
2087     cnf = Config()
2088     ret = 1
2089
2090     for suite in suites:
2091         q = session.query(DBSource).filter_by(source=source)
2092         if suite != "any":
2093             # source must exist in suite X, or in some other suite that's
2094             # mapped to X, recursively... silent-maps are counted too,
2095             # unreleased-maps aren't.
2096             maps = cnf.ValueList("SuiteMappings")[:]
2097             maps.reverse()
2098             maps = [ m.split() for m in maps ]
2099             maps = [ (x[1], x[2]) for x in maps
2100                             if x[0] == "map" or x[0] == "silent-map" ]
2101             s = [suite]
2102             for x in maps:
2103                 if x[1] in s and x[0] not in s:
2104                     s.append(x[0])
2105
2106             q = q.join(SrcAssociation).join(Suite)
2107             q = q.filter(Suite.suite_name.in_(s))
2108
2109         # Reduce the query results to a list of version numbers
2110         ql = [ j.version for j in q.all() ]
2111
2112         # Try (1)
2113         if source_version in ql:
2114             continue
2115
2116         # Try (2)
2117         from daklib.regexes import re_bin_only_nmu
2118         orig_source_version = re_bin_only_nmu.sub('', source_version)
2119         if orig_source_version in ql:
2120             continue
2121
2122         # No source found so return not ok
2123         ret = 0
2124
2125     return ret
2126
2127 __all__.append('source_exists')
2128
2129 @session_wrapper
2130 def get_suites_source_in(source, session=None):
2131     """
2132     Returns list of Suite objects which given C{source} name is in
2133
2134     @type source: str
2135     @param source: DBSource package name to search for
2136
2137     @rtype: list
2138     @return: list of Suite objects for the given source
2139     """
2140
2141     return session.query(Suite).join(SrcAssociation).join(DBSource).filter_by(source=source).all()
2142
2143 __all__.append('get_suites_source_in')
2144
2145 @session_wrapper
2146 def get_sources_from_name(source, version=None, dm_upload_allowed=None, session=None):
2147     """
2148     Returns list of DBSource objects for given C{source} name and other parameters
2149
2150     @type source: str
2151     @param source: DBSource package name to search for
2152
2153     @type version: str or None
2154     @param version: DBSource version name to search for or None if not applicable
2155
2156     @type dm_upload_allowed: bool
2157     @param dm_upload_allowed: If None, no effect.  If True or False, only
2158     return packages with that dm_upload_allowed setting
2159
2160     @type session: Session
2161     @param session: Optional SQL session object (a temporary one will be
2162     generated if not supplied)
2163
2164     @rtype: list
2165     @return: list of DBSource objects for the given name (may be empty)
2166     """
2167
2168     q = session.query(DBSource).filter_by(source=source)
2169
2170     if version is not None:
2171         q = q.filter_by(version=version)
2172
2173     if dm_upload_allowed is not None:
2174         q = q.filter_by(dm_upload_allowed=dm_upload_allowed)
2175
2176     return q.all()
2177
2178 __all__.append('get_sources_from_name')
2179
2180 @session_wrapper
2181 def get_source_in_suite(source, suite, session=None):
2182     """
2183     Returns list of DBSource objects for a combination of C{source} and C{suite}.
2184
2185       - B{source} - source package name, eg. I{mailfilter}, I{bbdb}, I{glibc}
2186       - B{suite} - a suite name, eg. I{unstable}
2187
2188     @type source: string
2189     @param source: source package name
2190
2191     @type suite: string
2192     @param suite: the suite name
2193
2194     @rtype: string
2195     @return: the version for I{source} in I{suite}
2196
2197     """
2198
2199     q = session.query(SrcAssociation)
2200     q = q.join('source').filter_by(source=source)
2201     q = q.join('suite').filter_by(suite_name=suite)
2202
2203     try:
2204         return q.one().source
2205     except NoResultFound:
2206         return None
2207
2208 __all__.append('get_source_in_suite')
2209
2210 ################################################################################
2211
2212 @session_wrapper
2213 def add_dsc_to_db(u, filename, session=None):
2214     entry = u.pkg.files[filename]
2215     source = DBSource()
2216     pfs = []
2217
2218     source.source = u.pkg.dsc["source"]
2219     source.version = u.pkg.dsc["version"] # NB: not files[file]["version"], that has no epoch
2220     source.maintainer_id = get_or_set_maintainer(u.pkg.dsc["maintainer"], session).maintainer_id
2221     source.changedby_id = get_or_set_maintainer(u.pkg.changes["changed-by"], session).maintainer_id
2222     source.fingerprint_id = get_or_set_fingerprint(u.pkg.changes["fingerprint"], session).fingerprint_id
2223     source.install_date = datetime.now().date()
2224
2225     dsc_component = entry["component"]
2226     dsc_location_id = entry["location id"]
2227
2228     source.dm_upload_allowed = (u.pkg.dsc.get("dm-upload-allowed", '') == "yes")
2229
2230     # Set up a new poolfile if necessary
2231     if not entry.has_key("files id") or not entry["files id"]:
2232         filename = entry["pool name"] + filename
2233         poolfile = add_poolfile(filename, entry, dsc_location_id, session)
2234         session.flush()
2235         pfs.append(poolfile)
2236         entry["files id"] = poolfile.file_id
2237
2238     source.poolfile_id = entry["files id"]
2239     session.add(source)
2240     session.flush()
2241
2242     for suite_name in u.pkg.changes["distribution"].keys():
2243         sa = SrcAssociation()
2244         sa.source_id = source.source_id
2245         sa.suite_id = get_suite(suite_name).suite_id
2246         session.add(sa)
2247
2248     session.flush()
2249
2250     # Add the source files to the DB (files and dsc_files)
2251     dscfile = DSCFile()
2252     dscfile.source_id = source.source_id
2253     dscfile.poolfile_id = entry["files id"]
2254     session.add(dscfile)
2255
2256     for dsc_file, dentry in u.pkg.dsc_files.items():
2257         df = DSCFile()
2258         df.source_id = source.source_id
2259
2260         # If the .orig tarball is already in the pool, it's
2261         # files id is stored in dsc_files by check_dsc().
2262         files_id = dentry.get("files id", None)
2263
2264         # Find the entry in the files hash
2265         # TODO: Bail out here properly
2266         dfentry = None
2267         for f, e in u.pkg.files.items():
2268             if f == dsc_file:
2269                 dfentry = e
2270                 break
2271
2272         if files_id is None:
2273             filename = dfentry["pool name"] + dsc_file
2274
2275             (found, obj) = check_poolfile(filename, dentry["size"], dentry["md5sum"], dsc_location_id)
2276             # FIXME: needs to check for -1/-2 and or handle exception
2277             if found and obj is not None:
2278                 files_id = obj.file_id
2279                 pfs.append(obj)
2280
2281             # If still not found, add it
2282             if files_id is None:
2283                 # HACK: Force sha1sum etc into dentry
2284                 dentry["sha1sum"] = dfentry["sha1sum"]
2285                 dentry["sha256sum"] = dfentry["sha256sum"]
2286                 poolfile = add_poolfile(filename, dentry, dsc_location_id, session)
2287                 pfs.append(poolfile)
2288                 files_id = poolfile.file_id
2289         else:
2290             poolfile = get_poolfile_by_id(files_id, session)
2291             if poolfile is None:
2292                 utils.fubar("INTERNAL ERROR. Found no poolfile with id %d" % files_id)
2293             pfs.append(poolfile)
2294
2295         df.poolfile_id = files_id
2296         session.add(df)
2297
2298     session.flush()
2299
2300     # Add the src_uploaders to the DB
2301     uploader_ids = [source.maintainer_id]
2302     if u.pkg.dsc.has_key("uploaders"):
2303         for up in u.pkg.dsc["uploaders"].split(","):
2304             up = up.strip()
2305             uploader_ids.append(get_or_set_maintainer(up, session).maintainer_id)
2306
2307     added_ids = {}
2308     for up_id in uploader_ids:
2309         if added_ids.has_key(up_id):
2310             import utils
2311             utils.warn("Already saw uploader %s for source %s" % (up_id, source.source))
2312             continue
2313
2314         added_ids[up_id]=1
2315
2316         su = SrcUploader()
2317         su.maintainer_id = up_id
2318         su.source_id = source.source_id
2319         session.add(su)
2320
2321     session.flush()
2322
2323     return source, dsc_component, dsc_location_id, pfs
2324
2325 __all__.append('add_dsc_to_db')
2326
2327 @session_wrapper
2328 def add_deb_to_db(u, filename, session=None):
2329     """
2330     Contrary to what you might expect, this routine deals with both
2331     debs and udebs.  That info is in 'dbtype', whilst 'type' is
2332     'deb' for both of them
2333     """
2334     cnf = Config()
2335     entry = u.pkg.files[filename]
2336
2337     bin = DBBinary()
2338     bin.package = entry["package"]
2339     bin.version = entry["version"]
2340     bin.maintainer_id = get_or_set_maintainer(entry["maintainer"], session).maintainer_id
2341     bin.fingerprint_id = get_or_set_fingerprint(u.pkg.changes["fingerprint"], session).fingerprint_id
2342     bin.arch_id = get_architecture(entry["architecture"], session).arch_id
2343     bin.binarytype = entry["dbtype"]
2344
2345     # Find poolfile id
2346     filename = entry["pool name"] + filename
2347     fullpath = os.path.join(cnf["Dir::Pool"], filename)
2348     if not entry.get("location id", None):
2349         entry["location id"] = get_location(cnf["Dir::Pool"], entry["component"], session=session).location_id
2350
2351     if entry.get("files id", None):
2352         poolfile = get_poolfile_by_id(bin.poolfile_id)
2353         bin.poolfile_id = entry["files id"]
2354     else:
2355         poolfile = add_poolfile(filename, entry, entry["location id"], session)
2356         bin.poolfile_id = entry["files id"] = poolfile.file_id
2357
2358     # Find source id
2359     bin_sources = get_sources_from_name(entry["source package"], entry["source version"], session=session)
2360     if len(bin_sources) != 1:
2361         raise NoSourceFieldError, "Unable to find a unique source id for %s (%s), %s, file %s, type %s, signed by %s" % \
2362                                   (bin.package, bin.version, bin.architecture.arch_string,
2363                                    filename, bin.binarytype, u.pkg.changes["fingerprint"])
2364
2365     bin.source_id = bin_sources[0].source_id
2366
2367     # Add and flush object so it has an ID
2368     session.add(bin)
2369     session.flush()
2370
2371     # Add BinAssociations
2372     for suite_name in u.pkg.changes["distribution"].keys():
2373         ba = BinAssociation()
2374         ba.binary_id = bin.binary_id
2375         ba.suite_id = get_suite(suite_name).suite_id
2376         session.add(ba)
2377
2378     session.flush()
2379
2380     # Deal with contents - disabled for now
2381     #contents = copy_temporary_contents(bin.package, bin.version, bin.architecture.arch_string, os.path.basename(filename), None, session)
2382     #if not contents:
2383     #    print "REJECT\nCould not determine contents of package %s" % bin.package
2384     #    session.rollback()
2385     #    raise MissingContents, "No contents stored for package %s, and couldn't determine contents of %s" % (bin.package, filename)
2386
2387     return poolfile
2388
2389 __all__.append('add_deb_to_db')
2390
2391 ################################################################################
2392
2393 class SourceACL(object):
2394     def __init__(self, *args, **kwargs):
2395         pass
2396
2397     def __repr__(self):
2398         return '<SourceACL %s>' % self.source_acl_id
2399
2400 __all__.append('SourceACL')
2401
2402 ################################################################################
2403
2404 class SrcAssociation(object):
2405     def __init__(self, *args, **kwargs):
2406         pass
2407
2408     def __repr__(self):
2409         return '<SrcAssociation %s (%s, %s)>' % (self.sa_id, self.source, self.suite)
2410
2411 __all__.append('SrcAssociation')
2412
2413 ################################################################################
2414
2415 class SrcFormat(object):
2416     def __init__(self, *args, **kwargs):
2417         pass
2418
2419     def __repr__(self):
2420         return '<SrcFormat %s>' % (self.format_name)
2421
2422 __all__.append('SrcFormat')
2423
2424 ################################################################################
2425
2426 class SrcUploader(object):
2427     def __init__(self, *args, **kwargs):
2428         pass
2429
2430     def __repr__(self):
2431         return '<SrcUploader %s>' % self.uploader_id
2432
2433 __all__.append('SrcUploader')
2434
2435 ################################################################################
2436
2437 SUITE_FIELDS = [ ('SuiteName', 'suite_name'),
2438                  ('SuiteID', 'suite_id'),
2439                  ('Version', 'version'),
2440                  ('Origin', 'origin'),
2441                  ('Label', 'label'),
2442                  ('Description', 'description'),
2443                  ('Untouchable', 'untouchable'),
2444                  ('Announce', 'announce'),
2445                  ('Codename', 'codename'),
2446                  ('OverrideCodename', 'overridecodename'),
2447                  ('ValidTime', 'validtime'),
2448                  ('Priority', 'priority'),
2449                  ('NotAutomatic', 'notautomatic'),
2450                  ('CopyChanges', 'copychanges'),
2451                  ('CopyDotDak', 'copydotdak'),
2452                  ('CommentsDir', 'commentsdir'),
2453                  ('OverrideSuite', 'overridesuite'),
2454                  ('ChangelogBase', 'changelogbase')]
2455
2456
2457 class Suite(object):
2458     def __init__(self, *args, **kwargs):
2459         pass
2460
2461     def __repr__(self):
2462         return '<Suite %s>' % self.suite_name
2463
2464     def __eq__(self, val):
2465         if isinstance(val, str):
2466             return (self.suite_name == val)
2467         # This signals to use the normal comparison operator
2468         return NotImplemented
2469
2470     def __ne__(self, val):
2471         if isinstance(val, str):
2472             return (self.suite_name != val)
2473         # This signals to use the normal comparison operator
2474         return NotImplemented
2475
2476     def details(self):
2477         ret = []
2478         for disp, field in SUITE_FIELDS:
2479             val = getattr(self, field, None)
2480             if val is not None:
2481                 ret.append("%s: %s" % (disp, val))
2482
2483         return "\n".join(ret)
2484
2485 __all__.append('Suite')
2486
2487 @session_wrapper
2488 def get_suite_architecture(suite, architecture, session=None):
2489     """
2490     Returns a SuiteArchitecture object given C{suite} and ${arch} or None if it
2491     doesn't exist
2492
2493     @type suite: str
2494     @param suite: Suite name to search for
2495
2496     @type architecture: str
2497     @param architecture: Architecture name to search for
2498
2499     @type session: Session
2500     @param session: Optional SQL session object (a temporary one will be
2501     generated if not supplied)
2502
2503     @rtype: SuiteArchitecture
2504     @return: the SuiteArchitecture object or None
2505     """
2506
2507     q = session.query(SuiteArchitecture)
2508     q = q.join(Architecture).filter_by(arch_string=architecture)
2509     q = q.join(Suite).filter_by(suite_name=suite)
2510
2511     try:
2512         return q.one()
2513     except NoResultFound:
2514         return None
2515
2516 __all__.append('get_suite_architecture')
2517
2518 @session_wrapper
2519 def get_suite(suite, session=None):
2520     """
2521     Returns Suite object for given C{suite name}.
2522
2523     @type suite: string
2524     @param suite: The name of the suite
2525
2526     @type session: Session
2527     @param session: Optional SQLA session object (a temporary one will be
2528     generated if not supplied)
2529
2530     @rtype: Suite
2531     @return: Suite object for the requested suite name (None if not present)
2532     """
2533
2534     q = session.query(Suite).filter_by(suite_name=suite)
2535
2536     try:
2537         return q.one()
2538     except NoResultFound:
2539         return None
2540
2541 __all__.append('get_suite')
2542
2543 ################################################################################
2544
2545 class SuiteArchitecture(object):
2546     def __init__(self, *args, **kwargs):
2547         pass
2548
2549     def __repr__(self):
2550         return '<SuiteArchitecture (%s, %s)>' % (self.suite_id, self.arch_id)
2551
2552 __all__.append('SuiteArchitecture')
2553
2554 @session_wrapper
2555 def get_suite_architectures(suite, skipsrc=False, skipall=False, session=None):
2556     """
2557     Returns list of Architecture objects for given C{suite} name
2558
2559     @type suite: str
2560     @param suite: Suite name to search for
2561
2562     @type skipsrc: boolean
2563     @param skipsrc: Whether to skip returning the 'source' architecture entry
2564     (Default False)
2565
2566     @type skipall: boolean
2567     @param skipall: Whether to skip returning the 'all' architecture entry
2568     (Default False)
2569
2570     @type session: Session
2571     @param session: Optional SQL session object (a temporary one will be
2572     generated if not supplied)
2573
2574     @rtype: list
2575     @return: list of Architecture objects for the given name (may be empty)
2576     """
2577
2578     q = session.query(Architecture)
2579     q = q.join(SuiteArchitecture)
2580     q = q.join(Suite).filter_by(suite_name=suite)
2581
2582     if skipsrc:
2583         q = q.filter(Architecture.arch_string != 'source')
2584
2585     if skipall:
2586         q = q.filter(Architecture.arch_string != 'all')
2587
2588     q = q.order_by('arch_string')
2589
2590     return q.all()
2591
2592 __all__.append('get_suite_architectures')
2593
2594 ################################################################################
2595
2596 class SuiteSrcFormat(object):
2597     def __init__(self, *args, **kwargs):
2598         pass
2599
2600     def __repr__(self):
2601         return '<SuiteSrcFormat (%s, %s)>' % (self.suite_id, self.src_format_id)
2602
2603 __all__.append('SuiteSrcFormat')
2604
2605 @session_wrapper
2606 def get_suite_src_formats(suite, session=None):
2607     """
2608     Returns list of allowed SrcFormat for C{suite}.
2609
2610     @type suite: str
2611     @param suite: Suite name to search for
2612
2613     @type session: Session
2614     @param session: Optional SQL session object (a temporary one will be
2615     generated if not supplied)
2616
2617     @rtype: list
2618     @return: the list of allowed source formats for I{suite}
2619     """
2620
2621     q = session.query(SrcFormat)
2622     q = q.join(SuiteSrcFormat)
2623     q = q.join(Suite).filter_by(suite_name=suite)
2624     q = q.order_by('format_name')
2625
2626     return q.all()
2627
2628 __all__.append('get_suite_src_formats')
2629
2630 ################################################################################
2631
2632 class Uid(object):
2633     def __init__(self, *args, **kwargs):
2634         pass
2635
2636     def __eq__(self, val):
2637         if isinstance(val, str):
2638             return (self.uid == val)
2639         # This signals to use the normal comparison operator
2640         return NotImplemented
2641
2642     def __ne__(self, val):
2643         if isinstance(val, str):
2644             return (self.uid != val)
2645         # This signals to use the normal comparison operator
2646         return NotImplemented
2647
2648     def __repr__(self):
2649         return '<Uid %s (%s)>' % (self.uid, self.name)
2650
2651 __all__.append('Uid')
2652
2653 @session_wrapper
2654 def add_database_user(uidname, session=None):
2655     """
2656     Adds a database user
2657
2658     @type uidname: string
2659     @param uidname: The uid of the user to add
2660
2661     @type session: SQLAlchemy
2662     @param session: Optional SQL session object (a temporary one will be
2663     generated if not supplied).  If not passed, a commit will be performed at
2664     the end of the function, otherwise the caller is responsible for commiting.
2665
2666     @rtype: Uid
2667     @return: the uid object for the given uidname
2668     """
2669
2670     session.execute("CREATE USER :uid", {'uid': uidname})
2671     session.commit_or_flush()
2672
2673 __all__.append('add_database_user')
2674
2675 @session_wrapper
2676 def get_or_set_uid(uidname, session=None):
2677     """
2678     Returns uid object for given uidname.
2679
2680     If no matching uidname is found, a row is inserted.
2681
2682     @type uidname: string
2683     @param uidname: The uid to add
2684
2685     @type session: SQLAlchemy
2686     @param session: Optional SQL session object (a temporary one will be
2687     generated if not supplied).  If not passed, a commit will be performed at
2688     the end of the function, otherwise the caller is responsible for commiting.
2689
2690     @rtype: Uid
2691     @return: the uid object for the given uidname
2692     """
2693
2694     q = session.query(Uid).filter_by(uid=uidname)
2695
2696     try:
2697         ret = q.one()
2698     except NoResultFound:
2699         uid = Uid()
2700         uid.uid = uidname
2701         session.add(uid)
2702         session.commit_or_flush()
2703         ret = uid
2704
2705     return ret
2706
2707 __all__.append('get_or_set_uid')
2708
2709 @session_wrapper
2710 def get_uid_from_fingerprint(fpr, session=None):
2711     q = session.query(Uid)
2712     q = q.join(Fingerprint).filter_by(fingerprint=fpr)
2713
2714     try:
2715         return q.one()
2716     except NoResultFound:
2717         return None
2718
2719 __all__.append('get_uid_from_fingerprint')
2720
2721 ################################################################################
2722
2723 class UploadBlock(object):
2724     def __init__(self, *args, **kwargs):
2725         pass
2726
2727     def __repr__(self):
2728         return '<UploadBlock %s (%s)>' % (self.source, self.upload_block_id)
2729
2730 __all__.append('UploadBlock')
2731
2732 ################################################################################
2733
2734 class DBConn(object):
2735     """
2736     database module init.
2737     """
2738     __shared_state = {}
2739
2740     def __init__(self, *args, **kwargs):
2741         self.__dict__ = self.__shared_state
2742
2743         if not getattr(self, 'initialised', False):
2744             self.initialised = True
2745             self.debug = kwargs.has_key('debug')
2746             self.__createconn()
2747
2748     def __setuptables(self):
2749         tables = (
2750             'architecture',
2751             'archive',
2752             'bin_associations',
2753             'binaries',
2754             'binary_acl',
2755             'binary_acl_map',
2756             'bin_contents'
2757             'build_queue',
2758             'build_queue_files',
2759             'component',
2760             'config',
2761             'changes_pending_binaries',
2762             'changes_pending_files',
2763             'changes_pending_files_map',
2764             'changes_pending_source',
2765             'changes_pending_source_files',
2766             'changes_pool_files',
2767             'deb_contents',
2768             'dsc_files',
2769             'files',
2770             'fingerprint',
2771             'keyrings',
2772             'changes',
2773             'keyring_acl_map',
2774             'location',
2775             'maintainer',
2776             'new_comments',
2777             'override',
2778             'override_type',
2779             'pending_bin_contents',
2780             'policy_queue',
2781             'priority',
2782             'section',
2783             'source',
2784             'source_acl',
2785             'src_associations',
2786             'src_format',
2787             'src_uploaders',
2788             'suite',
2789             'suite_architectures',
2790             'suite_src_formats',
2791             'suite_build_queue_copy',
2792             'udeb_contents',
2793             'uid',
2794             'upload_blocks',
2795         )
2796
2797         for table_name in tables:
2798             table = Table(table_name, self.db_meta, autoload=True)
2799             setattr(self, 'tbl_%s' % table_name, table)
2800
2801     def __setupmappers(self):
2802         mapper(Architecture, self.tbl_architecture,
2803                properties = dict(arch_id = self.tbl_architecture.c.id))
2804
2805         mapper(Archive, self.tbl_archive,
2806                properties = dict(archive_id = self.tbl_archive.c.id,
2807                                  archive_name = self.tbl_archive.c.name))
2808
2809         mapper(BinAssociation, self.tbl_bin_associations,
2810                properties = dict(ba_id = self.tbl_bin_associations.c.id,
2811                                  suite_id = self.tbl_bin_associations.c.suite,
2812                                  suite = relation(Suite),
2813                                  binary_id = self.tbl_bin_associations.c.bin,
2814                                  binary = relation(DBBinary)))
2815
2816         mapper(PendingBinContents, self.tbl_pending_bin_contents,
2817                properties = dict(contents_id =self.tbl_pending_bin_contents.c.id,
2818                                  filename = self.tbl_pending_bin_contents.c.filename,
2819                                  package = self.tbl_pending_bin_contents.c.package,
2820                                  version = self.tbl_pending_bin_contents.c.version,
2821                                  arch = self.tbl_pending_bin_contents.c.arch,
2822                                  otype = self.tbl_pending_bin_contents.c.type))
2823
2824         mapper(DebContents, self.tbl_deb_contents,
2825                properties = dict(binary_id=self.tbl_deb_contents.c.binary_id,
2826                                  package=self.tbl_deb_contents.c.package,
2827                                  component=self.tbl_deb_contents.c.component,
2828                                  arch=self.tbl_deb_contents.c.arch,
2829                                  section=self.tbl_deb_contents.c.section,
2830                                  filename=self.tbl_deb_contents.c.filename))
2831
2832         mapper(UdebContents, self.tbl_udeb_contents,
2833                properties = dict(binary_id=self.tbl_udeb_contents.c.binary_id,
2834                                  package=self.tbl_udeb_contents.c.package,
2835                                  component=self.tbl_udeb_contents.c.component,
2836                                  arch=self.tbl_udeb_contents.c.arch,
2837                                  section=self.tbl_udeb_contents.c.section,
2838                                  filename=self.tbl_udeb_contents.c.filename))
2839
2840         mapper(DBBinary, self.tbl_binaries,
2841                properties = dict(binary_id = self.tbl_binaries.c.id,
2842                                  package = self.tbl_binaries.c.package,
2843                                  version = self.tbl_binaries.c.version,
2844                                  maintainer_id = self.tbl_binaries.c.maintainer,
2845                                  maintainer = relation(Maintainer),
2846                                  source_id = self.tbl_binaries.c.source,
2847                                  source = relation(DBSource),
2848                                  arch_id = self.tbl_binaries.c.architecture,
2849                                  architecture = relation(Architecture),
2850                                  poolfile_id = self.tbl_binaries.c.file,
2851                                  poolfile = relation(PoolFile),
2852                                  binarytype = self.tbl_binaries.c.type,
2853                                  fingerprint_id = self.tbl_binaries.c.sig_fpr,
2854                                  fingerprint = relation(Fingerprint),
2855                                  install_date = self.tbl_binaries.c.install_date,
2856                                  binassociations = relation(BinAssociation,
2857                                                             primaryjoin=(self.tbl_binaries.c.id==self.tbl_bin_associations.c.bin))))
2858
2859         mapper(BinaryACL, self.tbl_binary_acl,
2860                properties = dict(binary_acl_id = self.tbl_binary_acl.c.id))
2861
2862         mapper(BinaryACLMap, self.tbl_binary_acl_map,
2863                properties = dict(binary_acl_map_id = self.tbl_binary_acl_map.c.id,
2864                                  fingerprint = relation(Fingerprint, backref="binary_acl_map"),
2865                                  architecture = relation(Architecture)))
2866
2867         mapper(Component, self.tbl_component,
2868                properties = dict(component_id = self.tbl_component.c.id,
2869                                  component_name = self.tbl_component.c.name))
2870
2871         mapper(DBConfig, self.tbl_config,
2872                properties = dict(config_id = self.tbl_config.c.id))
2873
2874         mapper(DSCFile, self.tbl_dsc_files,
2875                properties = dict(dscfile_id = self.tbl_dsc_files.c.id,
2876                                  source_id = self.tbl_dsc_files.c.source,
2877                                  source = relation(DBSource),
2878                                  poolfile_id = self.tbl_dsc_files.c.file,
2879                                  poolfile = relation(PoolFile)))
2880
2881         mapper(PoolFile, self.tbl_files,
2882                properties = dict(file_id = self.tbl_files.c.id,
2883                                  filesize = self.tbl_files.c.size,
2884                                  location_id = self.tbl_files.c.location,
2885                                  location = relation(Location)))
2886
2887         mapper(Fingerprint, self.tbl_fingerprint,
2888                properties = dict(fingerprint_id = self.tbl_fingerprint.c.id,
2889                                  uid_id = self.tbl_fingerprint.c.uid,
2890                                  uid = relation(Uid),
2891                                  keyring_id = self.tbl_fingerprint.c.keyring,
2892                                  keyring = relation(Keyring),
2893                                  source_acl = relation(SourceACL),
2894                                  binary_acl = relation(BinaryACL)))
2895
2896         mapper(Keyring, self.tbl_keyrings,
2897                properties = dict(keyring_name = self.tbl_keyrings.c.name,
2898                                  keyring_id = self.tbl_keyrings.c.id))
2899
2900         mapper(DBChange, self.tbl_changes,
2901                properties = dict(change_id = self.tbl_changes.c.id,
2902                                  poolfiles = relation(PoolFile,
2903                                                       secondary=self.tbl_changes_pool_files,
2904                                                       backref="changeslinks"),
2905                                  seen = self.tbl_changes.c.seen,
2906                                  source = self.tbl_changes.c.source,
2907                                  binaries = self.tbl_changes.c.binaries,
2908                                  architecture = self.tbl_changes.c.architecture,
2909                                  distribution = self.tbl_changes.c.distribution,
2910                                  urgency = self.tbl_changes.c.urgency,
2911                                  maintainer = self.tbl_changes.c.maintainer,
2912                                  changedby = self.tbl_changes.c.changedby,
2913                                  date = self.tbl_changes.c.date,
2914                                  version = self.tbl_changes.c.version,
2915                                  files = relation(ChangePendingFile,
2916                                                   secondary=self.tbl_changes_pending_files_map,
2917                                                   backref="changesfile"),
2918                                  in_queue_id = self.tbl_changes.c.in_queue,
2919                                  in_queue = relation(PolicyQueue,
2920                                                      primaryjoin=(self.tbl_changes.c.in_queue==self.tbl_policy_queue.c.id)),
2921                                  approved_for_id = self.tbl_changes.c.approved_for))
2922
2923         mapper(ChangePendingBinary, self.tbl_changes_pending_binaries,
2924                properties = dict(change_pending_binary_id = self.tbl_changes_pending_binaries.c.id))
2925
2926         mapper(ChangePendingFile, self.tbl_changes_pending_files,
2927                properties = dict(change_pending_file_id = self.tbl_changes_pending_files.c.id,
2928                                  filename = self.tbl_changes_pending_files.c.filename,
2929                                  size = self.tbl_changes_pending_files.c.size,
2930                                  md5sum = self.tbl_changes_pending_files.c.md5sum,
2931                                  sha1sum = self.tbl_changes_pending_files.c.sha1sum,
2932                                  sha256sum = self.tbl_changes_pending_files.c.sha256sum))
2933
2934         mapper(ChangePendingSource, self.tbl_changes_pending_source,
2935                properties = dict(change_pending_source_id = self.tbl_changes_pending_source.c.id,
2936                                  change = relation(DBChange),
2937                                  maintainer = relation(Maintainer,
2938                                                        primaryjoin=(self.tbl_changes_pending_source.c.maintainer_id==self.tbl_maintainer.c.id)),
2939                                  changedby = relation(Maintainer,
2940                                                       primaryjoin=(self.tbl_changes_pending_source.c.changedby_id==self.tbl_maintainer.c.id)),
2941                                  fingerprint = relation(Fingerprint),
2942                                  source_files = relation(ChangePendingFile,
2943                                                          secondary=self.tbl_changes_pending_source_files,
2944                                                          backref="pending_sources"),
2945                                  files = relation(KnownChangePendingFile, backref="changesfile")))
2946
2947         mapper(KnownChangePendingFile, self.tbl_changes_pending_files,
2948                properties = dict(known_change_pending_file_id = self.tbl_changes_pending_files.c.id))
2949
2950         mapper(KeyringACLMap, self.tbl_keyring_acl_map,
2951                properties = dict(keyring_acl_map_id = self.tbl_keyring_acl_map.c.id,
2952                                  keyring = relation(Keyring, backref="keyring_acl_map"),
2953                                  architecture = relation(Architecture)))
2954
2955         mapper(Location, self.tbl_location,
2956                properties = dict(location_id = self.tbl_location.c.id,
2957                                  component_id = self.tbl_location.c.component,
2958                                  component = relation(Component),
2959                                  archive_id = self.tbl_location.c.archive,
2960                                  archive = relation(Archive),
2961                                  archive_type = self.tbl_location.c.type))
2962
2963         mapper(Maintainer, self.tbl_maintainer,
2964                properties = dict(maintainer_id = self.tbl_maintainer.c.id))
2965
2966         mapper(NewComment, self.tbl_new_comments,
2967                properties = dict(comment_id = self.tbl_new_comments.c.id))
2968
2969         mapper(Override, self.tbl_override,
2970                properties = dict(suite_id = self.tbl_override.c.suite,
2971                                  suite = relation(Suite),
2972                                  package = self.tbl_override.c.package,
2973                                  component_id = self.tbl_override.c.component,
2974                                  component = relation(Component),
2975                                  priority_id = self.tbl_override.c.priority,
2976                                  priority = relation(Priority),
2977                                  section_id = self.tbl_override.c.section,
2978                                  section = relation(Section),
2979                                  overridetype_id = self.tbl_override.c.type,
2980                                  overridetype = relation(OverrideType)))
2981
2982         mapper(OverrideType, self.tbl_override_type,
2983                properties = dict(overridetype = self.tbl_override_type.c.type,
2984                                  overridetype_id = self.tbl_override_type.c.id))
2985
2986         mapper(PolicyQueue, self.tbl_policy_queue,
2987                properties = dict(policy_queue_id = self.tbl_policy_queue.c.id))
2988
2989         mapper(Priority, self.tbl_priority,
2990                properties = dict(priority_id = self.tbl_priority.c.id))
2991
2992         mapper(Section, self.tbl_section,
2993                properties = dict(section_id = self.tbl_section.c.id,
2994                                  section=self.tbl_section.c.section))
2995
2996         mapper(DBSource, self.tbl_source,
2997                properties = dict(source_id = self.tbl_source.c.id,
2998                                  version = self.tbl_source.c.version,
2999                                  maintainer_id = self.tbl_source.c.maintainer,
3000                                  maintainer = relation(Maintainer,
3001                                                        primaryjoin=(self.tbl_source.c.maintainer==self.tbl_maintainer.c.id)),
3002                                  poolfile_id = self.tbl_source.c.file,
3003                                  poolfile = relation(PoolFile),
3004                                  fingerprint_id = self.tbl_source.c.sig_fpr,
3005                                  fingerprint = relation(Fingerprint),
3006                                  changedby_id = self.tbl_source.c.changedby,
3007                                  changedby = relation(Maintainer,
3008                                                       primaryjoin=(self.tbl_source.c.changedby==self.tbl_maintainer.c.id)),
3009                                  srcfiles = relation(DSCFile,
3010                                                      primaryjoin=(self.tbl_source.c.id==self.tbl_dsc_files.c.source)),
3011                                  srcassociations = relation(SrcAssociation,
3012                                                             primaryjoin=(self.tbl_source.c.id==self.tbl_src_associations.c.source)),
3013                                  srcuploaders = relation(SrcUploader)))
3014
3015         mapper(SourceACL, self.tbl_source_acl,
3016                properties = dict(source_acl_id = self.tbl_source_acl.c.id))
3017
3018         mapper(SrcAssociation, self.tbl_src_associations,
3019                properties = dict(sa_id = self.tbl_src_associations.c.id,
3020                                  suite_id = self.tbl_src_associations.c.suite,
3021                                  suite = relation(Suite),
3022                                  source_id = self.tbl_src_associations.c.source,
3023                                  source = relation(DBSource)))
3024
3025         mapper(SrcFormat, self.tbl_src_format,
3026                properties = dict(src_format_id = self.tbl_src_format.c.id,
3027                                  format_name = self.tbl_src_format.c.format_name))
3028
3029         mapper(SrcUploader, self.tbl_src_uploaders,
3030                properties = dict(uploader_id = self.tbl_src_uploaders.c.id,
3031                                  source_id = self.tbl_src_uploaders.c.source,
3032                                  source = relation(DBSource,
3033                                                    primaryjoin=(self.tbl_src_uploaders.c.source==self.tbl_source.c.id)),
3034                                  maintainer_id = self.tbl_src_uploaders.c.maintainer,
3035                                  maintainer = relation(Maintainer,
3036                                                        primaryjoin=(self.tbl_src_uploaders.c.maintainer==self.tbl_maintainer.c.id))))
3037
3038         mapper(Suite, self.tbl_suite,
3039                properties = dict(suite_id = self.tbl_suite.c.id,
3040                                  policy_queue = relation(PolicyQueue),
3041                                  copy_queues = relation(BuildQueue, secondary=self.tbl_suite_build_queue_copy)))
3042
3043         mapper(SuiteArchitecture, self.tbl_suite_architectures,
3044                properties = dict(suite_id = self.tbl_suite_architectures.c.suite,
3045                                  suite = relation(Suite, backref='suitearchitectures'),
3046                                  arch_id = self.tbl_suite_architectures.c.architecture,
3047                                  architecture = relation(Architecture)))
3048
3049         mapper(SuiteSrcFormat, self.tbl_suite_src_formats,
3050                properties = dict(suite_id = self.tbl_suite_src_formats.c.suite,
3051                                  suite = relation(Suite, backref='suitesrcformats'),
3052                                  src_format_id = self.tbl_suite_src_formats.c.src_format,
3053                                  src_format = relation(SrcFormat)))
3054
3055         mapper(Uid, self.tbl_uid,
3056                properties = dict(uid_id = self.tbl_uid.c.id,
3057                                  fingerprint = relation(Fingerprint)))
3058
3059         mapper(UploadBlock, self.tbl_upload_blocks,
3060                properties = dict(upload_block_id = self.tbl_upload_blocks.c.id,
3061                                  fingerprint = relation(Fingerprint, backref="uploadblocks"),
3062                                  uid = relation(Uid, backref="uploadblocks")))
3063
3064     ## Connection functions
3065     def __createconn(self):
3066         from config import Config
3067         cnf = Config()
3068         if cnf["DB::Host"]:
3069             # TCP/IP
3070             connstr = "postgres://%s" % cnf["DB::Host"]
3071             if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
3072                 connstr += ":%s" % cnf["DB::Port"]
3073             connstr += "/%s" % cnf["DB::Name"]
3074         else:
3075             # Unix Socket
3076             connstr = "postgres:///%s" % cnf["DB::Name"]
3077             if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
3078                 connstr += "?port=%s" % cnf["DB::Port"]
3079
3080         self.db_pg   = create_engine(connstr, echo=self.debug)
3081         self.db_meta = MetaData()
3082         self.db_meta.bind = self.db_pg
3083         self.db_smaker = sessionmaker(bind=self.db_pg,
3084                                       autoflush=True,
3085                                       autocommit=False)
3086
3087         self.__setuptables()
3088         self.__setupmappers()
3089
3090     def session(self):
3091         return self.db_smaker()
3092
3093 __all__.append('DBConn')
3094
3095