69 from cubicweb.zmqclient import ZMQRepositoryClient |
72 from cubicweb.zmqclient import ZMQRepositoryClient |
70 return ZMQRepositoryClient(uri) |
73 return ZMQRepositoryClient(uri) |
71 else: |
74 else: |
72 raise ConnectionError('unknown protocol: `%s`' % protocol) |
75 raise ConnectionError('unknown protocol: `%s`' % protocol) |
73 |
76 |
|
77 def _srv_cnx_func(name): |
|
78 """Decorate ClientConnection method blindly forward to Connection |
|
79 THIS TRANSITIONAL PURPOSE |
|
80 |
|
81 will be dropped when we have standalone connection""" |
|
82 def proxy(clt_cnx, *args, **kwargs): |
|
83 # the ``with`` dance is transitional. We do not have Standalone |
|
84 # Connection yet so we use this trick to unsure the session have the |
|
85 # proper cnx loaded. This can be simplified one we have Standalone |
|
86 # Connection object |
|
87 with clt_cnx._srv_cnx as cnx: |
|
88 return getattr(cnx, name)(*args, **kwargs) |
|
89 return proxy |
|
90 |
|
91 class ClientConnection(RequestSessionBase): |
|
92 """A Connection object to be used Client side. |
|
93 |
|
94 This object is aimed to be used client side (so potential communication |
|
95 with the repo through RTC) and aims to offer some compatibility with the |
|
96 cubicweb.dbapi.Connection interface. |
|
97 """ |
|
98 # make exceptions available through the connection object |
|
99 ProgrammingError = ProgrammingError |
|
100 # attributes that may be overriden per connection instance |
|
101 anonymous_connection = False # XXX really needed ? |
|
102 is_repo_in_memory = True # BC, always true |
|
103 |
|
104 def __init__(self, session): |
|
105 self._session = session |
|
106 self._cnxid = None |
|
107 self._open = None |
|
108 self._web_request = False |
|
109 self.vreg = session.vreg |
|
110 self._set_user(session.user) |
|
111 |
|
112 def __enter__(self): |
|
113 assert self._open is None |
|
114 self._open = True |
|
115 self._cnxid = '%s-%s' % (self._session.id, uuid4().hex) |
|
116 self._session.set_cnx(self._cnxid) |
|
117 self._session._cnx.ctx_count += 1 |
|
118 |
|
119 def __exit__(self, exc_type, exc_val, exc_tb): |
|
120 self._open = False |
|
121 cnxid = self._cnxid |
|
122 self._cnxid = None |
|
123 self._session._cnx.ctx_count -= 1 |
|
124 self._session.close_cnx(cnxid) |
|
125 |
|
126 |
|
127 # begin silly BC |
|
128 @property |
|
129 def _closed(self): |
|
130 return not self._open |
|
131 |
|
132 def close(self): |
|
133 if self._open: |
|
134 self.__exit__(None, None, None) |
|
135 |
|
136 def __repr__(self): |
|
137 if self.anonymous_connection: |
|
138 return '<Connection %s (anonymous)>' % self._cnxid |
|
139 return '<Connection %s>' % self._cnxid |
|
140 # end silly BC |
|
141 |
|
142 @property |
|
143 @contextmanager |
|
144 def _srv_cnx(self): |
|
145 """ensure that the session is locked to the right transaction |
|
146 |
|
147 TRANSITIONAL PURPOSE, This will be dropped once we use standalone |
|
148 session object""" |
|
149 if not self._open: |
|
150 raise ProgrammingError('Closed connection %s' % self._cnxid) |
|
151 session = self._session |
|
152 old_cnx = session._current_cnx_id |
|
153 try: |
|
154 session.set_cnx(self._cnxid) |
|
155 session.set_cnxset() |
|
156 try: |
|
157 yield session |
|
158 finally: |
|
159 session.free_cnxset() |
|
160 finally: |
|
161 if old_cnx is not None: |
|
162 session.set_cnx(old_cnx) |
|
163 |
|
164 # Main Connection purpose in life ######################################### |
|
165 |
|
166 call_service = _srv_cnx_func('call_service') |
|
167 |
|
168 def execute(self, *args, **kwargs): |
|
169 # the ``with`` dance is transitional. We do not have Standalone |
|
170 # Connection yet so we use this trick to unsure the session have the |
|
171 # proper cnx loaded. This can be simplified one we have Standalone |
|
172 # Connection object |
|
173 with self._srv_cnx as cnx: |
|
174 rset = cnx.execute(*args, **kwargs) |
|
175 rset.req = self |
|
176 return rset |
|
177 |
|
178 commit = _srv_cnx_func('commit') |
|
179 rollback = _srv_cnx_func('rollback') |
|
180 |
|
181 # session data methods ##################################################### |
|
182 |
|
183 get_shared_data = _srv_cnx_func('get_shared_data') |
|
184 set_shared_data = _srv_cnx_func('set_shared_data') |
|
185 |
|
186 # meta-data accessors ###################################################### |
|
187 |
|
188 def source_defs(self): |
|
189 """Return the definition of sources used by the repository.""" |
|
190 return self._session.repo.source_defs() |
|
191 |
|
192 def get_schema(self): |
|
193 """Return the schema currently used by the repository.""" |
|
194 return self._session.repo.source_defs() |
|
195 |
|
196 def get_option_value(self, option, foreid=None): |
|
197 """Return the value for `option` in the configuration. If `foreid` is |
|
198 specified, the actual repository to which this entity belongs is |
|
199 dereferenced and the option value retrieved from it. |
|
200 """ |
|
201 return self._session.repo.get_option_value(option, foreid) |
|
202 |
|
203 describe = _srv_cnx_func('describe') |
|
204 |
|
205 # undo support ############################################################ |
|
206 |
|
207 def undoable_transactions(self, ueid=None, req=None, **actionfilters): |
|
208 """Return a list of undoable transaction objects by the connection's |
|
209 user, ordered by descendant transaction time. |
|
210 |
|
211 Managers may filter according to user (eid) who has done the transaction |
|
212 using the `ueid` argument. Others will only see their own transactions. |
|
213 |
|
214 Additional filtering capabilities is provided by using the following |
|
215 named arguments: |
|
216 |
|
217 * `etype` to get only transactions creating/updating/deleting entities |
|
218 of the given type |
|
219 |
|
220 * `eid` to get only transactions applied to entity of the given eid |
|
221 |
|
222 * `action` to get only transactions doing the given action (action in |
|
223 'C', 'U', 'D', 'A', 'R'). If `etype`, action can only be 'C', 'U' or |
|
224 'D'. |
|
225 |
|
226 * `public`: when additional filtering is provided, their are by default |
|
227 only searched in 'public' actions, unless a `public` argument is given |
|
228 and set to false. |
|
229 """ |
|
230 # the ``with`` dance is transitional. We do not have Standalone |
|
231 # Connection yet so we use this trick to unsure the session have the |
|
232 # proper cnx loaded. This can be simplified one we have Standalone |
|
233 # Connection object |
|
234 with self._srv_cnx as cnx: |
|
235 source = cnx.repo.system_source |
|
236 txinfos = source.undoable_transactions(cnx, ueid, **actionfilters) |
|
237 for txinfo in txinfos: |
|
238 txinfo.req = req or self # XXX mostly wrong |
|
239 return txinfos |
|
240 |
|
241 def transaction_info(self, txuuid, req=None): |
|
242 """Return transaction object for the given uid. |
|
243 |
|
244 raise `NoSuchTransaction` if not found or if session's user is not |
|
245 allowed (eg not in managers group and the transaction doesn't belong to |
|
246 him). |
|
247 """ |
|
248 # the ``with`` dance is transitional. We do not have Standalone |
|
249 # Connection yet so we use this trick to unsure the session have the |
|
250 # proper cnx loaded. This can be simplified one we have Standalone |
|
251 # Connection object |
|
252 with self._srv_cnx as cnx: |
|
253 txinfo = cnx.repo.system_source.tx_info(cnx, txuuid) |
|
254 if req: |
|
255 txinfo.req = req |
|
256 else: |
|
257 txinfo.cnx = self |
|
258 return txinfo |
|
259 |
|
260 def transaction_actions(self, txuuid, public=True): |
|
261 """Return an ordered list of action effectued during that transaction. |
|
262 |
|
263 If public is true, return only 'public' actions, eg not ones triggered |
|
264 under the cover by hooks, else return all actions. |
|
265 |
|
266 raise `NoSuchTransaction` if the transaction is not found or if |
|
267 session's user is not allowed (eg not in managers group and the |
|
268 transaction doesn't belong to him). |
|
269 """ |
|
270 # the ``with`` dance is transitional. We do not have Standalone |
|
271 # Connection yet so we use this trick to unsure the session have the |
|
272 # proper cnx loaded. This can be simplified one we have Standalone |
|
273 # Connection object |
|
274 with self._srv_cnx as cnx: |
|
275 return cnx.repo.system_source.tx_actions(cnx, txuuid, public) |
|
276 |
|
277 def undo_transaction(self, txuuid): |
|
278 """Undo the given transaction. Return potential restoration errors. |
|
279 |
|
280 raise `NoSuchTransaction` if not found or if session's user is not |
|
281 allowed (eg not in managers group and the transaction doesn't belong to |
|
282 him). |
|
283 """ |
|
284 # the ``with`` dance is transitional. We do not have Standalone |
|
285 # Connection yet so we use this trick to unsure the session have the |
|
286 # proper cnx loaded. This can be simplified one we have Standalone |
|
287 # Connection object |
|
288 with self._srv_cnx as cnx: |
|
289 return cnx.repo.system_source.undo_transaction(cnx, txuuid) |