Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
53828e7
Add test for type checking
haudren-woven Nov 30, 2022
51f3aab
Fix some type errors
haudren-woven Dec 1, 2022
7362932
Fix a few type errors:
haudren-woven Jan 5, 2023
430f594
Fix declare launch argument
haudren-woven Jan 5, 2023
9d3f77a
Fixup execute_local
haudren-woven Jan 5, 2023
2beeb74
Fixup path substitution
haudren-woven Jan 6, 2023
550f034
Fixup execute process
haudren-woven Jan 6, 2023
9f070d4
Use overloads to make get_attr type safe
haudren-woven Jan 6, 2023
63af13f
Minor fixes in include launch description
haudren-woven Jan 6, 2023
f588667
More small fixes
haudren-woven Jan 6, 2023
239858e
Fixup minor errors in launch config
haudren-woven Feb 16, 2023
a8446cb
Rename some_actions_type -> some_entities_type
haudren-woven Feb 16, 2023
e4b8edc
Rename coroutine -> coroutine_function
haudren-woven Feb 16, 2023
dfb1653
Minor typing mistake
haudren-woven Feb 16, 2023
bd37fdc
Fix typing of the handlers
haudren-woven Feb 16, 2023
fc271c6
Minor fixes
haudren-woven Feb 16, 2023
7ffa5e3
Another Sequence fix
haudren-woven Feb 16, 2023
c30e0a3
Some fixes to logging
haudren-woven Feb 16, 2023
83e9507
Many Iterable -> Sequence fixes
haudren-woven Feb 16, 2023
a33ed8e
Fixup launch_service
haudren-woven Feb 16, 2023
87f669e
Improve equals_substitution
haudren-woven Feb 16, 2023
3c153fc
Fixup environment variable
haudren-woven Feb 16, 2023
7433476
Fix some execute process / execute local things
haudren-woven Feb 16, 2023
77a73df
Fixup anon name
haudren-woven Feb 16, 2023
085f43f
Fixup command
haudren-woven Feb 16, 2023
1cdb863
Fix the source location annotation
haudren-woven Feb 16, 2023
f7571b0
More type errors...
haudren-woven Feb 16, 2023
d840ae6
Fix some more ExecuteLocal weirdness
haudren-woven Feb 16, 2023
d05fd86
Fix the type of launch file loaders
haudren-woven Feb 16, 2023
994201f
Some people need to modify the environment
haudren-woven Feb 16, 2023
b2676e8
Fixup register_event_handler
haudren-woven Feb 16, 2023
6031c2c
Remove superfluous cast
haudren-woven Feb 16, 2023
b258111
Minor signal process fix
haudren-woven Feb 17, 2023
43b1e97
Replace try-catch with version switch
haudren-woven Feb 17, 2023
c38cb84
Last touches to mypy
haudren-woven Feb 17, 2023
6f44cb3
Ignore untyped imports
haudren-woven Feb 17, 2023
6db85b6
Merge remote-tracking branch 'origin/rolling' into feat/type-checking
haudren-woven Feb 17, 2023
f6dda61
More fixes to python expression
haudren-woven Feb 17, 2023
1e3cf1b
Fixup simple bug
haudren-woven Feb 17, 2023
0abf6a7
Remove unused import
haudren-woven Feb 17, 2023
c4afced
Ignore docs
haudren-woven Feb 17, 2023
bddeb05
Just a few typechecks
haudren-woven Feb 17, 2023
d75b9ce
Ignore YAML checking
haudren-woven Feb 17, 2023
d26320b
Check for errors in loading spec
haudren-woven Feb 17, 2023
1ac22e3
Ignore a typing error in here
haudren-woven Feb 17, 2023
b17f51b
Ignore SIGBREAK
haudren-woven Feb 17, 2023
07f50e2
Fixup pep257
haudren-woven Feb 17, 2023
fd6efed
Fixup flake8 errors
haudren-woven Feb 17, 2023
72feb5b
Another small bug
haudren-woven Feb 17, 2023
8850288
Fixup flake8 in testing
haudren-woven Feb 17, 2023
8e9a8f5
Reintroduce SomeActionsType
haudren-woven Feb 27, 2023
32fa4cc
Make flake8 happy!
haudren-woven Feb 28, 2023
4b17097
One more round of lint...
haudren-woven Mar 1, 2023
a2e87bb
Use sys.platform to check for Windows
haudren-woven Mar 2, 2023
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
56 changes: 37 additions & 19 deletions launch/launch/actions/execute_local.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,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 @@ -62,7 +62,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 @@ -93,8 +93,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 @@ -189,9 +189,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 @@ -342,7 +349,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 @@ -351,12 +358,12 @@ 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
# buffering.
buffer.info(
logger.info(
self.__output_format.format(line=to_write, this=self)
)
else:
Expand Down Expand Up @@ -402,7 +409,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 @@ -429,7 +436,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 @@ -584,9 +591,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 @@ -614,7 +626,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 @@ -669,7 +681,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 @@ -684,9 +698,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 @@ -28,6 +28,8 @@
from ..frontend import expose_action
from ..frontend import Parser
from ..some_substitutions_type import SomeSubstitutionsType

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


Expand Down Expand Up @@ -250,7 +252,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