Skip to content

Commit 80493d0

Browse files
authored
Merge pull request #1: Use namespaces
Merged from original PR #86 Original: ros2/rclpy#86
2 parents 8e912be + c465a11 commit 80493d0

17 files changed

Lines changed: 1236 additions & 127 deletions

rclpy/package.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
<test_depend>ament_cmake_nose</test_depend>
2222
<test_depend>ament_lint_auto</test_depend>
2323
<test_depend>ament_lint_common</test_depend>
24+
<test_depend>rcl_interfaces</test_depend>
2425
<test_depend>rosidl_generator_py</test_depend>
2526
<test_depend>std_msgs</test_depend>
2627

rclpy/rclpy/__init__.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,30 @@
1717
from rclpy.constants import S_TO_NS
1818
from rclpy.impl.implementation_singleton import rclpy_implementation as _rclpy
1919
from rclpy.node import Node
20+
from rclpy.validate_namespace import validate_namespace
21+
from rclpy.validate_node_name import validate_node_name
2022

2123

2224
def init(*, args=None):
23-
# Now we can use _rclpy to call the implementation specific rclpy_init().
2425
return _rclpy.rclpy_init(args if args is not None else sys.argv)
2526

2627

2728
def create_node(node_name, *, namespace=None):
28-
node_handle = _rclpy.rclpy_create_node(node_name, namespace or '')
29+
namespace = namespace or ''
30+
failed = False
31+
try:
32+
node_handle = _rclpy.rclpy_create_node(node_name, namespace)
33+
except ValueError:
34+
failed = True
35+
if failed:
36+
# these will raise more specific errors if the name or namespace is bad
37+
validate_node_name(node_name)
38+
# emulate what rcl_node_init() does to accept '' and relative namespaces
39+
if not namespace:
40+
namespace = '/'
41+
if not namespace.startswith('/'):
42+
namespace = '/' + namespace
43+
validate_namespace(namespace)
2944
return Node(node_handle)
3045

3146

rclpy/rclpy/exceptions.py

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,48 @@ def __init__(self, *args):
2020
Exception.__init__(self, 'rclpy.init() has not been called', *args)
2121

2222

23-
class NoImplementationAvailableException(Exception):
24-
"""Raised when there is no rmw implementation with a Python extension available."""
25-
26-
def __init__(self, *args):
27-
Exception.__init__(self, 'no rmw implementation with a Python extension available')
28-
29-
3023
class NoTypeSupportImportedException(Exception):
3124
"""Raised when there is no type support imported."""
3225

3326
def __init__(self, *args):
3427
Exception.__init__(self, 'no type_support imported')
28+
29+
30+
class NameValidationException(Exception):
31+
"""Raised when a topic name, node name, or namespace are invalid."""
32+
33+
def __init__(self, name_type, name, error_msg, invalid_index, *args):
34+
msg = """\
35+
Invalid {name_type}: {error_msg}:
36+
'{name}'
37+
{indent}^\
38+
""".format(name_type=name_type, name=name, error_msg=error_msg, indent=' ' * invalid_index)
39+
Exception.__init__(self, msg)
40+
41+
42+
class InvalidNamespaceException(NameValidationException):
43+
"""Raised when a namespace is invalid."""
44+
45+
def __init__(self, name, error_msg, invalid_index, *args):
46+
NameValidationException.__init__(self, "namespace", name, error_msg, invalid_index)
47+
48+
49+
class InvalidNodeNameException(NameValidationException):
50+
"""Raised when a node name is invalid."""
51+
52+
def __init__(self, name, error_msg, invalid_index, *args):
53+
NameValidationException.__init__(self, "node name", name, error_msg, invalid_index)
54+
55+
56+
class InvalidTopicNameException(NameValidationException):
57+
"""Raised when a topic name is invalid."""
58+
59+
def __init__(self, name, error_msg, invalid_index, *args):
60+
NameValidationException.__init__(self, "topic name", name, error_msg, invalid_index)
61+
62+
63+
class InvalidServiceNameException(NameValidationException):
64+
"""Raised when a service name is invalid."""
65+
66+
def __init__(self, name, error_msg, invalid_index, *args):
67+
NameValidationException.__init__(self, "service name", name, error_msg, invalid_index)

