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.orm import validates
10 from sqlalchemy.ext.declarative import declarative_base
12 from sfa.util.sfalogging import logger
13 from sfa.util.xml import XML
15 from sfa.trust.gid import GID
17 ##############################
18 Base=declarative_base()
23 # historically the front end to the db dealt with dicts, so the code was only dealing with dicts
24 # sqlalchemy however offers an object interface, meaning that you write obj.id instead of obj['id']
25 # which is admittedly much nicer
26 # however we still need to deal with dictionaries if only for the xmlrpc layer
28 # here are a few utilities for this
30 # (*) first off, when an old pieve of code needs to be used as-is, if only temporarily, the simplest trick
31 # is to use obj.__dict__
32 # this behaves exactly like required, i.e. obj.__dict__['field']='new value' does change obj.field
33 # however this depends on sqlalchemy's implementation so it should be avoided
35 # (*) second, when an object needs to be exposed to the xmlrpc layer, we need to convert it into a dict
36 # remember though that writing the resulting dictionary won't change the object
37 # essentially obj.__dict__ would be fine too, except that we want to discard alchemy private keys starting with '_'
38 # 2 ways are provided for that:
41 # the former dict(obj) relies on __iter__() and next() below, and does not rely on the fields names
42 # although it seems to work fine, I've found cases where it issues a weird python error that I could not get right
43 # 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
45 # (*) finally for converting a dictionary into an sqlalchemy object, we provide
46 # obj.load_from_dict(dict)
50 self._i = iter(object_mapper(self).columns)
53 n = self._i.next().name
54 return n, getattr(self, n)
57 keys=[k for k in d.keys() if not k.startswith('_')]
58 return dict ( [ (k,d[k]) for k in keys ] )
59 def load_from_dict (self, d):
60 for (k,v) in d.iteritems():
62 if isinstance(v, StringTypes) and v.lower() in ['true']: v=True
63 if isinstance(v, StringTypes) and 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 logger.info("load from xml, keys=%s"%xml_dict.keys())
78 # for k in self.xml_fields():
79 for (k,v) in xml_dict.iteritems():
82 def save_as_xml (self):
83 # xxx not sure about the scope here
84 input_dict = dict( [ (key, getattr(self.key), ) for key in self.xml_fields() if getattr(self,key,None) ] )
85 xml_record=XML("<record />")
86 xml_record.parse_dict (input_dict)
87 return xml_record.toxml()
89 def dump(self, dump_parents=False):
90 for key in self.fields:
91 if key == 'gid' and self.gid:
92 gid = GID(string=self.gid)
94 gid.dump(8, dump_parents)
95 elif getattr(self,key,None):
96 print " %s: %s" % (key, getattr(self,key))
98 # # only intended for debugging
99 # def inspect (self, logger, message=""):
100 # logger.info("%s -- Inspecting AlchemyObj -- attrs"%message)
101 # for k in dir(self):
102 # if not k.startswith('_'):
103 # logger.info (" %s: %s"%(k,getattr(self,k)))
104 # logger.info("%s -- Inspecting AlchemyObj -- __dict__"%message)
106 # for (k,v) in d.iteritems():
107 # logger.info("[%s]=%s"%(k,v))
110 ##############################
112 __table__ = Table ('types', Base.metadata,
113 Column ('type',String, primary_key=True),
115 def __init__ (self, type): self.type=type
116 def __repr__ (self): return "<Type %s>"%self.type
118 #BUILTIN_TYPES = [ 'authority', 'slice', 'node', 'user' ]
119 # xxx for compat but sounds useless
120 BUILTIN_TYPES = [ 'authority', 'slice', 'node', 'user',
121 'authority+sa', 'authority+am', 'authority+sm' ]
123 def insert_builtin_types(dbsession):
124 for type in BUILTIN_TYPES :
125 count = dbsession.query (Type).filter_by (type=type).count()
127 dbsession.add (Type (type))
130 ##############################
131 class RegRecord (Base,AlchemyObj):
132 # xxx tmp would be 'records'
133 __table__ = Table ('records', Base.metadata,
134 Column ('record_id', Integer, primary_key=True),
135 Column ('type', String, ForeignKey ("types.type")),
136 Column ('hrn',String),
137 Column ('gid',String),
138 Column ('authority',String),
139 Column ('peer_authority',String),
140 Column ('pointer',Integer,default=-1),
141 Column ('date_created',DateTime),
142 Column ('last_updated',DateTime),
144 fields = [ 'type', 'hrn', 'gid', 'authority', 'peer_authority' ]
145 def __init__ (self, type='unknown', hrn=None, gid=None, authority=None, peer_authority=None,
146 pointer=None, dict=None):
150 if isinstance(gid, StringTypes): self.gid=gid
151 else: self.gid=gid.save_to_string(save_parents=True)
152 if authority: self.authority=authority
153 if peer_authority: self.peer_authority=peer_authority
154 if pointer: self.pointer=pointer
156 self.load_from_dict (dict)
159 result="[Record(record_id=%s, hrn=%s, type=%s, authority=%s, pointer=%s" % \
160 (self.record_id, self.hrn, self.type, self.authority, self.pointer)
161 if self.gid: result+=" %s..."%self.gid[:10]
162 else: result+=" no-gid"
166 # xxx - there might be smarter ways to handle get/set'ing gid using validation hooks
167 def get_gid_object (self):
168 if not self.gid: return None
169 else: return GID(string=self.gid)
171 def just_created (self):
173 self.date_created=now
174 self.last_updated=now
176 def just_updated (self):
178 self.last_updated=now
180 ##############################
183 __table__ = Table ('users', Base.metadata,
184 Column ('record_id', Integer, ForeignKey ("records.record_id"), primary_key=True),
185 Column ('email', String),
187 def __init__ (self, email):
189 def __repr__ (self): return "[User(%d) email=%s>"%(self.record_id,self.email,)
192 def validate_email(self, key, address):
193 assert '@' in address
197 __table__ = Table ('keys', Base.metadata,
198 Column ('key_id', Integer, primary_key=True),
199 Column ('key',String),
202 ##############################
203 #record_table = RegRecord.__table__
204 #user_table = User.__table__
205 #record_user_join = join (record_table, user_table)
207 #class UserRecord (Base):
208 # __table__ = record_user_join
209 # record_id = column_property (record_table.c.record_id, user_table.c.record_id)
210 # user_id = user_table.c.user_id
211 # def __init__ (self, gid, email):
215 # def __repr__ (self): return "<UserRecord %s %s>"%(self.email,self.gid)
217 ##############################
218 def init_tables(dbsession):
219 logger.info("Initializing db schema and builtin types")
220 # the doc states we could retrieve the engine this way
221 # engine=dbsession.get_bind()
222 # however I'm getting this
223 # TypeError: get_bind() takes at least 2 arguments (1 given)
224 # so let's import alchemy - but not from toplevel
225 from sfa.storage.alchemy import engine
226 Base.metadata.create_all(engine)
227 insert_builtin_types(dbsession)
229 def drop_tables(dbsession):
230 logger.info("Dropping tables")
231 # same as for init_tables
232 from sfa.storage.alchemy import engine
233 Base.metadata.drop_all(engine)