1 from types import StringTypes
2 from datetime import datetime
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
11 from sfa.util.sfalogging import logger
12 from sfa.util.xml import XML
14 from sfa.trust.gid import GID
16 ##############################
17 Base=declarative_base()
22 # historically the front end to the db dealt with dicts, so the code was only dealing with dicts
23 # sqlalchemy however offers an object interface, meaning that you write obj.id instead of obj['id']
24 # which is admittedly much nicer
25 # however we still need to deal with dictionaries if only for the xmlrpc layer
27 # here are a few utilities for this
29 # (*) first off, when an old pieve of code needs to be used as-is, if only temporarily, the simplest trick
30 # is to use obj.__dict__
31 # this behaves exactly like required, i.e. obj.__dict__['field']='new value' does change obj.field
32 # however this depends on sqlalchemy's implementation so it should be avoided
34 # (*) second, when an object needs to be exposed to the xmlrpc layer, we need to convert it into a dict
35 # remember though that writing the resulting dictionary won't change the object
36 # essentially obj.__dict__ would be fine too, except that we want to discard alchemy private keys starting with '_'
37 # 2 ways are provided for that:
40 # the former dict(obj) relies on __iter__() and next() below, and does not rely on the fields names
41 # although it seems to work fine, I've found cases where it issues a weird python error that I could not get right
42 # 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
44 # (*) finally for converting a dictionary into an sqlalchemy object, we provide
45 # obj.load_from_dict(dict)
49 self._i = iter(object_mapper(self).columns)
52 n = self._i.next().name
53 return n, getattr(self, n)
56 keys=[k for k in d.keys() if not k.startswith('_')]
57 return dict ( [ (k,d[k]) for k in keys ] )
58 def load_from_dict (self, d):
59 for (k,v) in d.iteritems():
61 if isinstance(v, StringTypes):
62 if v.lower() in ['true']: v=True
63 if v.lower() in ['false']: v=False
65 assert self.type in BUILTIN_TYPES
67 # in addition we provide convenience for converting to and from xml records
68 # for this purpose only, we need the subclasses to define 'fields' as either
69 # a list or a dictionary
70 def xml_fields (self):
72 if isinstance(fields,dict): fields=fields.keys()
74 def load_from_xml (self, xml):
76 xml_dict = xml_record.todict()
77 for k in self.xml_fields():
79 setattr(self,k,xml_dict[k])
81 def save_as_xml (self):
82 # xxx unset fields don't get exposed, is that right ?
83 input_dict = dict( [ (key, getattr(self.key), ) for key in self.xml_fields() if getattr(self,key,None) ] )
84 xml_record=XML("<record />")
85 xml_record.parse_dict (input_dict)
86 return xml_record.toxml()
88 ##############################
90 __table__ = Table ('types', Base.metadata,
91 Column ('type',String, primary_key=True)
93 def __init__ (self, type): self.type=type
94 def __repr__ (self): return "<Type %s>"%self.type
96 #BUILTIN_TYPES = [ 'authority', 'slice', 'node', 'user' ]
97 # xxx for compat but sounds useless
98 BUILTIN_TYPES = [ 'authority', 'slice', 'node', 'user',
99 'authority+sa', 'authority+am', 'authority+sm' ]
101 def insert_builtin_types(dbsession):
102 for type in BUILTIN_TYPES :
103 count = dbsession.query (Type).filter_by (type=type).count()
105 dbsession.add (Type (type))
108 ##############################
109 class RegRecord (Base,AlchemyObj):
110 # xxx tmp would be 'records'
111 __table__ = Table ('records', Base.metadata,
112 Column ('record_id', Integer, primary_key=True),
113 Column ('type', String, ForeignKey ("types.type")),
114 Column ('hrn',String),
115 Column ('gid',String),
116 Column ('authority',String),
117 Column ('peer_authority',String),
118 Column ('pointer',Integer,default=-1),
119 Column ('date_created',DateTime),
120 Column ('last_updated',DateTime),
122 fields = [ 'type', 'hrn', 'gid', 'authority', 'peer_authority' ]
123 def __init__ (self, type='unknown', hrn=None, gid=None, authority=None, peer_authority=None,
124 pointer=-1, dict=None):
128 if isinstance(gid, StringTypes): self.gid=gid
129 else: self.gid=gid.save_to_string(save_parents=True)
130 if authority: self.authority=authority
131 if peer_authority: self.peer_authority=peer_authority
132 if not hasattr(self,'pointer'): self.pointer=pointer
134 self.load_from_dict (dict)
137 result="[Record(record_id=%s, hrn=%s, type=%s, authority=%s, pointer=%s" % \
138 (self.record_id, self.hrn, self.type, self.authority, self.pointer)
139 if self.gid: result+=" %s..."%self.gid[:10]
140 else: result+=" no-gid"
144 # xxx - there might be smarter ways to handle get/set'ing gid using validation hooks
145 def get_gid_object (self):
146 if not self.gid: return None
147 else: return GID(string=self.gid)
149 def just_created (self):
151 self.date_created=now
152 self.last_updated=now
154 def just_updated (self):
156 self.last_updated=now
158 ##############################
160 __table__ = Table ('users', Base.metadata,
161 Column ('user_id', Integer, primary_key=True),
162 Column ('record_id',Integer, ForeignKey('records.record_id')),
163 Column ('email', String),
165 def __init__ (self, email):
167 def __repr__ (self): return "<User(%d) %s, record_id=%d>"%(self.user_id,self.email,self.record_id,)
169 record_table = RegRecord.__table__
170 user_table = User.__table__
171 record_user_join = join (record_table, user_table)
173 class UserRecord (Base):
174 __table__ = record_user_join
175 record_id = column_property (record_table.c.record_id, user_table.c.record_id)
176 user_id = user_table.c.user_id
177 def __init__ (self, gid, email):
181 def __repr__ (self): return "<UserRecord %s %s>"%(self.email,self.gid)
183 ##############################
184 def init_tables(dbsession):
185 logger.info("Initializing db schema and builtin types")
186 # the doc states we could retrieve the engine this way
187 # engine=dbsession.get_bind()
188 # however I'm getting this
189 # TypeError: get_bind() takes at least 2 arguments (1 given)
190 # so let's import alchemy - but not from toplevel
191 from sfa.storage.alchemy import engine
192 Base.metadata.create_all(engine)
193 insert_builtin_types(dbsession)
195 def drop_tables(dbsession):
196 logger.info("Dropping tables")
197 # same as for init_tables
198 from sfa.storage.alchemy import engine
199 Base.metadata.drop_all(engine)