15 |
15 |
16 class TimedCache(dict): |
16 class TimedCache(dict): |
17 def __init__(self, ttlm, ttls=0): |
17 def __init__(self, ttlm, ttls=0): |
18 # time to live in minutes |
18 # time to live in minutes |
19 self.ttl = timedelta(0, ttlm*60 + ttls, 0) |
19 self.ttl = timedelta(0, ttlm*60 + ttls, 0) |
20 |
20 |
21 def __setitem__(self, key, value): |
21 def __setitem__(self, key, value): |
22 dict.__setitem__(self, key, (datetime.now(), value)) |
22 dict.__setitem__(self, key, (datetime.now(), value)) |
23 |
23 |
24 def __getitem__(self, key): |
24 def __getitem__(self, key): |
25 return dict.__getitem__(self, key)[1] |
25 return dict.__getitem__(self, key)[1] |
26 |
26 |
27 def clear_expired(self): |
27 def clear_expired(self): |
28 now_ = datetime.now() |
28 now_ = datetime.now() |
29 ttl = self.ttl |
29 ttl = self.ttl |
30 for key, (timestamp, value) in self.items(): |
30 for key, (timestamp, value) in self.items(): |
31 if now_ - timestamp > ttl: |
31 if now_ - timestamp > ttl: |
52 uri = None |
52 uri = None |
53 # a reference to the system information helper |
53 # a reference to the system information helper |
54 repo = None |
54 repo = None |
55 # a reference to the application'schema (may differs from the source'schema) |
55 # a reference to the application'schema (may differs from the source'schema) |
56 schema = None |
56 schema = None |
57 |
57 |
58 def __init__(self, repo, appschema, source_config, *args, **kwargs): |
58 def __init__(self, repo, appschema, source_config, *args, **kwargs): |
59 self.repo = repo |
59 self.repo = repo |
60 self.uri = source_config['uri'] |
60 self.uri = source_config['uri'] |
61 set_log_methods(self, getLogger('cubicweb.sources.'+self.uri)) |
61 set_log_methods(self, getLogger('cubicweb.sources.'+self.uri)) |
62 self.set_schema(appschema) |
62 self.set_schema(appschema) |
63 self.support_relations['identity'] = False |
63 self.support_relations['identity'] = False |
64 |
64 |
65 def init_creating(self): |
65 def init_creating(self): |
66 """method called by the repository once ready to create a new instance""" |
66 """method called by the repository once ready to create a new instance""" |
67 pass |
67 pass |
68 |
68 |
69 def init(self): |
69 def init(self): |
70 """method called by the repository once ready to handle request""" |
70 """method called by the repository once ready to handle request""" |
71 pass |
71 pass |
72 |
72 |
73 def reset_caches(self): |
73 def reset_caches(self): |
74 """method called during test to reset potential source caches""" |
74 """method called during test to reset potential source caches""" |
75 pass |
75 pass |
76 |
76 |
77 def clear_eid_cache(self, eid, etype): |
77 def clear_eid_cache(self, eid, etype): |
78 """clear potential caches for the given eid""" |
78 """clear potential caches for the given eid""" |
79 pass |
79 pass |
80 |
80 |
81 def __repr__(self): |
81 def __repr__(self): |
82 return '<%s source @%#x>' % (self.uri, id(self)) |
82 return '<%s source @%#x>' % (self.uri, id(self)) |
83 |
83 |
84 def __cmp__(self, other): |
84 def __cmp__(self, other): |
85 """simple comparison function to get predictable source order, with the |
85 """simple comparison function to get predictable source order, with the |
202 % (dbhelper.fti_table, dbhelper.fti_table, |
202 % (dbhelper.fti_table, dbhelper.fti_table, |
203 dbhelper.fti_uid_attr), |
203 dbhelper.fti_uid_attr), |
204 {'uri': self.uri}) |
204 {'uri': self.uri}) |
205 session.system_sql('DELETE FROM entities WHERE source=%(uri)s', |
205 session.system_sql('DELETE FROM entities WHERE source=%(uri)s', |
206 {'uri': self.uri}) |
206 {'uri': self.uri}) |
207 |
207 |
208 # abstract methods to override (at least) in concrete source classes ####### |
208 # abstract methods to override (at least) in concrete source classes ####### |
209 |
209 |
210 def get_connection(self): |
210 def get_connection(self): |
211 """open and return a connection to the source""" |
211 """open and return a connection to the source""" |
212 raise NotImplementedError() |
212 raise NotImplementedError() |
213 |
213 |
214 def check_connection(self, cnx): |
214 def check_connection(self, cnx): |
215 """check connection validity, return None if the connection is still valid |
215 """check connection validity, return None if the connection is still valid |
216 else a new connection (called when the pool using the given connection is |
216 else a new connection (called when the pool using the given connection is |
217 being attached to a session) |
217 being attached to a session) |
218 |
218 |
219 do nothing by default |
219 do nothing by default |
220 """ |
220 """ |
221 pass |
221 pass |
222 |
222 |
223 def pool_reset(self, cnx): |
223 def pool_reset(self, cnx): |
224 """the pool using the given connection is being reseted from its current |
224 """the pool using the given connection is being reseted from its current |
225 attached session |
225 attached session |
226 |
226 |
227 do nothing by default |
227 do nothing by default |
228 """ |
228 """ |
229 pass |
229 pass |
230 |
230 |
231 def authenticate(self, session, login, password): |
231 def authenticate(self, session, login, password): |
232 """if the source support CWUser entity type, it should implements |
232 """if the source support CWUser entity type, it should implements |
233 this method which should return CWUser eid for the given login/password |
233 this method which should return CWUser eid for the given login/password |
234 if this account is defined in this source and valid login / password is |
234 if this account is defined in this source and valid login / password is |
235 given. Else raise `AuthenticationError` |
235 given. Else raise `AuthenticationError` |
236 """ |
236 """ |
237 raise NotImplementedError() |
237 raise NotImplementedError() |
238 |
238 |
239 def syntax_tree_search(self, session, union, |
239 def syntax_tree_search(self, session, union, |
240 args=None, cachekey=None, varmap=None, debug=0): |
240 args=None, cachekey=None, varmap=None, debug=0): |
241 """return result from this source for a rql query (actually from a rql |
241 """return result from this source for a rql query (actually from a rql |
242 syntax tree and a solution dictionary mapping each used variable to a |
242 syntax tree and a solution dictionary mapping each used variable to a |
243 possible type). If cachekey is given, the query necessary to fetch the |
243 possible type). If cachekey is given, the query necessary to fetch the |
244 results (but not the results themselves) may be cached using this key. |
244 results (but not the results themselves) may be cached using this key. |
245 """ |
245 """ |
246 raise NotImplementedError() |
246 raise NotImplementedError() |
247 |
247 |
248 def flying_insert(self, table, session, union, args=None, varmap=None): |
248 def flying_insert(self, table, session, union, args=None, varmap=None): |
249 """similar as .syntax_tree_search, but inserts data in the temporary |
249 """similar as .syntax_tree_search, but inserts data in the temporary |
250 table (on-the-fly if possible, eg for the system source whose the given |
250 table (on-the-fly if possible, eg for the system source whose the given |
251 cursor come from). If not possible, inserts all data by calling |
251 cursor come from). If not possible, inserts all data by calling |
252 .executemany(). |
252 .executemany(). |
253 """ |
253 """ |
254 res = self.syntax_tree_search(session, union, args, varmap=varmap) |
254 res = self.syntax_tree_search(session, union, args, varmap=varmap) |
255 session.pool.source('system')._manual_insert(res, table, session) |
255 session.pool.source('system')._manual_insert(res, table, session) |
256 |
256 |
257 |
257 |
258 # system source don't have to implement the two methods below |
258 # system source don't have to implement the two methods below |
259 |
259 |
260 def before_entity_insertion(self, session, lid, etype, eid): |
260 def before_entity_insertion(self, session, lid, etype, eid): |
261 """called by the repository when an eid has been attributed for an |
261 """called by the repository when an eid has been attributed for an |
262 entity stored here but the entity has not been inserted in the system |
262 entity stored here but the entity has not been inserted in the system |
263 table yet. |
263 table yet. |
264 |
264 |
265 This method must return the an Entity instance representation of this |
265 This method must return the an Entity instance representation of this |
266 entity. |
266 entity. |
267 """ |
267 """ |
268 entity = self.repo.vreg.etype_class(etype)(session, None) |
268 entity = self.repo.vreg.etype_class(etype)(session, None) |
269 entity.set_eid(eid) |
269 entity.set_eid(eid) |
270 return entity |
270 return entity |
271 |
271 |
272 def after_entity_insertion(self, session, lid, entity): |
272 def after_entity_insertion(self, session, lid, entity): |
273 """called by the repository after an entity stored here has been |
273 """called by the repository after an entity stored here has been |
274 inserted in the system table. |
274 inserted in the system table. |
275 """ |
275 """ |
276 pass |
276 pass |
278 # read-only sources don't have to implement methods below |
278 # read-only sources don't have to implement methods below |
279 |
279 |
280 def get_extid(self, entity): |
280 def get_extid(self, entity): |
281 """return the external id for the given newly inserted entity""" |
281 """return the external id for the given newly inserted entity""" |
282 raise NotImplementedError() |
282 raise NotImplementedError() |
283 |
283 |
284 def add_entity(self, session, entity): |
284 def add_entity(self, session, entity): |
285 """add a new entity to the source""" |
285 """add a new entity to the source""" |
286 raise NotImplementedError() |
286 raise NotImplementedError() |
287 |
287 |
288 def update_entity(self, session, entity): |
288 def update_entity(self, session, entity): |
289 """update an entity in the source""" |
289 """update an entity in the source""" |
290 raise NotImplementedError() |
290 raise NotImplementedError() |
291 |
291 |
292 def delete_entity(self, session, etype, eid): |
292 def delete_entity(self, session, etype, eid): |
294 raise NotImplementedError() |
294 raise NotImplementedError() |
295 |
295 |
296 def add_relation(self, session, subject, rtype, object): |
296 def add_relation(self, session, subject, rtype, object): |
297 """add a relation to the source""" |
297 """add a relation to the source""" |
298 raise NotImplementedError() |
298 raise NotImplementedError() |
299 |
299 |
300 def delete_relation(self, session, subject, rtype, object): |
300 def delete_relation(self, session, subject, rtype, object): |
301 """delete a relation from the source""" |
301 """delete a relation from the source""" |
302 raise NotImplementedError() |
302 raise NotImplementedError() |
303 |
303 |
304 # system source interface ################################################# |
304 # system source interface ################################################# |
305 |
305 |
306 def eid_type_source(self, session, eid): |
306 def eid_type_source(self, session, eid): |
307 """return a tuple (type, source, extid) for the entity with id <eid>""" |
307 """return a tuple (type, source, extid) for the entity with id <eid>""" |
308 raise NotImplementedError() |
308 raise NotImplementedError() |
309 |
309 |
310 def create_eid(self, session): |
310 def create_eid(self, session): |
311 raise NotImplementedError() |
311 raise NotImplementedError() |
312 |
312 |
313 def add_info(self, session, entity, source, extid=None): |
313 def add_info(self, session, entity, source, extid=None): |
314 """add type and source info for an eid into the system table""" |
314 """add type and source info for an eid into the system table""" |
317 def delete_info(self, session, eid, etype, uri, extid): |
317 def delete_info(self, session, eid, etype, uri, extid): |
318 """delete system information on deletion of an entity by transfering |
318 """delete system information on deletion of an entity by transfering |
319 record from the entities table to the deleted_entities table |
319 record from the entities table to the deleted_entities table |
320 """ |
320 """ |
321 raise NotImplementedError() |
321 raise NotImplementedError() |
322 |
322 |
323 def fti_unindex_entity(self, session, eid): |
323 def fti_unindex_entity(self, session, eid): |
324 """remove text content for entity with the given eid from the full text |
324 """remove text content for entity with the given eid from the full text |
325 index |
325 index |
326 """ |
326 """ |
327 raise NotImplementedError() |
327 raise NotImplementedError() |
328 |
328 |
329 def fti_index_entity(self, session, entity): |
329 def fti_index_entity(self, session, entity): |
330 """add text content of a created/modified entity to the full text index |
330 """add text content of a created/modified entity to the full text index |
331 """ |
331 """ |
332 raise NotImplementedError() |
332 raise NotImplementedError() |
333 |
333 |
334 def modified_entities(self, session, etypes, mtime): |
334 def modified_entities(self, session, etypes, mtime): |
335 """return a 2-uple: |
335 """return a 2-uple: |
336 * list of (etype, eid) of entities of the given types which have been |
336 * list of (etype, eid) of entities of the given types which have been |
337 modified since the given timestamp (actually entities whose full text |
337 modified since the given timestamp (actually entities whose full text |
338 index content has changed) |
338 index content has changed) |
344 # sql system source interface ############################################# |
344 # sql system source interface ############################################# |
345 |
345 |
346 def sqlexec(self, session, sql, args=None): |
346 def sqlexec(self, session, sql, args=None): |
347 """execute the query and return its result""" |
347 """execute the query and return its result""" |
348 raise NotImplementedError() |
348 raise NotImplementedError() |
349 |
349 |
350 def temp_table_def(self, selection, solution, table, basemap): |
350 def temp_table_def(self, selection, solution, table, basemap): |
351 raise NotImplementedError() |
351 raise NotImplementedError() |
352 |
352 |
353 def create_index(self, session, table, column, unique=False): |
353 def create_index(self, session, table, column, unique=False): |
354 raise NotImplementedError() |
354 raise NotImplementedError() |
355 |
355 |
356 def drop_index(self, session, table, column, unique=False): |
356 def drop_index(self, session, table, column, unique=False): |
357 raise NotImplementedError() |
357 raise NotImplementedError() |
358 |
358 |
359 def create_temp_table(self, session, table, schema): |
359 def create_temp_table(self, session, table, schema): |
360 raise NotImplementedError() |
360 raise NotImplementedError() |
361 |
361 |
362 def clean_temp_data(self, session, temptables): |
362 def clean_temp_data(self, session, temptables): |
363 """remove temporary data, usually associated to temporary tables""" |
363 """remove temporary data, usually associated to temporary tables""" |
364 pass |
364 pass |
365 |
365 |
366 |
366 |
367 class TrFunc(object): |
367 class TrFunc(object): |
368 """lower, upper""" |
368 """lower, upper""" |
369 def __init__(self, trname, index, attrname=None): |
369 def __init__(self, trname, index, attrname=None): |
370 self._tr = trname.lower() |
370 self._tr = trname.lower() |
371 self.index = index |
371 self.index = index |
372 self.attrname = attrname |
372 self.attrname = attrname |
373 |
373 |
374 def apply(self, resdict): |
374 def apply(self, resdict): |
375 value = resdict.get(self.attrname) |
375 value = resdict.get(self.attrname) |
376 if value is not None: |
376 if value is not None: |
377 return getattr(value, self._tr)() |
377 return getattr(value, self._tr)() |
378 return None |
378 return None |