Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions launch/doc/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@


# -- Project information -----------------------------------------------------
# type: ignore

project = 'launch'
copyright = '2018, Open Source Robotics Foundation, Inc.' # noqa
Expand Down
6 changes: 2 additions & 4 deletions launch/examples/disable_emulate_tty_counters.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@

import os
import sys
from typing import cast
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) # noqa

import launch # noqa: E402
Expand All @@ -37,10 +36,9 @@ def generate_launch_description():
ld.add_action(launch.actions.SetLaunchConfiguration('emulate_tty', 'false'))

# Wire up stdout from processes
def on_output(event: launch.Event) -> None:
def on_output(event: launch.events.process.ProcessIO) -> None:
for line in event.text.decode().splitlines():
print('[{}] {}'.format(
cast(launch.events.process.ProcessIO, event).process_name, line))
print('[{}] {}'.format(event.process_name, line))

ld.add_action(launch.actions.RegisterEventHandler(launch.event_handlers.OnProcessIO(
on_stdout=on_output,
Expand Down
6 changes: 2 additions & 4 deletions launch/examples/launch_counters.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
import os
import platform
import sys
from typing import cast
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) # noqa

import launch # noqa: E402
Expand Down Expand Up @@ -55,10 +54,9 @@ def main(argv=sys.argv[1:]):

# Setup a custom event handler for all stdout/stderr from processes.
# Later, this will be a configurable, but always present, extension to the LaunchService.
def on_output(event: launch.Event) -> None:
def on_output(event: launch.events.process.ProcessIO) -> None:
for line in event.text.decode().splitlines():
print('[{}] {}'.format(
cast(launch.events.process.ProcessIO, event).process_name, line))
print('[{}] {}'.format(event.process_name, line))

ld.add_action(launch.actions.RegisterEventHandler(launch.event_handlers.OnProcessIO(
# this is the action ^ and this, the event handler ^
Expand Down
8 changes: 4 additions & 4 deletions launch/launch/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@
from .launch_description_source import LaunchDescriptionSource
from .launch_introspector import LaunchIntrospector
from .launch_service import LaunchService
from .some_actions_type import SomeActionsType
from .some_actions_type import SomeActionsType_types_tuple
from .some_entities_type import SomeEntitiesType
from .some_entities_type import SomeEntitiesType_types_tuple
from .some_substitutions_type import SomeSubstitutionsType
from .some_substitutions_type import SomeSubstitutionsType_types_tuple
from .substitution import Substitution
Expand All @@ -57,8 +57,8 @@
'LaunchDescriptionSource',
'LaunchIntrospector',
'LaunchService',
'SomeActionsType',
'SomeActionsType_types_tuple',
'SomeEntitiesType',
'SomeEntitiesType_types_tuple',
'SomeSubstitutionsType',
'SomeSubstitutionsType_types_tuple',
'Substitution',
Expand Down
3 changes: 2 additions & 1 deletion launch/launch/action.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

"""Module for Action class."""

from typing import Dict
from typing import Iterable
from typing import List
from typing import Optional
Expand Down Expand Up @@ -62,7 +63,7 @@ def parse(entity: 'Entity', parser: 'Parser'):
from .conditions import UnlessCondition
if_cond = entity.get_attr('if', optional=True)
unless_cond = entity.get_attr('unless', optional=True)
kwargs = {}
kwargs: Dict[str, Condition] = {}
if if_cond is not None and unless_cond is not None:
raise RuntimeError("if and unless conditions can't be used simultaneously")
if if_cond is not None:
Expand Down
5 changes: 2 additions & 3 deletions launch/launch/actions/declare_launch_argument.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@

"""Module for the DeclareLaunchArgument action."""

from typing import Iterable
from typing import List
from typing import Optional
from typing import Text
Expand Down Expand Up @@ -109,7 +108,7 @@ def __init__(
*,
default_value: Optional[SomeSubstitutionsType] = None,
description: Optional[Text] = None,
choices: Iterable[Text] = None,
choices: List[Text] = None,
**kwargs
) -> None:
"""Create a DeclareLaunchArgument action."""
Expand Down Expand Up @@ -196,7 +195,7 @@ def description(self) -> Text:
return self.__description

@property
def choices(self) -> List[Text]:
def choices(self) -> Optional[List[Text]]:
"""Getter for self.__choices."""
return self.__choices

Expand Down
54 changes: 36 additions & 18 deletions launch/launch/actions/execute_local.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@

import launch.logging

from osrf_pycommon.process_utils import async_execute_process
from osrf_pycommon.process_utils import async_execute_process # type: ignore
from osrf_pycommon.process_utils import AsyncSubprocessProtocol

from .emit_event import EmitEvent
Expand Down Expand Up @@ -63,7 +63,7 @@
from ..launch_context import LaunchContext
from ..launch_description import LaunchDescription
from ..launch_description_entity import LaunchDescriptionEntity
from ..some_actions_type import SomeActionsType
from ..some_entities_type import SomeEntitiesType
from ..some_substitutions_type import SomeSubstitutionsType
from ..substitution import Substitution # noqa: F401
from ..substitutions import LaunchConfiguration
Expand Down Expand Up @@ -97,8 +97,8 @@ def __init__(
cached_output: bool = False,
log_cmd: bool = False,
on_exit: Optional[Union[
SomeActionsType,
Callable[[ProcessExited, LaunchContext], Optional[SomeActionsType]]
SomeEntitiesType,
Callable[[ProcessExited, LaunchContext], Optional[SomeEntitiesType]]
]] = None,
respawn: Union[bool, SomeSubstitutionsType] = False,
respawn_delay: Optional[float] = None,
Expand Down Expand Up @@ -193,9 +193,16 @@ def __init__(
self.__sigterm_timeout = normalize_to_list_of_substitutions(sigterm_timeout)
self.__sigkill_timeout = normalize_to_list_of_substitutions(sigkill_timeout)
self.__emulate_tty = emulate_tty
self.__output = os.environ.get('OVERRIDE_LAUNCH_PROCESS_OUTPUT', output)
if not isinstance(self.__output, dict):
self.__output = normalize_to_list_of_substitutions(self.__output)
# Note: we need to use a temporary here so that we don't assign values with different types
# to the same variable
tmp_output: SomeSubstitutionsType = os.environ.get(
'OVERRIDE_LAUNCH_PROCESS_OUTPUT', output
)
self.__output: Union[dict, List[Substitution]]
if not isinstance(tmp_output, dict):
self.__output = normalize_to_list_of_substitutions(tmp_output)
else:
self.__output = tmp_output
self.__output_format = output_format

self.__log_cmd = log_cmd
Expand Down Expand Up @@ -336,7 +343,7 @@ def __on_signal_process_event(
def __on_process_stdin(
self,
event: ProcessIO
) -> Optional[SomeActionsType]:
) -> Optional[SomeEntitiesType]:
self.__logger.warning(
"in ExecuteProcess('{}').__on_process_stdin_event()".format(id(self)),
)
Expand All @@ -345,7 +352,7 @@ def __on_process_stdin(

def __on_process_output(
self, event: ProcessIO, buffer: io.TextIOBase, logger: logging.Logger
) -> Optional[SomeActionsType]:
) -> None:
to_write = event.text.decode(errors='replace')
if buffer.closed:
# buffer was probably closed by __flush_buffers on shutdown. Output without
Expand Down Expand Up @@ -396,7 +403,7 @@ def __flush_buffers(self, event, context):

def __on_process_output_cached(
self, event: ProcessIO, buffer, logger
) -> Optional[SomeActionsType]:
) -> None:
to_write = event.text.decode(errors='replace')
last_cursor = buffer.tell()
buffer.seek(0, os.SEEK_END) # go to end of buffer
Expand All @@ -423,7 +430,7 @@ def __flush_cached_buffers(self, event, context):
self.__output_format.format(line=line, this=self)
)

def __on_shutdown(self, event: Event, context: LaunchContext) -> Optional[SomeActionsType]:
def __on_shutdown(self, event: Event, context: LaunchContext) -> Optional[SomeEntitiesType]:
due_to_sigint = cast(Shutdown, event).due_to_sigint
return self._shutdown_process(
context,
Expand Down Expand Up @@ -578,9 +585,14 @@ async def __execute_process(self, context: LaunchContext) -> None:
self.__logger.error("process has died [pid {}, exit code {}, cmd '{}'].".format(
pid, returncode, ' '.join(filter(lambda part: part.strip(), cmd))
))
await context.emit_event(ProcessExited(returncode=returncode, **process_event_args))
await context.emit_event(
ProcessExited(returncode=returncode, **process_event_args)
)
# respawn the process if necessary
if not context.is_shutdown and not self.__shutdown_future.done() and self.__respawn:
if not context.is_shutdown\
and self.__shutdown_future is not None\
and not self.__shutdown_future.done()\
and self.__respawn:
if self.__respawn_delay is not None and self.__respawn_delay > 0.0:
# wait for a timeout(`self.__respawn_delay`) to respawn the process
# and handle shutdown event with future(`self.__shutdown_future`)
Expand Down Expand Up @@ -608,7 +620,7 @@ def prepare(self, context: LaunchContext):
# pid is added to the dictionary in the connection_made() method of the protocol.
}

self.__respawn = perform_typed_substitution(context, self.__respawn, bool)
self.__respawn = cast(bool, perform_typed_substitution(context, self.__respawn, bool))

def execute(self, context: LaunchContext) -> Optional[List[LaunchDescriptionEntity]]:
"""
Expand Down Expand Up @@ -663,7 +675,9 @@ def execute(self, context: LaunchContext) -> Optional[List[LaunchDescriptionEnti
),
OnProcessExit(
target_action=self,
on_exit=self.__on_exit,
# TODO: This is also a little strange, OnProcessExit shouldn't ever be able to
# take a None for the callable, but this seems to be the default case?
on_exit=self.__on_exit, # type: ignore
),
OnProcessExit(
target_action=self,
Expand All @@ -678,9 +692,13 @@ def execute(self, context: LaunchContext) -> Optional[List[LaunchDescriptionEnti
self.__shutdown_future = create_future(context.asyncio_loop)
self.__logger = launch.logging.get_logger(name)
if not isinstance(self.__output, dict):
self.__output = perform_substitutions(context, self.__output)
self.__stdout_logger, self.__stderr_logger = \
launch.logging.get_output_loggers(name, self.__output)
self.__stdout_logger, self.__stderr_logger = \
launch.logging.get_output_loggers(
name, perform_substitutions(context, self.__output)
)
else:
self.__stdout_logger, self.__stderr_logger = \
launch.logging.get_output_loggers(name, self.__output)
context.asyncio_loop.create_task(self.__execute_process(context))
except Exception:
for event_handler in event_handlers:
Expand Down
4 changes: 3 additions & 1 deletion launch/launch/actions/execute_process.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
from ..frontend import expose_action
from ..frontend import Parser
from ..some_substitutions_type import SomeSubstitutionsType

from ..substitution import Substitution
from ..substitutions import TextSubstitution

_global_process_counter_lock = threading.Lock()
Expand Down Expand Up @@ -254,7 +256,7 @@ def _parse_cmdline(
:returns: a list of command line arguments.
"""
result_args = []
arg = []
arg: List[Substitution] = []

def _append_arg():
nonlocal arg
Expand Down
19 changes: 10 additions & 9 deletions launch/launch/actions/opaque_coroutine.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
import asyncio
import collections.abc
from typing import Any
from typing import Coroutine
from typing import Awaitable
from typing import Callable
from typing import Dict
from typing import Iterable
from typing import List
Expand All @@ -29,19 +30,19 @@
from ..event_handlers import OnShutdown
from ..launch_context import LaunchContext
from ..launch_description_entity import LaunchDescriptionEntity
from ..some_actions_type import SomeActionsType
from ..some_entities_type import SomeEntitiesType
from ..utilities import ensure_argument_type


class OpaqueCoroutine(Action):
"""
Action that adds a Python coroutine to the launch run loop.
Action that adds a Python coroutine function to the launch run loop.

The signature of a coroutine should be:
The signature of the coroutine function should be:

.. code-block:: python

async def coroutine(
async def coroutine_func(
context: LaunchContext,
*args,
**kwargs
Expand All @@ -52,7 +53,7 @@ async def coroutine(

.. code-block:: python

async def coroutine(
async def coroutine_func(
*args,
**kwargs
):
Expand All @@ -63,7 +64,7 @@ async def coroutine(

def __init__(
self, *,
coroutine: Coroutine,
coroutine: Callable[..., Awaitable[None]],
args: Optional[Iterable[Any]] = None,
kwargs: Optional[Dict[Text, Any]] = None,
ignore_context: bool = False,
Expand All @@ -73,7 +74,7 @@ def __init__(
super().__init__(**left_over_kwargs)
if not asyncio.iscoroutinefunction(coroutine):
raise TypeError(
"OpaqueCoroutine expected a coroutine for 'coroutine', got '{}'".format(
"OpaqueCoroutine expected a coroutine function for 'coroutine', got '{}'".format(
type(coroutine)
)
)
Expand All @@ -92,7 +93,7 @@ def __init__(
self.__ignore_context = ignore_context # type: bool
self.__future = None # type: Optional[asyncio.Future]

def __on_shutdown(self, event: Event, context: LaunchContext) -> Optional[SomeActionsType]:
def __on_shutdown(self, event: Event, context: LaunchContext) -> Optional[SomeEntitiesType]:
"""Cancel ongoing coroutine upon shutdown."""
if self.__future is not None:
self.__future.cancel()
Expand Down
5 changes: 4 additions & 1 deletion launch/launch/actions/register_event_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from ..event_handler import BaseEventHandler
from ..launch_context import LaunchContext
from ..launch_description_entity import LaunchDescriptionEntity
from ..utilities import normalize_to_list_of_entities


class RegisterEventHandler(Action):
Expand Down Expand Up @@ -57,6 +58,8 @@ def describe_conditional_sub_entities(self) -> List[Tuple[
Iterable[LaunchDescriptionEntity], # list of conditional sub-entities
]]:
event_handler_description = self.__event_handler.describe()

return [
(event_handler_description[0], event_handler_description[1])
(event_handler_description[0],
normalize_to_list_of_entities(event_handler_description[1]))
] if event_handler_description[1] else []
6 changes: 3 additions & 3 deletions launch/launch/actions/timer_action.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
from ..frontend import Parser
from ..launch_context import LaunchContext
from ..launch_description_entity import LaunchDescriptionEntity
from ..some_actions_type import SomeActionsType
from ..some_entities_type import SomeEntitiesType
from ..some_substitutions_type import SomeSubstitutionsType
from ..some_substitutions_type import SomeSubstitutionsType_types_tuple
from ..utilities import create_future
Expand Down Expand Up @@ -137,7 +137,7 @@ def describe_conditional_sub_entities(self) -> List[Tuple[
"""Return the actions that will result when the timer expires, but was not canceled."""
return [('{} seconds pass without being canceled'.format(self.__period), self.__actions)]

def handle(self, context: LaunchContext) -> Optional[SomeActionsType]:
def handle(self, context: LaunchContext) -> Optional[SomeEntitiesType]:
"""Handle firing of timer."""
context.extend_locals(self.__context_locals)
return self.__actions
Expand All @@ -157,7 +157,7 @@ def cancel(self) -> None:
self._canceled_future.set_result(True)
return None

def execute(self, context: LaunchContext) -> Optional[List['Action']]:
def execute(self, context: LaunchContext) -> Optional[List[LaunchDescriptionEntity]]:
"""
Execute the action.

Expand Down
Loading