diff --git a/CMakeLists.txt b/CMakeLists.txt index 4c37b5b..1464f25 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,10 +31,10 @@ add_service_files(FILES generate_messages(DEPENDENCIES std_msgs std_srvs) -catkin_package() +catkin_package(CATKIN_DEPENDS message_runtime std_msgs std_srvs) ## Install scripts -install(PROGRAMS scripts/capability_server +catkin_install_python(PROGRAMS scripts/capability_server DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}) if(CATKIN_ENABLE_TESTING) diff --git a/package.xml b/package.xml index 34a1783..739393a 100644 --- a/package.xml +++ b/package.xml @@ -25,6 +25,8 @@ std_srvs message_runtime + std_msgs + std_srvs bondpy message_runtime diff --git a/src/capabilities/client.py b/src/capabilities/client.py index e1595f0..121aa8d 100644 --- a/src/capabilities/client.py +++ b/src/capabilities/client.py @@ -134,7 +134,7 @@ def wait_for_services(self, timeout=None, services=None): :returns: :py:obj:`True` is the services are available, :py:obj:`False` otherwise (timeout) :rtype: :py:obj:`bool` """ - services = self._services.keys() if services is None else services + services = list(self._services.keys()) if services is None else services assert isinstance(services, list), services for service in services: if service not in self._services: diff --git a/src/capabilities/discovery.py b/src/capabilities/discovery.py index de9b06d..d4b3db0 100644 --- a/src/capabilities/discovery.py +++ b/src/capabilities/discovery.py @@ -436,7 +436,7 @@ def names(self): :rtype: :py:obj:`list` (:py:obj:`str`) """ return list(itertools.chain( - self.interfaces.keys(), self.semantic_interfaces.keys(), self.providers.keys())) + list(self.interfaces.keys()), list(self.semantic_interfaces.keys()), list(self.providers.keys()))) @property def specs(self): diff --git a/src/capabilities/server.py b/src/capabilities/server.py index 630a241..bc5b8ae 100644 --- a/src/capabilities/server.py +++ b/src/capabilities/server.py @@ -380,8 +380,11 @@ def spin(self): self.handle_get_remappings) rospy.loginfo("Capability Server Ready") - rospy.Publisher("~events", CapabilityEvent, queue_size=1000).publish( - CapabilityEvent(type=CapabilityEvent.SERVER_READY)) + heartbeat_interval = rospy.get_param('~heartbeat_interval', 1.0) + rospy.Timer( + rospy.Duration(heartbeat_interval), + lambda event: rospy.Publisher("~events", CapabilityEvent, queue_size=1000).publish( + CapabilityEvent(type=CapabilityEvent.SERVER_READY))) rospy.spin() @@ -398,7 +401,7 @@ def __load_capabilities(self): package_index = package_index_from_package_path(self.__package_paths) self.spec_file_index = spec_file_index_from_package_index(package_index) # Prune packages by black and white list - for package in self.spec_file_index.keys(): + for package in list(self.spec_file_index.keys()): if self.__package_whitelist and package not in self.__package_whitelist: rospy.loginfo("Package '{0}' not in whitelist, skipping.".format(package)) del self.spec_file_index[package] @@ -434,9 +437,9 @@ def __load_capabilities(self): spec_index.remove_provider(provider.name) self.__spec_index = spec_index # Prune spec_file_index - spec_paths = spec_index.interface_paths.values() + \ - spec_index.semantic_interface_paths.values() + \ - spec_index.provider_paths.values() + spec_paths = list(spec_index.interface_paths.values()) + \ + list(spec_index.semantic_interface_paths.values()) + \ + list(spec_index.provider_paths.values()) for package_name, package_dict in self.spec_file_index.items(): for spec_type in ['capability_interface', 'semantic_capability_interface', 'capability_provider']: package_dict[spec_type][:] = [path for path in package_dict[spec_type] if path in spec_paths] @@ -579,13 +582,13 @@ def __cleanup_graph(self): """ # Collect all running capabilities running_capabilities = [x - for x in self.__capability_instances.values() + for x in list(self.__capability_instances.values()) if x.state == 'running'] for cap in running_capabilities: if cap.started_by == USER_SERVICE_REASON: # Started by user, do not garbage collect this continue - rdepends = get_reverse_depends(cap.interface, self.__capability_instances.values()) + rdepends = get_reverse_depends(cap.interface, list(self.__capability_instances.values())) if rdepends: # Someone depends on me, do not garbage collect this rospy.logdebug("Keeping the '{0}' provider of the '{1}' interface, ".format(cap.name, cap.interface) + @@ -636,7 +639,7 @@ def __stop_capability(self, name): "which is not in the list of capability instances.") return capability = self.__capability_instances[name] - rdepends = get_reverse_depends(name, self.__capability_instances.values()) + rdepends = get_reverse_depends(name, list(self.__capability_instances.values())) for cap in rdepends: if cap.state in ['stopping', 'terminated']: # pragma: no cover # It is possible that this cap was stopped by another cap in this list @@ -685,7 +688,9 @@ def __get_providers_for_interface(self, interface, allow_semantic=False): return providers # Could be empty def __start_capability(self, capability, preferred_provider): - if capability not in self.__spec_index.interfaces.keys() + self.__spec_index.semantic_interfaces.keys(): + if capability not in ( + list(self.__spec_index.interfaces.keys()) + + list(self.__spec_index.semantic_interfaces.keys())): raise RuntimeError("Capability '{0}' not found.".format(capability)) # If no preferred provider is given, use the default preferred_provider = preferred_provider or self.__default_providers[capability] @@ -909,9 +914,13 @@ def handle_get_providers(self, req): def _handle_get_providers(self, req): if req.interface: - if req.interface not in self.__spec_index.interfaces.keys() + self.__spec_index.semantic_interfaces.keys(): + if req.interface not in ( + list(self.__spec_index.interfaces.keys()) + + list(self.__spec_index.semantic_interfaces.keys())): raise RuntimeError("Capability Interface '{0}' not found.".format(req.interface)) - providers = self.__get_providers_for_interface(req.interface, allow_semantic=req.include_semantic).keys() + providers = list( + self.__get_providers_for_interface( + req.interface, allow_semantic=req.include_semantic).keys()) default_provider = self.__default_providers[req.interface] else: providers = self.__spec_index.provider_names @@ -985,7 +994,7 @@ def _handle_get_remappings(self, req): remappings[map_type].update(mapping) # Collapse remapping chains for mapping in remappings.values(): - for key, value in mapping.items(): + for key, value in list(mapping.items()): if value in mapping: mapping[key] = mapping[value] del mapping[value] diff --git a/src/capabilities/specs/common.py b/src/capabilities/specs/common.py index b6fc41d..8b9f3f4 100644 --- a/src/capabilities/specs/common.py +++ b/src/capabilities/specs/common.py @@ -38,6 +38,11 @@ from __future__ import print_function +try: + __basestring = basestring +except NameError: # pragma: no cover + __basestring = str + def validate_spec_name(name): """Validates a given spec name follows the 'package/spec_name' format @@ -60,7 +65,7 @@ def split_spec_name(name): :raises: AssertionError if spec name is not a str :raises: ValueError if spec name is invalid """ - assert isinstance(name, basestring), "a spec name must be a string" + assert isinstance(name, __basestring), "a spec name must be a string" split_name = name.split('/') if len(split_name) != 2 or not split_name[0] or not split_name[1]: raise ValueError("Invalid spec name '{0}', it should be of the form 'package/spec_name'".format(name)) diff --git a/src/capabilities/specs/interface.py b/src/capabilities/specs/interface.py index 2f41b84..a0c0be5 100644 --- a/src/capabilities/specs/interface.py +++ b/src/capabilities/specs/interface.py @@ -122,7 +122,7 @@ def capability_interface_from_file_path(file_path): :raises: :py:exc:`OSError` if the given file does not exist """ with open(os.path.abspath(file_path), 'r') as f: - return capability_interface_from_dict(yaml.load(f), file_path) + return capability_interface_from_dict(yaml.safe_load(f), file_path) def capability_interface_from_file(file_handle): @@ -136,7 +136,7 @@ def capability_interface_from_file(file_handle): :rtype: :py:class:`CapabilityInterface` :raises: :py:exc:`OSError` if the given file does not exist """ - return capability_interface_from_dict(yaml.load(file_handle.read()), file_handle.name) + return capability_interface_from_dict(yaml.safe_load(file_handle.read()), file_handle.name) def capability_interface_from_string(string, file_name=''): @@ -152,7 +152,7 @@ def capability_interface_from_string(string, file_name=''): :rtype: :py:class:`CapabilityInterface` :raises: :py:exc:`AttributeError` if the given value for string is not a str """ - return capability_interface_from_dict(yaml.load(string), file_name) + return capability_interface_from_dict(yaml.safe_load(string), file_name) def capability_interface_from_dict(spec, file_name=''): @@ -226,7 +226,7 @@ def __add_element_to_interface(name, element, group_name=None): for group in element_groups: if group in ['requires', 'provides']: elements = element_groups[group] or {} - for name, element in elements.iteritems(): + for name, element in elements.items(): __add_element_to_interface(name, element, group) else: __add_element_to_interface(group, element_groups[group]) @@ -485,7 +485,7 @@ def __init__(self, name, spec_version, description=None): def __str__(self): elements = "topics:\n" - required_and_provided = list(self.required_topics.keys() + self.provided_topics.keys()) + required_and_provided = list(self.required_topics.keys()) + list(self.provided_topics.keys()) both = [x for x in self.topics if x not in required_and_provided] for name, topic in self.topics.items(): if name not in both: @@ -504,7 +504,7 @@ def __str__(self): elements += "\n ".join(str(topic).splitlines()) elements += "\n" elements += " services:\n" - required_and_provided = list(self.required_services.keys() + self.provided_services.keys()) + required_and_provided = list(self.required_services.keys()) + list(self.provided_services.keys()) both = [x for x in self.services if x not in required_and_provided] for name, service in self.services.items(): if name not in both: @@ -523,7 +523,7 @@ def __str__(self): elements += "\n ".join(str(service).splitlines()) elements += "\n" elements += " actions:\n" - required_and_provided = list(self.required_actions.keys() + self.provided_actions.keys()) + required_and_provided = list(self.required_actions.keys()) + list(self.provided_actions.keys()) both = [x for x in self.actions if x not in required_and_provided] for name, action in self.actions.items(): if name not in both: @@ -542,7 +542,7 @@ def __str__(self): elements += "\n ".join(str(action).splitlines()) elements += "\n" elements += " parameters:\n" - required_and_provided = list(self.required_parameters.keys() + self.provided_parameters.keys()) + required_and_provided = list(self.required_parameters.keys()) + list(self.provided_parameters.keys()) both = [x for x in self.parameters if x not in required_and_provided] for name, parameter in self.parameters.items(): if name not in both: diff --git a/src/capabilities/specs/provider.py b/src/capabilities/specs/provider.py index 34622c1..b857582 100644 --- a/src/capabilities/specs/provider.py +++ b/src/capabilities/specs/provider.py @@ -109,7 +109,7 @@ def capability_provider_from_file_path(file_path): :raises: :py:exc:`OSError` if the given file does not exist """ with open(os.path.abspath(file_path), 'r') as f: - return capability_provider_from_dict(yaml.load(f.read()), file_path) + return capability_provider_from_dict(yaml.safe_load(f.read()), file_path) def capability_provider_from_file(file_handle): @@ -123,7 +123,7 @@ def capability_provider_from_file(file_handle): :rtype: :py:class:`CapabilityProvider` :raises: :py:exc:`OSError` if the given file does not exist """ - return capability_provider_from_dict(yaml.load(file_handle.read()), file_handle.name) + return capability_provider_from_dict(yaml.safe_load(file_handle.read()), file_handle.name) def capability_provider_from_string(string, file_name=''): @@ -139,7 +139,7 @@ def capability_provider_from_string(string, file_name=''): :rtype: :py:class:`CapabilityProvider` :raises: :py:exc:`AttributeError` if the given value for string is not a str """ - return capability_provider_from_dict(yaml.load(string), file_name) + return capability_provider_from_dict(yaml.safe_load(string), file_name) def capability_provider_from_dict(spec, file_name=''): @@ -197,7 +197,7 @@ def capability_provider_from_dict(spec, file_name=''): raise InvalidProvider("Invalid depends_on section, expected dict got: '{0}'".format(type(depends_on)), file_name) valid_conditionals = ['provider'] - for interface, conditions in depends_on.iteritems(): + for interface, conditions in depends_on.items(): if not isinstance(conditions, dict): raise InvalidProvider("Invalid depends_on conditional section, expected dict got: '{0}'" .format(type(conditions)), file_name) diff --git a/src/capabilities/specs/semantic_interface.py b/src/capabilities/specs/semantic_interface.py index 92b2ba7..cf5ce85 100644 --- a/src/capabilities/specs/semantic_interface.py +++ b/src/capabilities/specs/semantic_interface.py @@ -116,7 +116,7 @@ def semantic_capability_interface_from_file_path(file_path): :raises: :py:exc:`OSError` if the given file does not exist """ with open(os.path.abspath(file_path), 'r') as f: - return semantic_capability_interface_from_dict(yaml.load(f.read()), file_path) + return semantic_capability_interface_from_dict(yaml.safe_load(f.read()), file_path) def semantic_capability_interface_from_file(file_handle): @@ -130,7 +130,7 @@ def semantic_capability_interface_from_file(file_handle): :rtype: :py:class:`SemanticCapabilityInterface` :raises: :py:exc:`OSError` if the given file does not exist """ - return semantic_capability_interface_from_dict(yaml.load(file_handle.read()), file_handle.name) + return semantic_capability_interface_from_dict(yaml.safe_load(file_handle.read()), file_handle.name) def semantic_capability_interface_from_string(string, file_name=''): @@ -146,7 +146,7 @@ def semantic_capability_interface_from_string(string, file_name=''): :rtype: :py:class:`SemanticCapabilityInterface` :raises: :py:exc:`AttributeError` if the given value for string is not a str """ - return semantic_capability_interface_from_dict(yaml.load(string), file_name) + return semantic_capability_interface_from_dict(yaml.safe_load(string), file_name) def semantic_capability_interface_from_dict(spec, file_name=''): diff --git a/test/rostest/test_server/test_client.py b/test/rostest/test_server/test_client.py index 0cae4b2..43d552a 100755 --- a/test/rostest/test_server/test_client.py +++ b/test/rostest/test_server/test_client.py @@ -32,7 +32,7 @@ def wait_for_result_to_happen(expected, initial_result, tries=10, sleep_period=1 class Test(unittest.TestCase): def test_use_and_free_capability(self): - assert wait_for_capability_server(10) + assert wait_for_capability_server(30) c = CapabilitiesClient() c.wait_for_services(timeout=3.0) # Give invalid bond id to use_capability diff --git a/test/rostest/test_server/test_default_provider.py b/test/rostest/test_server/test_default_provider.py index 73fdb8b..bb7cf27 100755 --- a/test/rostest/test_server/test_default_provider.py +++ b/test/rostest/test_server/test_default_provider.py @@ -16,7 +16,7 @@ class Test(unittest.TestCase): def test_default_provider(self): - assert wait_for_capability_server(10) + assert wait_for_capability_server(30) call_service('/capability_server/start_capability', 'no_default_provider_pkg/Minimal', '') rospy.sleep(1) # Wait for the system to settle resp = call_service('/capability_server/get_running_capabilities') diff --git a/test/rostest/test_server/test_ros_services.test b/test/rostest/test_server/test_ros_services.test index 1acc4fe..0abe310 100644 --- a/test/rostest/test_server/test_ros_services.test +++ b/test/rostest/test_server/test_ros_services.test @@ -17,5 +17,5 @@ 'invalid' - + diff --git a/test/unit/common.py b/test/unit/common.py index 8849c2e..84063ba 100644 --- a/test/unit/common.py +++ b/test/unit/common.py @@ -3,7 +3,12 @@ import re import sys -from StringIO import StringIO +try: + # Python 2 + from cStringIO import StringIO +except ImportError: + # Python 3 + from io import StringIO def assert_raises(exception_classes, callable_obj=None, *args, **kwargs): diff --git a/test/unit/specs/test_interface.py b/test/unit/specs/test_interface.py index 108b1b8..9aa0b27 100644 --- a/test/unit/specs/test_interface.py +++ b/test/unit/specs/test_interface.py @@ -32,7 +32,7 @@ def check_navigation(ci): ci.provided_parameters ci.required_parameters check_interface(ci) - str(ci.topics.values()[0]) + str(list(ci.topics.values())[0]) def check_all_interfaces(ci): @@ -80,7 +80,7 @@ def check_minimal(ci): def test_capability_interface_from_file_path(): default_checker = lambda x: None - for test_file, (checker, expected_exception, expected_exception_regex) in test_files_map.iteritems(): + for test_file, (checker, expected_exception, expected_exception_regex) in test_files_map.items(): checker = checker or default_checker print('running test on file ' + test_file) test_file_path = os.path.join(test_data_dir, test_file) diff --git a/test/unit/specs/test_provider.py b/test/unit/specs/test_provider.py index a883c9d..74e33a0 100644 --- a/test/unit/specs/test_provider.py +++ b/test/unit/specs/test_provider.py @@ -58,7 +58,7 @@ def check_minimal(cp): def test_capability_provider_from_file_path(): default_checker = lambda x: None print() # Keeps the output clean when doing nosetests -s - for test_file, (checker, expected_exception, expected_exception_regex) in test_files_map.iteritems(): + for test_file, (checker, expected_exception, expected_exception_regex) in test_files_map.items(): checker = checker or default_checker print('running test on file ' + test_file) test_file_path = os.path.join(test_data_dir, test_file) diff --git a/test/unit/specs/test_semantic_interface.py b/test/unit/specs/test_semantic_interface.py index e3efd66..21c0fab 100644 --- a/test/unit/specs/test_semantic_interface.py +++ b/test/unit/specs/test_semantic_interface.py @@ -48,7 +48,7 @@ def check_minimal(sci): def test_semantic_capability_interface_from_file_path(): default_checker = lambda x: None print() # Keeps the output clean when doing nosetests -s - for test_file, (checker, expected_exception, expected_exception_regex) in test_files_map.iteritems(): + for test_file, (checker, expected_exception, expected_exception_regex) in test_files_map.items(): checker = checker or default_checker print('running test on file ' + test_file) test_file_path = os.path.join(test_data_dir, test_file) diff --git a/test/unit/test_client.py b/test/unit/test_client.py index 65304bb..4be785e 100644 --- a/test/unit/test_client.py +++ b/test/unit/test_client.py @@ -1,4 +1,4 @@ -from common import assert_raises +from .common import assert_raises from capabilities.client import CapabilitiesClient from capabilities.client import ServiceNotAvailableException diff --git a/test/unit/test_server.py b/test/unit/test_server.py index 7e51e82..bd5efe5 100644 --- a/test/unit/test_server.py +++ b/test/unit/test_server.py @@ -60,7 +60,7 @@ def test_get_reverse_depends(): instances['foo'].depends_on = ['bar'] instances['bar'].depends_on = [] instances['baz'].depends_on = ['bar'] - result = [x.name for x in server.get_reverse_depends('bar', instances.values())] + result = [x.name for x in server.get_reverse_depends('bar', list(instances.values()))] assert sorted(['foo_pkg/foo', 'baz_pkg/baz']) == sorted(result), sorted(result) except ImportError as exc: