]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/python/oils/utils/csedit.py
LP1908763 Survey column sorting broken
[Evergreen.git] / Open-ILS / src / python / oils / utils / csedit.py
1 """
2 A Python-friendly wrapper for accessing the Evergreen open-ils.cstore service
3 """
4 # -----------------------------------------------------------------------
5 # Copyright (C) 2007  Georgia Public Library Service
6 # Bill Erickson <billserickson@gmail.com>
7
8 # This program is free software; you can redistribute it and/or
9 # modify it under the terms of the GNU General Public License
10 # as published by the Free Software Foundation; either version 2
11 # of the License, or (at your option) any later version.
12
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 # GNU General Public License for more details.
17 # -----------------------------------------------------------------------
18
19 from oils.utils.idl import IDLParser
20 from osrf.const import OSRF_APP_SESSION_CONNECTED
21 from osrf.log import log_debug, log_info, log_error
22 from osrf.ses import ClientSession
23 import oils.const
24 import re
25
26 ACTIONS = ['create', 'retrieve', 'batch_retrieve', 'update', 'delete', 'search']
27
28 class CSEditor(object):
29     """
30     Contains generated methods for accessing fieldmapper objects using the
31     following syntax:
32     
33         <ret> = <instance>.<action>_<schema>_<table>(<args>)
34
35       * <instance> = CSEditor class instance
36       * <action>   = 
37         * create 
38           <args>   = object to create
39           <ret>    = the numeric ID of the newly created object
40         * retrieve 
41           <args>   = numeric ID of the object to retrieve 
42           <ret>    = object, instance of osrf.net_obj.NetworkObject
43         * batch_retrieve
44           <args>   = list of numeric ID's
45           <ret>    = list of objects, instances of osrf.net_obj.NetworkObject
46         * update
47           <args>   = object to update
48           <ret>    = 1 on success
49         * delete
50           <args>   = object to delete
51           <ret>    = 1 on sucess
52         * search
53           <args>   = a cstore-compatible search dict.  e.g. {"id":1}.  
54             See cstore docs for the full range of search options.
55           <ret>    = a list of search results.  For standard searches, this
56                    will be a list of objects.  idlist searches will return
57                    a list of ID's.
58       * <schema>   = the name of the schema that contains the table
59       * <table>    = the name of the table
60
61     Each generated object has accessor methods corresponding to the fieldmapper
62     name attributes for a given field. The following example demonstrates how to
63     instantiate the CSEditor and a given table object, and how to invoke an
64     accessor method on that table object:
65
66     >>> import oils.utils.csedit
67     >>> import oils.utils.idl
68     >>> import osrf.system
69     >>> osrf.system.connect('/openils/conf/opensrf_core.xml', 'config.opensrf')
70     >>> oils.utils.idl.oilsParseIDL()
71     >>> oils.utils.csedit.oilsLoadCSEditor()
72     >>> editor = oils.utils.csedit.CSEditor()
73     >>> rec = editor.retrieve_biblio_record_entry(-1)
74     >>> print rec.tcn_value()
75     """
76
77     def __init__(self, **args):
78         ''' 
79             Creates a new editor object.
80
81             Support keyword arguments:
82             authtoken - Authtoken string -- used to determine 
83                 the requestor if none is provided.
84             requestor - existing user (au) object.  The requestor is 
85                 is the user performing the action.  This is important 
86                 for permission checks, logging, etc.
87             connect - boolean.  If true, a connect call is sent to the opensrf
88                 service at session create time
89             xact - boolean.  If true, a cstore transaction is created at 
90                 connect time.  xact implies connect.
91         '''
92
93         self.app = args.get('app', oils.const.OILS_APP_CSTORE)
94         self.authtoken = args.get('authtoken', args.get('auth'))
95         self.requestor = args.get('requestor')
96         self.connect = args.get('connect')
97         self.xact = args.get('xact')
98         self.__session = None
99
100     def die_event(self):
101         ''' Rolls back the existing transaction, disconnects our session, 
102             and returns the last received event.
103         '''
104         pass
105
106     def checkauth(self):
107         ''' Checks the authtoken against open-ils.auth and uses the 
108             retrieved user as the requestor
109         '''
110         pass
111
112     # -------------------------------------------------------------------------
113     # Creates a session if one does not already exist.  If necessary, connects
114     # to the remote service and starts a transaction
115     # -------------------------------------------------------------------------
116     def session(self, ses=None):
117         """
118         Creates a session if one does not already exist.
119
120         If necessary, connects to the remote service and starts a transaction.
121         """
122
123         if not self.__session:
124             self.__session = ClientSession(self.app)
125
126         if self.connect or self.xact:
127             self.log(log_debug,'connecting to ' + self.app)
128             self.__session.connect() 
129
130         if self.xact:
131             self.log(log_info, "starting new db transaction")
132             self.request(self.app + '.transaction.begin')
133
134         return self.__session
135
136     def log(self, func, string):
137         ''' Logs string with some meta info '''
138
139         meta = "editor["
140         if self.xact:
141             meta += "1|"
142         else:
143             meta += "0|"
144
145         if self.requestor:
146             meta += str(self.requestor.id())
147         else:
148             meta += "0"
149         meta += "]"
150         func("%s %s" % (meta, string))
151
152     def rollback(self):
153         ''' Rolls back the existing db transaction '''
154
155         if self.__session and self.xact:
156             self.log(log_info, "rolling back db transaction")
157             self.request(self.app + '.transaction.rollback')
158             self.disconnect()
159              
160     def commit(self):
161         ''' Commits the existing db transaction and disconnects '''
162
163         if self.__session and self.xact:
164             self.log(log_info, "comitting db transaction")
165             self.request(self.app + '.transaction.commit')
166             self.disconnect()
167
168     def disconnect(self):
169         ''' Disconnects from the remote service '''
170         if self.__session:
171             self.__session.disconnect()
172             self.__session = None
173
174     def request(self, method, params=[]):
175         """
176         Sends a request.
177         """
178
179         # XXX improve param logging here
180
181         self.log(log_info, "request %s %s" % (method, unicode(params)))
182
183         if self.xact and self.session().state != OSRF_APP_SESSION_CONNECTED:
184             self.log(log_error, "csedit lost its connection!")
185
186         val = None
187
188         try:
189             req = self.session().request2(method, params)
190             resp = req.recv()
191             val = resp.content()
192
193         except Exception, e:
194             self.log(log_error, "request error: %s" % unicode(e))
195             raise e
196
197         return val
198
199     # -------------------------------------------------------------------------
200     # Returns true if our requestor is allowed to perform the request action
201     # 'org' defaults to the requestors ws_ou
202     # -------------------------------------------------------------------------
203     def allowed(self, perm, org=None):
204         pass # XXX
205
206     def runMethod(self, action, obj_type, arg, options={}):
207
208         method = "%s.direct.%s.%s" % (self.app, obj_type, action)
209
210         if options.get('idlist'):
211             method = method.replace('search', 'id_list')
212             del options['idlist']
213
214         if action == 'search':
215             method += '.atomic'
216
217         if action == 'batch_retrieve':
218             method = method.replace('batch_retrieve', 'search')
219             method += '.atomic'
220             arg = {'id' : arg}
221
222         params = [arg]
223         if len(options.keys()):
224             params.append(options)
225
226         val = self.request( method, params )
227
228         return val
229
230     def rawSearch(self, args):
231         method = "%s.json_query.atomic" % self.app
232         self.log(log_debug, "rawSearch args: %s" % unicode(args))
233         return self.request(method, [args])
234
235     def rawSearch2(self, hint, fields, where, from_=None):
236         if not from_:   
237             from_ = {'%s' % hint : {}}
238
239         args = {
240             'select' : { '%s' % hint : fields },
241             'from' : from_,
242             'where' : { "+%s" % hint : where }
243         }
244         return self.rawSearch(args)
245
246     def fieldSearch(self, hint, fields, where):
247         return self.rawSearch2(hint, fields, where)
248
249 __editor_loaded = False
250 def oilsLoadCSEditor():
251     """
252     Creates a class method for each action on each type of fieldmapper object
253     """
254
255     global __editor_loaded
256     if __editor_loaded:
257         return
258     __editor_loaded = True
259
260     obj = IDLParser.get_parser().idl_object
261
262     for fmap in obj.itervalues():
263         for action in ACTIONS:
264
265             fmname = fmap.fieldmapper.replace('::', '_')
266             obj_type = fmap.fieldmapper.replace('::', '.')
267             name = "%s_%s" % (action, fmname)
268
269             method = 'def %s(self, arg, **options):\n' % name
270             method += '\treturn self.runMethod("%s", "%s"' % (action, obj_type)
271             method += ', arg, dict(options))\n'
272             method += 'setattr(CSEditor, "%s", %s)' % (name, name)
273
274             exec(method)
275