rclpy/rclpy/expand_topic_name.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Copyright 2017 Open Source Robotics Foundation, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from rclpy.impl.implementation_singleton import rclpy_implementation as _rclpy
16+
17+
18+
def expand_topic_name(topic_name, node_name, node_namespace):
19+
"""
20+
Expand a given topic name using given node name and namespace as well.
21+
22+
Note that this function can succeed but the expanded topic name may still
23+
be invalid.
24+
The :py:func:validate_full_topic_name(): should be used on the expanded
25+
topic name to ensure it is valid after expansion.
26+
27+
:param topic_name str: topic name to be expanded
28+
:param node_name str: name of the node that this topic is associated with
29+
:param namespace str: namespace that the topic is within
30+
:returns: expanded topic name which is fully qualified
31+
:raises: ValueError if the topic name, node name or namespace are invalid
32+
"""
33+
return _rclpy.rclpy_expand_topic_name(topic_name, node_name, node_namespace)

rclpy/rclpy/node.py

Lines changed: 55 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,17 @@
1515
from rclpy.client import Client
1616
from rclpy.constants import S_TO_NS
1717
from rclpy.exceptions import NoTypeSupportImportedException
18+
from rclpy.expand_topic_name import expand_topic_name
1819
from rclpy.impl.implementation_singleton import rclpy_implementation as _rclpy
1920
from rclpy.publisher import Publisher
2021
from rclpy.qos import qos_profile_default, qos_profile_services_default
2122
from rclpy.service import Service
2223
from rclpy.subscription import Subscription
2324
from rclpy.timer import WallTimer
25+
from rclpy.validate_full_topic_name import validate_full_topic_name
26+
from rclpy.validate_namespace import validate_namespace
27+
from rclpy.validate_node_name import validate_node_name
28+
from rclpy.validate_topic_name import validate_topic_name
2429

2530

2631
class Node:
@@ -44,14 +49,32 @@ def handle(self, value):
4449
def get_name(self):
4550
return _rclpy.rclpy_get_node_name(self.handle)
4651

