a1f4cb60058ec07974133e5829355e50ea1a9d5c
[sfa.git] / sfa / storage / persistentobjs.py
1 from types import StringTypes
2 from datetime import datetime
3
4 from sqlalchemy import Column, Integer, String, DateTime
5 from sqlalchemy import Table, Column, MetaData, join, ForeignKey
6 from sqlalchemy.orm import relationship, backref
7 from sqlalchemy.orm import column_property
8 from sqlalchemy.orm import object_mapper
9 from sqlalchemy.ext.declarative import declarative_base
10
11 from sfa.util.sfalogging import logger
12
13 from sfa.trust.gid import GID
14
15 ##############################
16 Base=declarative_base()
17
18 ####################
19 # dicts vs objects
20 ####################
21 # historically the front end to the db dealt with dicts, so the code was only dealing with dicts
22 # sqlalchemy however offers an object interface, meaning that you write obj.id instead of obj['id']
23 # which is admittedly much nicer
24 # however we still need to deal with dictionaries if only for the xmlrpc layer
25
26 # here are a few utilities for this 
27
28 # (*) first off, when an old pieve of code needs to be used as-is, if only temporarily, the simplest trick
29 # is to use obj.__dict__
30 # this behaves exactly like required, i.e. obj.__dict__['field']='new value' does change obj.field
31 # however this depends on sqlalchemy's implementation so it should be avoided 
32 #
33 # (*) second, when an object needs to be exposed to the xmlrpc layer, we need to convert it into a dict
34 # remember though that writing the resulting dictionary won't change the object
35 # essentially obj.__dict__ would be fine too, except that we want to discard alchemy private keys starting with '_'
36 # 2 ways are provided for that:
37 # . dict(obj)
38 # . obj.todict()
39 # the former dict(obj) relies on __iter__() and next() below, and does not rely on the fields names
40 # although it seems to work fine, I've found cases where it issues a weird python error that I could not get right
41 # so the latter obj.todict() seems more reliable but more hacky as is relies on the form of fields, so this can probably be improved
42 #
43 # (*) finally for converting a dictionary into an sqlalchemy object, we provide
44 # obj.set_from_dict(dict)
45
46 class AlchemyObj:
47     def __iter__(self): 
48         self._i = iter(object_mapper(self).columns)
49         return self 
50     def next(self): 
51         n = self._i.next().name
52         return n, getattr(self, n)
53     def todict (self):
54         d=self.__dict__
55         keys=[k for k in d.keys() if not k.startswith('_')]
56         return dict ( [ (k,d[k]) for k in keys ] )
57     def set_from_dict (self, d):
58         for (k,v) in d.iteritems():
59             # experimental
60             if isinstance(v, StringTypes):
61                 if v.lower() in ['true']: v=True
62                 if v.lower() in ['false']: v=False
63             setattr(self,k,v)
64
65 ##############################
66 class Type (Base):
67     __table__ = Table ('types', Base.metadata,
68                        Column ('type',String, primary_key=True)
69                        )
70     def __init__ (self, type): self.type=type
71     def __repr__ (self): return "<Type %s>"%self.type
72     
73 #BUILTIN_TYPES = [ 'authority', 'slice', 'node', 'user' ]
74 # xxx for compat but sounds useless
75 BUILTIN_TYPES = [ 'authority', 'slice', 'node', 'user',
76                   'authority+sa', 'authority+am', 'authority+sm' ]
77
78 def insert_builtin_types(dbsession):
79     for type in BUILTIN_TYPES :
80         count = dbsession.query (Type).filter_by (type=type).count()
81         if count==0:
82             dbsession.add (Type (type))
83     dbsession.commit()
84
85 ##############################
86 class RegRecord (Base,AlchemyObj):
87     # xxx tmp would be 'records'
88     __table__ = Table ('records', Base.metadata,
89                        Column ('record_id', Integer, primary_key=True),
90                        Column ('type', String, ForeignKey ("types.type")),
91                        Column ('hrn',String),
92                        Column ('gid',String),
93                        Column ('authority',String),
94                        Column ('peer_authority',String),
95                        Column ('pointer',Integer,default=-1),
96                        Column ('date_created',DateTime),
97                        Column ('last_updated',DateTime),
98                        )
99     def __init__ (self, type, hrn=None, gid=None, authority=None, peer_authority=None, pointer=-1):
100         self.type=type
101         if hrn: self.hrn=hrn
102         if gid: 
103             if isinstance(gid, StringTypes): self.gid=gid
104             else: self.gid=gid.save_to_string(save_parents=True)
105         if authority: self.authority=authority
106         if peer_authority: self.peer_authority=peer_authority
107         self.pointer=pointer
108
109     def __repr__(self):
110         result="[Record(record_id=%s, hrn=%s, type=%s, authority=%s, pointer=%s" % \
111                 (self.record_id, self.hrn, self.type, self.authority, self.pointer)
112         if self.gid: result+=" %s..."%self.gid[:10]
113         else: result+=" no-gid"
114         result += "]"
115         return result
116
117     def get_gid_object (self):
118         if not self.gid: return None
119         else: return GID(string=self.gid)
120
121     def just_created (self):
122         now=datetime.now()
123         self.date_created=now
124         self.last_updated=now
125
126     def just_updated (self):
127         now=datetime.now()
128         self.last_updated=now
129
130 ##############################
131 class User (Base):
132     __table__ = Table ('users', Base.metadata,
133                        Column ('user_id', Integer, primary_key=True),
134                        Column ('record_id',Integer, ForeignKey('records.record_id')),
135                        Column ('email', String),
136                        )
137     def __init__ (self, email):
138         self.email=email
139     def __repr__ (self): return "<User(%d) %s, record_id=%d>"%(self.user_id,self.email,self.record_id,)
140                            
141 record_table = RegRecord.__table__
142 user_table = User.__table__
143 record_user_join = join (record_table, user_table)
144
145 class UserRecord (Base):
146     __table__ = record_user_join
147     record_id = column_property (record_table.c.record_id, user_table.c.record_id)
148     user_id = user_table.c.user_id
149     def __init__ (self, gid, email):
150         self.type='user'
151         self.gid=gid
152         self.email=email
153     def __repr__ (self): return "<UserRecord %s %s>"%(self.email,self.gid)
154
155 ##############################    
156 def init_tables(dbsession):
157     logger.info("Initializing db schema and builtin types")
158     engine=dbsession.get_bind()
159     Base.metadata.create_all(engine)
160     insert_builtin_types(dbsession)
161
162 def drop_tables(dbsession):
163     logger.info("Dropping tables")
164     engine=dbsession.get_bind()
165     Base.metadata.drop_all(engine)