]> git.decadent.org.uk Git - dak.git/blob - daklib/dbconn.py
tidy up and add more methods to SQLA objects
[dak.git] / daklib / dbconn.py
1 #!/usr/bin/python
2
3 """ DB access class
4
5 @contact: Debian FTPMaster <ftpmaster@debian.org>
6 @copyright: 2000, 2001, 2002, 2003, 2004, 2006  James Troup <james@nocrew.org>
7 @copyright: 2008-2009  Mark Hymers <mhy@debian.org>
8 @copyright: 2009  Joerg Jaspert <joerg@debian.org>
9 @copyright: 2009  Mike O'Connor <stew@debian.org>
10 @license: GNU General Public License version 2 or later
11 """
12
13 # This program is free software; you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation; either version 2 of the License, or
16 # (at your option) any later version.
17
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21 # GNU General Public License for more details.
22
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
26
27 ################################################################################
28
29 # < mhy> I need a funny comment
30 # < sgran> two peanuts were walking down a dark street
31 # < sgran> one was a-salted
32 #  * mhy looks up the definition of "funny"
33
34 ################################################################################
35
36 import os
37 import psycopg2
38 import traceback
39
40 from inspect import getargspec
41
42 from sqlalchemy import create_engine, Table, MetaData
43 from sqlalchemy.orm import sessionmaker, mapper, relation
44
45 # Don't remove this, we re-export the exceptions to scripts which import us
46 from sqlalchemy.exc import *
47 from sqlalchemy.orm.exc import NoResultFound
48
49 # Only import Config until Queue stuff is changed to store its config
50 # in the database
51 from config import Config
52 from singleton import Singleton
53 from textutils import fix_maintainer
54
55 ################################################################################
56
57 __all__ = ['IntegrityError', 'SQLAlchemyError']
58
59 ################################################################################
60
61 def session_wrapper(fn):
62     """
63     Wrapper around common ".., session=None):" handling. If the wrapped
64     function is called without passing 'session', we create a local one
65     and destroy it when the function ends.
66
67     Also attaches a commit_or_flush method to the session; if we created a
68     local session, this is a synonym for session.commit(), otherwise it is a
69     synonym for session.flush().
70     """
71
72     def wrapped(*args, **kwargs):
73         private_transaction = False
74
75         # Find the session object
76         session = kwargs.get('session')
77
78         if session is None:
79             if len(args) <= len(getargspec(fn)[0]) - 1:
80                 # No session specified as last argument or in kwargs
81                 private_transaction = True
82                 session = kwargs['session'] = DBConn().session()
83             else:
84                 # Session is last argument in args
85                 session = args[-1]
86                 if session is None:
87                     args = list(args)
88                     session = args[-1] = DBConn().session()
89                     private_transaction = True
90
91         if private_transaction:
92             session.commit_or_flush = session.commit
93         else:
94             session.commit_or_flush = session.flush
95
96         try:
97             return fn(*args, **kwargs)
98         finally:
99             if private_transaction:
100                 # We created a session; close it.
101                 session.close()
102
103     wrapped.__doc__ = fn.__doc__
104     wrapped.func_name = fn.func_name
105
106     return wrapped
107
108 ################################################################################
109
110 class Architecture(object):
111     def __init__(self, *args, **kwargs):
112         pass
113
114     def __eq__(self, val):
115         if isinstance(val, str):
116             return (self.arch_string== val)
117         # This signals to use the normal comparison operator
118         return NotImplemented
119
120     def __ne__(self, val):
121         if isinstance(val, str):
122             return (self.arch_string != val)
123         # This signals to use the normal comparison operator
124         return NotImplemented
125
126     def __repr__(self):
127         return '<Architecture %s>' % self.arch_string
128
129 __all__.append('Architecture')
130
131 @session_wrapper
132 def get_architecture(architecture, session=None):
133     """
134     Returns database id for given C{architecture}.
135
136     @type architecture: string
137     @param architecture: The name of the architecture
138
139     @type session: Session
140     @param session: Optional SQLA session object (a temporary one will be
141     generated if not supplied)
142
143     @rtype: Architecture
144     @return: Architecture object for the given arch (None if not present)
145     """
146
147     q = session.query(Architecture).filter_by(arch_string=architecture)
148
149     try:
150         return q.one()
151     except NoResultFound:
152         return None
153
154 __all__.append('get_architecture')
155
156 @session_wrapper
157 def get_architecture_suites(architecture, session=None):
158     """
159     Returns list of Suite objects for given C{architecture} name
160
161     @type source: str
162     @param source: Architecture name to search for
163
164     @type session: Session
165     @param session: Optional SQL session object (a temporary one will be
166     generated if not supplied)
167
168     @rtype: list
169     @return: list of Suite objects for the given name (may be empty)
170     """
171
172     q = session.query(Suite)
173     q = q.join(SuiteArchitecture)
174     q = q.join(Architecture).filter_by(arch_string=architecture).order_by('suite_name')
175
176     ret = q.all()
177
178     return ret
179
180 __all__.append('get_architecture_suites')
181
182 ################################################################################
183
184 class Archive(object):
185     def __init__(self, *args, **kwargs):
186         pass
187
188     def __repr__(self):
189         return '<Archive %s>' % self.archive_name
190
191 __all__.append('Archive')
192
193 @session_wrapper
194 def get_archive(archive, session=None):
195     """
196     returns database id for given C{archive}.
197
198     @type archive: string
199     @param archive: the name of the arhive
200
201     @type session: Session
202     @param session: Optional SQLA session object (a temporary one will be
203     generated if not supplied)
204
205     @rtype: Archive
206     @return: Archive object for the given name (None if not present)
207
208     """
209     archive = archive.lower()
210
211     q = session.query(Archive).filter_by(archive_name=archive)
212
213     try:
214         return q.one()
215     except NoResultFound:
216         return None
217
218 __all__.append('get_archive')
219
220 ################################################################################
221
222 class BinAssociation(object):
223     def __init__(self, *args, **kwargs):
224         pass
225
226     def __repr__(self):
227         return '<BinAssociation %s (%s, %s)>' % (self.ba_id, self.binary, self.suite)
228
229 __all__.append('BinAssociation')
230
231 ################################################################################
232
233 class DBBinary(object):
234     def __init__(self, *args, **kwargs):
235         pass
236
237     def __repr__(self):
238         return '<DBBinary %s (%s, %s)>' % (self.package, self.version, self.architecture)
239
240 __all__.append('DBBinary')
241
242 @session_wrapper
243 def get_suites_binary_in(package, session=None):
244     """
245     Returns list of Suite objects which given C{package} name is in
246
247     @type source: str
248     @param source: DBBinary package name to search for
249
250     @rtype: list
251     @return: list of Suite objects for the given package
252     """
253
254     return session.query(Suite).join(BinAssociation).join(DBBinary).filter_by(package=package).all()
255
256 __all__.append('get_suites_binary_in')
257
258 @session_wrapper
259 def get_binary_from_id(id, session=None):
260     """
261     Returns DBBinary object for given C{id}
262
263     @type id: int
264     @param id: Id of the required binary
265
266     @type session: Session
267     @param session: Optional SQLA session object (a temporary one will be
268     generated if not supplied)
269
270     @rtype: DBBinary
271     @return: DBBinary object for the given binary (None if not present)
272     """
273
274     q = session.query(DBBinary).filter_by(binary_id=id)
275
276     try:
277         return q.one()
278     except NoResultFound:
279         return None
280
281 __all__.append('get_binary_from_id')
282
283 @session_wrapper
284 def get_binaries_from_name(package, version=None, architecture=None, session=None):
285     """
286     Returns list of DBBinary objects for given C{package} name
287
288     @type package: str
289     @param package: DBBinary package name to search for
290
291     @type version: str or None
292     @param version: Version to search for (or None)
293
294     @type package: str, list or None
295     @param package: Architectures to limit to (or None if no limit)
296
297     @type session: Session
298     @param session: Optional SQL session object (a temporary one will be
299     generated if not supplied)
300
301     @rtype: list
302     @return: list of DBBinary objects for the given name (may be empty)
303     """
304
305     q = session.query(DBBinary).filter_by(package=package)
306
307     if version is not None:
308         q = q.filter_by(version=version)
309
310     if architecture is not None:
311         if not isinstance(architecture, list):
312             architecture = [architecture]
313         q = q.join(Architecture).filter(Architecture.arch_string.in_(architecture))
314
315     ret = q.all()
316
317     return ret
318
319 __all__.append('get_binaries_from_name')
320
321 @session_wrapper
322 def get_binaries_from_source_id(source_id, session=None):
323     """
324     Returns list of DBBinary objects for given C{source_id}
325
326     @type source_id: int
327     @param source_id: source_id to search for
328
329     @type session: Session
330     @param session: Optional SQL session object (a temporary one will be
331     generated if not supplied)
332
333     @rtype: list
334     @return: list of DBBinary objects for the given name (may be empty)
335     """
336
337     return session.query(DBBinary).filter_by(source_id=source_id).all()
338
339 __all__.append('get_binaries_from_source_id')
340
341 @session_wrapper
342 def get_binary_from_name_suite(package, suitename, session=None):
343     ### For dak examine-package
344     ### XXX: Doesn't use object API yet
345
346     sql = """SELECT DISTINCT(b.package), b.version, c.name, su.suite_name
347              FROM binaries b, files fi, location l, component c, bin_associations ba, suite su
348              WHERE b.package=:package
349                AND b.file = fi.id
350                AND fi.location = l.id
351                AND l.component = c.id
352                AND ba.bin=b.id
353                AND ba.suite = su.id
354                AND su.suite_name=:suitename
355           ORDER BY b.version DESC"""
356
357     return session.execute(sql, {'package': package, 'suitename': suitename})
358
359 __all__.append('get_binary_from_name_suite')
360
361 @session_wrapper
362 def get_binary_components(package, suitename, arch, session=None):
363     # Check for packages that have moved from one component to another
364     query = """SELECT c.name FROM binaries b, bin_associations ba, suite s, location l, component c, architecture a, files f
365     WHERE b.package=:package AND s.suite_name=:suitename
366       AND (a.arch_string = :arch OR a.arch_string = 'all')
367       AND ba.bin = b.id AND ba.suite = s.id AND b.architecture = a.id
368       AND f.location = l.id
369       AND l.component = c.id
370       AND b.file = f.id"""
371
372     vals = {'package': package, 'suitename': suitename, 'arch': arch}
373
374     return session.execute(query, vals)
375
376 __all__.append('get_binary_components')
377
378 ################################################################################
379
380 class BinaryACL(object):
381     def __init__(self, *args, **kwargs):
382         pass
383
384     def __repr__(self):
385         return '<BinaryACL %s>' % self.binary_acl_id
386
387 __all__.append('BinaryACL')
388
389 ################################################################################
390
391 class BinaryACLMap(object):
392     def __init__(self, *args, **kwargs):
393         pass
394
395     def __repr__(self):
396         return '<BinaryACLMap %s>' % self.binary_acl_map_id
397
398 __all__.append('BinaryACLMap')
399
400 ################################################################################
401
402 class Component(object):
403     def __init__(self, *args, **kwargs):
404         pass
405
406     def __eq__(self, val):
407         if isinstance(val, str):
408             return (self.component_name == val)
409         # This signals to use the normal comparison operator
410         return NotImplemented
411
412     def __ne__(self, val):
413         if isinstance(val, str):
414             return (self.component_name != val)
415         # This signals to use the normal comparison operator
416         return NotImplemented
417
418     def __repr__(self):
419         return '<Component %s>' % self.component_name
420
421
422 __all__.append('Component')
423
424 @session_wrapper
425 def get_component(component, session=None):
426     """
427     Returns database id for given C{component}.
428
429     @type component: string
430     @param component: The name of the override type
431
432     @rtype: int
433     @return: the database id for the given component
434
435     """
436     component = component.lower()
437
438     q = session.query(Component).filter_by(component_name=component)
439
440     try:
441         return q.one()
442     except NoResultFound:
443         return None
444
445 __all__.append('get_component')
446
447 ################################################################################
448
449 class DBConfig(object):
450     def __init__(self, *args, **kwargs):
451         pass
452
453     def __repr__(self):
454         return '<DBConfig %s>' % self.name
455
456 __all__.append('DBConfig')
457
458 ################################################################################
459
460 class ContentFilename(object):
461     def __init__(self, *args, **kwargs):
462         pass
463
464     def __repr__(self):
465         return '<ContentFilename %s>' % self.filename
466
467 __all__.append('ContentFilename')
468
469 @session_wrapper
470 def get_or_set_contents_file_id(filename, session=None):
471     """
472     Returns database id for given filename.
473
474     If no matching file is found, a row is inserted.
475
476     @type filename: string
477     @param filename: The filename
478     @type session: SQLAlchemy
479     @param session: Optional SQL session object (a temporary one will be
480     generated if not supplied).  If not passed, a commit will be performed at
481     the end of the function, otherwise the caller is responsible for commiting.
482
483     @rtype: int
484     @return: the database id for the given component
485     """
486
487     q = session.query(ContentFilename).filter_by(filename=filename)
488
489     try:
490         ret = q.one().cafilename_id
491     except NoResultFound:
492         cf = ContentFilename()
493         cf.filename = filename
494         session.add(cf)
495         session.commit_or_flush()
496         ret = cf.cafilename_id
497
498     return ret
499
500 __all__.append('get_or_set_contents_file_id')
501
502 @session_wrapper
503 def get_contents(suite, overridetype, section=None, session=None):
504     """
505     Returns contents for a suite / overridetype combination, limiting
506     to a section if not None.
507
508     @type suite: Suite
509     @param suite: Suite object
510
511     @type overridetype: OverrideType
512     @param overridetype: OverrideType object
513
514     @type section: Section
515     @param section: Optional section object to limit results to
516
517     @type session: SQLAlchemy
518     @param session: Optional SQL session object (a temporary one will be
519     generated if not supplied)
520
521     @rtype: ResultsProxy
522     @return: ResultsProxy object set up to return tuples of (filename, section,
523     package, arch_id)
524     """
525
526     # find me all of the contents for a given suite
527     contents_q = """SELECT (p.path||'/'||n.file) AS fn,
528                             s.section,
529                             b.package,
530                             b.architecture
531                    FROM content_associations c join content_file_paths p ON (c.filepath=p.id)
532                    JOIN content_file_names n ON (c.filename=n.id)
533                    JOIN binaries b ON (b.id=c.binary_pkg)
534                    JOIN override o ON (o.package=b.package)
535                    JOIN section s ON (s.id=o.section)
536                    WHERE o.suite = :suiteid AND o.type = :overridetypeid
537                    AND b.type=:overridetypename"""
538
539     vals = {'suiteid': suite.suite_id,
540             'overridetypeid': overridetype.overridetype_id,
541             'overridetypename': overridetype.overridetype}
542
543     if section is not None:
544         contents_q += " AND s.id = :sectionid"
545         vals['sectionid'] = section.section_id
546
547     contents_q += " ORDER BY fn"
548
549     return session.execute(contents_q, vals)
550
551 __all__.append('get_contents')
552
553 ################################################################################
554
555 class ContentFilepath(object):
556     def __init__(self, *args, **kwargs):
557         pass
558
559     def __repr__(self):
560         return '<ContentFilepath %s>' % self.filepath
561
562 __all__.append('ContentFilepath')
563
564 @session_wrapper
565 def get_or_set_contents_path_id(filepath, session=None):
566     """
567     Returns database id for given path.
568
569     If no matching file is found, a row is inserted.
570
571     @type filename: string
572     @param filename: The filepath
573     @type session: SQLAlchemy
574     @param session: Optional SQL session object (a temporary one will be
575     generated if not supplied).  If not passed, a commit will be performed at
576     the end of the function, otherwise the caller is responsible for commiting.
577
578     @rtype: int
579     @return: the database id for the given path
580     """
581
582     q = session.query(ContentFilepath).filter_by(filepath=filepath)
583
584     try:
585         ret = q.one().cafilepath_id
586     except NoResultFound:
587         cf = ContentFilepath()
588         cf.filepath = filepath
589         session.add(cf)
590         session.commit_or_flush()
591         ret = cf.cafilepath_id
592
593     return ret
594
595 __all__.append('get_or_set_contents_path_id')
596
597 ################################################################################
598
599 class ContentAssociation(object):
600     def __init__(self, *args, **kwargs):
601         pass
602
603     def __repr__(self):
604         return '<ContentAssociation %s>' % self.ca_id
605
606 __all__.append('ContentAssociation')
607
608 def insert_content_paths(binary_id, fullpaths, session=None):
609     """
610     Make sure given path is associated with given binary id
611
612     @type binary_id: int
613     @param binary_id: the id of the binary
614     @type fullpaths: list
615     @param fullpaths: the list of paths of the file being associated with the binary
616     @type session: SQLAlchemy session
617     @param session: Optional SQLAlchemy session.  If this is passed, the caller
618     is responsible for ensuring a transaction has begun and committing the
619     results or rolling back based on the result code.  If not passed, a commit
620     will be performed at the end of the function, otherwise the caller is
621     responsible for commiting.
622
623     @return: True upon success
624     """
625
626     privatetrans = False
627     if session is None:
628         session = DBConn().session()
629         privatetrans = True
630
631     try:
632         # Insert paths
633         pathcache = {}
634         for fullpath in fullpaths:
635             # Get the necessary IDs ...
636             (path, file) = os.path.split(fullpath)
637
638             filepath_id = get_or_set_contents_path_id(path, session)
639             filename_id = get_or_set_contents_file_id(file, session)
640
641             pathcache[fullpath] = (filepath_id, filename_id)
642
643         for fullpath, dat in pathcache.items():
644             ca = ContentAssociation()
645             ca.binary_id = binary_id
646             ca.filepath_id = dat[0]
647             ca.filename_id = dat[1]
648             session.add(ca)
649
650         # Only commit if we set up the session ourself
651         if privatetrans:
652             session.commit()
653             session.close()
654         else:
655             session.flush()
656
657         return True
658
659     except:
660         traceback.print_exc()
661
662         # Only rollback if we set up the session ourself
663         if privatetrans:
664             session.rollback()
665             session.close()
666
667         return False
668
669 __all__.append('insert_content_paths')
670
671 ################################################################################
672
673 class DSCFile(object):
674     def __init__(self, *args, **kwargs):
675         pass
676
677     def __repr__(self):
678         return '<DSCFile %s>' % self.dscfile_id
679
680 __all__.append('DSCFile')
681
682 @session_wrapper
683 def get_dscfiles(dscfile_id=None, source_id=None, poolfile_id=None, session=None):
684     """
685     Returns a list of DSCFiles which may be empty
686
687     @type dscfile_id: int (optional)
688     @param dscfile_id: the dscfile_id of the DSCFiles to find
689
690     @type source_id: int (optional)
691     @param source_id: the source id related to the DSCFiles to find
692
693     @type poolfile_id: int (optional)
694     @param poolfile_id: the poolfile id related to the DSCFiles to find
695
696     @rtype: list
697     @return: Possibly empty list of DSCFiles
698     """
699
700     q = session.query(DSCFile)
701
702     if dscfile_id is not None:
703         q = q.filter_by(dscfile_id=dscfile_id)
704
705     if source_id is not None:
706         q = q.filter_by(source_id=source_id)
707
708     if poolfile_id is not None:
709         q = q.filter_by(poolfile_id=poolfile_id)
710
711     return q.all()
712
713 __all__.append('get_dscfiles')
714
715 ################################################################################
716
717 class PoolFile(object):
718     def __init__(self, *args, **kwargs):
719         pass
720
721     def __repr__(self):
722         return '<PoolFile %s>' % self.filename
723
724 __all__.append('PoolFile')
725
726 @session_wrapper
727 def check_poolfile(filename, filesize, md5sum, location_id, session=None):
728     """
729     Returns a tuple:
730      (ValidFileFound [boolean or None], PoolFile object or None)
731
732     @type filename: string
733     @param filename: the filename of the file to check against the DB
734
735     @type filesize: int
736     @param filesize: the size of the file to check against the DB
737
738     @type md5sum: string
739     @param md5sum: the md5sum of the file to check against the DB
740
741     @type location_id: int
742     @param location_id: the id of the location to look in
743
744     @rtype: tuple
745     @return: Tuple of length 2.
746              If more than one file found with that name:
747                     (None,  None)
748              If valid pool file found: (True, PoolFile object)
749              If valid pool file not found:
750                     (False, None) if no file found
751                     (False, PoolFile object) if file found with size/md5sum mismatch
752     """
753
754     q = session.query(PoolFile).filter_by(filename=filename)
755     q = q.join(Location).filter_by(location_id=location_id)
756
757     ret = None
758
759     if q.count() > 1:
760         ret = (None, None)
761     elif q.count() < 1:
762         ret = (False, None)
763     else:
764         obj = q.one()
765         if obj.md5sum != md5sum or obj.filesize != filesize:
766             ret = (False, obj)
767
768     if ret is None:
769         ret = (True, obj)
770
771     return ret
772
773 __all__.append('check_poolfile')
774
775 @session_wrapper
776 def get_poolfile_by_id(file_id, session=None):
777     """
778     Returns a PoolFile objects or None for the given id
779
780     @type file_id: int
781     @param file_id: the id of the file to look for
782
783     @rtype: PoolFile or None
784     @return: either the PoolFile object or None
785     """
786
787     q = session.query(PoolFile).filter_by(file_id=file_id)
788
789     try:
790         return q.one()
791     except NoResultFound:
792         return None
793
794 __all__.append('get_poolfile_by_id')
795
796
797 @session_wrapper
798 def get_poolfile_by_name(filename, location_id=None, session=None):
799     """
800     Returns an array of PoolFile objects for the given filename and
801     (optionally) location_id
802
803     @type filename: string
804     @param filename: the filename of the file to check against the DB
805
806     @type location_id: int
807     @param location_id: the id of the location to look in (optional)
808
809     @rtype: array
810     @return: array of PoolFile objects
811     """
812
813     q = session.query(PoolFile).filter_by(filename=filename)
814
815     if location_id is not None:
816         q = q.join(Location).filter_by(location_id=location_id)
817
818     return q.all()
819
820 __all__.append('get_poolfile_by_name')
821
822 @session_wrapper
823 def get_poolfile_like_name(filename, session=None):
824     """
825     Returns an array of PoolFile objects which are like the given name
826
827     @type filename: string
828     @param filename: the filename of the file to check against the DB
829
830     @rtype: array
831     @return: array of PoolFile objects
832     """
833
834     # TODO: There must be a way of properly using bind parameters with %FOO%
835     q = session.query(PoolFile).filter(PoolFile.filename.like('%%%s%%' % filename))
836
837     return q.all()
838
839 __all__.append('get_poolfile_like_name')
840
841 ################################################################################
842
843 class Fingerprint(object):
844     def __init__(self, *args, **kwargs):
845         pass
846
847     def __repr__(self):
848         return '<Fingerprint %s>' % self.fingerprint
849
850 __all__.append('Fingerprint')
851
852 @session_wrapper
853 def get_or_set_fingerprint(fpr, session=None):
854     """
855     Returns Fingerprint object for given fpr.
856
857     If no matching fpr is found, a row is inserted.
858
859     @type fpr: string
860     @param fpr: The fpr to find / add
861
862     @type session: SQLAlchemy
863     @param session: Optional SQL session object (a temporary one will be
864     generated if not supplied).  If not passed, a commit will be performed at
865     the end of the function, otherwise the caller is responsible for commiting.
866     A flush will be performed either way.
867
868     @rtype: Fingerprint
869     @return: the Fingerprint object for the given fpr
870     """
871
872     q = session.query(Fingerprint).filter_by(fingerprint=fpr)
873
874     try:
875         ret = q.one()
876     except NoResultFound:
877         fingerprint = Fingerprint()
878         fingerprint.fingerprint = fpr
879         session.add(fingerprint)
880         session.commit_or_flush()
881         ret = fingerprint
882
883     return ret
884
885 __all__.append('get_or_set_fingerprint')
886
887 ################################################################################
888
889 class Keyring(object):
890     def __init__(self, *args, **kwargs):
891         pass
892
893     def __repr__(self):
894         return '<Keyring %s>' % self.keyring_name
895
896 __all__.append('Keyring')
897
898 @session_wrapper
899 def get_or_set_keyring(keyring, session=None):
900     """
901     If C{keyring} does not have an entry in the C{keyrings} table yet, create one
902     and return the new Keyring
903     If C{keyring} already has an entry, simply return the existing Keyring
904
905     @type keyring: string
906     @param keyring: the keyring name
907
908     @rtype: Keyring
909     @return: the Keyring object for this keyring
910     """
911
912     q = session.query(Keyring).filter_by(keyring_name=keyring)
913
914     try:
915         return q.one()
916     except NoResultFound:
917         obj = Keyring(keyring_name=keyring)
918         session.add(obj)
919         session.commit_or_flush()
920         return obj
921
922 __all__.append('get_or_set_keyring')
923
924 ################################################################################
925
926 class Location(object):
927     def __init__(self, *args, **kwargs):
928         pass
929
930     def __repr__(self):
931         return '<Location %s (%s)>' % (self.path, self.location_id)
932
933 __all__.append('Location')
934
935 @session_wrapper
936 def get_location(location, component=None, archive=None, session=None):
937     """
938     Returns Location object for the given combination of location, component
939     and archive
940
941     @type location: string
942     @param location: the path of the location, e.g. I{/srv/ftp.debian.org/ftp/pool/}
943
944     @type component: string
945     @param component: the component name (if None, no restriction applied)
946
947     @type archive: string
948     @param archive_id: the archive name (if None, no restriction applied)
949
950     @rtype: Location / None
951     @return: Either a Location object or None if one can't be found
952     """
953
954     q = session.query(Location).filter_by(path=location)
955
956     if archive is not None:
957         q = q.join(Archive).filter_by(archive_name=archive)
958
959     if component is not None:
960         q = q.join(Component).filter_by(component_name=component)
961
962     try:
963         return q.one()
964     except NoResultFound:
965         return None
966
967 __all__.append('get_location')
968
969 ################################################################################
970
971 class Maintainer(object):
972     def __init__(self, *args, **kwargs):
973         pass
974
975     def __repr__(self):
976         return '''<Maintainer '%s' (%s)>''' % (self.name, self.maintainer_id)
977
978     def get_split_maintainer(self):
979         if not hasattr(self, 'name') or self.name is None:
980             return ('', '', '', '')
981
982         return fix_maintainer(self.name.strip())
983
984 __all__.append('Maintainer')
985
986 @session_wrapper
987 def get_or_set_maintainer(name, session=None):
988     """
989     Returns Maintainer object for given maintainer name.
990
991     If no matching maintainer name is found, a row is inserted.
992
993     @type name: string
994     @param name: The maintainer name to add
995
996     @type session: SQLAlchemy
997     @param session: Optional SQL session object (a temporary one will be
998     generated if not supplied).  If not passed, a commit will be performed at
999     the end of the function, otherwise the caller is responsible for commiting.
1000     A flush will be performed either way.
1001
1002     @rtype: Maintainer
1003     @return: the Maintainer object for the given maintainer
1004     """
1005
1006     q = session.query(Maintainer).filter_by(name=name)
1007     try:
1008         ret = q.one()
1009     except NoResultFound:
1010         maintainer = Maintainer()
1011         maintainer.name = name
1012         session.add(maintainer)
1013         session.commit_or_flush()
1014         ret = maintainer
1015
1016     return ret
1017
1018 __all__.append('get_or_set_maintainer')
1019
1020 @session_wrapper
1021 def get_maintainer(maintainer_id, session=None):
1022     """
1023     Return the name of the maintainer behind C{maintainer_id} or None if that
1024     maintainer_id is invalid.
1025
1026     @type maintainer_id: int
1027     @param maintainer_id: the id of the maintainer
1028
1029     @rtype: Maintainer
1030     @return: the Maintainer with this C{maintainer_id}
1031     """
1032
1033     return session.query(Maintainer).get(maintainer_id)
1034
1035 __all__.append('get_maintainer')
1036
1037 ################################################################################
1038
1039 class NewComment(object):
1040     def __init__(self, *args, **kwargs):
1041         pass
1042
1043     def __repr__(self):
1044         return '''<NewComment for '%s %s' (%s)>''' % (self.package, self.version, self.comment_id)
1045
1046 __all__.append('NewComment')
1047
1048 @session_wrapper
1049 def has_new_comment(package, version, session=None):
1050     """
1051     Returns true if the given combination of C{package}, C{version} has a comment.
1052
1053     @type package: string
1054     @param package: name of the package
1055
1056     @type version: string
1057     @param version: package version
1058
1059     @type session: Session
1060     @param session: Optional SQLA session object (a temporary one will be
1061     generated if not supplied)
1062
1063     @rtype: boolean
1064     @return: true/false
1065     """
1066
1067     q = session.query(NewComment)
1068     q = q.filter_by(package=package)
1069     q = q.filter_by(version=version)
1070
1071     return bool(q.count() > 0)
1072
1073 __all__.append('has_new_comment')
1074
1075 @session_wrapper
1076 def get_new_comments(package=None, version=None, comment_id=None, session=None):
1077     """
1078     Returns (possibly empty) list of NewComment objects for the given
1079     parameters
1080
1081     @type package: string (optional)
1082     @param package: name of the package
1083
1084     @type version: string (optional)
1085     @param version: package version
1086
1087     @type comment_id: int (optional)
1088     @param comment_id: An id of a comment
1089
1090     @type session: Session
1091     @param session: Optional SQLA session object (a temporary one will be
1092     generated if not supplied)
1093
1094     @rtype: list
1095     @return: A (possibly empty) list of NewComment objects will be returned
1096     """
1097
1098     q = session.query(NewComment)
1099     if package is not None: q = q.filter_by(package=package)
1100     if version is not None: q = q.filter_by(version=version)
1101     if comment_id is not None: q = q.filter_by(comment_id=comment_id)
1102
1103     return q.all()
1104
1105 __all__.append('get_new_comments')
1106
1107 ################################################################################
1108
1109 class Override(object):
1110     def __init__(self, *args, **kwargs):
1111         pass
1112
1113     def __repr__(self):
1114         return '<Override %s (%s)>' % (self.package, self.suite_id)
1115
1116 __all__.append('Override')
1117
1118 @session_wrapper
1119 def get_override(package, suite=None, component=None, overridetype=None, session=None):
1120     """
1121     Returns Override object for the given parameters
1122
1123     @type package: string
1124     @param package: The name of the package
1125
1126     @type suite: string, list or None
1127     @param suite: The name of the suite (or suites if a list) to limit to.  If
1128                   None, don't limit.  Defaults to None.
1129
1130     @type component: string, list or None
1131     @param component: The name of the component (or components if a list) to
1132                       limit to.  If None, don't limit.  Defaults to None.
1133
1134     @type overridetype: string, list or None
1135     @param overridetype: The name of the overridetype (or overridetypes if a list) to
1136                          limit to.  If None, don't limit.  Defaults to None.
1137
1138     @type session: Session
1139     @param session: Optional SQLA session object (a temporary one will be
1140     generated if not supplied)
1141
1142     @rtype: list
1143     @return: A (possibly empty) list of Override objects will be returned
1144     """
1145
1146     q = session.query(Override)
1147     q = q.filter_by(package=package)
1148
1149     if suite is not None:
1150         if not isinstance(suite, list): suite = [suite]
1151         q = q.join(Suite).filter(Suite.suite_name.in_(suite))
1152
1153     if component is not None:
1154         if not isinstance(component, list): component = [component]
1155         q = q.join(Component).filter(Component.component_name.in_(component))
1156
1157     if overridetype is not None:
1158         if not isinstance(overridetype, list): overridetype = [overridetype]
1159         q = q.join(OverrideType).filter(OverrideType.overridetype.in_(overridetype))
1160
1161     return q.all()
1162
1163 __all__.append('get_override')
1164
1165
1166 ################################################################################
1167
1168 class OverrideType(object):
1169     def __init__(self, *args, **kwargs):
1170         pass
1171
1172     def __repr__(self):
1173         return '<OverrideType %s>' % self.overridetype
1174
1175 __all__.append('OverrideType')
1176
1177 @session_wrapper
1178 def get_override_type(override_type, session=None):
1179     """
1180     Returns OverrideType object for given C{override type}.
1181
1182     @type override_type: string
1183     @param override_type: The name of the override type
1184
1185     @type session: Session
1186     @param session: Optional SQLA session object (a temporary one will be
1187     generated if not supplied)
1188
1189     @rtype: int
1190     @return: the database id for the given override type
1191     """
1192
1193     q = session.query(OverrideType).filter_by(overridetype=override_type)
1194
1195     try:
1196         return q.one()
1197     except NoResultFound:
1198         return None
1199
1200 __all__.append('get_override_type')
1201
1202 ################################################################################
1203
1204 class PendingContentAssociation(object):
1205     def __init__(self, *args, **kwargs):
1206         pass
1207
1208     def __repr__(self):
1209         return '<PendingContentAssociation %s>' % self.pca_id
1210
1211 __all__.append('PendingContentAssociation')
1212
1213 def insert_pending_content_paths(package, fullpaths, session=None):
1214     """
1215     Make sure given paths are temporarily associated with given
1216     package
1217
1218     @type package: dict
1219     @param package: the package to associate with should have been read in from the binary control file
1220     @type fullpaths: list
1221     @param fullpaths: the list of paths of the file being associated with the binary
1222     @type session: SQLAlchemy session
1223     @param session: Optional SQLAlchemy session.  If this is passed, the caller
1224     is responsible for ensuring a transaction has begun and committing the
1225     results or rolling back based on the result code.  If not passed, a commit
1226     will be performed at the end of the function
1227
1228     @return: True upon success, False if there is a problem
1229     """
1230
1231     privatetrans = False
1232
1233     if session is None:
1234         session = DBConn().session()
1235         privatetrans = True
1236
1237     try:
1238         arch = get_architecture(package['Architecture'], session)
1239         arch_id = arch.arch_id
1240
1241         # Remove any already existing recorded files for this package
1242         q = session.query(PendingContentAssociation)
1243         q = q.filter_by(package=package['Package'])
1244         q = q.filter_by(version=package['Version'])
1245         q = q.filter_by(architecture=arch_id)
1246         q.delete()
1247
1248         # Insert paths
1249         pathcache = {}
1250         for fullpath in fullpaths:
1251             (path, file) = os.path.split(fullpath)
1252
1253             if path.startswith( "./" ):
1254                 path = path[2:]
1255
1256             filepath_id = get_or_set_contents_path_id(path, session)
1257             filename_id = get_or_set_contents_file_id(file, session)
1258
1259             pathcache[fullpath] = (filepath_id, filename_id)
1260
1261         for fullpath, dat in pathcache.items():
1262             pca = PendingContentAssociation()
1263             pca.package = package['Package']
1264             pca.version = package['Version']
1265             pca.filepath_id = dat[0]
1266             pca.filename_id = dat[1]
1267             pca.architecture = arch_id
1268             session.add(pca)
1269
1270         # Only commit if we set up the session ourself
1271         if privatetrans:
1272             session.commit()
1273             session.close()
1274         else:
1275             session.flush()
1276
1277         return True
1278     except Exception, e:
1279         traceback.print_exc()
1280
1281         # Only rollback if we set up the session ourself
1282         if privatetrans:
1283             session.rollback()
1284             session.close()
1285
1286         return False
1287
1288 __all__.append('insert_pending_content_paths')
1289
1290 ################################################################################
1291
1292 class Priority(object):
1293     def __init__(self, *args, **kwargs):
1294         pass
1295
1296     def __eq__(self, val):
1297         if isinstance(val, str):
1298             return (self.priority == val)
1299         # This signals to use the normal comparison operator
1300         return NotImplemented
1301
1302     def __ne__(self, val):
1303         if isinstance(val, str):
1304             return (self.priority != val)
1305         # This signals to use the normal comparison operator
1306         return NotImplemented
1307
1308     def __repr__(self):
1309         return '<Priority %s (%s)>' % (self.priority, self.priority_id)
1310
1311 __all__.append('Priority')
1312
1313 @session_wrapper
1314 def get_priority(priority, session=None):
1315     """
1316     Returns Priority object for given C{priority name}.
1317
1318     @type priority: string
1319     @param priority: The name of the priority
1320
1321     @type session: Session
1322     @param session: Optional SQLA session object (a temporary one will be
1323     generated if not supplied)
1324
1325     @rtype: Priority
1326     @return: Priority object for the given priority
1327     """
1328
1329     q = session.query(Priority).filter_by(priority=priority)
1330
1331     try:
1332         return q.one()
1333     except NoResultFound:
1334         return None
1335
1336 __all__.append('get_priority')
1337
1338 @session_wrapper
1339 def get_priorities(session=None):
1340     """
1341     Returns dictionary of priority names -> id mappings
1342
1343     @type session: Session
1344     @param session: Optional SQL session object (a temporary one will be
1345     generated if not supplied)
1346
1347     @rtype: dictionary
1348     @return: dictionary of priority names -> id mappings
1349     """
1350
1351     ret = {}
1352     q = session.query(Priority)
1353     for x in q.all():
1354         ret[x.priority] = x.priority_id
1355
1356     return ret
1357
1358 __all__.append('get_priorities')
1359
1360 ################################################################################
1361
1362 class Queue(object):
1363     def __init__(self, *args, **kwargs):
1364         pass
1365
1366     def __repr__(self):
1367         return '<Queue %s>' % self.queue_name
1368
1369     def autobuild_upload(self, changes, srcpath, session=None):
1370         """
1371         Update queue_build database table used for incoming autobuild support.
1372
1373         @type changes: Changes
1374         @param changes: changes object for the upload to process
1375
1376         @type srcpath: string
1377         @param srcpath: path for the queue file entries/link destinations
1378
1379         @type session: SQLAlchemy session
1380         @param session: Optional SQLAlchemy session.  If this is passed, the
1381         caller is responsible for ensuring a transaction has begun and
1382         committing the results or rolling back based on the result code.  If
1383         not passed, a commit will be performed at the end of the function,
1384         otherwise the caller is responsible for commiting.
1385
1386         @rtype: NoneType or string
1387         @return: None if the operation failed, a string describing the error if not
1388         """
1389
1390         privatetrans = False
1391         if session is None:
1392             session = DBConn().session()
1393             privatetrans = True
1394
1395         # TODO: Remove by moving queue config into the database
1396         conf = Config()
1397
1398         for suitename in changes.changes["distribution"].keys():
1399             # TODO: Move into database as:
1400             #       buildqueuedir TEXT DEFAULT NULL (i.e. NULL is no build)
1401             #       buildqueuecopy BOOLEAN NOT NULL DEFAULT FALSE (i.e. default is symlink)
1402             #       This also gets rid of the SecurityQueueBuild hack below
1403             if suitename not in conf.ValueList("Dinstall::QueueBuildSuites"):
1404                 continue
1405
1406             # Find suite object
1407             s = get_suite(suitename, session)
1408             if s is None:
1409                 return "INTERNAL ERROR: Could not find suite %s" % suitename
1410
1411             # TODO: Get from database as above
1412             dest_dir = conf["Dir::QueueBuild"]
1413
1414             # TODO: Move into database as above
1415             if conf.FindB("Dinstall::SecurityQueueBuild"):
1416                 dest_dir = os.path.join(dest_dir, suitename)
1417
1418             for file_entry in changes.files.keys():
1419                 src = os.path.join(srcpath, file_entry)
1420                 dest = os.path.join(dest_dir, file_entry)
1421
1422                 # TODO: Move into database as above
1423                 if conf.FindB("Dinstall::SecurityQueueBuild"):
1424                     # Copy it since the original won't be readable by www-data
1425                     import utils
1426                     utils.copy(src, dest)
1427                 else:
1428                     # Create a symlink to it
1429                     os.symlink(src, dest)
1430
1431                 qb = QueueBuild()
1432                 qb.suite_id = s.suite_id
1433                 qb.queue_id = self.queue_id
1434                 qb.filename = dest
1435                 qb.in_queue = True
1436
1437                 session.add(qb)
1438
1439             # If the .orig tarballs are in the pool, create a symlink to
1440             # them (if one doesn't already exist)
1441             for dsc_file in changes.dsc_files.keys():
1442                 # Skip all files except orig tarballs
1443                 from daklib.regexes import re_is_orig_source
1444                 if not re_is_orig_source.match(dsc_file):
1445                     continue
1446                 # Skip orig files not identified in the pool
1447                 if not (changes.orig_files.has_key(dsc_file) and
1448                         changes.orig_files[dsc_file].has_key("id")):
1449                     continue
1450                 orig_file_id = changes.orig_files[dsc_file]["id"]
1451                 dest = os.path.join(dest_dir, dsc_file)
1452
1453                 # If it doesn't exist, create a symlink
1454                 if not os.path.exists(dest):
1455                     q = session.execute("SELECT l.path, f.filename FROM location l, files f WHERE f.id = :id and f.location = l.id",
1456                                         {'id': orig_file_id})
1457                     res = q.fetchone()
1458                     if not res:
1459                         return "[INTERNAL ERROR] Couldn't find id %s in files table." % (orig_file_id)
1460
1461                     src = os.path.join(res[0], res[1])
1462                     os.symlink(src, dest)
1463
1464                     # Add it to the list of packages for later processing by apt-ftparchive
1465                     qb = QueueBuild()
1466                     qb.suite_id = s.suite_id
1467                     qb.queue_id = self.queue_id
1468                     qb.filename = dest
1469                     qb.in_queue = True
1470                     session.add(qb)
1471
1472                 # If it does, update things to ensure it's not removed prematurely
1473                 else:
1474                     qb = get_queue_build(dest, s.suite_id, session)
1475                     if qb is None:
1476                         qb.in_queue = True
1477                         qb.last_used = None
1478                         session.add(qb)
1479
1480         if privatetrans:
1481             session.commit()
1482             session.close()
1483
1484         return None
1485
1486 __all__.append('Queue')
1487
1488 @session_wrapper
1489 def get_or_set_queue(queuename, session=None):
1490     """
1491     Returns Queue object for given C{queue name}, creating it if it does not
1492     exist.
1493
1494     @type queuename: string
1495     @param queuename: The name of the queue
1496
1497     @type session: Session
1498     @param session: Optional SQLA session object (a temporary one will be
1499     generated if not supplied)
1500
1501     @rtype: Queue
1502     @return: Queue object for the given queue
1503     """
1504
1505     q = session.query(Queue).filter_by(queue_name=queuename)
1506
1507     try:
1508         ret = q.one()
1509     except NoResultFound:
1510         queue = Queue()
1511         queue.queue_name = queuename
1512         session.add(queue)
1513         session.commit_or_flush()
1514         ret = queue
1515
1516     return ret
1517
1518 __all__.append('get_or_set_queue')
1519
1520 ################################################################################
1521
1522 class QueueBuild(object):
1523     def __init__(self, *args, **kwargs):
1524         pass
1525
1526     def __repr__(self):
1527         return '<QueueBuild %s (%s)>' % (self.filename, self.queue_id)
1528
1529 __all__.append('QueueBuild')
1530
1531 @session_wrapper
1532 def get_queue_build(filename, suite, session=None):
1533     """
1534     Returns QueueBuild object for given C{filename} and C{suite}.
1535
1536     @type filename: string
1537     @param filename: The name of the file
1538
1539     @type suiteid: int or str
1540     @param suiteid: Suite name or ID
1541
1542     @type session: Session
1543     @param session: Optional SQLA session object (a temporary one will be
1544     generated if not supplied)
1545
1546     @rtype: Queue
1547     @return: Queue object for the given queue
1548     """
1549
1550     if isinstance(suite, int):
1551         q = session.query(QueueBuild).filter_by(filename=filename).filter_by(suite_id=suite)
1552     else:
1553         q = session.query(QueueBuild).filter_by(filename=filename)
1554         q = q.join(Suite).filter_by(suite_name=suite)
1555
1556     try:
1557         return q.one()
1558     except NoResultFound:
1559         return None
1560
1561 __all__.append('get_queue_build')
1562
1563 ################################################################################
1564
1565 class Section(object):
1566     def __init__(self, *args, **kwargs):
1567         pass
1568
1569     def __eq__(self, val):
1570         if isinstance(val, str):
1571             return (self.section == val)
1572         # This signals to use the normal comparison operator
1573         return NotImplemented
1574
1575     def __ne__(self, val):
1576         if isinstance(val, str):
1577             return (self.section != val)
1578         # This signals to use the normal comparison operator
1579         return NotImplemented
1580
1581     def __repr__(self):
1582         return '<Section %s>' % self.section
1583
1584 __all__.append('Section')
1585
1586 @session_wrapper
1587 def get_section(section, session=None):
1588     """
1589     Returns Section object for given C{section name}.
1590
1591     @type section: string
1592     @param section: The name of the section
1593
1594     @type session: Session
1595     @param session: Optional SQLA session object (a temporary one will be
1596     generated if not supplied)
1597
1598     @rtype: Section
1599     @return: Section object for the given section name
1600     """
1601
1602     q = session.query(Section).filter_by(section=section)
1603
1604     try:
1605         return q.one()
1606     except NoResultFound:
1607         return None
1608
1609 __all__.append('get_section')
1610
1611 @session_wrapper
1612 def get_sections(session=None):
1613     """
1614     Returns dictionary of section names -> id mappings
1615
1616     @type session: Session
1617     @param session: Optional SQL session object (a temporary one will be
1618     generated if not supplied)
1619
1620     @rtype: dictionary
1621     @return: dictionary of section names -> id mappings
1622     """
1623
1624     ret = {}
1625     q = session.query(Section)
1626     for x in q.all():
1627         ret[x.section] = x.section_id
1628
1629     return ret
1630
1631 __all__.append('get_sections')
1632
1633 ################################################################################
1634
1635 class DBSource(object):
1636     def __init__(self, *args, **kwargs):
1637         pass
1638
1639     def __repr__(self):
1640         return '<DBSource %s (%s)>' % (self.source, self.version)
1641
1642 __all__.append('DBSource')
1643
1644 @session_wrapper
1645 def source_exists(source, source_version, suites = ["any"], session=None):
1646     """
1647     Ensure that source exists somewhere in the archive for the binary
1648     upload being processed.
1649       1. exact match     => 1.0-3
1650       2. bin-only NMU    => 1.0-3+b1 , 1.0-3.1+b1
1651
1652     @type package: string
1653     @param package: package source name
1654
1655     @type source_version: string
1656     @param source_version: expected source version
1657
1658     @type suites: list
1659     @param suites: list of suites to check in, default I{any}
1660
1661     @type session: Session
1662     @param session: Optional SQLA session object (a temporary one will be
1663     generated if not supplied)
1664
1665     @rtype: int
1666     @return: returns 1 if a source with expected version is found, otherwise 0
1667
1668     """
1669
1670     cnf = Config()
1671     ret = 1
1672
1673     for suite in suites:
1674         q = session.query(DBSource).filter_by(source=source)
1675         if suite != "any":
1676             # source must exist in suite X, or in some other suite that's
1677             # mapped to X, recursively... silent-maps are counted too,
1678             # unreleased-maps aren't.
1679             maps = cnf.ValueList("SuiteMappings")[:]
1680             maps.reverse()
1681             maps = [ m.split() for m in maps ]
1682             maps = [ (x[1], x[2]) for x in maps
1683                             if x[0] == "map" or x[0] == "silent-map" ]
1684             s = [suite]
1685             for x in maps:
1686                 if x[1] in s and x[0] not in s:
1687                     s.append(x[0])
1688
1689             q = q.join(SrcAssociation).join(Suite)
1690             q = q.filter(Suite.suite_name.in_(s))
1691
1692         # Reduce the query results to a list of version numbers
1693         ql = [ j.version for j in q.all() ]
1694
1695         # Try (1)
1696         if source_version in ql:
1697             continue
1698
1699         # Try (2)
1700         from daklib.regexes import re_bin_only_nmu
1701         orig_source_version = re_bin_only_nmu.sub('', source_version)
1702         if orig_source_version in ql:
1703             continue
1704
1705         # No source found so return not ok
1706         ret = 0
1707
1708     return ret
1709
1710 __all__.append('source_exists')
1711
1712 @session_wrapper
1713 def get_suites_source_in(source, session=None):
1714     """
1715     Returns list of Suite objects which given C{source} name is in
1716
1717     @type source: str
1718     @param source: DBSource package name to search for
1719
1720     @rtype: list
1721     @return: list of Suite objects for the given source
1722     """
1723
1724     return session.query(Suite).join(SrcAssociation).join(DBSource).filter_by(source=source).all()
1725
1726 __all__.append('get_suites_source_in')
1727
1728 @session_wrapper
1729 def get_sources_from_name(source, version=None, dm_upload_allowed=None, session=None):
1730     """
1731     Returns list of DBSource objects for given C{source} name and other parameters
1732
1733     @type source: str
1734     @param source: DBSource package name to search for
1735
1736     @type source: str or None
1737     @param source: DBSource version name to search for or None if not applicable
1738
1739     @type dm_upload_allowed: bool
1740     @param dm_upload_allowed: If None, no effect.  If True or False, only
1741     return packages with that dm_upload_allowed setting
1742
1743     @type session: Session
1744     @param session: Optional SQL session object (a temporary one will be
1745     generated if not supplied)
1746
1747     @rtype: list
1748     @return: list of DBSource objects for the given name (may be empty)
1749     """
1750
1751     q = session.query(DBSource).filter_by(source=source)
1752
1753     if version is not None:
1754         q = q.filter_by(version=version)
1755
1756     if dm_upload_allowed is not None:
1757         q = q.filter_by(dm_upload_allowed=dm_upload_allowed)
1758
1759     return q.all()
1760
1761 __all__.append('get_sources_from_name')
1762
1763 @session_wrapper
1764 def get_source_in_suite(source, suite, session=None):
1765     """
1766     Returns list of DBSource objects for a combination of C{source} and C{suite}.
1767
1768       - B{source} - source package name, eg. I{mailfilter}, I{bbdb}, I{glibc}
1769       - B{suite} - a suite name, eg. I{unstable}
1770
1771     @type source: string
1772     @param source: source package name
1773
1774     @type suite: string
1775     @param suite: the suite name
1776
1777     @rtype: string
1778     @return: the version for I{source} in I{suite}
1779
1780     """
1781
1782     q = session.query(SrcAssociation)
1783     q = q.join('source').filter_by(source=source)
1784     q = q.join('suite').filter_by(suite_name=suite)
1785
1786     try:
1787         return q.one().source
1788     except NoResultFound:
1789         return None
1790
1791 __all__.append('get_source_in_suite')
1792
1793 ################################################################################
1794
1795 class SourceACL(object):
1796     def __init__(self, *args, **kwargs):
1797         pass
1798
1799     def __repr__(self):
1800         return '<SourceACL %s>' % self.source_acl_id
1801
1802 __all__.append('SourceACL')
1803
1804 ################################################################################
1805
1806 class SrcAssociation(object):
1807     def __init__(self, *args, **kwargs):
1808         pass
1809
1810     def __repr__(self):
1811         return '<SrcAssociation %s (%s, %s)>' % (self.sa_id, self.source, self.suite)
1812
1813 __all__.append('SrcAssociation')
1814
1815 ################################################################################
1816
1817 class SrcFormat(object):
1818     def __init__(self, *args, **kwargs):
1819         pass
1820
1821     def __repr__(self):
1822         return '<SrcFormat %s>' % (self.format_name)
1823
1824 __all__.append('SrcFormat')
1825
1826 ################################################################################
1827
1828 class SrcUploader(object):
1829     def __init__(self, *args, **kwargs):
1830         pass
1831
1832     def __repr__(self):
1833         return '<SrcUploader %s>' % self.uploader_id
1834
1835 __all__.append('SrcUploader')
1836
1837 ################################################################################
1838
1839 SUITE_FIELDS = [ ('SuiteName', 'suite_name'),
1840                  ('SuiteID', 'suite_id'),
1841                  ('Version', 'version'),
1842                  ('Origin', 'origin'),
1843                  ('Label', 'label'),
1844                  ('Description', 'description'),
1845                  ('Untouchable', 'untouchable'),
1846                  ('Announce', 'announce'),
1847                  ('Codename', 'codename'),
1848                  ('OverrideCodename', 'overridecodename'),
1849                  ('ValidTime', 'validtime'),
1850                  ('Priority', 'priority'),
1851                  ('NotAutomatic', 'notautomatic'),
1852                  ('CopyChanges', 'copychanges'),
1853                  ('CopyDotDak', 'copydotdak'),
1854                  ('CommentsDir', 'commentsdir'),
1855                  ('OverrideSuite', 'overridesuite'),
1856                  ('ChangelogBase', 'changelogbase')]
1857
1858
1859 class Suite(object):
1860     def __init__(self, *args, **kwargs):
1861         pass
1862
1863     def __repr__(self):
1864         return '<Suite %s>' % self.suite_name
1865
1866     def __eq__(self, val):
1867         if isinstance(val, str):
1868             return (self.suite_name == val)
1869         # This signals to use the normal comparison operator
1870         return NotImplemented
1871
1872     def __ne__(self, val):
1873         if isinstance(val, str):
1874             return (self.suite_name != val)
1875         # This signals to use the normal comparison operator
1876         return NotImplemented
1877
1878     def details(self):
1879         ret = []
1880         for disp, field in SUITE_FIELDS:
1881             val = getattr(self, field, None)
1882             if val is not None:
1883                 ret.append("%s: %s" % (disp, val))
1884
1885         return "\n".join(ret)
1886
1887 __all__.append('Suite')
1888
1889 @session_wrapper
1890 def get_suite_architecture(suite, architecture, session=None):
1891     """
1892     Returns a SuiteArchitecture object given C{suite} and ${arch} or None if it
1893     doesn't exist
1894
1895     @type suite: str
1896     @param suite: Suite name to search for
1897
1898     @type architecture: str
1899     @param architecture: Architecture name to search for
1900
1901     @type session: Session
1902     @param session: Optional SQL session object (a temporary one will be
1903     generated if not supplied)
1904
1905     @rtype: SuiteArchitecture
1906     @return: the SuiteArchitecture object or None
1907     """
1908
1909     q = session.query(SuiteArchitecture)
1910     q = q.join(Architecture).filter_by(arch_string=architecture)
1911     q = q.join(Suite).filter_by(suite_name=suite)
1912
1913     try:
1914         return q.one()
1915     except NoResultFound:
1916         return None
1917
1918 __all__.append('get_suite_architecture')
1919
1920 @session_wrapper
1921 def get_suite(suite, session=None):
1922     """
1923     Returns Suite object for given C{suite name}.
1924
1925     @type suite: string
1926     @param suite: The name of the suite
1927
1928     @type session: Session
1929     @param session: Optional SQLA session object (a temporary one will be
1930     generated if not supplied)
1931
1932     @rtype: Suite
1933     @return: Suite object for the requested suite name (None if not present)
1934     """
1935
1936     q = session.query(Suite).filter_by(suite_name=suite)
1937
1938     try:
1939         return q.one()
1940     except NoResultFound:
1941         return None
1942
1943 __all__.append('get_suite')
1944
1945 ################################################################################
1946
1947 class SuiteArchitecture(object):
1948     def __init__(self, *args, **kwargs):
1949         pass
1950
1951     def __repr__(self):
1952         return '<SuiteArchitecture (%s, %s)>' % (self.suite_id, self.arch_id)
1953
1954 __all__.append('SuiteArchitecture')
1955
1956 @session_wrapper
1957 def get_suite_architectures(suite, skipsrc=False, skipall=False, session=None):
1958     """
1959     Returns list of Architecture objects for given C{suite} name
1960
1961     @type source: str
1962     @param source: Suite name to search for
1963
1964     @type skipsrc: boolean
1965     @param skipsrc: Whether to skip returning the 'source' architecture entry
1966     (Default False)
1967
1968     @type skipall: boolean
1969     @param skipall: Whether to skip returning the 'all' architecture entry
1970     (Default False)
1971
1972     @type session: Session
1973     @param session: Optional SQL session object (a temporary one will be
1974     generated if not supplied)
1975
1976     @rtype: list
1977     @return: list of Architecture objects for the given name (may be empty)
1978     """
1979
1980     q = session.query(Architecture)
1981     q = q.join(SuiteArchitecture)
1982     q = q.join(Suite).filter_by(suite_name=suite)
1983
1984     if skipsrc:
1985         q = q.filter(Architecture.arch_string != 'source')
1986
1987     if skipall:
1988         q = q.filter(Architecture.arch_string != 'all')
1989
1990     q = q.order_by('arch_string')
1991
1992     return q.all()
1993
1994 __all__.append('get_suite_architectures')
1995
1996 ################################################################################
1997
1998 class SuiteSrcFormat(object):
1999     def __init__(self, *args, **kwargs):
2000         pass
2001
2002     def __repr__(self):
2003         return '<SuiteSrcFormat (%s, %s)>' % (self.suite_id, self.src_format_id)
2004
2005 __all__.append('SuiteSrcFormat')
2006
2007 @session_wrapper
2008 def get_suite_src_formats(suite, session=None):
2009     """
2010     Returns list of allowed SrcFormat for C{suite}.
2011
2012     @type suite: str
2013     @param suite: Suite name to search for
2014
2015     @type session: Session
2016     @param session: Optional SQL session object (a temporary one will be
2017     generated if not supplied)
2018
2019     @rtype: list
2020     @return: the list of allowed source formats for I{suite}
2021     """
2022
2023     q = session.query(SrcFormat)
2024     q = q.join(SuiteSrcFormat)
2025     q = q.join(Suite).filter_by(suite_name=suite)
2026     q = q.order_by('format_name')
2027
2028     return q.all()
2029
2030 __all__.append('get_suite_src_formats')
2031
2032 ################################################################################
2033
2034 class Uid(object):
2035     def __init__(self, *args, **kwargs):
2036         pass
2037
2038     def __eq__(self, val):
2039         if isinstance(val, str):
2040             return (self.uid == val)
2041         # This signals to use the normal comparison operator
2042         return NotImplemented
2043
2044     def __ne__(self, val):
2045         if isinstance(val, str):
2046             return (self.uid != val)
2047         # This signals to use the normal comparison operator
2048         return NotImplemented
2049
2050     def __repr__(self):
2051         return '<Uid %s (%s)>' % (self.uid, self.name)
2052
2053 __all__.append('Uid')
2054
2055 @session_wrapper
2056 def add_database_user(uidname, session=None):
2057     """
2058     Adds a database user
2059
2060     @type uidname: string
2061     @param uidname: The uid of the user to add
2062
2063     @type session: SQLAlchemy
2064     @param session: Optional SQL session object (a temporary one will be
2065     generated if not supplied).  If not passed, a commit will be performed at
2066     the end of the function, otherwise the caller is responsible for commiting.
2067
2068     @rtype: Uid
2069     @return: the uid object for the given uidname
2070     """
2071
2072     session.execute("CREATE USER :uid", {'uid': uidname})
2073     session.commit_or_flush()
2074
2075 __all__.append('add_database_user')
2076
2077 @session_wrapper
2078 def get_or_set_uid(uidname, session=None):
2079     """
2080     Returns uid object for given uidname.
2081
2082     If no matching uidname is found, a row is inserted.
2083
2084     @type uidname: string
2085     @param uidname: The uid to add
2086
2087     @type session: SQLAlchemy
2088     @param session: Optional SQL session object (a temporary one will be
2089     generated if not supplied).  If not passed, a commit will be performed at
2090     the end of the function, otherwise the caller is responsible for commiting.
2091
2092     @rtype: Uid
2093     @return: the uid object for the given uidname
2094     """
2095
2096     q = session.query(Uid).filter_by(uid=uidname)
2097
2098     try:
2099         ret = q.one()
2100     except NoResultFound:
2101         uid = Uid()
2102         uid.uid = uidname
2103         session.add(uid)
2104         session.commit_or_flush()
2105         ret = uid
2106
2107     return ret
2108
2109 __all__.append('get_or_set_uid')
2110
2111 @session_wrapper
2112 def get_uid_from_fingerprint(fpr, session=None):
2113     q = session.query(Uid)
2114     q = q.join(Fingerprint).filter_by(fingerprint=fpr)
2115
2116     try:
2117         return q.one()
2118     except NoResultFound:
2119         return None
2120
2121 __all__.append('get_uid_from_fingerprint')
2122
2123 ################################################################################
2124
2125 class UploadBlock(object):
2126     def __init__(self, *args, **kwargs):
2127         pass
2128
2129     def __repr__(self):
2130         return '<UploadBlock %s (%s)>' % (self.source, self.upload_block_id)
2131
2132 __all__.append('UploadBlock')
2133
2134 ################################################################################
2135
2136 class DBConn(Singleton):
2137     """
2138     database module init.
2139     """
2140     def __init__(self, *args, **kwargs):
2141         super(DBConn, self).__init__(*args, **kwargs)
2142
2143     def _startup(self, *args, **kwargs):
2144         self.debug = False
2145         if kwargs.has_key('debug'):
2146             self.debug = True
2147         self.__createconn()
2148
2149     def __setuptables(self):
2150         self.tbl_architecture = Table('architecture', self.db_meta, autoload=True)
2151         self.tbl_archive = Table('archive', self.db_meta, autoload=True)
2152         self.tbl_bin_associations = Table('bin_associations', self.db_meta, autoload=True)
2153         self.tbl_binaries = Table('binaries', self.db_meta, autoload=True)
2154         self.tbl_binary_acl = Table('binary_acl', self.db_meta, autoload=True)
2155         self.tbl_binary_acl_map = Table('binary_acl_map', self.db_meta, autoload=True)
2156         self.tbl_component = Table('component', self.db_meta, autoload=True)
2157         self.tbl_config = Table('config', self.db_meta, autoload=True)
2158         self.tbl_content_associations = Table('content_associations', self.db_meta, autoload=True)
2159         self.tbl_content_file_names = Table('content_file_names', self.db_meta, autoload=True)
2160         self.tbl_content_file_paths = Table('content_file_paths', self.db_meta, autoload=True)
2161         self.tbl_dsc_files = Table('dsc_files', self.db_meta, autoload=True)
2162         self.tbl_files = Table('files', self.db_meta, autoload=True)
2163         self.tbl_fingerprint = Table('fingerprint', self.db_meta, autoload=True)
2164         self.tbl_keyrings = Table('keyrings', self.db_meta, autoload=True)
2165         self.tbl_location = Table('location', self.db_meta, autoload=True)
2166         self.tbl_maintainer = Table('maintainer', self.db_meta, autoload=True)
2167         self.tbl_new_comments = Table('new_comments', self.db_meta, autoload=True)
2168         self.tbl_override = Table('override', self.db_meta, autoload=True)
2169         self.tbl_override_type = Table('override_type', self.db_meta, autoload=True)
2170         self.tbl_pending_content_associations = Table('pending_content_associations', self.db_meta, autoload=True)
2171         self.tbl_priority = Table('priority', self.db_meta, autoload=True)
2172         self.tbl_queue = Table('queue', self.db_meta, autoload=True)
2173         self.tbl_queue_build = Table('queue_build', self.db_meta, autoload=True)
2174         self.tbl_section = Table('section', self.db_meta, autoload=True)
2175         self.tbl_source = Table('source', self.db_meta, autoload=True)
2176         self.tbl_source_acl = Table('source_acl', self.db_meta, autoload=True)
2177         self.tbl_src_associations = Table('src_associations', self.db_meta, autoload=True)
2178         self.tbl_src_format = Table('src_format', self.db_meta, autoload=True)
2179         self.tbl_src_uploaders = Table('src_uploaders', self.db_meta, autoload=True)
2180         self.tbl_suite = Table('suite', self.db_meta, autoload=True)
2181         self.tbl_suite_architectures = Table('suite_architectures', self.db_meta, autoload=True)
2182         self.tbl_suite_src_formats = Table('suite_src_formats', self.db_meta, autoload=True)
2183         self.tbl_uid = Table('uid', self.db_meta, autoload=True)
2184         self.tbl_upload_blocks = Table('upload_blocks', self.db_meta, autoload=True)
2185
2186     def __setupmappers(self):
2187         mapper(Architecture, self.tbl_architecture,
2188                properties = dict(arch_id = self.tbl_architecture.c.id))
2189
2190         mapper(Archive, self.tbl_archive,
2191                properties = dict(archive_id = self.tbl_archive.c.id,
2192                                  archive_name = self.tbl_archive.c.name))
2193
2194         mapper(BinAssociation, self.tbl_bin_associations,
2195                properties = dict(ba_id = self.tbl_bin_associations.c.id,
2196                                  suite_id = self.tbl_bin_associations.c.suite,
2197                                  suite = relation(Suite),
2198                                  binary_id = self.tbl_bin_associations.c.bin,
2199                                  binary = relation(DBBinary)))
2200
2201         mapper(DBBinary, self.tbl_binaries,
2202                properties = dict(binary_id = self.tbl_binaries.c.id,
2203                                  package = self.tbl_binaries.c.package,
2204                                  version = self.tbl_binaries.c.version,
2205                                  maintainer_id = self.tbl_binaries.c.maintainer,
2206                                  maintainer = relation(Maintainer),
2207                                  source_id = self.tbl_binaries.c.source,
2208                                  source = relation(DBSource),
2209                                  arch_id = self.tbl_binaries.c.architecture,
2210                                  architecture = relation(Architecture),
2211                                  poolfile_id = self.tbl_binaries.c.file,
2212                                  poolfile = relation(PoolFile),
2213                                  binarytype = self.tbl_binaries.c.type,
2214                                  fingerprint_id = self.tbl_binaries.c.sig_fpr,
2215                                  fingerprint = relation(Fingerprint),
2216                                  install_date = self.tbl_binaries.c.install_date,
2217                                  binassociations = relation(BinAssociation,
2218                                                             primaryjoin=(self.tbl_binaries.c.id==self.tbl_bin_associations.c.bin))))
2219
2220         mapper(BinaryACL, self.tbl_binary_acl,
2221                properties = dict(binary_acl_id = self.tbl_binary_acl.c.id))
2222
2223         mapper(BinaryACLMap, self.tbl_binary_acl_map,
2224                properties = dict(binary_acl_map_id = self.tbl_binary_acl_map.c.id,
2225                                  fingerprint = relation(Fingerprint, backref="binary_acl_map"),
2226                                  architecture = relation(Architecture)))
2227
2228         mapper(Component, self.tbl_component,
2229                properties = dict(component_id = self.tbl_component.c.id,
2230                                  component_name = self.tbl_component.c.name))
2231
2232         mapper(DBConfig, self.tbl_config,
2233                properties = dict(config_id = self.tbl_config.c.id))
2234
2235         mapper(ContentAssociation, self.tbl_content_associations,
2236                properties = dict(ca_id = self.tbl_content_associations.c.id,
2237                                  filename_id = self.tbl_content_associations.c.filename,
2238                                  filename    = relation(ContentFilename),
2239                                  filepath_id = self.tbl_content_associations.c.filepath,
2240                                  filepath    = relation(ContentFilepath),
2241                                  binary_id   = self.tbl_content_associations.c.binary_pkg,
2242                                  binary      = relation(DBBinary)))
2243
2244
2245         mapper(ContentFilename, self.tbl_content_file_names,
2246                properties = dict(cafilename_id = self.tbl_content_file_names.c.id,
2247                                  filename = self.tbl_content_file_names.c.file))
2248
2249         mapper(ContentFilepath, self.tbl_content_file_paths,
2250                properties = dict(cafilepath_id = self.tbl_content_file_paths.c.id,
2251                                  filepath = self.tbl_content_file_paths.c.path))
2252
2253         mapper(DSCFile, self.tbl_dsc_files,
2254                properties = dict(dscfile_id = self.tbl_dsc_files.c.id,
2255                                  source_id = self.tbl_dsc_files.c.source,
2256                                  source = relation(DBSource),
2257                                  poolfile_id = self.tbl_dsc_files.c.file,
2258                                  poolfile = relation(PoolFile)))
2259
2260         mapper(PoolFile, self.tbl_files,
2261                properties = dict(file_id = self.tbl_files.c.id,
2262                                  filesize = self.tbl_files.c.size,
2263                                  location_id = self.tbl_files.c.location,
2264                                  location = relation(Location)))
2265
2266         mapper(Fingerprint, self.tbl_fingerprint,
2267                properties = dict(fingerprint_id = self.tbl_fingerprint.c.id,
2268                                  uid_id = self.tbl_fingerprint.c.uid,
2269                                  uid = relation(Uid),
2270                                  keyring_id = self.tbl_fingerprint.c.keyring,
2271                                  keyring = relation(Keyring),
2272                                  source_acl = relation(SourceACL),
2273                                  binary_acl = relation(BinaryACL)))
2274
2275         mapper(Keyring, self.tbl_keyrings,
2276                properties = dict(keyring_name = self.tbl_keyrings.c.name,
2277                                  keyring_id = self.tbl_keyrings.c.id))
2278
2279         mapper(Location, self.tbl_location,
2280                properties = dict(location_id = self.tbl_location.c.id,
2281                                  component_id = self.tbl_location.c.component,
2282                                  component = relation(Component),
2283                                  archive_id = self.tbl_location.c.archive,
2284                                  archive = relation(Archive),
2285                                  archive_type = self.tbl_location.c.type))
2286
2287         mapper(Maintainer, self.tbl_maintainer,
2288                properties = dict(maintainer_id = self.tbl_maintainer.c.id))
2289
2290         mapper(NewComment, self.tbl_new_comments,
2291                properties = dict(comment_id = self.tbl_new_comments.c.id))
2292
2293         mapper(Override, self.tbl_override,
2294                properties = dict(suite_id = self.tbl_override.c.suite,
2295                                  suite = relation(Suite),
2296                                  component_id = self.tbl_override.c.component,
2297                                  component = relation(Component),
2298                                  priority_id = self.tbl_override.c.priority,
2299                                  priority = relation(Priority),
2300                                  section_id = self.tbl_override.c.section,
2301                                  section = relation(Section),
2302                                  overridetype_id = self.tbl_override.c.type,
2303                                  overridetype = relation(OverrideType)))
2304
2305         mapper(OverrideType, self.tbl_override_type,
2306                properties = dict(overridetype = self.tbl_override_type.c.type,
2307                                  overridetype_id = self.tbl_override_type.c.id))
2308
2309         mapper(PendingContentAssociation, self.tbl_pending_content_associations,
2310                properties = dict(pca_id = self.tbl_pending_content_associations.c.id,
2311                                  filepath_id = self.tbl_pending_content_associations.c.filepath,
2312                                  filepath = relation(ContentFilepath),
2313                                  filename_id = self.tbl_pending_content_associations.c.filename,
2314                                  filename = relation(ContentFilename)))
2315
2316         mapper(Priority, self.tbl_priority,
2317                properties = dict(priority_id = self.tbl_priority.c.id))
2318
2319         mapper(Queue, self.tbl_queue,
2320                properties = dict(queue_id = self.tbl_queue.c.id))
2321
2322         mapper(QueueBuild, self.tbl_queue_build,
2323                properties = dict(suite_id = self.tbl_queue_build.c.suite,
2324                                  queue_id = self.tbl_queue_build.c.queue,
2325                                  queue = relation(Queue, backref='queuebuild')))
2326
2327         mapper(Section, self.tbl_section,
2328                properties = dict(section_id = self.tbl_section.c.id))
2329
2330         mapper(DBSource, self.tbl_source,
2331                properties = dict(source_id = self.tbl_source.c.id,
2332                                  version = self.tbl_source.c.version,
2333                                  maintainer_id = self.tbl_source.c.maintainer,
2334                                  maintainer = relation(Maintainer,
2335                                                        primaryjoin=(self.tbl_source.c.maintainer==self.tbl_maintainer.c.id)),
2336                                  poolfile_id = self.tbl_source.c.file,
2337                                  poolfile = relation(PoolFile),
2338                                  fingerprint_id = self.tbl_source.c.sig_fpr,
2339                                  fingerprint = relation(Fingerprint),
2340                                  changedby_id = self.tbl_source.c.changedby,
2341                                  changedby = relation(Maintainer,
2342                                                       primaryjoin=(self.tbl_source.c.changedby==self.tbl_maintainer.c.id)),
2343                                  srcfiles = relation(DSCFile,
2344                                                      primaryjoin=(self.tbl_source.c.id==self.tbl_dsc_files.c.source)),
2345                                  srcassociations = relation(SrcAssociation,
2346                                                             primaryjoin=(self.tbl_source.c.id==self.tbl_src_associations.c.source))))
2347
2348         mapper(SourceACL, self.tbl_source_acl,
2349                properties = dict(source_acl_id = self.tbl_source_acl.c.id))
2350
2351         mapper(SrcAssociation, self.tbl_src_associations,
2352                properties = dict(sa_id = self.tbl_src_associations.c.id,
2353                                  suite_id = self.tbl_src_associations.c.suite,
2354                                  suite = relation(Suite),
2355                                  source_id = self.tbl_src_associations.c.source,
2356                                  source = relation(DBSource)))
2357
2358         mapper(SrcFormat, self.tbl_src_format,
2359                properties = dict(src_format_id = self.tbl_src_format.c.id,
2360                                  format_name = self.tbl_src_format.c.format_name))
2361
2362         mapper(SrcUploader, self.tbl_src_uploaders,
2363                properties = dict(uploader_id = self.tbl_src_uploaders.c.id,
2364                                  source_id = self.tbl_src_uploaders.c.source,
2365                                  source = relation(DBSource,
2366                                                    primaryjoin=(self.tbl_src_uploaders.c.source==self.tbl_source.c.id)),
2367                                  maintainer_id = self.tbl_src_uploaders.c.maintainer,
2368                                  maintainer = relation(Maintainer,
2369                                                        primaryjoin=(self.tbl_src_uploaders.c.maintainer==self.tbl_maintainer.c.id))))
2370
2371         mapper(Suite, self.tbl_suite,
2372                properties = dict(suite_id = self.tbl_suite.c.id))
2373
2374         mapper(SuiteArchitecture, self.tbl_suite_architectures,
2375                properties = dict(suite_id = self.tbl_suite_architectures.c.suite,
2376                                  suite = relation(Suite, backref='suitearchitectures'),
2377                                  arch_id = self.tbl_suite_architectures.c.architecture,
2378                                  architecture = relation(Architecture)))
2379
2380         mapper(SuiteSrcFormat, self.tbl_suite_src_formats,
2381                properties = dict(suite_id = self.tbl_suite_src_formats.c.suite,
2382                                  suite = relation(Suite, backref='suitesrcformats'),
2383                                  src_format_id = self.tbl_suite_src_formats.c.src_format,
2384                                  src_format = relation(SrcFormat)))
2385
2386         mapper(Uid, self.tbl_uid,
2387                properties = dict(uid_id = self.tbl_uid.c.id,
2388                                  fingerprint = relation(Fingerprint)))
2389
2390         mapper(UploadBlock, self.tbl_upload_blocks,
2391                properties = dict(upload_block_id = self.tbl_upload_blocks.c.id,
2392                                  fingerprint = relation(Fingerprint, backref="uploadblocks"),
2393                                  uid = relation(Uid, backref="uploadblocks")))
2394
2395     ## Connection functions
2396     def __createconn(self):
2397         from config import Config
2398         cnf = Config()
2399         if cnf["DB::Host"]:
2400             # TCP/IP
2401             connstr = "postgres://%s" % cnf["DB::Host"]
2402             if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
2403                 connstr += ":%s" % cnf["DB::Port"]
2404             connstr += "/%s" % cnf["DB::Name"]
2405         else:
2406             # Unix Socket
2407             connstr = "postgres:///%s" % cnf["DB::Name"]
2408             if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
2409                 connstr += "?port=%s" % cnf["DB::Port"]
2410
2411         self.db_pg   = create_engine(connstr, echo=self.debug)
2412         self.db_meta = MetaData()
2413         self.db_meta.bind = self.db_pg
2414         self.db_smaker = sessionmaker(bind=self.db_pg,
2415                                       autoflush=True,
2416                                       autocommit=False)
2417
2418         self.__setuptables()
2419         self.__setupmappers()
2420
2421     def session(self):
2422         return self.db_smaker()
2423
2424 __all__.append('DBConn')
2425
2426