52+
def get_namespace(self):
53+
return _rclpy.rclpy_get_node_namespace(self.handle)
54+
55+
def _validate_topic_or_service_name(self, topic_or_service_name, *, is_service=False):
56+
name = self.get_name()
57+
namespace = self.get_namespace()
58+
validate_node_name(name)
59+
validate_namespace(namespace)
60+
validate_topic_name(topic_or_service_name, is_service=is_service)
61+
expanded_topic_or_service_name = expand_topic_name(topic_or_service_name, name, namespace)
62+
validate_full_topic_name(expanded_topic_or_service_name, is_service=is_service)
63+
4764
def create_publisher(self, msg_type, topic, *, qos_profile=qos_profile_default):
4865
# this line imports the typesupport for the message module if not already done
4966
if msg_type.__class__._TYPE_SUPPORT is None:
5067
msg_type.__class__.__import_type_support__()
5168
if msg_type.__class__._TYPE_SUPPORT is None:
5269
raise NoTypeSupportImportedException
53-
publisher_handle = _rclpy.rclpy_create_publisher(
54-
self.handle, msg_type, topic, qos_profile.get_c_qos_profile())
70+
failed = False
71+
try:
72+
publisher_handle = _rclpy.rclpy_create_publisher(
73+
self.handle, msg_type, topic, qos_profile.get_c_qos_profile())
74+
except ValueError:
75+
failed = True
76+
if failed:
77+
self._validate_topic_or_service_name(topic)
5578
publisher = Publisher(publisher_handle, msg_type, topic, qos_profile, self.handle)
5679
self.publishers.append(publisher)
5780
return publisher
@@ -62,8 +85,14 @@ def create_subscription(self, msg_type, topic, callback, *, qos_profile=qos_prof
6285
msg_type.__class__.__import_type_support__()
6386
if msg_type.__class__._TYPE_SUPPORT is None:
6487
raise NoTypeSupportImportedException
65-
[subscription_handle, subscription_pointer] = _rclpy.rclpy_create_subscription(
66-
self.handle, msg_type, topic, qos_profile.get_c_qos_profile())
88+
failed = False
89+
try:
90+
[subscription_handle, subscription_pointer] = _rclpy.rclpy_create_subscription(
91+
self.handle, msg_type, topic, qos_profile.get_c_qos_profile())
92+
except ValueError:
93+
failed = True
94+
if failed:
95+
self._validate_topic_or_service_name(topic)
6796

6897
subscription = Subscription(
6998
subscription_handle, subscription_pointer, msg_type,
@@ -76,11 +105,17 @@ def create_client(self, srv_type, srv_name, *, qos_profile=qos_profile_services_
76105
srv_type.__class__.__import_type_support__()
77106
if srv_type.__class__._TYPE_SUPPORT is None:
78107
raise NoTypeSupportImportedException
79-
[client_handle, client_pointer] = _rclpy.rclpy_create_client(
80-
self.handle,
81-
srv_type,
82-
srv_name,
83-
qos_profile.get_c_qos_profile())
108+
failed = False
109+
try:
110+
[client_handle, client_pointer] = _rclpy.rclpy_create_client(
111+
self.handle,
112+
srv_type,
113+
srv_name,
114+
qos_profile.get_c_qos_profile())
115+
except ValueError:
116+
failed = True
117+
if failed:
118+
self._validate_topic_or_service_name(srv_name, is_service=True)
84119
client = Client(
85120
self.handle, client_handle, client_pointer, srv_type, srv_name, qos_profile)
86121
self.clients.append(client)
@@ -92,11 +127,17 @@ def create_service(
92127
srv_type.__class__.__import_type_support__()
93128
if srv_type.__class__._TYPE_SUPPORT is None:
94129
raise NoTypeSupportImportedException
95-
[service_handle, service_pointer] = _rclpy.rclpy_create_service(
96-
self.handle,
97-
srv_type,
98-
srv_name,
99-
qos_profile.get_c_qos_profile())
130+
failed = False
131+
try:
132+
[service_handle, service_pointer] = _rclpy.rclpy_create_service(
133+
self.handle,
134+
srv_type,
135+
srv_name,
136+
qos_profile.get_c_qos_profile())
137+
except ValueError:
138+
failed = True
139+
if failed:
140+
self._validate_topic_or_service_name(srv_name, is_service=True)
100141
service = Service(
101142
self.handle, service_handle, service_pointer,
102143
srv_type, srv_name, callback, qos_profile)
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Copyright 2017 Open Source Robotics Foundation, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from rclpy.exceptions import InvalidServiceNameException
16+
from rclpy.exceptions import InvalidTopicNameException
17+
from rclpy.impl.implementation_singleton import rclpy_implementation as _rclpy
18+
19+
20+
def validate_full_topic_name(name, *, is_service=False):
21+
"""
22+
Validate a given topic or service name, and raise an exception if invalid.
23+
24+
The name must be fully-qualified and already expanded.
25+
26+
If the name is invalid then rclpy.exceptions.InvalidTopicNameException
27+
will be raised.
28+
29+
:param name str: topic or service name to be validated
30+
:param is_service bool: if true, InvalidServiceNameException is raised
31+
:returns: True when it is valid
32+
:raises: InvalidTopicNameException: when the name is invalid
33+
"""
34+
result = _rclpy.rclpy_get_validation_error_for_full_topic_name(name)
35+
if result is None:
36+
return True
37+
error_msg, invalid_index = result
38+
if is_service:
39+
raise InvalidServiceNameException(name, error_msg, invalid_index)
40+
else:
41+
raise InvalidTopicNameException(name, error_msg, invalid_index)

rclpy/rclpy/validate_namespace.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Copyright 2017 Open Source Robotics Foundation, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from rclpy.exceptions import InvalidNamespaceException
16+
from rclpy.impl.implementation_singleton import rclpy_implementation as _rclpy
17+
18+
19+
def validate_namespace(namespace):
20+
"""
21+
Validate a given namespace, and raise an exception if it is invalid.
22+
23+
Unlike the node constructor, which allows namespaces without a leading '/'
24+
and empty namespaces "", this function requires that the namespace be
25+
absolute and non-empty.
26+
27+
If the namespace is invalid then rclpy.exceptions.InvalidNamespaceException
28+
will be raised.
29+
30+
:param namespace str: namespace to be validated
31+
:returns: True when it is valid
32+
:raises: InvalidNamespaceException: when the namespace is invalid
33+
"""
34+
result = _rclpy.rclpy_get_validation_error_for_namespace(namespace)
35+
if result is None:
36+
return True
37+
error_msg, invalid_index = result
38+
raise InvalidNamespaceException(namespace, error_msg, invalid_index)

rclpy/rclpy/validate_node_name.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Copyright 2017 Open Source Robotics Foundation, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from rclpy.exceptions import InvalidNodeNameException
16+
from rclpy.impl.implementation_singleton import rclpy_implementation as _rclpy
17+
18+
19+
def validate_node_name(node_name):
20+
"""
21+
Validate a given node_name, and raise an exception if it is invalid.
22+
23+
If the node_name is invalid then rclpy.exceptions.InvalidNodeNameException
24+
will be raised.
25+
26+
:param node_name str: node_name to be validated
27+
:returns: True when it is valid
28+
:raises: InvalidNodeNameException: when the node_name is invalid
29+
"""
30+
result = _rclpy.rclpy_get_validation_error_for_node_name(node_name)
31+
if result is None:
32+
return True
33+
error_msg, invalid_index = result
34+
raise InvalidNodeNameException(node_name, error_msg, invalid_index)

0 commit comments

Comments
 (0)