80 raise AuthenticationError('anonymous access is not authorized') |
78 raise AuthenticationError('anonymous access is not authorized') |
81 anon_login, anon_password = anoninfo |
79 anon_login, anon_password = anoninfo |
82 # use vreg's repository cache |
80 # use vreg's repository cache |
83 return connect(repo, anon_login, password=anon_password) |
81 return connect(repo, anon_login, password=anon_password) |
84 |
82 |
85 def _srv_cnx_func(name): |
|
86 """Decorate ClientConnection method blindly forward to Connection |
|
87 THIS TRANSITIONAL PURPOSE |
|
88 |
83 |
89 will be dropped when we have standalone connection""" |
84 class ClientConnection(Connection): |
90 def proxy(clt_cnx, *args, **kwargs): |
85 __metaclass__ = class_deprecated |
91 # the ``with`` dance is transitional. We do not have Standalone |
86 __deprecation_warning__ = '[3.20] %(cls)s is deprecated, use Connection instead' |
92 # Connection yet so we use this trick to unsure the session have the |
|
93 # proper cnx loaded. This can be simplified one we have Standalone |
|
94 # Connection object |
|
95 if not clt_cnx._open: |
|
96 raise ProgrammingError('Closed client connection') |
|
97 return getattr(clt_cnx._cnx, name)(*args, **kwargs) |
|
98 return proxy |
|
99 |
|
100 def _open_only(func): |
|
101 """decorator for ClientConnection method that check it is open""" |
|
102 @wraps(func) |
|
103 def check_open(clt_cnx, *args, **kwargs): |
|
104 if not clt_cnx._open: |
|
105 raise ProgrammingError('Closed client connection') |
|
106 return func(clt_cnx, *args, **kwargs) |
|
107 return check_open |
|
108 |
|
109 |
|
110 class ClientConnection(RequestSessionBase): |
|
111 """A Connection object to be used Client side. |
|
112 |
|
113 This object is aimed to be used client side (so potential communication |
|
114 with the repo through RPC) and aims to offer some compatibility with the |
|
115 cubicweb.dbapi.Connection interface. |
|
116 |
|
117 The autoclose_session parameter informs the connection that this session |
|
118 has been opened explicitly and only for this client connection. The |
|
119 connection will close the session on exit. |
|
120 """ |
|
121 # make exceptions available through the connection object |
|
122 ProgrammingError = ProgrammingError |
|
123 # attributes that may be overriden per connection instance |
|
124 anonymous_connection = False # XXX really needed ? |
|
125 is_repo_in_memory = True # BC, always true |
|
126 |
|
127 def __init__(self, session, autoclose_session=False): |
|
128 super(ClientConnection, self).__init__(session.vreg) |
|
129 self._session = session # XXX there is no real reason to keep the |
|
130 # session around function still using it should |
|
131 # be rewritten and migrated. |
|
132 self._cnx = None |
|
133 self._open = None |
|
134 self._web_request = False |
|
135 #: cache entities built during the connection |
|
136 self._eid_cache = {} |
|
137 self._set_user(session.user) |
|
138 self._autoclose_session = autoclose_session |
|
139 |
|
140 def __enter__(self): |
|
141 assert self._open is None |
|
142 self._open = True |
|
143 self._cnx = self._session.new_cnx() |
|
144 self._cnx.__enter__() |
|
145 self._cnx.ctx_count += 1 |
|
146 return self |
|
147 |
|
148 def __exit__(self, exc_type, exc_val, exc_tb): |
|
149 self._open = False |
|
150 self._cnx.ctx_count -= 1 |
|
151 self._cnx.__exit__(exc_type, exc_val, exc_tb) |
|
152 self._cnx = None |
|
153 if self._autoclose_session: |
|
154 # we have to call repo.close to ensure the repo properly forgets the |
|
155 # session; calling session.close() is not enough :-( |
|
156 self._session.repo.close(self._session.sessionid) |
|
157 |
|
158 |
|
159 # begin silly BC |
|
160 @property |
|
161 def _closed(self): |
|
162 return not self._open |
|
163 |
|
164 def close(self): |
|
165 if self._open: |
|
166 self.__exit__(None, None, None) |
|
167 |
|
168 def __repr__(self): |
|
169 # XXX we probably want to reference the user of the session here |
|
170 if self._open is None: |
|
171 return '<ClientConnection (not open yet)>' |
|
172 elif not self._open: |
|
173 return '<ClientConnection (closed)>' |
|
174 elif self.anonymous_connection: |
|
175 return '<ClientConnection %s (anonymous)>' % self._cnx.connectionid |
|
176 else: |
|
177 return '<ClientConnection %s>' % self._cnx.connectionid |
|
178 # end silly BC |
|
179 |
|
180 # Main Connection purpose in life ######################################### |
|
181 |
|
182 call_service = _srv_cnx_func('call_service') |
|
183 |
|
184 @_open_only |
|
185 def execute(self, *args, **kwargs): |
|
186 # the ``with`` dance is transitional. We do not have Standalone |
|
187 # Connection yet so we use this trick to unsure the session have the |
|
188 # proper cnx loaded. This can be simplified one we have Standalone |
|
189 # Connection object |
|
190 rset = self._cnx.execute(*args, **kwargs) |
|
191 rset.req = self |
|
192 return rset |
|
193 |
|
194 @_open_only |
|
195 def commit(self, *args, **kwargs): |
|
196 try: |
|
197 return self._cnx.commit(*args, **kwargs) |
|
198 finally: |
|
199 self.drop_entity_cache() |
|
200 |
|
201 @_open_only |
|
202 def rollback(self, *args, **kwargs): |
|
203 try: |
|
204 return self._cnx.rollback(*args, **kwargs) |
|
205 finally: |
|
206 self.drop_entity_cache() |
|
207 |
|
208 # security ################################################################# |
|
209 |
|
210 allow_all_hooks_but = _srv_cnx_func('allow_all_hooks_but') |
|
211 deny_all_hooks_but = _srv_cnx_func('deny_all_hooks_but') |
|
212 security_enabled = _srv_cnx_func('security_enabled') |
|
213 |
|
214 # direct sql ############################################################### |
|
215 |
|
216 system_sql = _srv_cnx_func('system_sql') |
|
217 |
|
218 # session data methods ##################################################### |
|
219 |
|
220 get_shared_data = _srv_cnx_func('get_shared_data') |
|
221 set_shared_data = _srv_cnx_func('set_shared_data') |
|
222 |
|
223 @property |
|
224 def transaction_data(self): |
|
225 return self._cnx.transaction_data |
|
226 |
|
227 # meta-data accessors ###################################################### |
|
228 |
|
229 @_open_only |
|
230 def source_defs(self): |
|
231 """Return the definition of sources used by the repository.""" |
|
232 return self._session.repo.source_defs() |
|
233 |
|
234 @_open_only |
|
235 def get_schema(self): |
|
236 """Return the schema currently used by the repository.""" |
|
237 return self._session.repo.source_defs() |
|
238 |
|
239 @_open_only |
|
240 def get_option_value(self, option): |
|
241 """Return the value for `option` in the configuration.""" |
|
242 return self._session.repo.get_option_value(option) |
|
243 |
|
244 entity_metas = _srv_cnx_func('entity_metas') |
|
245 describe = _srv_cnx_func('describe') # XXX deprecated in 3.19 |
|
246 |
|
247 # undo support ############################################################ |
|
248 |
|
249 @_open_only |
|
250 def undoable_transactions(self, ueid=None, req=None, **actionfilters): |
|
251 """Return a list of undoable transaction objects by the connection's |
|
252 user, ordered by descendant transaction time. |
|
253 |
|
254 Managers may filter according to user (eid) who has done the transaction |
|
255 using the `ueid` argument. Others will only see their own transactions. |
|
256 |
|
257 Additional filtering capabilities is provided by using the following |
|
258 named arguments: |
|
259 |
|
260 * `etype` to get only transactions creating/updating/deleting entities |
|
261 of the given type |
|
262 |
|
263 * `eid` to get only transactions applied to entity of the given eid |
|
264 |
|
265 * `action` to get only transactions doing the given action (action in |
|
266 'C', 'U', 'D', 'A', 'R'). If `etype`, action can only be 'C', 'U' or |
|
267 'D'. |
|
268 |
|
269 * `public`: when additional filtering is provided, their are by default |
|
270 only searched in 'public' actions, unless a `public` argument is given |
|
271 and set to false. |
|
272 """ |
|
273 # the ``with`` dance is transitional. We do not have Standalone |
|
274 # Connection yet so we use this trick to unsure the session have the |
|
275 # proper cnx loaded. This can be simplified one we have Standalone |
|
276 # Connection object |
|
277 source = self._cnx.repo.system_source |
|
278 txinfos = source.undoable_transactions(self._cnx, ueid, **actionfilters) |
|
279 for txinfo in txinfos: |
|
280 txinfo.req = req or self # XXX mostly wrong |
|
281 return txinfos |
|
282 |
|
283 @_open_only |
|
284 def transaction_info(self, txuuid, req=None): |
|
285 """Return transaction object for the given uid. |
|
286 |
|
287 raise `NoSuchTransaction` if not found or if session's user is not |
|
288 allowed (eg not in managers group and the transaction doesn't belong to |
|
289 him). |
|
290 """ |
|
291 # the ``with`` dance is transitional. We do not have Standalone |
|
292 # Connection yet so we use this trick to unsure the session have the |
|
293 # proper cnx loaded. This can be simplified one we have Standalone |
|
294 # Connection object |
|
295 txinfo = self._cnx.repo.system_source.tx_info(self._cnx, txuuid) |
|
296 if req: |
|
297 txinfo.req = req |
|
298 else: |
|
299 txinfo.cnx = self |
|
300 return txinfo |
|
301 |
|
302 @_open_only |
|
303 def transaction_actions(self, txuuid, public=True): |
|
304 """Return an ordered list of action effectued during that transaction. |
|
305 |
|
306 If public is true, return only 'public' actions, eg not ones triggered |
|
307 under the cover by hooks, else return all actions. |
|
308 |
|
309 raise `NoSuchTransaction` if the transaction is not found or if |
|
310 session's user is not allowed (eg not in managers group and the |
|
311 transaction doesn't belong to him). |
|
312 """ |
|
313 # the ``with`` dance is transitional. We do not have Standalone |
|
314 # Connection yet so we use this trick to unsure the session have the |
|
315 # proper cnx loaded. This can be simplified one we have Standalone |
|
316 # Connection object |
|
317 return self._cnx.repo.system_source.tx_actions(self._cnx, txuuid, public) |
|
318 |
|
319 @_open_only |
|
320 def undo_transaction(self, txuuid): |
|
321 """Undo the given transaction. Return potential restoration errors. |
|
322 |
|
323 raise `NoSuchTransaction` if not found or if session's user is not |
|
324 allowed (eg not in managers group and the transaction doesn't belong to |
|
325 him). |
|
326 """ |
|
327 # the ``with`` dance is transitional. We do not have Standalone |
|
328 # Connection yet so we use this trick to unsure the session have the |
|
329 # proper cnx loaded. This can be simplified one we have Standalone |
|
330 # Connection object |
|
331 return self._cnx.repo.system_source.undo_transaction(self._cnx, txuuid) |
|
332 |
|
333 # cache management |
|
334 |
|
335 def entity_cache(self, eid): |
|
336 return self._eid_cache[eid] |
|
337 |
|
338 def set_entity_cache(self, entity): |
|
339 self._eid_cache[entity.eid] = entity |
|
340 |
|
341 def cached_entities(self): |
|
342 return self._eid_cache.values() |
|
343 |
|
344 def drop_entity_cache(self, eid=None): |
|
345 if eid is None: |
|
346 self._eid_cache = {} |
|
347 else: |
|
348 del self._eid_cache[eid] |
|
349 |
|
350 # deprecated stuff |
|
351 |
|
352 @deprecated('[3.19] This is a repoapi.ClientConnection object not a dbapi one') |
|
353 def request(self): |
|
354 return self |
|
355 |
|
356 @deprecated('[3.19] This is a repoapi.ClientConnection object not a dbapi one') |
|
357 def cursor(self): |
|
358 return self |
|
359 |
|
360 @property |
|
361 @deprecated('[3.19] This is a repoapi.ClientConnection object not a dbapi one') |
|
362 def sessionid(self): |
|
363 return self._session.sessionid |
|
364 |
|
365 @property |
|
366 @deprecated('[3.19] This is a repoapi.ClientConnection object not a dbapi one') |
|
367 def connection(self): |
|
368 return self |
|
369 |
|
370 @property |
|
371 @deprecated('[3.19] This is a repoapi.ClientConnection object not a dbapi one') |
|
372 def _repo(self): |
|
373 return self._session.repo |
|