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