-
Notifications
You must be signed in to change notification settings - Fork 165
Description
Some effort was already invested to express intent without side-effects in the new launch api, and each entity in a launch description can express itself and related, or "sub" entities. However, the introspection interfaces of various entities (Actions, Event Handlers, Events, Subsitutions, etc...) need to be refined to avoid impassible and opaque boundaries in the description when introspecting.
I have some ideas of what needs to change already, but ran out of time before the bouncy release. The launch.LaunchDescriptionEntity interfaces for this are the most "mature" and reflect somewhat closely what I'd like to update the other entities (like EventHandler and Substitution) to look like in the future:
launch/launch/launch/launch_description_entity.py
Lines 57 to 101 in b78d87f
| def describe(self) -> Text: | |
| """ | |
| Return a description of this entity as a string. | |
| When inherited from, calling this base class's default method is not | |
| required, and in fact it will raise NotImplementedError. | |
| """ | |
| raise NotImplementedError() | |
| def describe_sub_entities(self) -> List['LaunchDescriptionEntity']: | |
| """ | |
| Return a list of sub-entities which need to be described as well. | |
| The list may be empty. | |
| The sub-entities in this list should always be returned when this | |
| entity is visited at runtime. | |
| If there are entities which are only returned under some condition, | |
| use the describe_conditional_sub_entities() method instead. | |
| In the case of multiple layers of inheritance, you may wish to call | |
| other base class's describe_sub_entities() and extend your own list of | |
| sub-entities depending on the behavior of your class, but calling this | |
| default method is not required. | |
| """ | |
| return [] | |
| def describe_conditional_sub_entities(self) -> List[Tuple[ | |
| Text, # text description of the condition | |
| Iterable['LaunchDescriptionEntity'], # list of conditional sub-entities | |
| ]]: | |
| """ | |
| Return a list of condition descriptions and lists of sub-entities. | |
| The list of conditional sub-entities are made up of two item tuples, | |
| where the first item is a text description of the condition, and the | |
| second item is a list of sub-entities which would be visited if the | |
| condition is evaluated to be true during launch. | |
| In the case of multiple layers of inheritance, you may wish to call the | |
| base class's describe_conditional_sub_entities() and extend your own | |
| list of sub-entities depending on the behavior of your class, but | |
| calling this default method is not required. | |
| """ | |
| return [] |
The above interfaces may also need some work. Additionally, I think that right now the mechanism to "render" an entity during introspection (render to text for printing on the console in the case of launch.LaunchIntrospector) needs to be placed with or along side the entities themselves. Currently this logic is in the LaunchIntrospector, which was only done to get something working rapidly:
launch/launch/launch/launch_introspector.py
Lines 72 to 140 in b78d87f
| def format_entities(entities: List[LaunchDescriptionEntity]) -> List[Text]: | |
| """Return a list of lines of text that represent of a list of LaunchDescriptionEntity's.""" | |
| result = [] | |
| for entity in entities: | |
| if is_a(entity, Action): | |
| result.extend(format_action(cast(Action, entity))) | |
| else: | |
| result.append("Unknown entity('{}')".format(entity)) | |
| return result | |
| def format_substitutions(substitutions: SomeSubstitutionsType) -> Text: | |
| """Return a text representation of some set of substitutions.""" | |
| normalized_substitutions = normalize_to_list_of_substitutions(substitutions) | |
| result = '' | |
| for sub in normalized_substitutions: | |
| result += ' + ' | |
| if is_a(sub, TextSubstitution): | |
| result += "'{}'".format(cast(TextSubstitution, sub).text) | |
| elif is_a(sub, EnvironmentVariable): | |
| result += 'EnvVarSub({})'.format( | |
| format_substitutions(cast(EnvironmentVariable, sub).name)) | |
| elif is_a(sub, FindExecutable): | |
| result += 'FindExecSub({})'.format( | |
| format_substitutions(cast(FindExecutable, sub).name)) | |
| else: | |
| result += "Substitution('{}')".format(sub) | |
| return result[3:] | |
| def format_event_handler(event_handler: EventHandler) -> List[Text]: | |
| """Return a text representation of an event handler.""" | |
| if hasattr(event_handler, 'describe'): | |
| # TODO(wjwwood): consider supporting mode complex descriptions of branching | |
| description, entities = event_handler.describe() # type: ignore | |
| result = [description] | |
| result.extend(indent(format_entities(entities))) | |
| return result | |
| else: | |
| return ["EventHandler('{}')".format(hex(id(event_handler)))] | |
| def format_action(action: Action) -> List[Text]: | |
| """Return a text representation of an action.""" | |
| if is_a(action, LogInfo): | |
| return ['LogInfo({})'.format(format_substitutions(cast(LogInfo, action).msg))] | |
| elif is_a(action, EmitEvent): | |
| return ["EmitEvent(event='{}')".format(cast(EmitEvent, action).event.name)] | |
| elif is_a(action, ExecuteProcess): | |
| typed_action = cast(ExecuteProcess, action) | |
| msg = 'ExecuteProcess(cmd=[{}], cwd={}, env={}, shell={})'.format( | |
| ', '.join([format_substitutions(x) for x in typed_action.cmd]), | |
| typed_action.cwd if typed_action.cwd is None else "'{}'".format( | |
| format_substitutions(typed_action.cwd) | |
| ), | |
| typed_action.env if typed_action.env is None else '{' + ', '.join( | |
| ['{}: {}'.format(format_substitutions(k), format_substitutions(v)) | |
| for k, v in typed_action.env.items()] + '}'), | |
| typed_action.shell, | |
| ) | |
| return [msg] | |
| elif is_a(action, RegisterEventHandler): | |
| # Different variable name used to assist with type checking. | |
| typed_action2 = cast(RegisterEventHandler, action) | |
| result = ["RegisterEventHandler('{}'):".format(typed_action2.event_handler)] | |
| result.extend(indent(format_event_handler(typed_action2.event_handler))) | |
| return result | |
| else: | |
| return ["Action('{}')".format(action)] |
Lastly, specific entities need to implement these interfaces and handle any special logic to make them useful during introspection. Specifically I noticed:
launch.EventHandlerscould report what is yielded when an event is handled- Update introspection support for some
launch.Actions (e.g. theExecuteProcessaction could report more of what it might do in certain situations)
A stretch goal of this refactoring would be to add some notion of "details" when introspecting, e.g. you might just want to see that a process is getting run and not all the various and tedious things it might do in every situation, unless you asked for those details, in which case it would show them. Basically some way for an entity to express that some sub entities and parts of its own description are implementation details and not usually important to express.