Skip to content

Commit

Permalink
Add Python3 compatibility (RobotWebTools#300)
Browse files Browse the repository at this point in the history
* First pass at Python 3 compatibility
* message_conversion: Only call encode on a Python2 str or bytes type
* protocol.py: Changes for dict in Python3. Compatible with Python 2 too.
* More Python 3 fixes, all tests pass
* Move definition of string_types to rosbridge_library.util
  • Loading branch information
kartikmohta authored and Mohamed Behery committed Jan 9, 2018
1 parent 5c798bf commit ace5ec5
Show file tree
Hide file tree
Showing 31 changed files with 282 additions and 219 deletions.
68 changes: 33 additions & 35 deletions rosapi/src/rosapi/objectutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,33 +31,31 @@
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.

from string import find

from rosbridge_library.internal import ros_loader

# Keep track of atomic types and special types
atomics = ['bool', 'byte','int8', 'uint8', 'int16', 'uint16', 'int32', 'uint32', 'int64', 'uint64', 'float32', 'float64', 'string']
specials = ['time', 'duration']


def get_typedef(type):
""" A typedef is a dict containing the following fields:
- string type
- string[] fieldnames
- string[] fieldtypes
- int[] fieldarraylen
- string[] examples
- string[] examples
get_typedef will return a typedef dict for the specified message type """
if type in atomics:
# Atomics don't get a typedef
return None
return None

if type in specials:
# Specials get their type def mocked up
return _get_special_typedef(type)

# Fetch an instance and return its typedef
instance = ros_loader.get_message_instance(type)
instance = ros_loader.get_message_instance(type)
return _get_typedef(instance)

def get_service_request_typedef(servicetype):
Expand All @@ -68,7 +66,7 @@ def get_service_request_typedef(servicetype):

def get_service_response_typedef(servicetype):
""" Returns a typedef dict for the service response class for the specified service type """
# Get an instance of the service response class and return its typedef
# Get an instance of the service response class and return its typedef
instance = ros_loader.get_service_response_instance(servicetype)
return _get_typedef(instance)

Expand All @@ -82,7 +80,7 @@ def get_service_request_typedef_recursive(servicetype):
# Get an instance of the service request class and get its typedef
instance = ros_loader.get_service_request_instance(servicetype)
typedef = _get_typedef(instance)

# Return the list of sub-typedefs
return _get_subtypedefs_recursive(typedef, [])

Expand All @@ -91,24 +89,24 @@ def get_service_response_typedef_recursive(servicetype):
# Get an instance of the service response class and get its typedef
instance = ros_loader.get_service_response_instance(servicetype)
typedef = _get_typedef(instance)

# Return the list of sub-typedefs
return _get_subtypedefs_recursive(typedef, [])

def _get_typedef(instance):
""" Gets a typedef dict for the specified instance """
if instance is None or not hasattr(instance, "__slots__") or not hasattr(instance, "_slot_types"):
return None

fieldnames = []
fieldtypes = []
fieldarraylen = []
examples = []
for i in xrange(len(instance.__slots__)):
# Pull out the name
name = instance.__slots__[i]
name = instance.__slots__[i]
fieldnames.append(name)

# Pull out the type and determine whether it's an array
field_type = instance._slot_types[i]
arraylen = -1
Expand All @@ -117,33 +115,33 @@ def _get_typedef(instance):
arraylen = 0
field_type = field_type[:-2]
else:
split = find(field_type, '[')
split = field_type.find('[')
arraylen = int(field_type[split+1:-1])
field_type = field_type[:split]
fieldarraylen.append(arraylen)

# Get the fully qualified type
field_instance = getattr(instance, name)
fieldtypes.append(_type_name(field_type, field_instance))

# Set the example as appropriate
example = field_instance
if arraylen>=0:
example = []
elif field_type not in atomics:
example = {}
examples.append(str(example))

typedef = {
"type": _type_name_from_instance(instance),
"fieldnames": fieldnames,
"fieldtypes": fieldtypes,
"fieldarraylen": fieldarraylen,
"examples": examples
}

return typedef

def _get_special_typedef(type):
example = None
if type=="time" or type=="duration":
Expand All @@ -161,43 +159,43 @@ def _get_typedefs_recursive(type, typesseen):
if type in typesseen:
# Don't put a type if it's already been seen
return []

# Note that we have now seen this type
typesseen.append(type)

# Get the typedef for this type and make sure it's not None
typedef = get_typedef(type)

return _get_subtypedefs_recursive(typedef, typesseen)

def _get_subtypedefs_recursive(typedef, typesseen):
if typedef is None:
return []

# Create the list of subtypes and get the typedefs for fields
typedefs = [ typedef ]
for fieldtype in typedef["fieldtypes"]:
typedefs = typedefs + _get_typedefs_recursive(fieldtype, typesseen)

return typedefs

def _type_name(type, instance):
""" given a short type, and an object instance of that type,
determines and returns the fully qualified type """
""" given a short type, and an object instance of that type,
determines and returns the fully qualified type """
# The fully qualified type of atomic and special types is just their original name
if type in atomics or type in specials:
return type

# If the instance is a list, then we can get no more information from the instance.
# However, luckily, the 'type' field for list types is usually already inflated to the full type.
if isinstance(instance, list):
return type
# Otherwise, the type will come from the module and class name of the instance

# Otherwise, the type will come from the module and class name of the instance
return _type_name_from_instance(instance)

def _type_name_from_instance(instance):
mod = instance.__module__
type = mod[0:find(mod, '.')]+"/"+instance.__class__.__name__
return type
type = mod[0:mod.find('.')]+"/"+instance.__class__.__name__
return type

Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import fnmatch
from rosbridge_library.capability import Capability
from rosbridge_library.internal.publishers import manager
from rosbridge_library.util import string_types


class Registration():
Expand Down Expand Up @@ -73,8 +74,8 @@ def is_empty(self):

class Advertise(Capability):

advertise_msg_fields = [(True, "topic", (str, unicode)), (True, "type", (str, unicode))]
unadvertise_msg_fields = [(True, "topic", (str, unicode))]
advertise_msg_fields = [(True, "topic", string_types), (True, "type", string_types)]
unadvertise_msg_fields = [(True, "topic", string_types)]

topics_glob = None

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from rosbridge_library.internal.ros_loader import get_service_class
from rosbridge_library.internal import message_conversion
from rosbridge_library.capability import Capability
from rosbridge_library.util import string_types
import fnmatch
import rospy
import time
Expand Down Expand Up @@ -49,7 +50,7 @@ def handle_request(self, req):

class AdvertiseService(Capability):

advertise_service_msg_fields = [(True, "service", (str, unicode)), (True, "type", (str, unicode))]
advertise_service_msg_fields = [(True, "service", string_types), (True, "type", string_types)]

def __init__(self, protocol):
# Call superclass constructor
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,14 @@
from functools import partial
from rosbridge_library.capability import Capability
from rosbridge_library.internal.services import ServiceCaller
from rosbridge_library.util import string_types


class CallService(Capability):

call_service_msg_fields = [(True, "service", (str, unicode)),
call_service_msg_fields = [(True, "service", string_types),
(False, "fragment_size", (int, type(None))),
(False, "compression", (str, unicode))]
(False, "compression", string_types)]

services_glob = None

Expand Down Expand Up @@ -77,7 +78,7 @@ def call_service(self, message):
return
else:
self.protocol.log("debug", "No service security glob, not checking service call.")

# Check for deprecated service ID, eg. /rosbridge/topics#33
cid = extract_id(service, cid)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,12 @@
import fnmatch
from rosbridge_library.capability import Capability
from rosbridge_library.internal.publishers import manager
from rosbridge_library.util import string_types


class Publish(Capability):

publish_msg_fields = [(True, "topic", (str, unicode))]
publish_msg_fields = [(True, "topic", string_types)]

topics_glob = None

Expand Down Expand Up @@ -83,7 +84,7 @@ def publish(self, message):

# Publish the message
manager.publish(client_id, topic, msg, latch=latch, queue_size=queue_size)

def finish(self):
client_id = self.protocol.client_id
for topic in self._published:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
from rosbridge_library.capability import Capability
from rosbridge_library.internal import ros_loader, message_conversion
from rosbridge_library.util import string_types


class ServiceResponse(Capability):

service_response_msg_fields = [
(True, "service", (str, unicode)), (False, "id", (str, unicode)),
(False, "values", (str, unicode)), (True, "result", bool)
(True, "service", string_types), (False, "id", string_types),
(False, "values", string_types), (True, "result", bool)
]

def __init__(self, protocol):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@
except ImportError:
from json import dumps

from rosbridge_library.util import string_types


class Subscription():
""" Keeps track of the clients multiple calls to subscribe.
Expand Down Expand Up @@ -180,10 +182,10 @@ def f(fieldname):

class Subscribe(Capability):

subscribe_msg_fields = [(True, "topic", (str, unicode)), (False, "type", (str, unicode)),
subscribe_msg_fields = [(True, "topic", string_types), (False, "type", string_types),
(False, "throttle_rate", int), (False, "fragment_size", int),
(False, "queue_length", int), (False, "compression", (str, unicode))]
unsubscribe_msg_fields = [(True, "topic", (str, unicode))]
(False, "queue_length", int), (False, "compression", string_types)]
unsubscribe_msg_fields = [(True, "topic", string_types)]

topics_glob = None

Expand Down Expand Up @@ -242,7 +244,7 @@ def subscribe(self, msg):
def unsubscribe(self, msg):
# Pull out the ID
sid = msg.get("id", None)

self.basic_type_check(msg, self.unsubscribe_msg_fields)

topic = msg["topic"]
Expand Down
Loading

0 comments on commit ace5ec5

Please sign in to comment.