2 A Python-friendly wrapper for accessing the Evergreen open-ils.cstore service
4 # -----------------------------------------------------------------------
5 # Copyright (C) 2007 Georgia Public Library Service
6 # Bill Erickson <billserickson@gmail.com>
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.
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 # -----------------------------------------------------------------------
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
26 ACTIONS = ['create', 'retrieve', 'batch_retrieve', 'update', 'delete', 'search']
28 class CSEditor(object):
30 Contains generated methods for accessing fieldmapper objects using the
33 <ret> = <instance>.<action>_<schema>_<table>(<args>)
35 * <instance> = CSEditor class instance
38 <args> = object to create
39 <ret> = the numeric ID of the newly created object
41 <args> = numeric ID of the object to retrieve
42 <ret> = object, instance of osrf.net_obj.NetworkObject
44 <args> = list of numeric ID's
45 <ret> = list of objects, instances of osrf.net_obj.NetworkObject
47 <args> = object to update
50 <args> = object to delete
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
58 * <schema> = the name of the schema that contains the table
59 * <table> = the name of the table
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:
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()
77 def __init__(self, **args):
79 Creates a new editor object.
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.
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')
101 ''' Rolls back the existing transaction, disconnects our session,
102 and returns the last received event.
107 ''' Checks the authtoken against open-ils.auth and uses the
108 retrieved user as the requestor
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):
118 Creates a session if one does not already exist.
120 If necessary, connects to the remote service and starts a transaction.
123 if not self.__session:
124 self.__session = ClientSession(self.app)
126 if self.connect or self.xact:
127 self.log(log_debug,'connecting to ' + self.app)
128 self.__session.connect()
131 self.log(log_info, "starting new db transaction")
132 self.request(self.app + '.transaction.begin')
134 return self.__session
136 def log(self, func, string):
137 ''' Logs string with some meta info '''
146 meta += str(self.requestor.id())
150 func("%s %s" % (meta, string))
153 ''' Rolls back the existing db transaction '''
155 if self.__session and self.xact:
156 self.log(log_info, "rolling back db transaction")
157 self.request(self.app + '.transaction.rollback')
161 ''' Commits the existing db transaction and disconnects '''
163 if self.__session and self.xact:
164 self.log(log_info, "comitting db transaction")
165 self.request(self.app + '.transaction.commit')
168 def disconnect(self):
169 ''' Disconnects from the remote service '''
171 self.__session.disconnect()
172 self.__session = None
174 def request(self, method, params=[]):
179 # XXX improve param logging here
181 self.log(log_info, "request %s %s" % (method, unicode(params)))
183 if self.xact and self.session().state != OSRF_APP_SESSION_CONNECTED:
184 self.log(log_error, "csedit lost its connection!")
189 req = self.session().request2(method, params)
194 self.log(log_error, "request error: %s" % unicode(e))
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):
206 def runMethod(self, action, obj_type, arg, options={}):
208 method = "%s.direct.%s.%s" % (self.app, obj_type, action)
210 if options.get('idlist'):
211 method = method.replace('search', 'id_list')
212 del options['idlist']
214 if action == 'search':
217 if action == 'batch_retrieve':
218 method = method.replace('batch_retrieve', 'search')
223 if len(options.keys()):
224 params.append(options)
226 val = self.request( method, params )
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])
235 def rawSearch2(self, hint, fields, where, from_=None):
237 from_ = {'%s' % hint : {}}
240 'select' : { '%s' % hint : fields },
242 'where' : { "+%s" % hint : where }
244 return self.rawSearch(args)
246 def fieldSearch(self, hint, fields, where):
247 return self.rawSearch2(hint, fields, where)
249 __editor_loaded = False
250 def oilsLoadCSEditor():
252 Creates a class method for each action on each type of fieldmapper object
255 global __editor_loaded
258 __editor_loaded = True
260 obj = IDLParser.get_parser().idl_object
262 for fmap in obj.itervalues():
263 for action in ACTIONS:
265 fmname = fmap.fieldmapper.replace('::', '_')
266 obj_type = fmap.fieldmapper.replace('::', '.')
267 name = "%s_%s" % (action, fmname)
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)