Source code for dynrec.topics
# Copyright 2026 Juan Daniel Suárez González
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Turning what a caller writes into the parallel arrays the services take.
No ROS import: this is string handling, and keeping it that way is what makes the awkward case
below testable in isolation.
"""
from dynrec.errors import InvalidRequest
[docs]
def parse_topic_specs(values):
"""Split `TOPIC` or `TOPIC:TYPE` items into `(topics, topic_types)`.
An explicit type is not decoration: the recorder only consults the graph when the type is
empty, so naming it is the way to record a topic whose publisher has not started yet -- which
from a script is the common case, since the script often starts the publisher itself.
A plain string is accepted as well as a list, because `add('/scan')` is what anyone writes
first and iterating it into fourteen single-character topics would be a baffling way to fail.
"""
if isinstance(values, str):
values = [values]
topics = []
types = []
for value in values or ():
if not isinstance(value, str):
raise InvalidRequest('{!r} is not a topic name'.format(value))
topic, separator, type_name = value.partition(':')
if not topic:
raise InvalidRequest("'{}' has no topic name".format(value))
if separator and not type_name:
raise InvalidRequest("'{}' has a trailing ':' but no type after it".format(value))
topics.append(topic)
types.append(type_name)
# The services require topic_types to be empty or exactly as long as topics, so an all-empty
# list is sent as empty rather than as a list of empty strings.
return topics, ([] if not any(types) else types)
[docs]
def require_a_selection(topics, regex):
"""Refuse a call that names nothing and matches nothing.
Without this an empty `add()` would be a silent no-op reported as success, and a supervisor
script has no way to notice that -- the same reason `ros2 dynrec add` refuses it.
"""
if not topics and not regex:
raise InvalidRequest('name at least one topic, or select some with a regex')