abstract_db_interface.py
Go to the documentation of this file.
00001 # Base class for database interfaces.
00002 
00003 from abc import ABCMeta, abstractmethod, abstractproperty
00004 import sqlalchemy
00005 
00006 import rospkg
00007 import rospy
00008 import roslib.message
00009 
00010 # sqlalchemy engine (argument to sqlalchemy.create_engine).
00011 # Defaults to '"ros_home"/lama.sqlite'.
00012 ros_home = rospkg.get_ros_home()
00013 default_engine_name = 'sqlite:///' + ros_home + '/lama.sqlite'
00014 _engine_name = rospy.get_param('/database_engine', default_engine_name)
00015 del ros_home
00016 del default_engine_name
00017 
00018 # Table name for type description
00019 _interfaces_table_name = 'map_interfaces'
00020 
00021 
00022 class AbstractDBInterface(object):
00023     __metaclass__ = ABCMeta
00024 
00025     # Database related class attributes.
00026     engine_name = _engine_name
00027     engine = sqlalchemy.create_engine(_engine_name)
00028     metadata = sqlalchemy.MetaData()
00029     metadata.bind = engine
00030 
00031     def __init__(self, interface_name, getter_srv_type, setter_srv_type,
00032                  start=False):
00033         """Build the map interface and possibly start ROS services
00034 
00035         Parameters
00036         ----------
00037         - interface_name: string, name of the map interface.
00038         - getter_srv_type: string, service message to write into the map.
00039         - setter_srv_type: string, service message to read from the map.
00040         - start: {True|False}, defaults to True. The ROS services for getter and
00041             setter will be started only if start is True. If start is False, the
00042             clients proxies will be None.
00043         """
00044         if '@' in interface_name:
00045             rospy.logerr('@ not allowd in interface name')
00046             raise ValueError('@ not allowd in interface name')
00047 
00048         get_srv_class = roslib.message.get_service_class(getter_srv_type)
00049         set_srv_class = roslib.message.get_service_class(setter_srv_type)
00050 
00051         rospy.logdebug('Map interface: {} ({}, {})'.format(interface_name,
00052                                                            getter_srv_type,
00053                                                            setter_srv_type))
00054         rospy.logdebug('Getter class {}'.format(get_srv_class))
00055         rospy.logdebug('Getter request slots: {}'.format(
00056             get_srv_class._request_class.__slots__))
00057         rospy.logdebug('Getter response slots: {}'.format(
00058             get_srv_class._response_class.__slots__))
00059         rospy.logdebug('Setter class {}'.format(set_srv_class))
00060         rospy.logdebug('Setter request slots: {}'.format(
00061             set_srv_class._request_class.__slots__))
00062         rospy.logdebug('Setter response slots: {}'.format(
00063             set_srv_class._response_class.__slots__))
00064 
00065         # getter class
00066         self.getter_service_name = self.default_getter_service_name(
00067             interface_name)
00068         self.getter_service_class = get_srv_class
00069         self.getter_service_type = getter_srv_type
00070         # setter class
00071         self.setter_service_name = self.default_setter_service_name(
00072             interface_name)
00073         self.setter_service_class = set_srv_class
00074         self.setter_service_type = setter_srv_type
00075         # interface name
00076         self.interface_name = interface_name
00077 
00078         # Read tables from the possibly existing database.
00079         self.metadata.reflect()
00080         # Add the table for type description.
00081         self.interface_table = self._interface_table()
00082         self.metadata.create_all()
00083         # Create new tables.
00084         self._generate_schema()
00085 
00086         # Possibly start the services.
00087         self._getter_service = None
00088         self._setter_service = None
00089         if start:
00090             self.start_services()
00091 
00092         # Get the service clients.
00093         self.getter_service_proxy = rospy.ServiceProxy(
00094             self.getter_service_name,
00095             self.getter_service_class)
00096         self.setter_service_proxy = rospy.ServiceProxy(
00097             self.setter_service_name,
00098             self.setter_service_class)
00099 
00100     @classmethod
00101     def default_getter_service_name(cls, interface_name):
00102         return interface_name + '_getter'
00103 
00104     @classmethod
00105     def default_setter_service_name(cls, interface_name):
00106         return interface_name + '_setter'
00107 
00108     @abstractproperty
00109     def interface_type(self):
00110         pass
00111 
00112     @abstractmethod
00113     def _generate_schema():
00114         pass
00115 
00116     @abstractmethod
00117     def getter_callback(self):
00118         pass
00119 
00120     @abstractmethod
00121     def setter_callback(self):
00122         pass
00123 
00124     def _interface_table(self):
00125         """Return the table for the type description (may already exists).
00126 
00127         Return a table with columns ('interface_name', 'message_type',
00128         interface_type, timestamp_secs, timestamp_nsecs). If the
00129         table already exists, it will be returned.
00130         """
00131         table = sqlalchemy.Table(_interfaces_table_name,
00132                                  self.metadata,
00133                                  sqlalchemy.Column('interface_name',
00134                                                    sqlalchemy.String,
00135                                                    unique=True),
00136                                  sqlalchemy.Column('message_type',
00137                                                    sqlalchemy.String),
00138                                  sqlalchemy.Column('get_service_type',
00139                                                    sqlalchemy.String),
00140                                  sqlalchemy.Column('set_service_type',
00141                                                    sqlalchemy.String),
00142                                  sqlalchemy.Column('interface_type',
00143                                                    sqlalchemy.String),
00144                                  sqlalchemy.Column('timestamp_secs',
00145                                                    sqlalchemy.BigInteger),
00146                                  sqlalchemy.Column('timestamp_nsecs',
00147                                                    sqlalchemy.BigInteger),
00148                                  extend_existing=True)
00149 
00150         return table
00151 
00152     def _add_interface_description(self):
00153         """Add the inteface description if not already existing
00154 
00155         Add the inteface description with unconflicting
00156         (interface_name / message_type / interface_type).
00157 
00158         A ValueError is raised if a table with the same name (i.e. same
00159         interface_name) but different message type and interface type already
00160         exists.
00161         """
00162         # Check for an existing table.
00163         table = self.interface_table
00164         name = self.interface_name
00165         msg_type = self.getter_service_class._response_class._slot_types[0]
00166 
00167         query = table.select(whereclause=(table.c.interface_name == name))
00168         connection = self.engine.connect()
00169         with connection.begin():
00170             result = connection.execute(query).fetchone()
00171 
00172         add_interface = True
00173         if result:
00174             add_interface = False
00175             if (result['message_type'] != msg_type or
00176                 result['interface_type'] != self.interface_type):
00177                 err = ('A table "{}" with message type "{}" and interface ' +
00178                        'type "{}" already exists, cannot change to ' +
00179                        '"{}"/"{}"').format(
00180                            name,
00181                            result['message_type'], result['interface_type'],
00182                            msg_type, self.interface_type)
00183                 rospy.logfatal(err)
00184                 raise rospy.ROSException(err)
00185 
00186         # Add the table description.
00187         if add_interface:
00188             insert_args = {
00189                 'interface_name': name,
00190                 'message_type': msg_type,
00191                 'interface_type': self.interface_type,
00192                 'get_service_type': self.getter_service_type,
00193                 'set_service_type': self.setter_service_type,
00194             }
00195             with connection.begin():
00196                 connection.execute(table.insert(), insert_args)
00197         connection.close()
00198 
00199     def _get_last_modified(self):
00200         """Return the date of last modification for this interface
00201 
00202         Return a rospy.Time instance.
00203         """
00204         table = self.interface_table
00205         name = self.interface_name
00206         query = table.select(whereclause=(table.c.interface_name == name))
00207         connection = self.engine.connect()
00208         with connection.begin():
00209             result = connection.execute(query).fetchone()
00210         connection.close()
00211 
00212         if not result:
00213             raise rospy.ServiceException('Corrupted database')
00214         time = rospy.Time(0)
00215         if result['timestamp_secs'] is None:
00216             return time
00217         time.secs = result['timestamp_secs']
00218         time.nsecs = result['timestamp_nsecs']
00219 
00220     def _set_timestamp(self, time):
00221         """Set the timestamp to the given time for this interface
00222 
00223         Parameters
00224         ----------
00225         - time: a rospy.Time instance.
00226         """
00227         table = self.interface_table
00228         name = self.interface_name
00229         update_args = {
00230             'timestamp_secs': time.secs,
00231             'timestamp_nsecs': time.nsecs,
00232         }
00233         update = table.update().where(table.c.interface_name == name)
00234         connection = self.engine.connect()
00235         with connection.begin():
00236             connection.execute(update, update_args)
00237         connection.close()
00238 
00239     def has_table(self, table):
00240         """Return true if table is in the database"""
00241         if table in self.metadata.tables:
00242             return True
00243         # Tables can be created by other instances, update and check again.
00244         self.metadata.reflect()
00245         return table in self.metadata.tables
00246 
00247     def start_services(self):
00248         self._getter_service = rospy.Service(self.getter_service_name,
00249                                              self.getter_service_class,
00250                                              self.getter_callback)
00251         self._setter_service = rospy.Service(self.setter_service_name,
00252                                              self.setter_service_class,
00253                                              self.setter_callback)
00254         rospy.loginfo('Services %s and %s started',
00255                       self.getter_service_name, self.setter_service_name)


interfaces
Author(s): Gaël Ecorchard , Karel Košnar
autogenerated on Sat Jun 8 2019 19:03:14