server/sources/__init__.py
branchtls-sprint
changeset 1792 9eadf34fd860
parent 1398 5fe84a5f7035
child 1977 606923dff11b
equal deleted inserted replaced
1791:c77629112437 1792:9eadf34fd860
    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:
    39     # modified in this source
    39     # modified in this source
    40     should_call_hooks = True
    40     should_call_hooks = True
    41     # boolean telling if the repository should connect to this source during
    41     # boolean telling if the repository should connect to this source during
    42     # migration
    42     # migration
    43     connect_for_migration = True
    43     connect_for_migration = True
    44     
    44 
    45     # mappings telling which entities and relations are available in the source
    45     # mappings telling which entities and relations are available in the source
    46     # keys are supported entity/relation types and values are boolean indicating
    46     # keys are supported entity/relation types and values are boolean indicating
    47     # wether the support is read-only (False) or read-write (True)
    47     # wether the support is read-only (False) or read-write (True)
    48     support_entities = {}
    48     support_entities = {}
    49     support_relations = {}
    49     support_relations = {}
    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
    90         if self.uri == 'system':
    90         if self.uri == 'system':
    91             return 1
    91             return 1
    92         if other.uri == 'system':
    92         if other.uri == 'system':
    93             return -1
    93             return -1
    94         return cmp(self.uri, other.uri)
    94         return cmp(self.uri, other.uri)
    95         
    95 
    96     def set_schema(self, schema):
    96     def set_schema(self, schema):
    97         """set the application'schema"""
    97         """set the application'schema"""
    98         self.schema = schema
    98         self.schema = schema
    99         
    99 
   100     def support_entity(self, etype, write=False):
   100     def support_entity(self, etype, write=False):
   101         """return true if the given entity's type is handled by this adapter
   101         """return true if the given entity's type is handled by this adapter
   102         if write is true, return true only if it's a RW support
   102         if write is true, return true only if it's a RW support
   103         """
   103         """
   104         try:
   104         try:
   106         except KeyError:
   106         except KeyError:
   107             return False
   107             return False
   108         if write:
   108         if write:
   109             return wsupport
   109             return wsupport
   110         return True
   110         return True
   111     
   111 
   112     def support_relation(self, rtype, write=False):
   112     def support_relation(self, rtype, write=False):
   113         """return true if the given relation's type is handled by this adapter
   113         """return true if the given relation's type is handled by this adapter
   114         if write is true, return true only if it's a RW support
   114         if write is true, return true only if it's a RW support
   115 
   115 
   116         current implementation return true if the relation is defined into 
   116         current implementation return true if the relation is defined into
   117         `support_relations` or if it is a final relation of a supported entity 
   117         `support_relations` or if it is a final relation of a supported entity
   118         type
   118         type
   119         """
   119         """
   120         try:
   120         try:
   121             wsupport = self.support_relations[rtype]
   121             wsupport = self.support_relations[rtype]
   122         except KeyError:
   122         except KeyError:
   131                     continue
   131                     continue
   132             else:
   132             else:
   133                 return False
   133                 return False
   134         if write:
   134         if write:
   135             return wsupport
   135             return wsupport
   136         return True    
   136         return True
   137     
   137 
   138     def eid2extid(self, eid, session=None):
   138     def eid2extid(self, eid, session=None):
   139         return self.repo.eid2extid(self, eid, session)
   139         return self.repo.eid2extid(self, eid, session)
   140 
   140 
   141     def extid2eid(self, value, etype, session=None, **kwargs):
   141     def extid2eid(self, value, etype, session=None, **kwargs):
   142         return self.repo.extid2eid(self, value, etype, session, **kwargs)
   142         return self.repo.extid2eid(self, value, etype, session, **kwargs)
   185                 if self.support_entity(etype):
   185                 if self.support_entity(etype):
   186                     sql = 'DELETE FROM %s_relation WHERE eid_to IN (%s);' % (
   186                     sql = 'DELETE FROM %s_relation WHERE eid_to IN (%s);' % (
   187                         rschema.type, myeids)
   187                         rschema.type, myeids)
   188                     session.system_sql(sql)
   188                     session.system_sql(sql)
   189                     break
   189                     break
   190         
   190 
   191     def cleanup_entities_info(self, session):
   191     def cleanup_entities_info(self, session):
   192         """cleanup system tables from information for entities coming from
   192         """cleanup system tables from information for entities coming from
   193         this source. This should be called when a source is removed to
   193         this source. This should be called when a source is removed to
   194         properly cleanup the database
   194         properly cleanup the database
   195         """
   195         """
   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
   426     adapter_type = source_config['adapter'].lower()
   426     adapter_type = source_config['adapter'].lower()
   427     try:
   427     try:
   428         return SOURCE_TYPES[adapter_type]
   428         return SOURCE_TYPES[adapter_type]
   429     except KeyError:
   429     except KeyError:
   430         raise RuntimeError('Unknown adapter %r' % adapter_type)
   430         raise RuntimeError('Unknown adapter %r' % adapter_type)
   431     
   431 
   432 def get_source(source_config, global_schema, repo):
   432 def get_source(source_config, global_schema, repo):
   433     """return a source adapter according to the adapter field in the
   433     """return a source adapter according to the adapter field in the
   434     source's configuration
   434     source's configuration
   435     """
   435     """
   436     return source_adapter(source_config)(repo, global_schema, source_config)
   436     return source_adapter(source_config)(repo, global_schema, source_config)